Include held locks in SIGQUIT thread dumps.
Handy if you have an ANR that's locking related. Quick tour:
at org.apache.harmony.dalvik.NativeTestTarget.emptyJniStaticSynchronizedMethod0(Native method)
- locked <0x60135aa8> (a java.lang.Class<org.apache.harmony.dalvik.NativeTestTarget>)
at java.lang.reflect.Method.invoke(Native method)
at C.whileTrue(Main.java:63)
at C.synchronizedOnClassString(Main.java:56)
- locked <0x60002a70> (a java.lang.Class<java.lang.String>)
at C.nestedSynchronizationWithTryCatch(Main.java:44)
- locked <0x61336b90> (a java.lang.String)
- locked <0x61336bd0> (a java.lang.String)
at C.nestedSynchronization(Main.java:35)
- locked <0x61336b18> (a java.lang.String)
- locked <0x61336b50> (a java.lang.String)
at C.synchronizedOnClassC(Main.java:30)
- locked <0x613366f8> (a java.lang.Class<C>)
at C.noLocks(Main.java:27)
at C.<clinit>(Main.java:24)
- locked <0x613366f8> (a java.lang.Class<C>)
at Main.main(Main.java:19)
A non-static synchronized native method works too:
at org.apache.harmony.dalvik.NativeTestTarget.emptyJniSynchronizedMethod0(Native method)
- locked <0x613371a8> (a org.apache.harmony.dalvik.NativeTestTarget)
...
Note that most stack traces don't look any different; the above is a
pathological example that exercises different kinds of locking. Testing
with system_server shows most threads don't hold any locks.
Future work (marked by TODO) is that explicit JNI MonitorEnter calls in
native code aren't shown.
Change-Id: I2747f5cddb4ef64b1935736f084a68fe8e4005e9
diff --git a/src/monitor.cc b/src/monitor.cc
index de08b88..e5e867a 100644
--- a/src/monitor.cc
+++ b/src/monitor.cc
@@ -24,7 +24,10 @@
#include <time.h>
#include <unistd.h>
+#include <vector>
+
#include "class_linker.h"
+#include "dex_instruction.h"
#include "mutex.h"
#include "object.h"
#include "object_utils.h"
@@ -33,6 +36,7 @@
#include "stl_util.h"
#include "thread.h"
#include "thread_list.h"
+#include "verifier/method_verifier.h"
#include "well_known_classes.h"
namespace art {
@@ -867,11 +871,10 @@
// We're not waiting on anything.
return;
}
- os << "<" << object << ">";
// - waiting on <0x613f83d8> (a java.lang.ThreadLock) held by thread 5
// - waiting on <0x6008c468> (a java.lang.Class<java.lang.ref.ReferenceQueue>)
- os << " (a " << PrettyTypeOf(object) << ")";
+ os << "<" << object << "> (a " << PrettyTypeOf(object) << ")";
if (lock_owner != ThreadList::kInvalidId) {
os << " held by thread " << lock_owner;
@@ -880,6 +883,69 @@
os << "\n";
}
+static void DumpLockedObject(std::ostream& os, Object* o) {
+ os << " - locked <" << o << "> (a " << PrettyTypeOf(o) << ")\n";
+}
+
+void Monitor::DescribeLocks(std::ostream& os, StackVisitor* stack_visitor) {
+ Method* m = stack_visitor->GetMethod();
+ CHECK(m != NULL);
+
+ // Native methods are an easy special case.
+ // TODO: use the JNI implementation's table of explicit MonitorEnter calls and dump those too.
+ if (m->IsNative()) {
+ if (m->IsSynchronized()) {
+ Object* jni_this = stack_visitor->GetCurrentSirt()->GetReference(0);
+ DumpLockedObject(os, jni_this);
+ }
+ return;
+ }
+
+ // <clinit> is another special case. The runtime holds the class lock while calling <clinit>.
+ MethodHelper mh(m);
+ if (mh.IsClassInitializer()) {
+ DumpLockedObject(os, m->GetDeclaringClass());
+ // Fall through because there might be synchronization in the user code too.
+ }
+
+ // Is there any reason to believe there's any synchronization in this method?
+ const DexFile::CodeItem* code_item = mh.GetCodeItem();
+ CHECK(code_item != NULL);
+ if (code_item->tries_size_ == 0) {
+ return; // No "tries" implies no synchronization, so no held locks to report.
+ }
+
+ // Ask the verifier for the dex pcs of all the monitor-enter instructions corresponding to
+ // the locks held in this stack frame.
+ std::vector<uint32_t> monitor_enter_dex_pcs;
+ verifier::MethodVerifier::FindLocksAtDexPc(m, stack_visitor->GetDexPc(), monitor_enter_dex_pcs);
+ if (monitor_enter_dex_pcs.empty()) {
+ return;
+ }
+
+ // Verification is an iterative process, so it can visit the same monitor-enter instruction
+ // repeatedly with increasingly accurate type information. Our callers don't want to see
+ // duplicates.
+ STLSortAndRemoveDuplicates(&monitor_enter_dex_pcs);
+
+ for (size_t i = 0; i < monitor_enter_dex_pcs.size(); ++i) {
+ // The verifier works in terms of the dex pcs of the monitor-enter instructions.
+ // We want the registers used by those instructions (so we can read the values out of them).
+ uint32_t dex_pc = monitor_enter_dex_pcs[i];
+ uint16_t monitor_enter_instruction = code_item->insns_[dex_pc];
+
+ // Quick sanity check.
+ if ((monitor_enter_instruction & 0xff) != Instruction::MONITOR_ENTER) {
+ LOG(FATAL) << "expected monitor-enter @" << dex_pc << "; was "
+ << reinterpret_cast<void*>(monitor_enter_instruction);
+ }
+
+ uint16_t monitor_register = ((monitor_enter_instruction >> 8) & 0xff);
+ Object* o = reinterpret_cast<Object*>(stack_visitor->GetVReg(m, monitor_register));
+ DumpLockedObject(os, o);
+ }
+}
+
void Monitor::TranslateLocation(const Method* method, uint32_t dex_pc,
const char*& source_file, uint32_t& line_number) const {
// If method is null, location is unknown