diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
| commit | f6c387128427e121477c1b32ad35cdcaa5101ba3 (patch) | |
| tree | 2aa25fa8c8c3a9caeecf98fd8ac4cd9b12717997 /vm/Debugger.c | |
| parent | f72d5de56a522ac3be03873bdde26f23a5eeeb3c (diff) | |
| download | android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.tar.gz android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.tar.bz2 android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.zip | |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'vm/Debugger.c')
| -rw-r--r-- | vm/Debugger.c | 2951 |
1 files changed, 2951 insertions, 0 deletions
diff --git a/vm/Debugger.c b/vm/Debugger.c new file mode 100644 index 000000000..c667893c3 --- /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); +} + |
