More debugger support.
This wires up method-entry events and fixes a bug in exception events.
Change-Id: Ia7c46786a8073434fbb4546615072622f301ef84
diff --git a/src/debugger.cc b/src/debugger.cc
index 40405eb..6f381d3 100644
--- a/src/debugger.cc
+++ b/src/debugger.cc
@@ -797,7 +797,7 @@
return gRegistry->Get<Class*>(instClassId)->InstanceOf(gRegistry->Get<Class*>(classId));
}
-JDWP::FieldId ToFieldId(Field* f) {
+JDWP::FieldId ToFieldId(const Field* f) {
#ifdef MOVING_GARBAGE_COLLECTOR
UNIMPLEMENTED(FATAL);
#else
@@ -805,7 +805,7 @@
#endif
}
-JDWP::MethodId ToMethodId(Method* m) {
+JDWP::MethodId ToMethodId(const Method* m) {
#ifdef MOVING_GARBAGE_COLLECTOR
UNIMPLEMENTED(FATAL);
#else
@@ -830,11 +830,15 @@
}
void SetLocation(JDWP::JdwpLocation& location, Method* m, uintptr_t native_pc) {
- Class* c = m->GetDeclaringClass();
- location.typeTag = c->IsInterface() ? JDWP::TT_INTERFACE : JDWP::TT_CLASS;
- location.classId = gRegistry->Add(c);
- location.methodId = ToMethodId(m);
- location.idx = m->IsNative() ? -1 : m->ToDexPC(native_pc);
+ if (m == NULL) {
+ memset(&location, 0, sizeof(location));
+ } else {
+ Class* c = m->GetDeclaringClass();
+ location.typeTag = c->IsInterface() ? JDWP::TT_INTERFACE : JDWP::TT_CLASS;
+ location.classId = gRegistry->Add(c);
+ location.methodId = ToMethodId(m);
+ location.idx = m->IsNative() ? -1 : m->ToDexPC(native_pc);
+ }
}
std::string Dbg::GetMethodName(JDWP::RefTypeId refTypeId, JDWP::MethodId methodId) {
@@ -880,11 +884,12 @@
return newSlot;
}
-static uint16_t DemangleSlot(uint16_t slot, Frame& f) {
+static uint16_t DemangleSlot(uint16_t slot, Method* m) {
if (slot == kEclipseWorkaroundSlot) {
return 0;
} else if (slot == 0) {
- const DexFile::CodeItem* code_item = MethodHelper(f.GetMethod()).GetCodeItem();
+ const DexFile::CodeItem* code_item = MethodHelper(m).GetCodeItem();
+ CHECK(code_item != NULL);
return code_item->registers_size_ - code_item->ins_size_;
}
return slot;
@@ -1368,27 +1373,28 @@
Runtime::Current()->GetThreadList()->SuspendSelfForDebugger();
}
-bool Dbg::GetThisObject(JDWP::FrameId frameId, JDWP::ObjectId* pThisId) {
- Method** sp = reinterpret_cast<Method**>(frameId);
- Frame f;
- f.SetSP(sp);
+static Object* GetThis(Frame& f) {
Method* m = f.GetMethod();
-
Object* o = NULL;
if (!m->IsNative() && !m->IsStatic()) {
- uint16_t reg = DemangleSlot(0, f);
+ uint16_t reg = DemangleSlot(0, m);
o = reinterpret_cast<Object*>(f.GetVReg(m, reg));
}
+ return o;
+}
+
+void Dbg::GetThisObject(JDWP::FrameId frameId, JDWP::ObjectId* pThisId) {
+ Method** sp = reinterpret_cast<Method**>(frameId);
+ Frame f(sp);
+ Object* o = GetThis(f);
*pThisId = gRegistry->Add(o);
- return true;
}
void Dbg::GetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, JDWP::JdwpTag tag, uint8_t* buf, size_t width) {
Method** sp = reinterpret_cast<Method**>(frameId);
- Frame f;
- f.SetSP(sp);
- uint16_t reg = DemangleSlot(slot, f);
+ Frame f(sp);
Method* m = f.GetMethod();
+ uint16_t reg = DemangleSlot(slot, m);
const VmapTable vmap_table(m->GetVmapTableRaw());
uint32_t vmap_offset;
@@ -1476,10 +1482,9 @@
void Dbg::SetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, JDWP::JdwpTag tag, uint64_t value, size_t width) {
Method** sp = reinterpret_cast<Method**>(frameId);
- Frame f;
- f.SetSP(sp);
- uint16_t reg = DemangleSlot(slot, f);
+ Frame f(sp);
Method* m = f.GetMethod();
+ uint16_t reg = DemangleSlot(slot, m);
const VmapTable vmap_table(m->GetVmapTableRaw());
uint32_t vmap_offset;
@@ -1524,8 +1529,25 @@
}
}
-void Dbg::PostLocationEvent(const Method* method, int pcOffset, Object* thisPtr, int eventFlags) {
- UNIMPLEMENTED(FATAL);
+void Dbg::PostLocationEvent(const Method* m, int dex_pc, Object* this_object, int event_flags) {
+ Class* c = m->GetDeclaringClass();
+
+ JDWP::JdwpLocation location;
+ location.typeTag = c->IsInterface() ? JDWP::TT_INTERFACE : JDWP::TT_CLASS;
+ location.classId = gRegistry->Add(c);
+ location.methodId = ToMethodId(m);
+ location.idx = m->IsNative() ? -1 : dex_pc;
+
+ // Note we use "NoReg" so we don't keep track of references that are
+ // never actually sent to the debugger. 'this_id' is only used to
+ // compare against registered events...
+ JDWP::ObjectId this_id = static_cast<JDWP::ObjectId>(reinterpret_cast<uintptr_t>(this_object));
+ if (gJdwpState->PostLocationEvent(&location, this_id, event_flags)) {
+ // ...unless there's a registered event, in which case we
+ // need to really track the class and 'this'.
+ gRegistry->Add(c);
+ gRegistry->Add(this_object);
+ }
}
void Dbg::PostException(Method** sp, Method* throwMethod, uintptr_t throwNativePc, Method* catchMethod, uintptr_t catchNativePc, Object* exception) {
@@ -1570,6 +1592,127 @@
gJdwpState->PostClassPrepare(tag, gRegistry->Add(c), ClassHelper(c).GetDescriptor(), state);
}
+void Dbg::UpdateDebugger(int32_t dex_pc, Thread* self, Method** sp) {
+ if (!gDebuggerActive) {
+ return;
+ }
+
+ int event_flags = 0;
+
+ // Update xtra.currentPc on every instruction. We need to do this if
+ // there's a chance that we could get suspended. This can happen if
+ // event_flags != 0 here, or somebody manually requests a suspend
+ // (which gets handled at PERIOD_CHECKS time). One place where this
+ // needs to be correct is in dvmAddSingleStep().
+ //dvmExportPC(pc, fp);
+
+ // We use a pc of -1 to represent method entry, since we might branch back to pc 0 later.
+ if (dex_pc == -1) {
+ event_flags |= kMethodEntry;
+ }
+
+ /*
+ // See if we have a breakpoint here.
+ // Depending on the "mods" associated with event(s) on this address,
+ // we may or may not actually send a message to the debugger.
+ if (GET_OPCODE(*pc) == OP_BREAKPOINT) {
+ ALOGV("+++ breakpoint hit at %p", pc);
+ event_flags |= kBreakPoint;
+ }
+ */
+
+ /*
+ // If the debugger is single-stepping one of our threads, check to
+ // see if we're that thread and we've reached a step point.
+ const StepControl* pCtrl = &gDvm.stepControl;
+ if (pCtrl->active && pCtrl->thread == self) {
+ int frameDepth;
+ bool doStop = false;
+ const char* msg = NULL;
+
+ assert(!dvmIsNativeMethod(method));
+
+ if (pCtrl->depth == SD_INTO) {
+ // Step into method calls. We break when the line number
+ // or method pointer changes. If we're in SS_MIN mode, we
+ // always stop.
+ if (pCtrl->method != method) {
+ doStop = true;
+ msg = "new method";
+ } else if (pCtrl->size == SS_MIN) {
+ doStop = true;
+ msg = "new instruction";
+ } else if (!dvmAddressSetGet(pCtrl->pAddressSet, pc - method->insns)) {
+ doStop = true;
+ msg = "new line";
+ }
+ } else if (pCtrl->depth == SD_OVER) {
+ // Step over method calls. We break when the line number is
+ // different and the frame depth is <= the original frame
+ // depth. (We can't just compare on the method, because we
+ // might get unrolled past it by an exception, and it's tricky
+ // to identify recursion.)
+ frameDepth = dvmComputeVagueFrameDepth(self, fp);
+ if (frameDepth < pCtrl->frameDepth) {
+ // popped up one or more frames, always trigger
+ doStop = true;
+ msg = "method pop";
+ } else if (frameDepth == pCtrl->frameDepth) {
+ // same depth, see if we moved
+ if (pCtrl->size == SS_MIN) {
+ doStop = true;
+ msg = "new instruction";
+ } else if (!dvmAddressSetGet(pCtrl->pAddressSet, pc - method->insns)) {
+ doStop = true;
+ msg = "new line";
+ }
+ }
+ } else {
+ assert(pCtrl->depth == SD_OUT);
+ // Return from the current method. We break when the frame
+ // depth pops up.
+
+ // This differs from the "method exit" break in that it stops
+ // with the PC at the next instruction in the returned-to
+ // function, rather than the end of the returning function.
+ frameDepth = dvmComputeVagueFrameDepth(self, fp);
+ if (frameDepth < pCtrl->frameDepth) {
+ doStop = true;
+ msg = "method pop";
+ }
+ }
+
+ if (doStop) {
+ ALOGV("#####S %s", msg);
+ event_flags |= kSingleStep;
+ }
+ }
+ */
+
+ /*
+ // Check to see if this is a "return" instruction. JDWP says we should
+ // send the event *after* the code has been executed, but it also says
+ // the location we provide is the last instruction. Since the "return"
+ // instruction has no interesting side effects, we should be safe.
+ // (We can't just move this down to the returnFromMethod label because
+ // we potentially need to combine it with other events.)
+ // We're also not supposed to generate a method exit event if the method
+ // terminates "with a thrown exception".
+ u2 opcode = GET_OPCODE(*pc);
+ if (opcode == OP_RETURN_VOID || opcode == OP_RETURN || opcode == OP_RETURN_WIDE ||opcode == OP_RETURN_OBJECT) {
+ event_flags |= kMethodExit;
+ }
+ */
+
+ // If there's something interesting going on, see if it matches one
+ // of the debugger filters.
+ if (event_flags != 0) {
+ Frame f(sp);
+ f.Next(); // Skip callee save frame.
+ Dbg::PostLocationEvent(f.GetMethod(), dex_pc, GetThis(f), event_flags);
+ }
+}
+
bool Dbg::WatchLocation(const JDWP::JdwpLocation* pLoc) {
UNIMPLEMENTED(FATAL);
return false;
diff --git a/src/debugger.h b/src/debugger.h
index 8a702a1..dbb99ff 100644
--- a/src/debugger.h
+++ b/src/debugger.h
@@ -201,7 +201,7 @@
static void ResumeThread(JDWP::ObjectId threadId);
static void SuspendSelf();
- static bool GetThisObject(JDWP::FrameId frameId, JDWP::ObjectId* pThisId);
+ static void GetThisObject(JDWP::FrameId frameId, JDWP::ObjectId* pThisId);
static void GetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, JDWP::JdwpTag tag, uint8_t* buf, size_t expectedLen);
static void SetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, JDWP::JdwpTag tag, uint64_t value, size_t width);
@@ -220,6 +220,8 @@
static void PostThreadDeath(Thread* t);
static void PostClassPrepare(Class* c);
+ static void UpdateDebugger(int32_t dex_pc, Thread* self, Method** sp);
+
static bool WatchLocation(const JDWP::JdwpLocation* pLoc);
static void UnwatchLocation(const JDWP::JdwpLocation* pLoc);
static bool ConfigureStep(JDWP::ObjectId threadId, JDWP::JdwpStepSize size, JDWP::JdwpStepDepth depth);
diff --git a/src/jdwp/jdwp_event.cc b/src/jdwp/jdwp_event.cc
index f3e98f5..c0e6295 100644
--- a/src/jdwp/jdwp_event.cc
+++ b/src/jdwp/jdwp_event.cc
@@ -352,12 +352,8 @@
*/
static bool PatternMatch(const char* pattern, const std::string& target) {
size_t patLen = strlen(pattern);
-
if (pattern[0] == '*') {
patLen--;
- // TODO: remove printf when we find a test case to verify this
- LOG(ERROR) << ">>> comparing '" << (pattern + 1) << "' to '" << (target.c_str() + (target.size()-patLen)) << "'";
-
if (target.size() < patLen) {
return false;
}
@@ -701,12 +697,6 @@
return true;
}
-// TODO: This doesn't behave like the real dvmDescriptorToName.
-// I'm hoping this isn't used to communicate with the debugger, and we can just use descriptors.
-std::string dvmDescriptorToName(const std::string& descriptor) {
- return descriptor;
-}
-
/*
* A location of interest has been reached. This handles:
* Breakpoint
@@ -736,7 +726,7 @@
basket.classId = pLoc->classId;
basket.thisPtr = thisPtr;
basket.threadId = Dbg::GetThreadSelfId();
- basket.className = dvmDescriptorToName(Dbg::GetClassDescriptor(pLoc->classId));
+ basket.className = DescriptorToName(Dbg::GetClassDescriptor(pLoc->classId).c_str());
/*
* On rare occasions we may need to execute interpreted code in the VM
@@ -931,7 +921,7 @@
basket.pLoc = pThrowLoc;
basket.classId = pThrowLoc->classId;
basket.threadId = Dbg::GetThreadSelfId();
- basket.className = dvmDescriptorToName(Dbg::GetClassDescriptor(basket.classId));
+ basket.className = DescriptorToName(Dbg::GetClassDescriptor(basket.classId).c_str());
basket.excepClassId = exceptionClassId;
basket.caught = (pCatchLoc->classId != 0);
basket.thisPtr = thisPtr;
@@ -1014,7 +1004,7 @@
memset(&basket, 0, sizeof(basket));
basket.classId = refTypeId;
basket.threadId = Dbg::GetThreadSelfId();
- basket.className = dvmDescriptorToName(Dbg::GetClassDescriptor(basket.classId));
+ basket.className = DescriptorToName(Dbg::GetClassDescriptor(basket.classId).c_str());
/* suppress class prep caused by debugger */
if (InvokeInProgress()) {
diff --git a/src/jdwp/jdwp_handler.cc b/src/jdwp/jdwp_handler.cc
index 7914aa7..3dbf437 100644
--- a/src/jdwp/jdwp_handler.cc
+++ b/src/jdwp/jdwp_handler.cc
@@ -1482,9 +1482,7 @@
FrameId frameId = ReadFrameId(&buf);
ObjectId objectId;
- if (!Dbg::GetThisObject(frameId, &objectId)) {
- return ERR_INVALID_FRAMEID;
- }
+ Dbg::GetThisObject(frameId, &objectId);
uint8_t objectTag = Dbg::GetObjectTag(objectId);
VLOG(jdwp) << StringPrintf(" Req for 'this' in frame=%llx --> %llx '%c'", frameId, objectId, (char)objectTag);
diff --git a/src/runtime_support.cc b/src/runtime_support.cc
index eb06df5..670049a 100644
--- a/src/runtime_support.cc
+++ b/src/runtime_support.cc
@@ -16,6 +16,7 @@
#include "runtime_support.h"
+#include "debugger.h"
#include "dex_cache.h"
#include "dex_verifier.h"
#include "macros.h"
@@ -100,14 +101,14 @@
}
/*
- * Report location to debugger. Note: dalvikPC is the current offset within
+ * Report location to debugger. Note: dex_pc is the current offset within
* the method. However, because the offset alone cannot distinguish between
* method entry and offset 0 within the method, we'll use an offset of -1
* to denote method entry.
*/
-extern "C" void artUpdateDebuggerFromCode(int32_t dalvikPC, Thread* self, Method** sp) {
+extern "C" void artUpdateDebuggerFromCode(int32_t dex_pc, Thread* self, Method** sp) {
FinishCalleeSaveFrameSetup(self, sp, Runtime::kRefsAndArgs);
- // TODO: fill this out similar to old "updateDebugger"
+ Dbg::UpdateDebugger(dex_pc, self, sp);
}
// Temporary debugging hook for compiler.
diff --git a/src/runtime_support.h b/src/runtime_support.h
index 954c362..a5e4c87 100644
--- a/src/runtime_support.h
+++ b/src/runtime_support.h
@@ -28,7 +28,7 @@
extern Array* CheckAndAllocArrayFromCode(uint32_t type_idx, Method* method, int32_t component_count,
Thread* self, bool access_check);
extern void DebugMe(Method* method, uint32_t info);
-extern void UpdateDebuggerFromCode(Method* method, Thread* thread , int32_t dalvikPC, Method** sp);
+extern void UpdateDebuggerFromCode(Method* method, Thread* thread , int32_t dex_pc, Method** sp);
extern Object* DecodeJObjectInThread(Thread* thread, jobject obj);
extern Field* FindFieldFromCode(uint32_t field_idx, const Method* referrer, Thread* self,
bool is_static, bool is_primitive, size_t expected_size);
diff --git a/src/runtime_support_arm.S b/src/runtime_support_arm.S
index 7456853..69e908d 100644
--- a/src/runtime_support_arm.S
+++ b/src/runtime_support_arm.S
@@ -67,15 +67,15 @@
.global art_update_debugger
.extern artUpdateDebuggerFromCode
/*
- * On entry, r0 and r1 must be preserved, r2 is DalvikPC
+ * On entry, r0 and r1 must be preserved, r2 is dex PC
*/
art_update_debugger:
mov r3, r0 @ stash away r0 so that it's saved as if it were an argument
SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME
- mov r0, r2 @ arg0 is DalvikPC
+ mov r0, r2 @ arg0 is dex PC
mov r1, rSELF @ arg1 is Thread*
mov r2, sp @ arg2 is sp
- bl artUpdateDebuggerFromCode @ artUpdateDebuggerFromCode(Method*, Thread*, dPC, sp)
+ bl artUpdateDebuggerFromCode @ artUpdateDebuggerFromCode(int32_t, Thread*, Method**)
RESTORE_REF_AND_ARGS_CALLEE_SAVE_FRAME
mov r0, r3 @ restore original r0
bx lr
diff --git a/src/stack.h b/src/stack.h
index 3a0903f..382ad24 100644
--- a/src/stack.h
+++ b/src/stack.h
@@ -41,6 +41,8 @@
public:
Frame() : sp_(NULL) {}
+ Frame(Method** sp) : sp_(sp) {}
+
Method* GetMethod() const {
return (sp_ != NULL) ? *sp_ : NULL;
}
@@ -68,8 +70,6 @@
return sp_;
}
- // TODO: this is here for testing, remove when we have exception unit tests
- // that use the real stack
void SetSP(Method** sp) {
sp_ = sp;
}
diff --git a/src/utils.cc b/src/utils.cc
index 09a01c6..f599c0e 100644
--- a/src/utils.cc
+++ b/src/utils.cc
@@ -375,13 +375,18 @@
}
std::string DescriptorToDot(const char* descriptor) {
+ std::string result(DescriptorToName(descriptor));
+ std::replace(result.begin(), result.end(), '/', '.');
+ return result;
+}
+
+std::string DescriptorToName(const char* descriptor) {
size_t length = strlen(descriptor);
DCHECK_GT(length, 0U);
DCHECK_EQ(descriptor[0], 'L');
DCHECK_EQ(descriptor[length - 1], ';');
- std::string dot(descriptor + 1, length - 2);
- std::replace(dot.begin(), dot.end(), '/', '.');
- return dot;
+ std::string result(descriptor + 1, length - 2);
+ return result;
}
std::string JniShortName(const Method* m) {
diff --git a/src/utils.h b/src/utils.h
index 39a5d4f..e774a3b 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -218,6 +218,9 @@
// Turn "Ljava/lang/String;" into "java.lang.String".
std::string DescriptorToDot(const char* descriptor);
+// Turn "Ljava/lang/String;" into "java/lang/String".
+std::string DescriptorToName(const char* descriptor);
+
// Tests for whether 's' is a valid class name in the three common forms:
bool IsValidBinaryClassName(const char* s); // "java.lang.String"
bool IsValidJniClassName(const char* s); // "java/lang/String"