JDWP: properly combine location events
This CL properly groups JDWP events at the same location: Breakpoint,
Single-step, Method Entry and Method Exit. This is necessary if the
debugger is not the only instrumentation listener. This matches the
behavior of Dalvik, especially for methods with a single return
instruction.
The interpreter was tuned so the instrumentation callbacks were
called to satisfy the debugger with the idea the debugger was the
only instrumentation listener. This is not true when method tracing
is enabled at the same time.
When tracing is enabled, there is always a listener for MethodEntry
and MethodExit events (art::Trace class). However, if the debugger
is only listening to DexPcMoved event (to manage JDWP Breakpoint
event), it will not be notified of this event.
We now properly call all the instrumentation callbacks in the
interpreter and move the logic specific to debugging into the class
DebugInstrumentationListener. This allows to properly group JDWP
location events together depending on the sequence of instrumentation
callbacks.
We add Thread::tls_32bit_sized_values::debug_method_entry_ flag to
remember we just entered a method. It replaces the local variable
notified_method_entry_event in the interpreter and simplifies the
code.
Bump oat version to force recompilation because the layout of the
Thread class is modified.
Bug: 19829329
Change-Id: I204af9112e37d2eebc86661fb7c961a41c74e598
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index dc1b4f1..47371e5 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -232,13 +232,29 @@
virtual ~DebugInstrumentationListener() {}
void MethodEntered(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method,
- uint32_t dex_pc ATTRIBUTE_UNUSED)
+ uint32_t dex_pc)
OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
if (method->IsNative()) {
// TODO: post location events is a suspension point and native method entry stubs aren't.
return;
}
- Dbg::UpdateDebugger(thread, this_object, method, 0, Dbg::kMethodEntry, nullptr);
+ if (IsListeningToDexPcMoved()) {
+ // We also listen to kDexPcMoved instrumentation event so we know the DexPcMoved method is
+ // going to be called right after us. To avoid sending JDWP events twice for this location,
+ // we report the event in DexPcMoved. However, we must remind this is method entry so we
+ // send the METHOD_ENTRY event. And we can also group it with other events for this location
+ // like BREAKPOINT or SINGLE_STEP (or even METHOD_EXIT if this is a RETURN instruction).
+ thread->SetDebugMethodEntry();
+ } else if (IsListeningToMethodExit() && IsReturn(method, dex_pc)) {
+ // We also listen to kMethodExited instrumentation event and the current instruction is a
+ // RETURN so we know the MethodExited method is going to be called right after us. To avoid
+ // sending JDWP events twice for this location, we report the event(s) in MethodExited.
+ // However, we must remind this is method entry so we send the METHOD_ENTRY event. And we can
+ // also group it with other events for this location like BREAKPOINT or SINGLE_STEP.
+ thread->SetDebugMethodEntry();
+ } else {
+ Dbg::UpdateDebugger(thread, this_object, method, 0, Dbg::kMethodEntry, nullptr);
+ }
}
void MethodExited(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method,
@@ -248,14 +264,20 @@
// TODO: post location events is a suspension point and native method entry stubs aren't.
return;
}
- Dbg::UpdateDebugger(thread, this_object, method, dex_pc, Dbg::kMethodExit, &return_value);
+ uint32_t events = Dbg::kMethodExit;
+ if (thread->IsDebugMethodEntry()) {
+ // It is also the method entry.
+ DCHECK(IsReturn(method, dex_pc));
+ events |= Dbg::kMethodEntry;
+ thread->ClearDebugMethodEntry();
+ }
+ Dbg::UpdateDebugger(thread, this_object, method, dex_pc, events, &return_value);
}
- void MethodUnwind(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method,
- uint32_t dex_pc)
+ void MethodUnwind(Thread* thread ATTRIBUTE_UNUSED, mirror::Object* this_object ATTRIBUTE_UNUSED,
+ mirror::ArtMethod* method, uint32_t dex_pc)
OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// We're not recorded to listen to this kind of event, so complain.
- UNUSED(thread, this_object, method, dex_pc);
LOG(ERROR) << "Unexpected method unwind event in debugger " << PrettyMethod(method)
<< " " << dex_pc;
}
@@ -263,13 +285,27 @@
void DexPcMoved(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method,
uint32_t new_dex_pc)
OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- Dbg::UpdateDebugger(thread, this_object, method, new_dex_pc, 0, nullptr);
+ if (IsListeningToMethodExit() && IsReturn(method, new_dex_pc)) {
+ // We also listen to kMethodExited instrumentation event and the current instruction is a
+ // RETURN so we know the MethodExited method is going to be called right after us. Like in
+ // MethodEntered, we delegate event reporting to MethodExited.
+ // Besides, if this RETURN instruction is the only one in the method, we can send multiple
+ // JDWP events in the same packet: METHOD_ENTRY, METHOD_EXIT, BREAKPOINT and/or SINGLE_STEP.
+ // Therefore, we must not clear the debug method entry flag here.
+ } else {
+ uint32_t events = 0;
+ if (thread->IsDebugMethodEntry()) {
+ // It is also the method entry.
+ events = Dbg::kMethodEntry;
+ thread->ClearDebugMethodEntry();
+ }
+ Dbg::UpdateDebugger(thread, this_object, method, new_dex_pc, events, nullptr);
+ }
}
- void FieldRead(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method,
- uint32_t dex_pc, ArtField* field)
+ void FieldRead(Thread* thread ATTRIBUTE_UNUSED, mirror::Object* this_object,
+ mirror::ArtMethod* method, uint32_t dex_pc, ArtField* field)
OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- UNUSED(thread);
Dbg::PostFieldAccessEvent(method, dex_pc, this_object, field);
}
@@ -293,6 +329,26 @@
}
private:
+ static bool IsReturn(mirror::ArtMethod* method, uint32_t dex_pc)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ const DexFile::CodeItem* code_item = method->GetCodeItem();
+ const Instruction* instruction = Instruction::At(&code_item->insns_[dex_pc]);
+ return instruction->IsReturn();
+ }
+
+ static bool IsListeningToDexPcMoved() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return IsListeningTo(instrumentation::Instrumentation::kDexPcMoved);
+ }
+
+ static bool IsListeningToMethodExit() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return IsListeningTo(instrumentation::Instrumentation::kMethodExited);
+ }
+
+ static bool IsListeningTo(instrumentation::Instrumentation::InstrumentationEvent event)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return (Dbg::GetInstrumentationEvents() & event) != 0;
+ }
+
DISALLOW_COPY_AND_ASSIGN(DebugInstrumentationListener);
} gDebugInstrumentationListener;