auto import from //depot/cupcake/@135843
diff --git a/vm/Debugger.c b/vm/Debugger.c
new file mode 100644
index 0000000..c667893
--- /dev/null
+++ b/vm/Debugger.c
@@ -0,0 +1,2951 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Link between JDWP and the VM. The code here only runs as a result of
+ * requests from the debugger, so speed is not essential. Maintaining
+ * isolation of the JDWP code should make it easier to maintain and reuse.
+ *
+ * Collecting all debugger-related pieces here will also allow us to #ifdef
+ * the JDWP code out of release builds.
+ */
+#include "Dalvik.h"
+
+/*
+Notes on garbage collection and object registration
+
+JDWP does not allow the debugger to assume that objects passed to it
+will not be garbage collected. It specifies explicit commands (e.g.
+ObjectReference.DisableCollection) to allow the debugger to manage
+object lifetime. It does, however, require that the VM not re-use an
+object ID unless an explicit "dispose" call has been made, and if the
+VM asks for a now-collected object we must return INVALID_OBJECT.
+
+JDWP also requires that, while the VM is suspended, no garbage collection
+occur. The JDWP docs suggest that this is obvious, because no threads
+can be running. Unfortunately it's not entirely clear how to deal
+with situations where the debugger itself allocates strings or executes
+code as part of displaying variables. The easiest way to enforce this,
+short of disabling GC whenever the debugger is connected, is to ensure
+that the debugger thread can't cause a GC: it has to expand the heap or
+fail to allocate. (Might want to make that "is debugger thread AND all
+other threads are suspended" to avoid unnecessary heap expansion by a
+poorly-timed JDWP request.)
+
+We use an "object registry" so that we can separate our internal
+representation from what we show the debugger. This allows us to
+return a registry table index instead of a pointer or handle.
+
+There are various approaches we can take to achieve correct behavior:
+
+(1) Disable garbage collection entirely while the debugger is attached.
+This is very easy, but doesn't allow extended debugging sessions on
+small devices.
+
+(2) Keep a list of all object references requested by or sent to the
+debugger, and include the list in the GC root set. This ensures that
+objects the debugger might care about don't go away. This is straightforward,
+but it can cause us to hold on to large objects and prevent finalizers from
+being executed.
+
+(3) Keep a list of what amount to weak object references. This way we
+don't interfere with the GC, and can support JDWP requests like
+"ObjectReference.IsCollected".
+
+The current implementation is #2. The set should be reasonably small and
+performance isn't critical, so a simple expanding array can be used.
+
+
+Notes on threads:
+
+The VM has a Thread struct associated with every active thread. The
+ThreadId we pass to the debugger is the ObjectId for the java/lang/Thread
+object, so to retrieve the VM's Thread struct we have to scan through the
+list looking for a match.
+
+When a thread goes away, we lock the list and free the struct. To
+avoid having the thread list updated or Thread structs freed out from
+under us, we want to acquire and hold the thread list lock while we're
+performing operations on Threads. Exceptions to this rule are noted in
+a couple of places.
+
+We can speed this up a bit by adding a Thread struct pointer to the
+java/lang/Thread object, and ensuring that both are discarded at the
+same time.
+*/
+
+#define THREAD_GROUP_ALL ((ObjectId) 0x12345) // magic, internal-only value
+
+#define kSlot0Sub 1000 // Eclipse workaround
+
+/*
+ * System init. We don't allocate the registry until first use.
+ * Make sure we do this before initializing JDWP.
+ */
+bool dvmDebuggerStartup(void)
+{
+ gDvm.dbgRegistry = dvmHashTableCreate(1000, NULL);
+ return (gDvm.dbgRegistry != NULL);
+}
+
+/*
+ * Free registry storage.
+ */
+void dvmDebuggerShutdown(void)
+{
+ dvmHashTableFree(gDvm.dbgRegistry);
+ gDvm.dbgRegistry = NULL;
+}
+
+
+/*
+ * Pass these through to the VM functions. Allows extended checking
+ * (e.g. "errorcheck" mutexes). If nothing else we can assert() success.
+ */
+void dvmDbgInitMutex(pthread_mutex_t* pMutex)
+{
+ dvmInitMutex(pMutex);
+}
+void dvmDbgLockMutex(pthread_mutex_t* pMutex)
+{
+ dvmLockMutex(pMutex);
+}
+void dvmDbgUnlockMutex(pthread_mutex_t* pMutex)
+{
+ dvmUnlockMutex(pMutex);
+}
+void dvmDbgInitCond(pthread_cond_t* pCond)
+{
+ pthread_cond_init(pCond, NULL);
+}
+void dvmDbgCondWait(pthread_cond_t* pCond, pthread_mutex_t* pMutex)
+{
+ int cc = pthread_cond_wait(pCond, pMutex);
+ assert(cc == 0);
+}
+void dvmDbgCondSignal(pthread_cond_t* pCond)
+{
+ int cc = pthread_cond_signal(pCond);
+ assert(cc == 0);
+}
+void dvmDbgCondBroadcast(pthread_cond_t* pCond)
+{
+ int cc = pthread_cond_broadcast(pCond);
+ assert(cc == 0);
+}
+
+
+/* keep track of type, in case we need to distinguish them someday */
+typedef enum RegistryType {
+ kObjectId = 0xc1, kRefTypeId
+} RegistryType;
+
+/*
+ * Hash function for object IDs. Since objects are at least 8 bytes, and
+ * could someday be allocated on 16-byte boundaries, we don't want to use
+ * the low 4 bits in our hash.
+ */
+static inline u4 registryHash(u4 val)
+{
+ return val >> 4;
+}
+
+/*
+ * (This is a dvmHashTableLookup() callback.)
+ */
+static int registryCompare(const void* obj1, const void* obj2)
+{
+ return (int) obj1 - (int) obj2;
+}
+
+
+/*
+ * Determine if an id is already in the list.
+ *
+ * If the list doesn't yet exist, this creates it.
+ *
+ * Lock the registry before calling here.
+ */
+static bool lookupId(ObjectId id)
+{
+ void* found;
+
+ found = dvmHashTableLookup(gDvm.dbgRegistry, registryHash((u4) id),
+ (void*)(u4) id, registryCompare, false);
+ if (found == NULL)
+ return false;
+ assert(found == (void*)(u4) id);
+ return true;
+}
+
+/*
+ * Register an object, if it hasn't already been.
+ *
+ * This is used for both ObjectId and RefTypeId. In theory we don't have
+ * to register RefTypeIds unless we're worried about classes unloading.
+ *
+ * Null references must be represented as zero, or the debugger will get
+ * very confused.
+ */
+static ObjectId registerObject(const Object* obj, RegistryType type, bool reg)
+{
+ ObjectId id;
+
+ if (obj == NULL)
+ return 0;
+
+ assert((u4) obj != 0xcccccccc);
+ assert((u4) obj > 0x100);
+
+ id = (ObjectId)(u4)obj | ((u8) type) << 32;
+ if (!reg)
+ return id;
+
+ dvmHashTableLock(gDvm.dbgRegistry);
+ if (!gDvm.debuggerConnected) {
+ /* debugger has detached while we were doing stuff? */
+ LOGI("ignoring registerObject request in thread=%d\n",
+ dvmThreadSelf()->threadId);
+ //dvmAbort();
+ goto bail;
+ }
+
+ (void) dvmHashTableLookup(gDvm.dbgRegistry, registryHash((u4) id),
+ (void*)(u4) id, registryCompare, true);
+
+bail:
+ dvmHashTableUnlock(gDvm.dbgRegistry);
+ return id;
+}
+
+/*
+ * (This is a HashForeachFunc callback.)
+ */
+static int markRef(void* data, void* arg)
+{
+ UNUSED_PARAMETER(arg);
+
+ //LOGI("dbg mark %p\n", data);
+ dvmMarkObjectNonNull(data);
+ return 0;
+}
+
+/* Mark all of the registered debugger references so the
+ * GC doesn't collect them.
+ */
+void dvmGcMarkDebuggerRefs()
+{
+ /* dvmDebuggerStartup() may not have been called before the first GC.
+ */
+ if (gDvm.dbgRegistry != NULL) {
+ dvmHashTableLock(gDvm.dbgRegistry);
+ dvmHashForeach(gDvm.dbgRegistry, markRef, NULL);
+ dvmHashTableUnlock(gDvm.dbgRegistry);
+ }
+}
+
+/*
+ * Verify that an object has been registered. If it hasn't, the debugger
+ * is asking for something we didn't send it, which means something
+ * somewhere is broken.
+ *
+ * If speed is an issue we can encode the registry index in the high
+ * four bytes. We could also just hard-wire this to "true".
+ *
+ * Note this actually takes both ObjectId and RefTypeId.
+ */
+static bool objectIsRegistered(ObjectId id, RegistryType type)
+{
+ UNUSED_PARAMETER(type);
+
+ if (id == 0) // null reference?
+ return true;
+
+ dvmHashTableLock(gDvm.dbgRegistry);
+ bool result = lookupId(id);
+ dvmHashTableUnlock(gDvm.dbgRegistry);
+ return result;
+}
+
+/*
+ * Convert to/from a RefTypeId.
+ *
+ * These are rarely NULL, but can be (e.g. java/lang/Object's superclass).
+ */
+static RefTypeId classObjectToRefTypeId(ClassObject* clazz)
+{
+ return (RefTypeId) registerObject((Object*) clazz, kRefTypeId, true);
+}
+static RefTypeId classObjectToRefTypeIdNoReg(ClassObject* clazz)
+{
+ return (RefTypeId) registerObject((Object*) clazz, kRefTypeId, false);
+}
+static ClassObject* refTypeIdToClassObject(RefTypeId id)
+{
+ assert(objectIsRegistered(id, kRefTypeId) || !gDvm.debuggerConnected);
+ return (ClassObject*)(u4) id;
+}
+
+/*
+ * Convert to/from an ObjectId.
+ */
+static ObjectId objectToObjectId(const Object* obj)
+{
+ return registerObject(obj, kObjectId, true);
+}
+static ObjectId objectToObjectIdNoReg(const Object* obj)
+{
+ return registerObject(obj, kObjectId, false);
+}
+static Object* objectIdToObject(ObjectId id)
+{
+ assert(objectIsRegistered(id, kObjectId) || !gDvm.debuggerConnected);
+ return (Object*)(u4) id;
+}
+
+/*
+ * Convert to/from a MethodId.
+ *
+ * These IDs are only guaranteed unique within a class, so they could be
+ * an enumeration index. For now we just use the Method*.
+ */
+static MethodId methodToMethodId(const Method* meth)
+{
+ return (MethodId)(u4) meth;
+}
+static Method* methodIdToMethod(RefTypeId refTypeId, MethodId id)
+{
+ // TODO? verify "id" is actually a method in "refTypeId"
+ return (Method*)(u4) id;
+}
+
+/*
+ * Convert to/from a FieldId.
+ *
+ * These IDs are only guaranteed unique within a class, so they could be
+ * an enumeration index. For now we just use the Field*.
+ */
+static FieldId fieldToFieldId(const Field* field)
+{
+ return (FieldId)(u4) field;
+}
+static Field* fieldIdToField(RefTypeId refTypeId, FieldId id)
+{
+ // TODO? verify "id" is actually a field in "refTypeId"
+ return (Field*)(u4) id;
+}
+
+/*
+ * Convert to/from a FrameId.
+ *
+ * We just return a pointer to the stack frame.
+ */
+static FrameId frameToFrameId(const void* frame)
+{
+ return (FrameId)(u4) frame;
+}
+static void* frameIdToFrame(FrameId id)
+{
+ return (void*)(u4) id;
+}
+
+
+/*
+ * Get the invocation request state.
+ */
+DebugInvokeReq* dvmDbgGetInvokeReq(void)
+{
+ return &dvmThreadSelf()->invokeReq;
+}
+
+/*
+ * Enable the object registry, but don't enable debugging features yet.
+ *
+ * Only called from the JDWP handler thread.
+ */
+void dvmDbgConnected(void)
+{
+ assert(!gDvm.debuggerConnected);
+
+ LOGV("JDWP has attached\n");
+ assert(dvmHashTableNumEntries(gDvm.dbgRegistry) == 0);
+ gDvm.debuggerConnected = true;
+}
+
+/*
+ * Enable all debugging features, including scans for breakpoints.
+ *
+ * This is a no-op if we're already active.
+ *
+ * Only called from the JDWP handler thread.
+ */
+void dvmDbgActive(void)
+{
+ if (gDvm.debuggerActive)
+ return;
+
+ LOGI("Debugger is active\n");
+ dvmInitBreakpoints();
+ gDvm.debuggerActive = true;
+}
+
+/*
+ * Disable debugging features.
+ *
+ * Set "debuggerConnected" to false, which disables use of the object
+ * registry.
+ *
+ * Only called from the JDWP handler thread.
+ */
+void dvmDbgDisconnected(void)
+{
+ assert(gDvm.debuggerConnected);
+
+ gDvm.debuggerActive = false;
+
+ dvmHashTableLock(gDvm.dbgRegistry);
+ gDvm.debuggerConnected = false;
+
+ LOGI("Debugger has detached; object registry had %d entries\n",
+ dvmHashTableNumEntries(gDvm.dbgRegistry));
+ //int i;
+ //for (i = 0; i < gDvm.dbgRegistryNext; i++)
+ // LOGVV("%4d: 0x%llx\n", i, gDvm.dbgRegistryTable[i]);
+
+ dvmHashTableClear(gDvm.dbgRegistry);
+ dvmHashTableUnlock(gDvm.dbgRegistry);
+}
+
+/*
+ * Returns "true" if a debugger is connected.
+ *
+ * Does not return "true" if it's just a DDM server.
+ */
+bool dvmDbgIsDebuggerConnected(void)
+{
+ return gDvm.debuggerActive;
+}
+
+/*
+ * Get time since last debugger activity. Used when figuring out if the
+ * debugger has finished configuring us.
+ */
+s8 dvmDbgLastDebuggerActivity(void)
+{
+ return dvmJdwpLastDebuggerActivity(gDvm.jdwpState);
+}
+
+/*
+ * JDWP thread is running, don't allow GC.
+ */
+int dvmDbgThreadRunning(void)
+{
+ return dvmChangeStatus(NULL, THREAD_RUNNING);
+}
+
+/*
+ * JDWP thread is idle, allow GC.
+ */
+int dvmDbgThreadWaiting(void)
+{
+ return dvmChangeStatus(NULL, THREAD_VMWAIT);
+}
+
+/*
+ * Restore state returned by Running/Waiting calls.
+ */
+int dvmDbgThreadContinuing(int status)
+{
+ return dvmChangeStatus(NULL, status);
+}
+
+/*
+ * The debugger wants us to exit.
+ */
+void dvmDbgExit(int status)
+{
+ // TODO? invoke System.exit() to perform exit processing; ends up
+ // in System.exitInternal(), which can call JNI exit hook
+#ifdef WITH_PROFILER
+ LOGI("GC lifetime allocation: %d bytes\n", gDvm.allocProf.allocCount);
+ if (CALC_CACHE_STATS) {
+ dvmDumpAtomicCacheStats(gDvm.instanceofCache);
+ dvmDumpBootClassPath();
+ }
+#endif
+#ifdef PROFILE_FIELD_ACCESS
+ dvmDumpFieldAccessCounts();
+#endif
+
+ exit(status);
+}
+
+
+/*
+ * ===========================================================================
+ * Class, Object, Array
+ * ===========================================================================
+ */
+
+/*
+ * Get the class's type descriptor from a reference type ID.
+ */
+const char* dvmDbgGetClassDescriptor(RefTypeId id)
+{
+ ClassObject* clazz;
+
+ clazz = refTypeIdToClassObject(id);
+ return clazz->descriptor;
+}
+
+/*
+ * Return the superclass of a class (will be NULL for java/lang/Object).
+ */
+RefTypeId dvmDbgGetSuperclass(RefTypeId id)
+{
+ ClassObject* clazz = refTypeIdToClassObject(id);
+ return classObjectToRefTypeId(clazz->super);
+}
+
+/*
+ * Return a class's defining class loader.
+ */
+RefTypeId dvmDbgGetClassLoader(RefTypeId id)
+{
+ ClassObject* clazz = refTypeIdToClassObject(id);
+ return objectToObjectId(clazz->classLoader);
+}
+
+/*
+ * Return a class's access flags.
+ */
+u4 dvmDbgGetAccessFlags(RefTypeId id)
+{
+ ClassObject* clazz = refTypeIdToClassObject(id);
+ return clazz->accessFlags & JAVA_FLAGS_MASK;
+}
+
+/*
+ * Is this class an interface?
+ */
+bool dvmDbgIsInterface(RefTypeId id)
+{
+ ClassObject* clazz = refTypeIdToClassObject(id);
+ return dvmIsInterfaceClass(clazz);
+}
+
+/*
+ * dvmHashForeach callback
+ */
+static int copyRefType(void* vclazz, void* varg)
+{
+ RefTypeId** pRefType = (RefTypeId**)varg;
+ **pRefType = classObjectToRefTypeId((ClassObject*) vclazz);
+ (*pRefType)++;
+ return 0;
+}
+
+/*
+ * Get the complete list of reference classes (i.e. all classes except
+ * the primitive types).
+ *
+ * Returns a newly-allocated buffer full of RefTypeId values.
+ */
+void dvmDbgGetClassList(u4* pNumClasses, RefTypeId** pClassRefBuf)
+{
+ RefTypeId* pRefType;
+
+ dvmHashTableLock(gDvm.loadedClasses);
+ *pNumClasses = dvmHashTableNumEntries(gDvm.loadedClasses);
+ pRefType = *pClassRefBuf = malloc(sizeof(RefTypeId) * *pNumClasses);
+
+ if (dvmHashForeach(gDvm.loadedClasses, copyRefType, &pRefType) != 0) {
+ LOGW("Warning: problem getting class list\n");
+ /* not really expecting this to happen */
+ } else {
+ assert(pRefType - *pClassRefBuf == (int) *pNumClasses);
+ }
+
+ dvmHashTableUnlock(gDvm.loadedClasses);
+}
+
+/*
+ * Get the list of reference classes "visible" to the specified class
+ * loader. A class is visible to a class loader if the ClassLoader object
+ * is the defining loader or is listed as an initiating loader.
+ *
+ * Returns a newly-allocated buffer full of RefTypeId values.
+ */
+void dvmDbgGetVisibleClassList(ObjectId classLoaderId, u4* pNumClasses,
+ RefTypeId** pClassRefBuf)
+{
+ Object* classLoader;
+ int numClasses = 0, maxClasses;
+
+ classLoader = objectIdToObject(classLoaderId);
+ // I don't think classLoader can be NULL, but the spec doesn't say
+
+ LOGVV("GetVisibleList: comparing to %p\n", classLoader);
+
+ dvmHashTableLock(gDvm.loadedClasses);
+
+ /* over-allocate the return buffer */
+ maxClasses = dvmHashTableNumEntries(gDvm.loadedClasses);
+ *pClassRefBuf = malloc(sizeof(RefTypeId) * maxClasses);
+
+ /*
+ * Run through the list, looking for matches.
+ */
+ HashIter iter;
+ for (dvmHashIterBegin(gDvm.loadedClasses, &iter); !dvmHashIterDone(&iter);
+ dvmHashIterNext(&iter))
+ {
+ ClassObject* clazz = (ClassObject*) dvmHashIterData(&iter);
+
+ if (clazz->classLoader == classLoader ||
+ dvmLoaderInInitiatingList(clazz, classLoader))
+ {
+ LOGVV(" match '%s'\n", clazz->descriptor);
+ (*pClassRefBuf)[numClasses++] = classObjectToRefTypeId(clazz);
+ }
+ }
+ *pNumClasses = numClasses;
+
+ dvmHashTableUnlock(gDvm.loadedClasses);
+}
+
+/*
+ * Generate the "JNI signature" for a class, e.g. "Ljava/lang/String;".
+ *
+ * Our class descriptors are in the correct format, so we just copy that.
+ * TODO: figure out if we can avoid the copy now that we're using
+ * descriptors instead of unadorned class names.
+ *
+ * Returns a newly-allocated string.
+ */
+static char* generateJNISignature(ClassObject* clazz)
+{
+ return strdup(clazz->descriptor);
+}
+
+/*
+ * Get information about a class.
+ *
+ * If "pSignature" is not NULL, *pSignature gets the "JNI signature" of
+ * the class.
+ */
+void dvmDbgGetClassInfo(RefTypeId classId, u1* pTypeTag, u4* pStatus,
+ char** pSignature)
+{
+ ClassObject* clazz = refTypeIdToClassObject(classId);
+
+ if (clazz->descriptor[0] == '[') {
+ /* generated array class */
+ *pStatus = CS_VERIFIED | CS_PREPARED;
+ *pTypeTag = TT_ARRAY;
+ } else {
+ if (clazz->status == CLASS_ERROR)
+ *pStatus = CS_ERROR;
+ else
+ *pStatus = CS_VERIFIED | CS_PREPARED | CS_INITIALIZED;
+ if (dvmIsInterfaceClass(clazz))
+ *pTypeTag = TT_INTERFACE;
+ else
+ *pTypeTag = TT_CLASS;
+ }
+ if (pSignature != NULL)
+ *pSignature = generateJNISignature(clazz);
+}
+
+/*
+ * Search the list of loaded classes for a match.
+ */
+bool dvmDbgFindLoadedClassBySignature(const char* classDescriptor,
+ RefTypeId* pRefTypeId)
+{
+ ClassObject* clazz;
+
+ clazz = dvmFindLoadedClass(classDescriptor);
+ if (clazz != NULL) {
+ *pRefTypeId = classObjectToRefTypeId(clazz);
+ return true;
+ } else
+ return false;
+}
+
+
+/*
+ * Get an object's class and "type tag".
+ */
+void dvmDbgGetObjectType(ObjectId objectId, u1* pRefTypeTag,
+ RefTypeId* pRefTypeId)
+{
+ Object* obj = objectIdToObject(objectId);
+
+ if (dvmIsArrayClass(obj->clazz))
+ *pRefTypeTag = TT_ARRAY;
+ else if (dvmIsInterfaceClass(obj->clazz))
+ *pRefTypeTag = TT_INTERFACE;
+ else
+ *pRefTypeTag = TT_CLASS;
+ *pRefTypeId = classObjectToRefTypeId(obj->clazz);
+}
+
+/*
+ * Get a class object's "type tag".
+ */
+u1 dvmDbgGetClassObjectType(RefTypeId refTypeId)
+{
+ ClassObject* clazz = refTypeIdToClassObject(refTypeId);
+
+ if (dvmIsArrayClass(clazz))
+ return TT_ARRAY;
+ else if (dvmIsInterfaceClass(clazz))
+ return TT_INTERFACE;
+ else
+ return TT_CLASS;
+}
+
+/*
+ * Get a class' signature.
+ *
+ * Returns a newly-allocated string.
+ */
+char* dvmDbgGetSignature(RefTypeId refTypeId)
+{
+ ClassObject* clazz;
+
+ clazz = refTypeIdToClassObject(refTypeId);
+ assert(clazz != NULL);
+
+ return generateJNISignature(clazz);
+}
+
+/*
+ * Get class' source file.
+ *
+ * Returns a newly-allocated string.
+ */
+const char* dvmDbgGetSourceFile(RefTypeId refTypeId)
+{
+ ClassObject* clazz;
+
+ clazz = refTypeIdToClassObject(refTypeId);
+ assert(clazz != NULL);
+
+ return clazz->sourceFile;
+}
+
+/*
+ * Get an object's type name. Converted to a "JNI signature".
+ *
+ * Returns a newly-allocated string.
+ */
+char* dvmDbgGetObjectTypeName(ObjectId objectId)
+{
+ Object* obj = objectIdToObject(objectId);
+
+ assert(obj != NULL);
+
+ return generateJNISignature(obj->clazz);
+}
+
+/*
+ * Given a type signature (e.g. "Ljava/lang/String;"), return the JDWP
+ * "type tag".
+ *
+ * In many cases this is necessary but not sufficient. For example, if
+ * we have a NULL String object, we want to return JT_STRING. If we have
+ * a java/lang/Object that holds a String reference, we also want to
+ * return JT_STRING. See dvmDbgGetObjectTag().
+ */
+int dvmDbgGetSignatureTag(const char* type)
+{
+ /*
+ * We're not checking the class loader here (to guarantee that JT_STRING
+ * is truly the one and only String), but it probably doesn't matter
+ * for our purposes.
+ */
+ if (strcmp(type, "Ljava/lang/String;") == 0)
+ return JT_STRING;
+ else if (strcmp(type, "Ljava/lang/Class;") == 0)
+ return JT_CLASS_OBJECT;
+ else if (strcmp(type, "Ljava/lang/Thread;") == 0)
+ return JT_THREAD;
+ else if (strcmp(type, "Ljava/lang/ThreadGroup;") == 0)
+ return JT_THREAD_GROUP;
+ else if (strcmp(type, "Ljava/lang/ClassLoader;") == 0)
+ return JT_CLASS_LOADER;
+
+ switch (type[0]) {
+ case '[': return JT_ARRAY;
+ case 'B': return JT_BYTE;
+ case 'C': return JT_CHAR;
+ case 'L': return JT_OBJECT;
+ case 'F': return JT_FLOAT;
+ case 'D': return JT_DOUBLE;
+ case 'I': return JT_INT;
+ case 'J': return JT_LONG;
+ case 'S': return JT_SHORT;
+ case 'V': return JT_VOID;
+ case 'Z': return JT_BOOLEAN;
+ default:
+ LOGE("ERROR: unhandled type '%s'\n", type);
+ assert(false);
+ return -1;
+ }
+}
+
+/*
+ * Methods declared to return Object might actually be returning one
+ * of the "refined types". We need to check the object explicitly.
+ */
+static u1 resultTagFromObject(Object* obj)
+{
+ ClassObject* clazz;
+
+ if (obj == NULL)
+ return JT_OBJECT;
+
+ clazz = obj->clazz;
+
+ /*
+ * Comparing against the known classes is faster than string
+ * comparisons. It ensures that we only find the classes in the
+ * bootstrap class loader, which may or may not be what we want.
+ */
+ if (clazz == gDvm.classJavaLangString)
+ return JT_STRING;
+ else if (clazz == gDvm.classJavaLangClass)
+ return JT_CLASS_OBJECT;
+ else if (clazz == gDvm.classJavaLangThread)
+ return JT_THREAD;
+ else if (clazz == gDvm.classJavaLangThreadGroup)
+ return JT_THREAD_GROUP;
+ else if (strcmp(clazz->descriptor, "Ljava/lang/ClassLoader;") == 0)
+ return JT_CLASS_LOADER;
+ else if (clazz->descriptor[0] == '[')
+ return JT_ARRAY;
+ else
+ return JT_OBJECT;
+}
+
+/*
+ * Determine the tag for an object with a known type.
+ */
+int dvmDbgGetObjectTag(ObjectId objectId, const char* type)
+{
+ u1 tag;
+
+ tag = dvmDbgGetSignatureTag(type);
+ if (tag == JT_OBJECT && objectId != 0)
+ tag = resultTagFromObject(objectIdToObject(objectId));
+
+ return tag;
+}
+
+/*
+ * Get the widths of the specified JDWP.Tag value.
+ */
+int dvmDbgGetTagWidth(int tag)
+{
+ switch (tag) {
+ case JT_VOID:
+ return 0;
+ case JT_BYTE:
+ case JT_BOOLEAN:
+ return 1;
+ case JT_CHAR:
+ case JT_SHORT:
+ return 2;
+ case JT_FLOAT:
+ case JT_INT:
+ return 4;
+ case JT_ARRAY:
+ case JT_OBJECT:
+ case JT_STRING:
+ case JT_THREAD:
+ case JT_THREAD_GROUP:
+ case JT_CLASS_LOADER:
+ case JT_CLASS_OBJECT:
+ return sizeof(ObjectId);
+ case JT_DOUBLE:
+ case JT_LONG:
+ return 8;
+ default:
+ LOGE("ERROR: unhandled tag '%c'\n", tag);
+ assert(false);
+ return -1;
+ }
+}
+
+/*
+ * Determine whether or not a tag represents a primitive type.
+ */
+static bool isTagPrimitive(u1 tag)
+{
+ switch (tag) {
+ case JT_BYTE:
+ case JT_CHAR:
+ case JT_FLOAT:
+ case JT_DOUBLE:
+ case JT_INT:
+ case JT_LONG:
+ case JT_SHORT:
+ case JT_VOID:
+ case JT_BOOLEAN:
+ return true;
+ case JT_ARRAY:
+ case JT_OBJECT:
+ case JT_STRING:
+ case JT_CLASS_OBJECT:
+ case JT_THREAD:
+ case JT_THREAD_GROUP:
+ case JT_CLASS_LOADER:
+ return false;
+ default:
+ LOGE("ERROR: unhandled tag '%c'\n", tag);
+ assert(false);
+ return false;
+ }
+}
+
+
+/*
+ * Return the length of the specified array.
+ */
+int dvmDbgGetArrayLength(ObjectId arrayId)
+{
+ ArrayObject* arrayObj = (ArrayObject*) objectIdToObject(arrayId);
+ assert(dvmIsArray(arrayObj));
+ return arrayObj->length;
+}
+
+/*
+ * Return a tag indicating the general type of elements in the array.
+ */
+int dvmDbgGetArrayElementTag(ObjectId arrayId)
+{
+ ArrayObject* arrayObj = (ArrayObject*) objectIdToObject(arrayId);
+
+ assert(dvmIsArray(arrayObj));
+
+ return dvmDbgGetSignatureTag(arrayObj->obj.clazz->descriptor + 1);
+}
+
+/*
+ * Copy a series of values with the specified width, changing the byte
+ * ordering to big-endian.
+ */
+static void copyValuesToBE(u1* out, const u1* in, int count, int width)
+{
+ int i;
+
+ switch (width) {
+ case 1:
+ memcpy(out, in, count);
+ break;
+ case 2:
+ for (i = 0; i < count; i++)
+ *(((u2*) out)+i) = get2BE(in + i*2);
+ break;
+ case 4:
+ for (i = 0; i < count; i++)
+ *(((u4*) out)+i) = get4BE(in + i*4);
+ break;
+ case 8:
+ for (i = 0; i < count; i++)
+ *(((u8*) out)+i) = get8BE(in + i*8);
+ break;
+ default:
+ assert(false);
+ }
+}
+
+/*
+ * Copy a series of values with the specified with, changing the
+ * byte order from big-endian.
+ */
+static void copyValuesFromBE(u1* out, const u1* in, int count, int width)
+{
+ int i;
+
+ switch (width) {
+ case 1:
+ memcpy(out, in, count);
+ break;
+ case 2:
+ for (i = 0; i < count; i++)
+ set2BE(out + i*2, *((u2*)in + i));
+ break;
+ case 4:
+ for (i = 0; i < count; i++)
+ set4BE(out + i*4, *((u4*)in + i));
+ break;
+ case 8:
+ for (i = 0; i < count; i++)
+ set8BE(out + i*8, *((u8*)in + i));
+ break;
+ default:
+ assert(false);
+ }
+}
+
+/*
+ * Output a piece of an array to the reply buffer.
+ *
+ * Returns "false" if something looks fishy.
+ */
+bool dvmDbgOutputArray(ObjectId arrayId, int firstIndex, int count,
+ ExpandBuf* pReply)
+{
+ ArrayObject* arrayObj = (ArrayObject*) objectIdToObject(arrayId);
+ const u1* data = (const u1*)arrayObj->contents;
+ u1 tag;
+
+ assert(dvmIsArray(arrayObj));
+
+ if (firstIndex + count > (int)arrayObj->length) {
+ LOGW("Request for index=%d + count=%d excceds length=%d\n",
+ firstIndex, count, arrayObj->length);
+ return false;
+ }
+
+ tag = dvmDbgGetSignatureTag(arrayObj->obj.clazz->descriptor + 1);
+
+ if (isTagPrimitive(tag)) {
+ int width = dvmDbgGetTagWidth(tag);
+ u1* outBuf;
+
+ outBuf = expandBufAddSpace(pReply, count * width);
+
+ copyValuesToBE(outBuf, data + firstIndex*width, count, width);
+ } else {
+ Object** pObjects;
+ int i;
+
+ pObjects = (Object**) data;
+ pObjects += firstIndex;
+
+ LOGV(" --> copying %d object IDs\n", count);
+ //assert(tag == JT_OBJECT); // could be object or "refined" type
+
+ for (i = 0; i < count; i++, pObjects++) {
+ u1 thisTag;
+ if (*pObjects != NULL)
+ thisTag = resultTagFromObject(*pObjects);
+ else
+ thisTag = tag;
+ expandBufAdd1(pReply, thisTag);
+ expandBufAddObjectId(pReply, objectToObjectId(*pObjects));
+ }
+ }
+
+ return true;
+}
+
+/*
+ * Set a range of elements in an array from the data in "buf".
+ */
+bool dvmDbgSetArrayElements(ObjectId arrayId, int firstIndex, int count,
+ const u1* buf)
+{
+ ArrayObject* arrayObj = (ArrayObject*) objectIdToObject(arrayId);
+ u1* data = (u1*)arrayObj->contents;
+ u1 tag;
+
+ assert(dvmIsArray(arrayObj));
+
+ if (firstIndex + count > (int)arrayObj->length) {
+ LOGW("Attempt to set index=%d + count=%d excceds length=%d\n",
+ firstIndex, count, arrayObj->length);
+ return false;
+ }
+
+ tag = dvmDbgGetSignatureTag(arrayObj->obj.clazz->descriptor + 1);
+
+ if (isTagPrimitive(tag)) {
+ int width = dvmDbgGetTagWidth(tag);
+
+ LOGV(" --> setting %d '%c' width=%d\n", count, tag, width);
+
+ copyValuesFromBE(data + firstIndex*width, buf, count, width);
+ } else {
+ Object** pObjects;
+ int i;
+
+ pObjects = (Object**) data;
+ pObjects += firstIndex;
+
+ LOGV(" --> setting %d objects", count);
+
+ /* should do array type check here */
+ for (i = 0; i < count; i++) {
+ ObjectId id = dvmReadObjectId(&buf);
+ *pObjects++ = objectIdToObject(id);
+ }
+ }
+
+ return true;
+}
+
+/*
+ * Create a new string.
+ *
+ * The only place the reference will be held in the VM is in our registry.
+ */
+ObjectId dvmDbgCreateString(const char* str)
+{
+ StringObject* strObj;
+
+ strObj = dvmCreateStringFromCstr(str, ALLOC_DEFAULT);
+ dvmReleaseTrackedAlloc((Object*) strObj, NULL);
+ return objectToObjectId((Object*) strObj);
+}
+
+/*
+ * Determine if "instClassId" is an instance of "classId".
+ */
+bool dvmDbgMatchType(RefTypeId instClassId, RefTypeId classId)
+{
+ ClassObject* instClazz = refTypeIdToClassObject(instClassId);
+ ClassObject* clazz = refTypeIdToClassObject(classId);
+
+ return dvmInstanceof(instClazz, clazz);
+}
+
+
+/*
+ * ===========================================================================
+ * Method and Field
+ * ===========================================================================
+ */
+
+/*
+ * Get the method name from a MethodId.
+ */
+const char* dvmDbgGetMethodName(RefTypeId refTypeId, MethodId id)
+{
+ Method* meth;
+
+ meth = methodIdToMethod(refTypeId, id);
+ return meth->name;
+}
+
+/*
+ * For ReferenceType.Fields and ReferenceType.FieldsWithGeneric:
+ * output all fields declared by the class. Inerhited fields are
+ * not included.
+ */
+void dvmDbgOutputAllFields(RefTypeId refTypeId, bool withGeneric,
+ ExpandBuf* pReply)
+{
+ static const u1 genericSignature[1] = "";
+ ClassObject* clazz;
+ Field* field;
+ u4 declared;
+ int i;
+
+ clazz = refTypeIdToClassObject(refTypeId);
+ assert(clazz != NULL);
+
+ declared = clazz->sfieldCount + clazz->ifieldCount;
+ expandBufAdd4BE(pReply, declared);
+
+ for (i = 0; i < clazz->sfieldCount; i++) {
+ field = (Field*) &clazz->sfields[i];
+
+ expandBufAddFieldId(pReply, fieldToFieldId(field));
+ expandBufAddUtf8String(pReply, (const u1*) field->name);
+ expandBufAddUtf8String(pReply, (const u1*) field->signature);
+ if (withGeneric)
+ expandBufAddUtf8String(pReply, genericSignature);
+ expandBufAdd4BE(pReply, field->accessFlags);
+ }
+ for (i = 0; i < clazz->ifieldCount; i++) {
+ field = (Field*) &clazz->ifields[i];
+
+ expandBufAddFieldId(pReply, fieldToFieldId(field));
+ expandBufAddUtf8String(pReply, (const u1*) field->name);
+ expandBufAddUtf8String(pReply, (const u1*) field->signature);
+ if (withGeneric)
+ expandBufAddUtf8String(pReply, genericSignature);
+ expandBufAdd4BE(pReply, field->accessFlags);
+ }
+}
+
+/*
+ * For ReferenceType.Methods and ReferenceType.MethodsWithGeneric:
+ * output all methods declared by the class. Inherited methods are
+ * not included.
+ */
+void dvmDbgOutputAllMethods(RefTypeId refTypeId, bool withGeneric,
+ ExpandBuf* pReply)
+{
+ DexStringCache stringCache;
+ static const u1 genericSignature[1] = "";
+ ClassObject* clazz;
+ Method* meth;
+ u4 declared;
+ int i;
+
+ dexStringCacheInit(&stringCache);
+
+ clazz = refTypeIdToClassObject(refTypeId);
+ assert(clazz != NULL);
+
+ declared = clazz->directMethodCount + clazz->virtualMethodCount;
+ expandBufAdd4BE(pReply, declared);
+
+ for (i = 0; i < clazz->directMethodCount; i++) {
+ meth = &clazz->directMethods[i];
+
+ expandBufAddMethodId(pReply, methodToMethodId(meth));
+ expandBufAddUtf8String(pReply, (const u1*) meth->name);
+
+ expandBufAddUtf8String(pReply,
+ (const u1*) dexProtoGetMethodDescriptor(&meth->prototype,
+ &stringCache));
+
+ if (withGeneric)
+ expandBufAddUtf8String(pReply, genericSignature);
+ expandBufAdd4BE(pReply, meth->accessFlags);
+ }
+ for (i = 0; i < clazz->virtualMethodCount; i++) {
+ meth = &clazz->virtualMethods[i];
+
+ expandBufAddMethodId(pReply, methodToMethodId(meth));
+ expandBufAddUtf8String(pReply, (const u1*) meth->name);
+
+ expandBufAddUtf8String(pReply,
+ (const u1*) dexProtoGetMethodDescriptor(&meth->prototype,
+ &stringCache));
+
+ if (withGeneric)
+ expandBufAddUtf8String(pReply, genericSignature);
+ expandBufAdd4BE(pReply, meth->accessFlags);
+ }
+
+ dexStringCacheRelease(&stringCache);
+}
+
+/*
+ * Output all interfaces directly implemented by the class.
+ */
+void dvmDbgOutputAllInterfaces(RefTypeId refTypeId, ExpandBuf* pReply)
+{
+ ClassObject* clazz;
+ int i, start, count;
+
+ clazz = refTypeIdToClassObject(refTypeId);
+ assert(clazz != NULL);
+
+ if (clazz->super == NULL)
+ start = 0;
+ else
+ start = clazz->super->iftableCount;
+
+ count = clazz->iftableCount - start;
+ expandBufAdd4BE(pReply, count);
+ for (i = start; i < clazz->iftableCount; i++) {
+ ClassObject* iface = clazz->iftable[i].clazz;
+ expandBufAddRefTypeId(pReply, classObjectToRefTypeId(iface));
+ }
+}
+
+typedef struct DebugCallbackContext {
+ int numItems;
+ ExpandBuf* pReply;
+ // used by locals table
+ bool withGeneric;
+} DebugCallbackContext;
+
+static int lineTablePositionsCb(void *cnxt, u4 address, u4 lineNum)
+{
+ DebugCallbackContext *pContext = (DebugCallbackContext *)cnxt;
+
+ expandBufAdd8BE(pContext->pReply, address);
+ expandBufAdd4BE(pContext->pReply, lineNum);
+ pContext->numItems++;
+
+ return 0;
+}
+
+/*
+ * For Method.LineTable: output the line table.
+ *
+ * Note we operate in Dalvik's 16-bit units rather than bytes.
+ */
+void dvmDbgOutputLineTable(RefTypeId refTypeId, MethodId methodId,
+ ExpandBuf* pReply)
+{
+ Method* method;
+ u8 start, end;
+ int i;
+ DebugCallbackContext context;
+
+ memset (&context, 0, sizeof(DebugCallbackContext));
+
+ method = methodIdToMethod(refTypeId, methodId);
+ if (dvmIsNativeMethod(method)) {
+ start = (u8) -1;
+ end = (u8) -1;
+ } else {
+ start = 0;
+ end = dvmGetMethodInsnsSize(method);
+ }
+
+ expandBufAdd8BE(pReply, start);
+ expandBufAdd8BE(pReply, end);
+
+ // Add numLines later
+ size_t numLinesOffset = expandBufGetLength(pReply);
+ expandBufAdd4BE(pReply, 0);
+
+ context.pReply = pReply;
+
+ dexDecodeDebugInfo(method->clazz->pDvmDex->pDexFile,
+ dvmGetMethodCode(method),
+ method->clazz->descriptor,
+ method->prototype.protoIdx,
+ method->accessFlags,
+ lineTablePositionsCb, NULL, &context);
+
+ set4BE(expandBufGetBuffer(pReply) + numLinesOffset, context.numItems);
+}
+
+/*
+ * Eclipse appears to expect that the "this" reference is in slot zero.
+ * If it's not, the "variables" display will show two copies of "this",
+ * possibly because it gets "this" from SF.ThisObject and then displays
+ * all locals with nonzero slot numbers.
+ *
+ * So, we remap the item in slot 0 to 1000, and remap "this" to zero. On
+ * SF.GetValues / SF.SetValues we map them back.
+ */
+static int tweakSlot(int slot, const char* name)
+{
+ int newSlot = slot;
+
+ if (strcmp(name, "this") == 0) // only remap "this" ptr
+ newSlot = 0;
+ else if (slot == 0) // always remap slot 0
+ newSlot = kSlot0Sub;
+
+ LOGV("untweak: %d to %d\n", slot, newSlot);
+ return newSlot;
+}
+
+/*
+ * Reverse Eclipse hack.
+ */
+static int untweakSlot(int slot, const void* framePtr)
+{
+ int newSlot = slot;
+
+ if (slot == kSlot0Sub) {
+ newSlot = 0;
+ } else if (slot == 0) {
+ const StackSaveArea* saveArea = SAVEAREA_FROM_FP(framePtr);
+ const Method* method = saveArea->method;
+ newSlot = method->registersSize - method->insSize;
+ }
+
+ LOGV("untweak: %d to %d\n", slot, newSlot);
+ return newSlot;
+}
+
+static void variableTableCb (void *cnxt, u2 reg, u4 startAddress,
+ u4 endAddress, const char *name, const char *descriptor,
+ const char *signature)
+{
+ DebugCallbackContext *pContext = (DebugCallbackContext *)cnxt;
+
+ reg = (u2) tweakSlot(reg, name);
+
+ LOGV(" %2d: %d(%d) '%s' '%s' slot=%d\n",
+ pContext->numItems, startAddress, endAddress - startAddress,
+ name, descriptor, reg);
+
+ expandBufAdd8BE(pContext->pReply, startAddress);
+ expandBufAddUtf8String(pContext->pReply, (const u1*)name);
+ expandBufAddUtf8String(pContext->pReply, (const u1*)descriptor);
+ if (pContext->withGeneric) {
+ expandBufAddUtf8String(pContext->pReply, (const u1*) signature);
+ }
+ expandBufAdd4BE(pContext->pReply, endAddress - startAddress);
+ expandBufAdd4BE(pContext->pReply, reg);
+
+ pContext->numItems++;
+}
+
+/*
+ * For Method.VariableTable[WithGeneric]: output information about local
+ * variables for the specified method.
+ */
+void dvmDbgOutputVariableTable(RefTypeId refTypeId, MethodId methodId,
+ bool withGeneric, ExpandBuf* pReply)
+{
+ Method* method;
+ DebugCallbackContext context;
+
+ memset (&context, 0, sizeof(DebugCallbackContext));
+
+ method = methodIdToMethod(refTypeId, methodId);
+
+ expandBufAdd4BE(pReply, method->insSize);
+
+ // Add numLocals later
+ size_t numLocalsOffset = expandBufGetLength(pReply);
+ expandBufAdd4BE(pReply, 0);
+
+ context.pReply = pReply;
+ context.withGeneric = withGeneric;
+ dexDecodeDebugInfo(method->clazz->pDvmDex->pDexFile,
+ dvmGetMethodCode(method),
+ method->clazz->descriptor,
+ method->prototype.protoIdx,
+ method->accessFlags,
+ NULL, variableTableCb, &context);
+
+ set4BE(expandBufGetBuffer(pReply) + numLocalsOffset, context.numItems);
+}
+
+/*
+ * Get the type tag for the field's type.
+ */
+int dvmDbgGetFieldTag(ObjectId objId, FieldId fieldId)
+{
+ Object* obj = objectIdToObject(objId);
+ RefTypeId classId = classObjectToRefTypeId(obj->clazz);
+ Field* field = fieldIdToField(classId, fieldId);
+
+ return dvmDbgGetSignatureTag(field->signature);
+}
+
+/*
+ * Get the type tag for the static field's type.
+ */
+int dvmDbgGetStaticFieldTag(RefTypeId refTypeId, FieldId fieldId)
+{
+ Field* field = fieldIdToField(refTypeId, fieldId);
+ return dvmDbgGetSignatureTag(field->signature);
+}
+
+/*
+ * Copy the value of a field into the specified buffer.
+ */
+void dvmDbgGetFieldValue(ObjectId objectId, FieldId fieldId, u1* buf,
+ int expectedLen)
+{
+ Object* obj = objectIdToObject(objectId);
+ RefTypeId classId = classObjectToRefTypeId(obj->clazz);
+ InstField* field = (InstField*) fieldIdToField(classId, fieldId);
+ Object* objVal;
+ u4 intVal;
+ u8 longVal;
+
+ switch (field->field.signature[0]) {
+ case JT_BOOLEAN:
+ assert(expectedLen == 1);
+ intVal = dvmGetFieldBoolean(obj, field->byteOffset);
+ set1(buf, intVal != 0);
+ break;
+ case JT_BYTE:
+ assert(expectedLen == 1);
+ intVal = dvmGetFieldInt(obj, field->byteOffset);
+ set1(buf, intVal);
+ break;
+ case JT_SHORT:
+ case JT_CHAR:
+ assert(expectedLen == 2);
+ intVal = dvmGetFieldInt(obj, field->byteOffset);
+ set2BE(buf, intVal);
+ break;
+ case JT_INT:
+ case JT_FLOAT:
+ assert(expectedLen == 4);
+ intVal = dvmGetFieldInt(obj, field->byteOffset);
+ set4BE(buf, intVal);
+ break;
+ case JT_ARRAY:
+ case JT_OBJECT:
+ assert(expectedLen == sizeof(ObjectId));
+ objVal = dvmGetFieldObject(obj, field->byteOffset);
+ dvmSetObjectId(buf, objectToObjectId(objVal));
+ break;
+ case JT_DOUBLE:
+ case JT_LONG:
+ assert(expectedLen == 8);
+ longVal = dvmGetFieldLong(obj, field->byteOffset);
+ set8BE(buf, longVal);
+ break;
+ default:
+ LOGE("ERROR: unhandled class type '%s'\n", field->field.signature);
+ assert(false);
+ break;
+ }
+}
+
+/*
+ * Set the value of the specified field.
+ */
+void dvmDbgSetFieldValue(ObjectId objectId, FieldId fieldId, u8 value,
+ int width)
+{
+ Object* obj = objectIdToObject(objectId);
+ RefTypeId classId = classObjectToRefTypeId(obj->clazz);
+ InstField* field = (InstField*) fieldIdToField(classId, fieldId);
+
+ switch (field->field.signature[0]) {
+ case JT_BOOLEAN:
+ assert(width == 1);
+ dvmSetFieldBoolean(obj, field->byteOffset, value != 0);
+ break;
+ case JT_BYTE:
+ assert(width == 1);
+ dvmSetFieldInt(obj, field->byteOffset, value);
+ break;
+ case JT_SHORT:
+ case JT_CHAR:
+ assert(width == 2);
+ dvmSetFieldInt(obj, field->byteOffset, value);
+ break;
+ case JT_INT:
+ case JT_FLOAT:
+ assert(width == 4);
+ dvmSetFieldInt(obj, field->byteOffset, value);
+ break;
+ case JT_ARRAY:
+ case JT_OBJECT:
+ assert(width == sizeof(ObjectId));
+ dvmSetFieldObject(obj, field->byteOffset, objectIdToObject(value));
+ break;
+ case JT_DOUBLE:
+ case JT_LONG:
+ assert(width == 8);
+ dvmSetFieldLong(obj, field->byteOffset, value);
+ break;
+ default:
+ LOGE("ERROR: unhandled class type '%s'\n", field->field.signature);
+ assert(false);
+ break;
+ }
+}
+
+/*
+ * Copy the value of a static field into the specified buffer.
+ */
+void dvmDbgGetStaticFieldValue(RefTypeId refTypeId, FieldId fieldId, u1* buf,
+ int expectedLen)
+{
+ StaticField* sfield = (StaticField*) fieldIdToField(refTypeId, fieldId);
+ Object* objVal;
+ JValue value;
+
+ switch (sfield->field.signature[0]) {
+ case JT_BOOLEAN:
+ assert(expectedLen == 1);
+ set1(buf, dvmGetStaticFieldBoolean(sfield));
+ break;
+ case JT_BYTE:
+ assert(expectedLen == 1);
+ set1(buf, dvmGetStaticFieldByte(sfield));
+ break;
+ case JT_SHORT:
+ assert(expectedLen == 2);
+ set2BE(buf, dvmGetStaticFieldShort(sfield));
+ break;
+ case JT_CHAR:
+ assert(expectedLen == 2);
+ set2BE(buf, dvmGetStaticFieldChar(sfield));
+ break;
+ case JT_INT:
+ assert(expectedLen == 4);
+ set4BE(buf, dvmGetStaticFieldInt(sfield));
+ break;
+ case JT_FLOAT:
+ assert(expectedLen == 4);
+ value.f = dvmGetStaticFieldFloat(sfield);
+ set4BE(buf, value.i);
+ break;
+ case JT_ARRAY:
+ case JT_OBJECT:
+ assert(expectedLen == sizeof(ObjectId));
+ objVal = dvmGetStaticFieldObject(sfield);
+ dvmSetObjectId(buf, objectToObjectId(objVal));
+ break;
+ case JT_LONG:
+ assert(expectedLen == 8);
+ set8BE(buf, dvmGetStaticFieldLong(sfield));
+ break;
+ case JT_DOUBLE:
+ assert(expectedLen == 8);
+ value.d = dvmGetStaticFieldDouble(sfield);
+ set8BE(buf, value.j);
+ break;
+ default:
+ LOGE("ERROR: unhandled class type '%s'\n", sfield->field.signature);
+ assert(false);
+ break;
+ }
+}
+
+/*
+ * Set the value of a static field.
+ */
+void dvmDbgSetStaticFieldValue(RefTypeId refTypeId, FieldId fieldId,
+ u8 rawValue, int width)
+{
+ StaticField* sfield = (StaticField*) fieldIdToField(refTypeId, fieldId);
+ Object* objVal;
+ JValue value;
+
+ value.j = rawValue;
+
+ switch (sfield->field.signature[0]) {
+ case JT_BOOLEAN:
+ assert(width == 1);
+ dvmSetStaticFieldBoolean(sfield, value.z);
+ break;
+ case JT_BYTE:
+ assert(width == 1);
+ dvmSetStaticFieldByte(sfield, value.b);
+ break;
+ case JT_SHORT:
+ assert(width == 2);
+ dvmSetStaticFieldShort(sfield, value.s);
+ break;
+ case JT_CHAR:
+ assert(width == 2);
+ dvmSetStaticFieldChar(sfield, value.c);
+ break;
+ case JT_INT:
+ assert(width == 4);
+ dvmSetStaticFieldInt(sfield, value.i);
+ break;
+ case JT_FLOAT:
+ assert(width == 4);
+ dvmSetStaticFieldFloat(sfield, value.f);
+ break;
+ case JT_ARRAY:
+ case JT_OBJECT:
+ assert(width == sizeof(ObjectId));
+ objVal = objectIdToObject(rawValue);
+ dvmSetStaticFieldObject(sfield, objVal);
+ break;
+ case JT_LONG:
+ assert(width == 8);
+ dvmSetStaticFieldLong(sfield, value.j);
+ break;
+ case JT_DOUBLE:
+ assert(width == 8);
+ dvmSetStaticFieldDouble(sfield, value.d);
+ break;
+ default:
+ LOGE("ERROR: unhandled class type '%s'\n", sfield->field.signature);
+ assert(false);
+ break;
+ }
+}
+
+/*
+ * Convert a string object to a UTF-8 string.
+ *
+ * Returns a newly-allocated string.
+ */
+char* dvmDbgStringToUtf8(ObjectId strId)
+{
+ StringObject* strObj = (StringObject*) objectIdToObject(strId);
+
+ return dvmCreateCstrFromString(strObj);
+}
+
+
+/*
+ * ===========================================================================
+ * Thread and ThreadGroup
+ * ===========================================================================
+ */
+
+/*
+ * Convert a thread object to a Thread ptr.
+ *
+ * This currently requires running through the list of threads and finding
+ * a match.
+ *
+ * IMPORTANT: grab gDvm.threadListLock before calling here.
+ */
+static Thread* threadObjToThread(Object* threadObj)
+{
+ Thread* thread;
+
+ for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
+ if (thread->threadObj == threadObj)
+ break;
+ }
+
+ return thread;
+}
+
+/*
+ * Get the status and suspend state of a thread.
+ */
+bool dvmDbgGetThreadStatus(ObjectId threadId, u4* pThreadStatus,
+ u4* pSuspendStatus)
+{
+ Object* threadObj;
+ Thread* thread;
+ bool result = false;
+
+ threadObj = objectIdToObject(threadId);
+ assert(threadObj != NULL);
+
+ /* lock the thread list, so the thread doesn't vanish while we work */
+ dvmLockThreadList(NULL);
+
+ thread = threadObjToThread(threadObj);
+ if (thread == NULL)
+ goto bail;
+
+ switch (thread->status) {
+ case THREAD_ZOMBIE: *pThreadStatus = TS_ZOMBIE; break;
+ case THREAD_RUNNING: *pThreadStatus = TS_RUNNING; break;
+ case THREAD_TIMED_WAIT: *pThreadStatus = TS_SLEEPING; break;
+ case THREAD_MONITOR: *pThreadStatus = TS_MONITOR; break;
+ case THREAD_WAIT: *pThreadStatus = TS_WAIT; break;
+ case THREAD_INITIALIZING: *pThreadStatus = TS_ZOMBIE; break;
+ case THREAD_STARTING: *pThreadStatus = TS_ZOMBIE; break;
+ case THREAD_NATIVE: *pThreadStatus = TS_RUNNING; break;
+ case THREAD_VMWAIT: *pThreadStatus = TS_WAIT; break;
+ default:
+ assert(false);
+ *pThreadStatus = THREAD_ZOMBIE;
+ break;
+ }
+
+ if (dvmIsSuspended(thread))
+ *pSuspendStatus = SUSPEND_STATUS_SUSPENDED;
+ else
+ *pSuspendStatus = 0;
+
+ result = true;
+
+bail:
+ dvmUnlockThreadList();
+ return result;
+}
+
+/*
+ * Get the thread's suspend count.
+ */
+u4 dvmDbgGetThreadSuspendCount(ObjectId threadId)
+{
+ Object* threadObj;
+ Thread* thread;
+ u4 result = 0;
+
+ threadObj = objectIdToObject(threadId);
+ assert(threadObj != NULL);
+
+ /* lock the thread list, so the thread doesn't vanish while we work */
+ dvmLockThreadList(NULL);
+
+ thread = threadObjToThread(threadObj);
+ if (thread == NULL)
+ goto bail;
+
+ result = thread->suspendCount;
+
+bail:
+ dvmUnlockThreadList();
+ return result;
+}
+
+/*
+ * Determine whether or not a thread exists in the VM's thread list.
+ *
+ * Returns "true" if the thread exists.
+ */
+bool dvmDbgThreadExists(ObjectId threadId)
+{
+ Object* threadObj;
+ Thread* thread;
+ bool result;
+
+ threadObj = objectIdToObject(threadId);
+ assert(threadObj != NULL);
+
+ /* lock the thread list, so the thread doesn't vanish while we work */
+ dvmLockThreadList(NULL);
+
+ thread = threadObjToThread(threadObj);
+ if (thread == NULL)
+ result = false;
+ else
+ result = true;
+
+ dvmUnlockThreadList();
+ return result;
+}
+
+/*
+ * Determine whether or not a thread is suspended.
+ *
+ * Returns "false" if the thread is running or doesn't exist.
+ */
+bool dvmDbgIsSuspended(ObjectId threadId)
+{
+ Object* threadObj;
+ Thread* thread;
+ bool result = false;
+
+ threadObj = objectIdToObject(threadId);
+ assert(threadObj != NULL);
+
+ /* lock the thread list, so the thread doesn't vanish while we work */
+ dvmLockThreadList(NULL);
+
+ thread = threadObjToThread(threadObj);
+ if (thread == NULL)
+ goto bail;
+
+ result = dvmIsSuspended(thread);
+
+bail:
+ dvmUnlockThreadList();
+ return result;
+}
+
+#if 0
+/*
+ * Wait until a thread suspends.
+ *
+ * We stray from the usual pattern here, and release the thread list lock
+ * before we use the Thread. This is necessary and should be safe in this
+ * circumstance; see comments in dvmWaitForSuspend().
+ */
+void dvmDbgWaitForSuspend(ObjectId threadId)
+{
+ Object* threadObj;
+ Thread* thread;
+
+ threadObj = objectIdToObject(threadId);
+ assert(threadObj != NULL);
+
+ dvmLockThreadList(NULL);
+ thread = threadObjToThread(threadObj);
+ dvmUnlockThreadList();
+
+ if (thread != NULL)
+ dvmWaitForSuspend(thread);
+}
+#endif
+
+
+/*
+ * Return the ObjectId for the "system" thread group.
+ */
+ObjectId dvmDbgGetSystemThreadGroupId(void)
+{
+ Object* groupObj = dvmGetSystemThreadGroup();
+ return objectToObjectId(groupObj);
+}
+
+/*
+ * Return the ObjectId for the "system" thread group.
+ */
+ObjectId dvmDbgGetMainThreadGroupId(void)
+{
+ Object* groupObj = dvmGetMainThreadGroup();
+ return objectToObjectId(groupObj);
+}
+
+/*
+ * Get the name of a thread.
+ *
+ * Returns a newly-allocated string.
+ */
+char* dvmDbgGetThreadName(ObjectId threadId)
+{
+ Object* threadObj;
+ StringObject* nameStr;
+ char* str;
+ char* result;
+
+ threadObj = objectIdToObject(threadId);
+ assert(threadObj != NULL);
+
+ nameStr = (StringObject*) dvmGetFieldObject(threadObj,
+ gDvm.offJavaLangThread_name);
+ str = dvmCreateCstrFromString(nameStr);
+ result = (char*) malloc(strlen(str) + 20);
+
+ /* lock the thread list, so the thread doesn't vanish while we work */
+ dvmLockThreadList(NULL);
+ Thread* thread = threadObjToThread(threadObj);
+ if (thread != NULL)
+ sprintf(result, "<%d> %s", thread->threadId, str);
+ else
+ sprintf(result, "%s", str);
+ dvmUnlockThreadList();
+
+ free(str);
+ return result;
+}
+
+/*
+ * Get a thread's group.
+ */
+ObjectId dvmDbgGetThreadGroup(ObjectId threadId)
+{
+ Object* threadObj;
+ Object* group;
+
+ threadObj = objectIdToObject(threadId);
+ assert(threadObj != NULL);
+
+ group = dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_group);
+ return objectToObjectId(group);
+}
+
+
+/*
+ * Get the name of a thread group.
+ *
+ * Returns a newly-allocated string.
+ */
+char* dvmDbgGetThreadGroupName(ObjectId threadGroupId)
+{
+ Object* threadGroup;
+ InstField* nameField;
+ StringObject* nameStr;
+
+ threadGroup = objectIdToObject(threadGroupId);
+ assert(threadGroup != NULL);
+
+ nameField = dvmFindInstanceField(gDvm.classJavaLangThreadGroup,
+ "name", "Ljava/lang/String;");
+ if (nameField == NULL) {
+ LOGE("unable to find name field in ThreadGroup\n");
+ return NULL;
+ }
+
+ nameStr = (StringObject*) dvmGetFieldObject(threadGroup,
+ nameField->byteOffset);
+ return dvmCreateCstrFromString(nameStr);
+}
+
+/*
+ * Get the parent of a thread group.
+ *
+ * Returns a newly-allocated string.
+ */
+ObjectId dvmDbgGetThreadGroupParent(ObjectId threadGroupId)
+{
+ Object* threadGroup;
+ InstField* parentField;
+ Object* parent;
+
+ threadGroup = objectIdToObject(threadGroupId);
+ assert(threadGroup != NULL);
+
+ parentField = dvmFindInstanceField(gDvm.classJavaLangThreadGroup,
+ "parent", "Ljava/lang/ThreadGroup;");
+ if (parentField == NULL) {
+ LOGE("unable to find parent field in ThreadGroup\n");
+ parent = NULL;
+ } else {
+ parent = dvmGetFieldObject(threadGroup, parentField->byteOffset);
+ }
+ return objectToObjectId(parent);
+}
+
+/*
+ * Get the list of threads in the thread group.
+ *
+ * We do this by running through the full list of threads and returning
+ * the ones that have the ThreadGroup object as their owner.
+ *
+ * If threadGroupId is set to "kAllThreads", we ignore the group field and
+ * return all threads.
+ *
+ * The caller must free "*ppThreadIds".
+ */
+void dvmDbgGetThreadGroupThreads(ObjectId threadGroupId,
+ ObjectId** ppThreadIds, u4* pThreadCount)
+{
+ Object* targetThreadGroup = NULL;
+ InstField* groupField = NULL;
+ Thread* thread;
+ int count;
+
+ if (threadGroupId != THREAD_GROUP_ALL) {
+ targetThreadGroup = objectIdToObject(threadGroupId);
+ assert(targetThreadGroup != NULL);
+ }
+
+ groupField = dvmFindInstanceField(gDvm.classJavaLangThread,
+ "group", "Ljava/lang/ThreadGroup;");
+
+ dvmLockThreadList(NULL);
+
+ thread = gDvm.threadList;
+ count = 0;
+ for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
+ Object* group;
+
+ /* Skip over the JDWP support thread. Some debuggers
+ * get bent out of shape when they can't suspend and
+ * query all threads, so it's easier if we just don't
+ * tell them about us.
+ */
+ if (thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState))
+ continue;
+
+ /* This thread is currently being created, and isn't ready
+ * to be seen by the debugger yet.
+ */
+ if (thread->threadObj == NULL)
+ continue;
+
+ group = dvmGetFieldObject(thread->threadObj, groupField->byteOffset);
+ if (threadGroupId == THREAD_GROUP_ALL || group == targetThreadGroup)
+ count++;
+ }
+
+ *pThreadCount = count;
+
+ if (count == 0) {
+ *ppThreadIds = NULL;
+ } else {
+ ObjectId* ptr;
+ ptr = *ppThreadIds = (ObjectId*) malloc(sizeof(ObjectId) * count);
+
+ for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
+ Object* group;
+
+ /* Skip over the JDWP support thread. Some debuggers
+ * get bent out of shape when they can't suspend and
+ * query all threads, so it's easier if we just don't
+ * tell them about us.
+ */
+ if (thread->handle == dvmJdwpGetDebugThread(gDvm.jdwpState))
+ continue;
+
+ /* This thread is currently being created, and isn't ready
+ * to be seen by the debugger yet.
+ */
+ if (thread->threadObj == NULL)
+ continue;
+
+ group = dvmGetFieldObject(thread->threadObj,groupField->byteOffset);
+ if (threadGroupId == THREAD_GROUP_ALL || group == targetThreadGroup)
+ {
+ *ptr++ = objectToObjectId(thread->threadObj);
+ count--;
+ }
+ }
+
+ assert(count == 0);
+ }
+
+ dvmUnlockThreadList();
+}
+
+/*
+ * Get all threads.
+ *
+ * The caller must free "*ppThreadIds".
+ */
+void dvmDbgGetAllThreads(ObjectId** ppThreadIds, u4* pThreadCount)
+{
+ dvmDbgGetThreadGroupThreads(THREAD_GROUP_ALL, ppThreadIds, pThreadCount);
+}
+
+
+/*
+ * Count up the #of frames on the thread's stack.
+ *
+ * Returns -1 on failure;
+ */
+int dvmDbgGetThreadFrameCount(ObjectId threadId)
+{
+ Object* threadObj;
+ Thread* thread;
+ void* framePtr;
+ u4 count = 0;
+
+ threadObj = objectIdToObject(threadId);
+
+ dvmLockThreadList(NULL);
+
+ thread = threadObjToThread(threadObj);
+ if (thread == NULL)
+ goto bail;
+
+ framePtr = thread->curFrame;
+ while (framePtr != NULL) {
+ if (!dvmIsBreakFrame(framePtr))
+ count++;
+
+ framePtr = SAVEAREA_FROM_FP(framePtr)->prevFrame;
+ }
+
+bail:
+ dvmUnlockThreadList();
+ return count;
+}
+
+/*
+ * Get info for frame N from the specified thread's stack.
+ */
+bool dvmDbgGetThreadFrame(ObjectId threadId, int num, FrameId* pFrameId,
+ JdwpLocation* pLoc)
+{
+ Object* threadObj;
+ Thread* thread;
+ void* framePtr;
+ int count;
+
+ threadObj = objectIdToObject(threadId);
+
+ dvmLockThreadList(NULL);
+
+ thread = threadObjToThread(threadObj);
+ if (thread == NULL)
+ goto bail;
+
+ framePtr = thread->curFrame;
+ count = 0;
+ while (framePtr != NULL) {
+ const StackSaveArea* saveArea = SAVEAREA_FROM_FP(framePtr);
+ const Method* method = saveArea->method;
+
+ if (!dvmIsBreakFrame(framePtr)) {
+ if (count == num) {
+ *pFrameId = frameToFrameId(framePtr);
+ if (dvmIsInterfaceClass(method->clazz))
+ pLoc->typeTag = TT_INTERFACE;
+ else
+ pLoc->typeTag = TT_CLASS;
+ pLoc->classId = classObjectToRefTypeId(method->clazz);
+ pLoc->methodId = methodToMethodId(method);
+ if (dvmIsNativeMethod(method))
+ pLoc->idx = (u8)-1;
+ else
+ pLoc->idx = saveArea->xtra.currentPc - method->insns;
+ dvmUnlockThreadList();
+ return true;
+ }
+
+ count++;
+ }
+
+ framePtr = saveArea->prevFrame;
+ }
+
+bail:
+ dvmUnlockThreadList();
+ return false;
+}
+
+/*
+ * Get the ThreadId for the current thread.
+ */
+ObjectId dvmDbgGetThreadSelfId(void)
+{
+ Thread* self = dvmThreadSelf();
+ return objectToObjectId(self->threadObj);
+}
+
+/*
+ * Suspend the VM.
+ */
+void dvmDbgSuspendVM(bool isEvent)
+{
+ dvmSuspendAllThreads(isEvent ? SUSPEND_FOR_DEBUG_EVENT : SUSPEND_FOR_DEBUG);
+}
+
+/*
+ * Resume the VM.
+ */
+void dvmDbgResumeVM()
+{
+ dvmResumeAllThreads(SUSPEND_FOR_DEBUG);
+}
+
+/*
+ * Suspend one thread (not ourselves).
+ */
+void dvmDbgSuspendThread(ObjectId threadId)
+{
+ Object* threadObj = objectIdToObject(threadId);
+ Thread* thread;
+
+ dvmLockThreadList(NULL);
+
+ thread = threadObjToThread(threadObj);
+ if (thread == NULL) {
+ /* can happen if our ThreadDeath notify crosses in the mail */
+ LOGW("WARNING: threadid=%llx obj=%p no match\n", threadId, threadObj);
+ } else {
+ dvmSuspendThread(thread);
+ }
+
+ dvmUnlockThreadList();
+}
+
+/*
+ * Resume one thread (not ourselves).
+ */
+void dvmDbgResumeThread(ObjectId threadId)
+{
+ Object* threadObj = objectIdToObject(threadId);
+ Thread* thread;
+
+ dvmLockThreadList(NULL);
+
+ thread = threadObjToThread(threadObj);
+ if (thread == NULL) {
+ LOGW("WARNING: threadid=%llx obj=%p no match\n", threadId, threadObj);
+ } else {
+ dvmResumeThread(thread);
+ }
+
+ dvmUnlockThreadList();
+}
+
+/*
+ * Suspend ourselves after sending an event to the debugger.
+ */
+void dvmDbgSuspendSelf(void)
+{
+ dvmSuspendSelf(true);
+}
+
+/*
+ * Get the "this" object for the specified frame.
+ */
+static Object* getThisObject(const u4* framePtr)
+{
+ const StackSaveArea* saveArea = SAVEAREA_FROM_FP(framePtr);
+ const Method* method = saveArea->method;
+ int argOffset = method->registersSize - method->insSize;
+ Object* thisObj;
+
+ if (method == NULL) {
+ /* this is a "break" frame? */
+ assert(false);
+ return NULL;
+ }
+
+ LOGVV(" Pulling this object for frame at %p\n", framePtr);
+ LOGVV(" Method='%s' native=%d static=%d this=%p\n",
+ method->name, dvmIsNativeMethod(method),
+ dvmIsStaticMethod(method), (Object*) framePtr[argOffset]);
+
+ /*
+ * No "this" pointer for statics. No args on the interp stack for
+ * native methods invoked directly from the VM.
+ */
+ if (dvmIsNativeMethod(method) || dvmIsStaticMethod(method))
+ thisObj = NULL;
+ else
+ thisObj = (Object*) framePtr[argOffset];
+
+ if (thisObj != NULL && !dvmIsValidObject(thisObj)) {
+ LOGW("Debugger: invalid 'this' pointer %p in %s.%s; returning NULL\n",
+ framePtr, method->clazz->descriptor, method->name);
+ thisObj = NULL;
+ }
+
+ return thisObj;
+}
+
+/*
+ * Return the "this" object for the specified frame. The thread must be
+ * suspended.
+ */
+bool dvmDbgGetThisObject(ObjectId threadId, FrameId frameId, ObjectId* pThisId)
+{
+ const u4* framePtr = frameIdToFrame(frameId);
+ Object* thisObj;
+
+ UNUSED_PARAMETER(threadId);
+
+ thisObj = getThisObject(framePtr);
+
+ *pThisId = objectToObjectId(thisObj);
+ return true;
+}
+
+/*
+ * Copy the value of a method argument or local variable into the
+ * specified buffer. The value will be preceeded with the tag.
+ */
+void dvmDbgGetLocalValue(ObjectId threadId, FrameId frameId, int slot,
+ u1 tag, u1* buf, int expectedLen)
+{
+ const u4* framePtr = frameIdToFrame(frameId);
+ Object* objVal;
+ u4 intVal;
+ u8 longVal;
+
+ UNUSED_PARAMETER(threadId);
+
+ slot = untweakSlot(slot, framePtr); // Eclipse workaround
+
+ switch (tag) {
+ case JT_BOOLEAN:
+ assert(expectedLen == 1);
+ intVal = framePtr[slot];
+ set1(buf+1, intVal != 0);
+ break;
+ case JT_BYTE:
+ assert(expectedLen == 1);
+ intVal = framePtr[slot];
+ set1(buf+1, intVal);
+ break;
+ case JT_SHORT:
+ case JT_CHAR:
+ assert(expectedLen == 2);
+ intVal = framePtr[slot];
+ set2BE(buf+1, intVal);
+ break;
+ case JT_INT:
+ case JT_FLOAT:
+ assert(expectedLen == 4);
+ intVal = framePtr[slot];
+ set4BE(buf+1, intVal);
+ break;
+ case JT_ARRAY:
+ assert(expectedLen == 8);
+ {
+ /* convert to "ObjectId" */
+ objVal = (Object*)framePtr[slot];
+ if (objVal != NULL && !dvmIsValidObject(objVal)) {
+ LOGW("JDWP: slot %d expected to hold array, %p invalid\n",
+ slot, objVal);
+ dvmAbort(); // DEBUG: make it obvious
+ objVal = NULL;
+ tag = JT_OBJECT; // JT_ARRAY not expected for NULL ref
+ }
+ dvmSetObjectId(buf+1, objectToObjectId(objVal));
+ }
+ break;
+ case JT_OBJECT:
+ assert(expectedLen == 8);
+ {
+ /* convert to "ObjectId" */
+ objVal = (Object*)framePtr[slot];
+ //char* name;
+
+ if (objVal != NULL) {
+ if (!dvmIsValidObject(objVal)) {
+ LOGW("JDWP: slot %d expected to hold object, %p invalid\n",
+ slot, objVal);
+ dvmAbort(); // DEBUG: make it obvious
+ objVal = NULL;
+ }
+ //name = generateJNISignature(objVal->clazz);
+ tag = resultTagFromObject(objVal);
+ //free(name);
+ } else {
+ tag = JT_OBJECT;
+ }
+ dvmSetObjectId(buf+1, objectToObjectId(objVal));
+ }
+ break;
+ case JT_DOUBLE:
+ case JT_LONG:
+ assert(expectedLen == 8);
+ longVal = *(u8*)(&framePtr[slot]);
+ set8BE(buf+1, longVal);
+ break;
+ default:
+ LOGE("ERROR: unhandled tag '%c'\n", tag);
+ assert(false);
+ break;
+ }
+
+ set1(buf, tag);
+}
+
+/*
+ * Copy a new value into an argument or local variable.
+ */
+void dvmDbgSetLocalValue(ObjectId threadId, FrameId frameId, int slot, u1 tag,
+ u8 value, int width)
+{
+ u4* framePtr = frameIdToFrame(frameId);
+
+ UNUSED_PARAMETER(threadId);
+
+ slot = untweakSlot(slot, framePtr); // Eclipse workaround
+
+ switch (tag) {
+ case JT_BOOLEAN:
+ assert(width == 1);
+ framePtr[slot] = (u4)value;
+ break;
+ case JT_BYTE:
+ assert(width == 1);
+ framePtr[slot] = (u4)value;
+ break;
+ case JT_SHORT:
+ case JT_CHAR:
+ assert(width == 2);
+ framePtr[slot] = (u4)value;
+ break;
+ case JT_INT:
+ case JT_FLOAT:
+ assert(width == 4);
+ framePtr[slot] = (u4)value;
+ break;
+ case JT_STRING:
+ /* The debugger calls VirtualMachine.CreateString to create a new
+ * string, then uses this to set the object reference, when you
+ * edit a String object */
+ case JT_ARRAY:
+ case JT_OBJECT:
+ assert(width == sizeof(ObjectId));
+ framePtr[slot] = (u4) objectIdToObject(value);
+ break;
+ case JT_DOUBLE:
+ case JT_LONG:
+ assert(width == 8);
+ *(u8*)(&framePtr[slot]) = value;
+ break;
+ case JT_VOID:
+ case JT_CLASS_OBJECT:
+ case JT_THREAD:
+ case JT_THREAD_GROUP:
+ case JT_CLASS_LOADER:
+ default:
+ LOGE("ERROR: unhandled tag '%c'\n", tag);
+ assert(false);
+ break;
+ }
+}
+
+
+/*
+ * ===========================================================================
+ * Debugger notification
+ * ===========================================================================
+ */
+
+/*
+ * Tell JDWP that a breakpoint address has been reached.
+ *
+ * "pcOffset" will be -1 for native methods.
+ * "thisPtr" will be NULL for static methods.
+ */
+void dvmDbgPostLocationEvent(const Method* method, int pcOffset,
+ Object* thisPtr, int eventFlags)
+{
+ JdwpLocation loc;
+
+ if (dvmIsInterfaceClass(method->clazz))
+ loc.typeTag = TT_INTERFACE;
+ else
+ loc.typeTag = TT_CLASS;
+ loc.classId = classObjectToRefTypeId(method->clazz);
+ loc.methodId = methodToMethodId(method);
+ loc.idx = pcOffset;
+
+ /*
+ * Note we use "NoReg" so we don't keep track of references that are
+ * never actually sent to the debugger. The "thisPtr" is used to
+ * compare against registered events.
+ */
+
+ if (dvmJdwpPostLocationEvent(gDvm.jdwpState, &loc,
+ objectToObjectIdNoReg(thisPtr), eventFlags))
+ {
+ classObjectToRefTypeId(method->clazz);
+ objectToObjectId(thisPtr);
+ }
+}
+
+/*
+ * Tell JDWP that an exception has occurred.
+ */
+void dvmDbgPostException(void* throwFp, int throwRelPc, void* catchFp,
+ int catchRelPc, Object* exception)
+{
+ JdwpLocation throwLoc, catchLoc;
+ const Method* throwMeth;
+ const Method* catchMeth;
+
+ throwMeth = SAVEAREA_FROM_FP(throwFp)->method;
+ if (dvmIsInterfaceClass(throwMeth->clazz))
+ throwLoc.typeTag = TT_INTERFACE;
+ else
+ throwLoc.typeTag = TT_CLASS;
+ throwLoc.classId = classObjectToRefTypeId(throwMeth->clazz);
+ throwLoc.methodId = methodToMethodId(throwMeth);
+ throwLoc.idx = throwRelPc;
+
+ if (catchRelPc < 0) {
+ memset(&catchLoc, 0, sizeof(catchLoc));
+ } else {
+ catchMeth = SAVEAREA_FROM_FP(catchFp)->method;
+ if (dvmIsInterfaceClass(catchMeth->clazz))
+ catchLoc.typeTag = TT_INTERFACE;
+ else
+ catchLoc.typeTag = TT_CLASS;
+ catchLoc.classId = classObjectToRefTypeId(catchMeth->clazz);
+ catchLoc.methodId = methodToMethodId(catchMeth);
+ catchLoc.idx = catchRelPc;
+ }
+
+ /* need this for InstanceOnly filters */
+ Object* thisObj = getThisObject(throwFp);
+
+ dvmJdwpPostException(gDvm.jdwpState, &throwLoc, objectToObjectId(exception),
+ classObjectToRefTypeId(exception->clazz), &catchLoc,
+ objectToObjectId(thisObj));
+}
+
+/*
+ * Tell JDWP and/or DDMS that a thread has started.
+ */
+void dvmDbgPostThreadStart(Thread* thread)
+{
+ if (gDvm.debuggerActive) {
+ dvmJdwpPostThreadChange(gDvm.jdwpState,
+ objectToObjectId(thread->threadObj), true);
+ }
+ if (gDvm.ddmThreadNotification)
+ dvmDdmSendThreadNotification(thread, true);
+}
+
+/*
+ * Tell JDWP and/or DDMS that a thread has gone away.
+ */
+void dvmDbgPostThreadDeath(Thread* thread)
+{
+ if (gDvm.debuggerActive) {
+ dvmJdwpPostThreadChange(gDvm.jdwpState,
+ objectToObjectId(thread->threadObj), false);
+ }
+ if (gDvm.ddmThreadNotification)
+ dvmDdmSendThreadNotification(thread, false);
+}
+
+/*
+ * Tell JDWP that a new class has been prepared.
+ */
+void dvmDbgPostClassPrepare(ClassObject* clazz)
+{
+ int tag;
+ char* signature;
+
+ if (dvmIsInterfaceClass(clazz))
+ tag = TT_INTERFACE;
+ else
+ tag = TT_CLASS;
+
+ // TODO - we currently always send both "verified" and "prepared" since
+ // debuggers seem to like that. There might be some advantage to honesty,
+ // since the class may not yet be verified.
+ signature = generateJNISignature(clazz);
+ dvmJdwpPostClassPrepare(gDvm.jdwpState, tag, classObjectToRefTypeId(clazz),
+ signature, CS_VERIFIED | CS_PREPARED);
+ free(signature);
+}
+
+/*
+ * The JDWP event mechanism has registered an event with a LocationOnly
+ * mod. Tell the interpreter to call us if we hit the specified
+ * address.
+ */
+bool dvmDbgWatchLocation(const JdwpLocation* pLoc)
+{
+ Method* method = methodIdToMethod(pLoc->classId, pLoc->methodId);
+ assert(!dvmIsNativeMethod(method));
+ dvmAddBreakAddr(method, pLoc->idx);
+ return true; /* assume success */
+}
+
+/*
+ * An event with a LocationOnly mod has been removed.
+ */
+void dvmDbgUnwatchLocation(const JdwpLocation* pLoc)
+{
+ Method* method = methodIdToMethod(pLoc->classId, pLoc->methodId);
+ assert(!dvmIsNativeMethod(method));
+ dvmClearBreakAddr(method, pLoc->idx);
+}
+
+/*
+ * The JDWP event mechanism has registered a single-step event. Tell
+ * the interpreter about it.
+ */
+bool dvmDbgConfigureStep(ObjectId threadId, enum JdwpStepSize size,
+ enum JdwpStepDepth depth)
+{
+ Object* threadObj;
+ Thread* thread;
+ bool result = false;
+
+ threadObj = objectIdToObject(threadId);
+ assert(threadObj != NULL);
+
+ /*
+ * Get a pointer to the Thread struct for this ID. The pointer will
+ * be used strictly for comparisons against the current thread pointer
+ * after the setup is complete, so we can safely release the lock.
+ */
+ dvmLockThreadList(NULL);
+ thread = threadObjToThread(threadObj);
+
+ if (thread == NULL) {
+ LOGE("Thread for single-step not found\n");
+ goto bail;
+ }
+ if (!dvmIsSuspended(thread)) {
+ LOGE("Thread for single-step not suspended\n");
+ assert(!"non-susp step"); // I want to know if this can happen
+ goto bail;
+ }
+
+ assert(dvmIsSuspended(thread));
+ if (!dvmAddSingleStep(thread, size, depth))
+ goto bail;
+
+ result = true;
+
+bail:
+ dvmUnlockThreadList();
+ return result;
+}
+
+/*
+ * A single-step event has been removed.
+ */
+void dvmDbgUnconfigureStep(ObjectId threadId)
+{
+ UNUSED_PARAMETER(threadId);
+
+ /* right now it's global, so don't need to find Thread */
+ dvmClearSingleStep(NULL);
+}
+
+/*
+ * Invoke a method in a thread that has been stopped on a breakpoint or
+ * other debugger event. (This function is called from the JDWP thread.)
+ *
+ * Note that access control is not enforced, per spec.
+ */
+JdwpError dvmDbgInvokeMethod(ObjectId threadId, ObjectId objectId,
+ RefTypeId classId, MethodId methodId, u4 numArgs, ObjectId* argArray,
+ u4 options, u1* pResultTag, u8* pResultValue, ObjectId* pExceptObj)
+{
+ Object* threadObj = objectIdToObject(threadId);
+ Thread* targetThread;
+ JdwpError err = ERR_NONE;
+
+ dvmLockThreadList(NULL);
+
+ targetThread = threadObjToThread(threadObj);
+ if (targetThread == NULL) {
+ err = ERR_INVALID_THREAD; /* thread does not exist */
+ dvmUnlockThreadList();
+ goto bail;
+ }
+ if (!targetThread->invokeReq.ready) {
+ err = ERR_INVALID_THREAD; /* thread not stopped by event */
+ dvmUnlockThreadList();
+ goto bail;
+ }
+
+ /*
+ * TODO: ought to screen the various IDs, and verify that the argument
+ * list is valid.
+ */
+
+ targetThread->invokeReq.obj = objectIdToObject(objectId);
+ targetThread->invokeReq.thread = threadObj;
+ targetThread->invokeReq.clazz = refTypeIdToClassObject(classId);
+ targetThread->invokeReq.method = methodIdToMethod(classId, methodId);
+ targetThread->invokeReq.numArgs = numArgs;
+ targetThread->invokeReq.argArray = argArray;
+ targetThread->invokeReq.options = options;
+ targetThread->invokeReq.invokeNeeded = true;
+
+ /*
+ * This is a bit risky -- if the thread goes away we're sitting high
+ * and dry -- but we must release this before the dvmResumeAllThreads
+ * call, and it's unwise to hold it during dvmWaitForSuspend.
+ */
+ dvmUnlockThreadList();
+
+ /*
+ * We change our thread status (which should be THREAD_RUNNING) so the
+ * VM can suspend for a GC if the invoke request causes us to run out
+ * of memory. It's also a good idea to change it before locking the
+ * invokeReq mutex, although that should never be held for long.
+ */
+ Thread* self = dvmThreadSelf();
+ int oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);
+
+ LOGV(" Transferring control to event thread\n");
+ dvmLockMutex(&targetThread->invokeReq.lock);
+
+ if ((options & INVOKE_SINGLE_THREADED) == 0) {
+ LOGV(" Resuming all threads\n");
+ dvmResumeAllThreads(SUSPEND_FOR_DEBUG_EVENT);
+ } else {
+ LOGV(" Resuming event thread only\n");
+ dvmResumeThread(targetThread);
+ }
+
+ /*
+ * Wait for the request to finish executing.
+ */
+ while (targetThread->invokeReq.invokeNeeded) {
+ pthread_cond_wait(&targetThread->invokeReq.cv,
+ &targetThread->invokeReq.lock);
+ }
+ dvmUnlockMutex(&targetThread->invokeReq.lock);
+ LOGV(" Control has returned from event thread\n");
+
+ /* wait for thread to re-suspend itself */
+ dvmWaitForSuspend(targetThread);
+
+ /*
+ * Done waiting, switch back to RUNNING.
+ */
+ dvmChangeStatus(self, oldStatus);
+
+ /*
+ * Suspend the threads. We waited for the target thread to suspend
+ * itself, so all we need to do is suspend the others.
+ *
+ * The suspendAllThreads() call will double-suspend the event thread,
+ * so we want to resume the target thread once to keep the books straight.
+ */
+ if ((options & INVOKE_SINGLE_THREADED) == 0) {
+ LOGV(" Suspending all threads\n");
+ dvmSuspendAllThreads(SUSPEND_FOR_DEBUG_EVENT);
+ LOGV(" Resuming event thread to balance the count\n");
+ dvmResumeThread(targetThread);
+ }
+
+ /*
+ * Set up the result.
+ */
+ *pResultTag = targetThread->invokeReq.resultTag;
+ if (isTagPrimitive(targetThread->invokeReq.resultTag))
+ *pResultValue = targetThread->invokeReq.resultValue.j;
+ else
+ *pResultValue = objectToObjectId(targetThread->invokeReq.resultValue.l);
+ *pExceptObj = targetThread->invokeReq.exceptObj;
+ err = targetThread->invokeReq.err;
+
+bail:
+ return err;
+}
+
+/*
+ * Determine the tag type for the return value for this method.
+ */
+static u1 resultTagFromSignature(const Method* method)
+{
+ const char* descriptor = dexProtoGetReturnType(&method->prototype);
+ return dvmDbgGetSignatureTag(descriptor);
+}
+
+/*
+ * Execute the method described by "*pReq".
+ */
+void dvmDbgExecuteMethod(DebugInvokeReq* pReq)
+{
+ Thread* self = dvmThreadSelf();
+ const Method* meth;
+ Object* oldExcept;
+
+ /*
+ * We can be called while an exception is pending in the VM. We need
+ * to preserve that across the method invocation.
+ */
+ oldExcept = dvmGetException(self);
+
+ /*
+ * Translate the method through the vtable, unless we're calling a
+ * static method or the debugger wants to suppress it.
+ */
+ if ((pReq->options & INVOKE_NONVIRTUAL) != 0 || pReq->obj == NULL) {
+ meth = pReq->method;
+ } else {
+ meth = dvmGetVirtualizedMethod(pReq->clazz, pReq->method);
+ }
+ assert(meth != NULL);
+
+ assert(sizeof(jvalue) == sizeof(u8));
+
+ IF_LOGV() {
+ char* desc = dexProtoCopyMethodDescriptor(&meth->prototype);
+ LOGV("JDWP invoking method %s.%s %s\n",
+ meth->clazz->descriptor, meth->name, desc);
+ free(desc);
+ }
+
+ dvmCallMethodA(self, meth, pReq->obj, &pReq->resultValue,
+ (jvalue*)pReq->argArray);
+ pReq->exceptObj = objectToObjectId(dvmGetException(self));
+ pReq->resultTag = resultTagFromSignature(meth);
+ if (pReq->exceptObj != 0) {
+ LOGD(" JDWP invocation returning with exceptObj=%p\n",
+ dvmGetException(self));
+ dvmClearException(self);
+ /*
+ * Nothing should try to use this, but it looks like something is.
+ * Make it null to be safe.
+ */
+ pReq->resultValue.j = 0; /*0xadadadad;*/
+ } else if (pReq->resultTag == JT_OBJECT) {
+ /* if no exception thrown, examine object result more closely */
+ u1 newTag = resultTagFromObject(pReq->resultValue.l);
+ if (newTag != pReq->resultTag) {
+ LOGVV(" JDWP promoted result from %d to %d\n",
+ pReq->resultTag, newTag);
+ pReq->resultTag = newTag;
+ }
+ }
+
+ if (oldExcept != NULL)
+ dvmSetException(self, oldExcept);
+}
+
+// for dvmAddressSetForLine
+typedef struct AddressSetContext {
+ bool lastAddressValid;
+ u4 lastAddress;
+ u4 lineNum;
+ AddressSet *pSet;
+} AddressSetContext;
+
+// for dvmAddressSetForLine
+static int addressSetCb (void *cnxt, u4 address, u4 lineNum)
+{
+ AddressSetContext *pContext = (AddressSetContext *)cnxt;
+
+ if (lineNum == pContext->lineNum) {
+ if (!pContext->lastAddressValid) {
+ // Everything from this address until the next line change is ours
+ pContext->lastAddress = address;
+ pContext->lastAddressValid = true;
+ }
+ // else, If we're already in a valid range for this lineNum,
+ // just keep going (shouldn't really happen)
+ } else if (pContext->lastAddressValid) { // and the line number is new
+ u4 i;
+ // Add everything from the last entry up until here to the set
+ for (i = pContext->lastAddress; i < address; i++) {
+ dvmAddressSetSet(pContext->pSet, i);
+ }
+
+ pContext->lastAddressValid = false;
+ }
+
+ // there may be multiple entries for a line
+ return 0;
+}
+/*
+ * Build up a set of bytecode addresses associated with a line number
+ */
+const AddressSet *dvmAddressSetForLine(const Method* method, int line)
+{
+ AddressSet *result;
+ const DexFile *pDexFile = method->clazz->pDvmDex->pDexFile;
+ u4 insnsSize = dvmGetMethodInsnsSize(method);
+ AddressSetContext context;
+
+ result = calloc(1, sizeof(AddressSet) + (insnsSize/8) + 1);
+ result->setSize = insnsSize;
+
+ memset(&context, 0, sizeof(context));
+ context.pSet = result;
+ context.lineNum = line;
+ context.lastAddressValid = false;
+
+ dexDecodeDebugInfo(pDexFile, dvmGetMethodCode(method),
+ method->clazz->descriptor,
+ method->prototype.protoIdx,
+ method->accessFlags,
+ addressSetCb, NULL, &context);
+
+ // If the line number was the last in the position table...
+ if (context.lastAddressValid) {
+ u4 i;
+ for (i = context.lastAddress; i < insnsSize; i++) {
+ dvmAddressSetSet(result, i);
+ }
+ }
+
+ return result;
+}
+
+
+/*
+ * ===========================================================================
+ * Dalvik Debug Monitor support
+ * ===========================================================================
+ */
+
+/*
+ * We have received a DDM packet over JDWP. Hand it off to the VM.
+ */
+bool dvmDbgDdmHandlePacket(const u1* buf, int dataLen, u1** pReplyBuf,
+ int* pReplyLen)
+{
+ return dvmDdmHandlePacket(buf, dataLen, pReplyBuf, pReplyLen);
+}
+
+/*
+ * First DDM packet has arrived over JDWP. Notify the press.
+ */
+void dvmDbgDdmConnected(void)
+{
+ dvmDdmConnected();
+}
+
+/*
+ * JDWP connection has dropped.
+ */
+void dvmDbgDdmDisconnected(void)
+{
+ dvmDdmDisconnected();
+}
+
+/*
+ * Send up a JDWP event packet with a DDM chunk in it.
+ */
+void dvmDbgDdmSendChunk(int type, int len, const u1* buf)
+{
+ if (gDvm.jdwpState == NULL) {
+ LOGI("Debugger thread not active, ignoring DDM send (t=0x%08x l=%d)\n",
+ type, len);
+ return;
+ }
+
+ dvmJdwpDdmSendChunk(gDvm.jdwpState, type, len, buf);
+}
+