/*
 * 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.
 */

/*
 * dalvik.system.VMDebug
 */
#include "Dalvik.h"
#include "native/InternalNativePriv.h"

#include <string.h>
#include <unistd.h>
#include <errno.h>


/*
 * Extracts the fd from a FileDescriptor object.
 *
 * If an error is encountered, or the extracted descriptor is numerically
 * invalid, this returns -1 with an exception raised.
 */
static int getFileDescriptor(Object* obj)
{
    assert(obj != NULL);
    assert(strcmp(obj->clazz->descriptor, "Ljava/io/FileDescriptor;") == 0);

    InstField* field = dvmFindInstanceField(obj->clazz, "descriptor", "I");
    if (field == NULL) {
        dvmThrowException("Ljava/lang/NoSuchFieldException;",
            "No FileDescriptor.descriptor field");
        return -1;
    }

    int fd = dvmGetFieldInt(obj, field->byteOffset);
    if (fd < 0) {
        dvmThrowExceptionFmt("Ljava/lang/RuntimeException;",
            "Invalid file descriptor");
        return -1;
    }

    return fd;
}

/*
 * Convert an array of char* into a String[].
 *
 * Returns NULL on failure, with an exception raised.
 */
static ArrayObject* convertStringArray(char** strings, size_t count)
{
    Thread* self = dvmThreadSelf();

    /*
     * Allocate an array to hold the String objects.
     */
    ClassObject* stringArrayClass =
        dvmFindArrayClass("[Ljava/lang/String;", NULL);
    if (stringArrayClass == NULL) {
        /* shouldn't happen */
        LOGE("Unable to find [Ljava/lang/String;\n");
        dvmAbort();
    }

    ArrayObject* stringArray =
        dvmAllocArrayByClass(stringArrayClass, count, ALLOC_DEFAULT);
    if (stringArray == NULL) {
        /* probably OOM */
        LOGD("Failed allocating array of %d strings\n", count);
        assert(dvmCheckException(self));
        return NULL;
    }

    /*
     * Create the individual String objects and add them to the array.
     */
    size_t i;
    for (i = 0; i < count; i++) {
        Object *str =
            (Object *)dvmCreateStringFromCstr(strings[i]);
        if (str == NULL) {
            /* probably OOM; drop out now */
            assert(dvmCheckException(self));
            dvmReleaseTrackedAlloc((Object*)stringArray, self);
            return NULL;
        }
        dvmSetObjectArrayElement(stringArray, i, str);
        /* stored in tracked array, okay to release */
        dvmReleaseTrackedAlloc(str, self);
    }

    dvmReleaseTrackedAlloc((Object*)stringArray, self);
    return stringArray;
}

/*
 * static String[] getVmFeatureList()
 *
 * Return a set of strings describing available VM features (this is chiefly
 * of interest to DDMS).  Some features may be controlled by compile-time
 * or command-line flags.
 */
static void Dalvik_dalvik_system_VMDebug_getVmFeatureList(const u4* args,
    JValue* pResult)
{
    static const int MAX_FEATURE_COUNT = 10;
    char* features[MAX_FEATURE_COUNT];
    int idx = 0;

    /* VM responds to DDMS method profiling requests */
    features[idx++] = "method-trace-profiling";
    features[idx++] = "method-trace-profiling-streaming";
#ifdef WITH_HPROF
    /* VM responds to DDMS heap dump requests */
    features[idx++] = "hprof-heap-dump";
    features[idx++] = "hprof-heap-dump-streaming";
#endif

    assert(idx <= MAX_FEATURE_COUNT);

    LOGV("+++ sending up %d features\n", idx);
    ArrayObject* arrayObj = convertStringArray(features, idx);
    RETURN_PTR(arrayObj);       /* will be null on OOM */
}


/* These must match the values in dalvik.system.VMDebug.
 */
enum {
    KIND_ALLOCATED_OBJECTS      = 1<<0,
    KIND_ALLOCATED_BYTES        = 1<<1,
    KIND_FREED_OBJECTS          = 1<<2,
    KIND_FREED_BYTES            = 1<<3,
    KIND_GC_INVOCATIONS         = 1<<4,
    KIND_CLASS_INIT_COUNT       = 1<<5,
    KIND_CLASS_INIT_TIME        = 1<<6,
#if PROFILE_EXTERNAL_ALLOCATIONS
    KIND_EXT_ALLOCATED_OBJECTS = 1<<12,
    KIND_EXT_ALLOCATED_BYTES   = 1<<13,
    KIND_EXT_FREED_OBJECTS     = 1<<14,
    KIND_EXT_FREED_BYTES       = 1<<15,
#endif // PROFILE_EXTERNAL_ALLOCATIONS

    KIND_GLOBAL_ALLOCATED_OBJECTS   = KIND_ALLOCATED_OBJECTS,
    KIND_GLOBAL_ALLOCATED_BYTES     = KIND_ALLOCATED_BYTES,
    KIND_GLOBAL_FREED_OBJECTS       = KIND_FREED_OBJECTS,
    KIND_GLOBAL_FREED_BYTES         = KIND_FREED_BYTES,
    KIND_GLOBAL_GC_INVOCATIONS      = KIND_GC_INVOCATIONS,
    KIND_GLOBAL_CLASS_INIT_COUNT    = KIND_CLASS_INIT_COUNT,
    KIND_GLOBAL_CLASS_INIT_TIME     = KIND_CLASS_INIT_TIME,
#if PROFILE_EXTERNAL_ALLOCATIONS
    KIND_GLOBAL_EXT_ALLOCATED_OBJECTS = KIND_EXT_ALLOCATED_OBJECTS,
    KIND_GLOBAL_EXT_ALLOCATED_BYTES = KIND_EXT_ALLOCATED_BYTES,
    KIND_GLOBAL_EXT_FREED_OBJECTS   = KIND_EXT_FREED_OBJECTS,
    KIND_GLOBAL_EXT_FREED_BYTES     = KIND_EXT_FREED_BYTES,
#endif // PROFILE_EXTERNAL_ALLOCATIONS

    KIND_THREAD_ALLOCATED_OBJECTS   = KIND_ALLOCATED_OBJECTS << 16,
    KIND_THREAD_ALLOCATED_BYTES     = KIND_ALLOCATED_BYTES << 16,
    KIND_THREAD_FREED_OBJECTS       = KIND_FREED_OBJECTS << 16,
    KIND_THREAD_FREED_BYTES         = KIND_FREED_BYTES << 16,
#if PROFILE_EXTERNAL_ALLOCATIONS
    KIND_THREAD_EXT_ALLOCATED_OBJECTS = KIND_EXT_ALLOCATED_OBJECTS << 16,
    KIND_THREAD_EXT_ALLOCATED_BYTES = KIND_EXT_ALLOCATED_BYTES << 16,
    KIND_THREAD_EXT_FREED_OBJECTS   = KIND_EXT_FREED_OBJECTS << 16,
    KIND_THREAD_EXT_FREED_BYTES     = KIND_EXT_FREED_BYTES << 16,
#endif // PROFILE_EXTERNAL_ALLOCATIONS
    KIND_THREAD_GC_INVOCATIONS      = KIND_GC_INVOCATIONS << 16,

    // TODO: failedAllocCount, failedAllocSize
};

#define KIND_ALL_COUNTS 0xffffffff

/*
 * Zero out the specified fields.
 */
static void clearAllocProfStateFields(AllocProfState *allocProf,
    unsigned int kinds)
{
    if (kinds & KIND_ALLOCATED_OBJECTS) {
        allocProf->allocCount = 0;
    }
    if (kinds & KIND_ALLOCATED_BYTES) {
        allocProf->allocSize = 0;
    }
    if (kinds & KIND_FREED_OBJECTS) {
        allocProf->freeCount = 0;
    }
    if (kinds & KIND_FREED_BYTES) {
        allocProf->freeSize = 0;
    }
    if (kinds & KIND_GC_INVOCATIONS) {
        allocProf->gcCount = 0;
    }
    if (kinds & KIND_CLASS_INIT_COUNT) {
        allocProf->classInitCount = 0;
    }
    if (kinds & KIND_CLASS_INIT_TIME) {
        allocProf->classInitTime = 0;
    }
#if PROFILE_EXTERNAL_ALLOCATIONS
    if (kinds & KIND_EXT_ALLOCATED_OBJECTS) {
        allocProf->externalAllocCount = 0;
    }
    if (kinds & KIND_EXT_ALLOCATED_BYTES) {
        allocProf->externalAllocSize = 0;
    }
    if (kinds & KIND_EXT_FREED_OBJECTS) {
        allocProf->externalFreeCount = 0;
    }
    if (kinds & KIND_EXT_FREED_BYTES) {
        allocProf->externalFreeSize = 0;
    }
#endif // PROFILE_EXTERNAL_ALLOCATIONS
}

/*
 * static void startAllocCounting()
 *
 * Reset the counters and enable counting.
 *
 * TODO: this currently only resets the per-thread counters for the current
 * thread.  If we actually start using the per-thread counters we'll
 * probably want to fix this.
 */
static void Dalvik_dalvik_system_VMDebug_startAllocCounting(const u4* args,
    JValue* pResult)
{
    UNUSED_PARAMETER(args);

    clearAllocProfStateFields(&gDvm.allocProf, KIND_ALL_COUNTS);
    clearAllocProfStateFields(&dvmThreadSelf()->allocProf, KIND_ALL_COUNTS);
    dvmStartAllocCounting();
    RETURN_VOID();
}

/*
 * public static void stopAllocCounting()
 */
static void Dalvik_dalvik_system_VMDebug_stopAllocCounting(const u4* args,
    JValue* pResult)
{
    UNUSED_PARAMETER(args);

    dvmStopAllocCounting();
    RETURN_VOID();
}

/*
 * private static int getAllocCount(int kind)
 */
static void Dalvik_dalvik_system_VMDebug_getAllocCount(const u4* args,
    JValue* pResult)
{
    AllocProfState *allocProf;
    unsigned int kind = args[0];
    if (kind < (1<<16)) {
        allocProf = &gDvm.allocProf;
    } else {
        allocProf = &dvmThreadSelf()->allocProf;
        kind >>= 16;
    }
    switch (kind) {
    case KIND_ALLOCATED_OBJECTS:
        pResult->i = allocProf->allocCount;
        break;
    case KIND_ALLOCATED_BYTES:
        pResult->i = allocProf->allocSize;
        break;
    case KIND_FREED_OBJECTS:
        pResult->i = allocProf->freeCount;
        break;
    case KIND_FREED_BYTES:
        pResult->i = allocProf->freeSize;
        break;
    case KIND_GC_INVOCATIONS:
        pResult->i = allocProf->gcCount;
        break;
    case KIND_CLASS_INIT_COUNT:
        pResult->i = allocProf->classInitCount;
        break;
    case KIND_CLASS_INIT_TIME:
        /* convert nsec to usec, reduce to 32 bits */
        pResult->i = (int) (allocProf->classInitTime / 1000);
        break;
#if PROFILE_EXTERNAL_ALLOCATIONS
    case KIND_EXT_ALLOCATED_OBJECTS:
        pResult->i = allocProf->externalAllocCount;
        break;
    case KIND_EXT_ALLOCATED_BYTES:
        pResult->i = allocProf->externalAllocSize;
        break;
    case KIND_EXT_FREED_OBJECTS:
        pResult->i = allocProf->externalFreeCount;
        break;
    case KIND_EXT_FREED_BYTES:
        pResult->i = allocProf->externalFreeSize;
        break;
#endif // PROFILE_EXTERNAL_ALLOCATIONS
    default:
        assert(false);
        pResult->i = -1;
    }
}

/*
 * public static void resetAllocCount(int kinds)
 */
static void Dalvik_dalvik_system_VMDebug_resetAllocCount(const u4* args,
    JValue* pResult)
{
    unsigned int kinds = args[0];
    clearAllocProfStateFields(&gDvm.allocProf, kinds & 0xffff);
    clearAllocProfStateFields(&dvmThreadSelf()->allocProf, kinds >> 16);
    RETURN_VOID();
}

/*
 * static void startMethodTracingNative(String traceFileName,
 *     FileDescriptor fd, int bufferSize, int flags)
 *
 * Start method trace profiling.
 *
 * If both "traceFileName" and "fd" are null, the result will be sent
 * directly to DDMS.  (The non-DDMS versions of the calls are expected
 * to enforce non-NULL filenames.)
 */
static void Dalvik_dalvik_system_VMDebug_startMethodTracingNative(const u4* args,
    JValue* pResult)
{
    StringObject* traceFileStr = (StringObject*) args[0];
    Object* traceFd = (Object*) args[1];
    int bufferSize = args[2];
    int flags = args[3];

    if (bufferSize == 0) {
        // Default to 8MB per the documentation.
        bufferSize = 8 * 1024 * 1024;
    }

    if (bufferSize < 1024) {
        dvmThrowException("Ljava/lang/IllegalArgumentException;", NULL);
        RETURN_VOID();
    }

    char* traceFileName = NULL;
    if (traceFileStr != NULL)
        traceFileName = dvmCreateCstrFromString(traceFileStr);

    int fd = -1;
    if (traceFd != NULL) {
        int origFd = getFileDescriptor(traceFd);
        if (origFd < 0)
            RETURN_VOID();

        fd = dup(origFd);
        if (fd < 0) {
            dvmThrowExceptionFmt("Ljava/lang/RuntimeException;",
                "dup(%d) failed: %s", origFd, strerror(errno));
            RETURN_VOID();
        }
    }

    dvmMethodTraceStart(traceFileName != NULL ? traceFileName : "[DDMS]",
        fd, bufferSize, flags, (traceFileName == NULL && fd == -1));
    free(traceFileName);
    RETURN_VOID();
}

/*
 * static boolean isMethodTracingActive()
 *
 * Determine whether method tracing is currently active.
 */
static void Dalvik_dalvik_system_VMDebug_isMethodTracingActive(const u4* args,
    JValue* pResult)
{
    UNUSED_PARAMETER(args);

    RETURN_BOOLEAN(dvmIsMethodTraceActive());
}

/*
 * static void stopMethodTracing()
 *
 * Stop method tracing.
 */
static void Dalvik_dalvik_system_VMDebug_stopMethodTracing(const u4* args,
    JValue* pResult)
{
    UNUSED_PARAMETER(args);

    dvmMethodTraceStop();
    RETURN_VOID();
}

/*
 * static void startEmulatorTracing()
 *
 * Start sending method trace info to the emulator.
 */
static void Dalvik_dalvik_system_VMDebug_startEmulatorTracing(const u4* args,
    JValue* pResult)
{
    UNUSED_PARAMETER(args);

    dvmEmulatorTraceStart();
    RETURN_VOID();
}

/*
 * static void stopEmulatorTracing()
 *
 * Start sending method trace info to the emulator.
 */
static void Dalvik_dalvik_system_VMDebug_stopEmulatorTracing(const u4* args,
    JValue* pResult)
{
    UNUSED_PARAMETER(args);

    dvmEmulatorTraceStop();
    RETURN_VOID();
}

/*
 * static int setAllocationLimit(int limit)
 *
 * Set the current allocation limit in this thread.  Return the previous
 * value.
 */
static void Dalvik_dalvik_system_VMDebug_setAllocationLimit(const u4* args,
    JValue* pResult)
{
#if defined(WITH_ALLOC_LIMITS)
    gDvm.checkAllocLimits = true;

    Thread* self = dvmThreadSelf();
    int newLimit = args[0];
    int oldLimit = self->allocLimit;

    if (newLimit < -1) {
        LOGE("WARNING: bad limit request (%d)\n", newLimit);
        newLimit = -1;
    }
    self->allocLimit = newLimit;
    RETURN_INT(oldLimit);
#else
    UNUSED_PARAMETER(args);
    RETURN_INT(-1);
#endif
}

/*
 * static int setGlobalAllocationLimit(int limit)
 *
 * Set the allocation limit for this process.  Returns the previous value.
 */
static void Dalvik_dalvik_system_VMDebug_setGlobalAllocationLimit(const u4* args,
    JValue* pResult)
{
#if defined(WITH_ALLOC_LIMITS)
    gDvm.checkAllocLimits = true;

    int newLimit = args[0];
    int oldLimit = gDvm.allocationLimit;

    if (newLimit < -1 || newLimit > 0) {
        LOGE("WARNING: bad limit request (%d)\n", newLimit);
        newLimit = -1;
    }
    // TODO: should use an atomic swap here
    gDvm.allocationLimit = newLimit;
    RETURN_INT(oldLimit);
#else
    UNUSED_PARAMETER(args);
    RETURN_INT(-1);
#endif
}

/*
 * static boolean isDebuggerConnected()
 *
 * Returns "true" if a debugger is attached.
 */
static void Dalvik_dalvik_system_VMDebug_isDebuggerConnected(const u4* args,
    JValue* pResult)
{
    UNUSED_PARAMETER(args);

    RETURN_BOOLEAN(dvmDbgIsDebuggerConnected());
}

/*
 * static boolean isDebuggingEnabled()
 *
 * Returns "true" if debugging is enabled.
 */
static void Dalvik_dalvik_system_VMDebug_isDebuggingEnabled(const u4* args,
    JValue* pResult)
{
    UNUSED_PARAMETER(args);

    RETURN_BOOLEAN(gDvm.jdwpConfigured);
}

/*
 * static long lastDebuggerActivity()
 *
 * Returns the time, in msec, since we last had an interaction with the
 * debugger (send or receive).
 */
static void Dalvik_dalvik_system_VMDebug_lastDebuggerActivity(const u4* args,
    JValue* pResult)
{
    UNUSED_PARAMETER(args);

    RETURN_LONG(dvmDbgLastDebuggerActivity());
}

/*
 * static void startInstructionCounting()
 */
static void Dalvik_dalvik_system_VMDebug_startInstructionCounting(const u4* args,
    JValue* pResult)
{
    dvmStartInstructionCounting();
    RETURN_VOID();
}

/*
 * static void stopInstructionCounting()
 */
static void Dalvik_dalvik_system_VMDebug_stopInstructionCounting(const u4* args,
    JValue* pResult)
{
    dvmStopInstructionCounting();
    RETURN_VOID();
}

/*
 * static boolean getInstructionCount(int[] counts)
 *
 * Grab a copy of the global instruction count array.
 *
 * Since the instruction counts aren't synchronized, we use sched_yield
 * to improve our chances of finishing without contention.  (Only makes
 * sense on a uniprocessor.)
 */
static void Dalvik_dalvik_system_VMDebug_getInstructionCount(const u4* args,
    JValue* pResult)
{
    ArrayObject* countArray = (ArrayObject*) args[0];
    int* storage;

    storage = (int*) countArray->contents;
    sched_yield();
    memcpy(storage, gDvm.executedInstrCounts,
        kNumDalvikInstructions * sizeof(int));
    RETURN_VOID();
}

/*
 * static boolean resetInstructionCount()
 *
 * Reset the instruction count array.
 */
static void Dalvik_dalvik_system_VMDebug_resetInstructionCount(const u4* args,
    JValue* pResult)
{
    sched_yield();
    memset(gDvm.executedInstrCounts, 0, kNumDalvikInstructions * sizeof(int));
    RETURN_VOID();
}

/*
 * static void printLoadedClasses(int flags)
 *
 * Dump the list of loaded classes.
 */
static void Dalvik_dalvik_system_VMDebug_printLoadedClasses(const u4* args,
    JValue* pResult)
{
    int flags = args[0];

    dvmDumpAllClasses(flags);

    RETURN_VOID();
}

/*
 * static int getLoadedClassCount()
 *
 * Return the number of loaded classes
 */
static void Dalvik_dalvik_system_VMDebug_getLoadedClassCount(const u4* args,
    JValue* pResult)
{
    int count;

    UNUSED_PARAMETER(args);

    count = dvmGetNumLoadedClasses();

    RETURN_INT(count);
}

/*
 * Returns the thread-specific CPU-time clock value for the current thread,
 * or -1 if the feature isn't supported.
 */
static void Dalvik_dalvik_system_VMDebug_threadCpuTimeNanos(const u4* args,
    JValue* pResult)
{
    jlong result;

#ifdef HAVE_POSIX_CLOCKS
    struct timespec now;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now);
    result = (jlong) (now.tv_sec*1000000000LL + now.tv_nsec);
#else
    result = (jlong) -1;
#endif

    RETURN_LONG(result);
}

/*
 * static void dumpHprofData(String fileName, FileDescriptor fd)
 *
 * Cause "hprof" data to be dumped.  We can throw an IOException if an
 * error occurs during file handling.
 */
static void Dalvik_dalvik_system_VMDebug_dumpHprofData(const u4* args,
    JValue* pResult)
{
#ifdef WITH_HPROF
    StringObject* fileNameStr = (StringObject*) args[0];
    Object* fileDescriptor = (Object*) args[1];
    char* fileName;
    int result;

    /*
     * Only one of these may be NULL.
     */
    if (fileNameStr == NULL && fileDescriptor == NULL) {
        dvmThrowException("Ljava/lang/NullPointerException;", NULL);
        RETURN_VOID();
    }

    if (fileNameStr != NULL) {
        fileName = dvmCreateCstrFromString(fileNameStr);
        if (fileName == NULL) {
            /* unexpected -- malloc failure? */
            dvmThrowException("Ljava/lang/RuntimeException;", "malloc failure?");
            RETURN_VOID();
        }
    } else {
        fileName = strdup("[fd]");
    }

    int fd = -1;
    if (fileDescriptor != NULL) {
        fd = getFileDescriptor(fileDescriptor);
        if (fd < 0)
            RETURN_VOID();
    }

    result = hprofDumpHeap(fileName, fd, false);
    free(fileName);

    if (result != 0) {
        /* ideally we'd throw something more specific based on actual failure */
        dvmThrowException("Ljava/lang/RuntimeException;",
            "Failure during heap dump -- check log output for details");
        RETURN_VOID();
    }
#else
    dvmThrowException("Ljava/lang/UnsupportedOperationException;", NULL);
#endif

    RETURN_VOID();
}

/*
 * static void dumpHprofDataDdms()
 *
 * Cause "hprof" data to be computed and sent directly to DDMS.
 */
static void Dalvik_dalvik_system_VMDebug_dumpHprofDataDdms(const u4* args,
    JValue* pResult)
{
#ifdef WITH_HPROF
    int result;

    result = hprofDumpHeap("[DDMS]", -1, true);

    if (result != 0) {
        /* ideally we'd throw something more specific based on actual failure */
        dvmThrowException("Ljava/lang/RuntimeException;",
            "Failure during heap dump -- check log output for details");
        RETURN_VOID();
    }
#else
    dvmThrowException("Ljava/lang/UnsupportedOperationException;", NULL);
#endif

    RETURN_VOID();
}

/*
 * static boolean cacheRegisterMap(String classAndMethodDescr)
 *
 * If the specified class is loaded, and the named method exists, ensure
 * that the method's register map is ready for use.  If the class/method
 * cannot be found, nothing happens.
 *
 * This can improve the zygote's sharing of compressed register maps.  Do
 * this after class preloading.
 *
 * Returns true if the register map is cached and ready, either as a result
 * of this call or earlier activity.  Returns false if the class isn't loaded,
 * if the method couldn't be found, or if the method has no register map.
 *
 * (Uncomment logs in dvmGetExpandedRegisterMap0() to gather stats.)
 */
static void Dalvik_dalvik_system_VMDebug_cacheRegisterMap(const u4* args,
    JValue* pResult)
{
    StringObject* classAndMethodDescStr = (StringObject*) args[0];
    ClassObject* clazz;
    bool result = false;

    if (classAndMethodDescStr == NULL) {
        dvmThrowException("Ljava/lang/NullPointerException;", NULL);
        RETURN_VOID();
    }

    char* classAndMethodDesc = NULL;

    /*
     * Pick the string apart.  We have a local copy, so just modify it
     * in place.
     */
    classAndMethodDesc = dvmCreateCstrFromString(classAndMethodDescStr);

    char* methodName = strchr(classAndMethodDesc, '.');
    if (methodName == NULL) {
        dvmThrowException("Ljava/lang/RuntimeException;",
            "method name not found in string");
        RETURN_VOID();
    }
    *methodName++ = '\0';

    char* methodDescr = strchr(methodName, ':');
    if (methodDescr == NULL) {
        dvmThrowException("Ljava/lang/RuntimeException;",
            "method descriptor not found in string");
        RETURN_VOID();
    }
    *methodDescr++ = '\0';

    //LOGD("GOT: %s %s %s\n", classAndMethodDesc, methodName, methodDescr);

    /*
     * Find the class, but only if it's already loaded.
     */
    clazz = dvmLookupClass(classAndMethodDesc, NULL, false);
    if (clazz == NULL) {
        LOGD("Class %s not found in bootstrap loader\n", classAndMethodDesc);
        goto bail;
    }

    Method* method;

    /*
     * Find the method, which could be virtual or direct, defined directly
     * or inherited.
     */
    if (methodName[0] == '<') {
        /*
         * Constructor or class initializer.  Only need to examine the
         * "direct" list, and don't need to search up the class hierarchy.
         */
        method = dvmFindDirectMethodByDescriptor(clazz, methodName,
                    methodDescr);
    } else {
        /*
         * Try both lists, and scan up the tree.
         */
        method = dvmFindVirtualMethodHierByDescriptor(clazz, methodName,
                    methodDescr);
        if (method == NULL) {
            method = dvmFindDirectMethodHierByDescriptor(clazz, methodName,
                        methodDescr);
        }
    }

    if (method != NULL) {
        /*
         * Got it.  See if there's a register map here.
         */
        const RegisterMap* pMap;
        pMap = dvmGetExpandedRegisterMap(method);
        if (pMap == NULL) {
            LOGV("No map for %s.%s %s\n",
                classAndMethodDesc, methodName, methodDescr);
        } else {
            LOGV("Found map %s.%s %s\n",
                classAndMethodDesc, methodName, methodDescr);
            result = true;
        }
    } else {
        LOGV("Unable to find %s.%s %s\n",
            classAndMethodDesc, methodName, methodDescr);
    }

bail:
    free(classAndMethodDesc);
    RETURN_BOOLEAN(result);
}

/*
 * static void dumpReferenceTables()
 */
static void Dalvik_dalvik_system_VMDebug_dumpReferenceTables(const u4* args,
    JValue* pResult)
{
    UNUSED_PARAMETER(args);
    UNUSED_PARAMETER(pResult);

    LOGI("--- reference table dump ---\n");
    dvmDumpJniReferenceTables();
    // could dump thread's internalLocalRefTable, probably not useful
    // ditto for thread's jniMonitorRefTable
    LOGI("---\n");
    RETURN_VOID();
}

/*
 * static void crash()
 *
 * Dump the current thread's interpreted stack and abort the VM.  Useful
 * for seeing both interpreted and native stack traces.
 *
 * (Might want to restrict this to debuggable processes as a security
 * measure, or check SecurityManager.checkExit().)
 */
static void Dalvik_dalvik_system_VMDebug_crash(const u4* args,
    JValue* pResult)
{
    UNUSED_PARAMETER(args);
    UNUSED_PARAMETER(pResult);

    LOGW("Crashing VM on request\n");
    dvmDumpThread(dvmThreadSelf(), false);
    dvmAbort();
}

/*
 * static void infopoint(int id)
 *
 * Provide a hook for gdb to hang to so that the VM can be stopped when
 * user-tagged source locations are being executed.
 */
static void Dalvik_dalvik_system_VMDebug_infopoint(const u4* args,
    JValue* pResult)
{
    gDvm.nativeDebuggerActive = true;

    LOGD("VMDebug infopoint %d hit", args[0]);

    gDvm.nativeDebuggerActive = false;
    RETURN_VOID();
}

static void Dalvik_dalvik_system_VMDebug_countInstancesOfClass(const u4* args,
    JValue* pResult)
{
    ClassObject* clazz = (ClassObject*)args[0];
    bool countAssignable = args[1];
    if (clazz == NULL) {
        RETURN_LONG(0);
    }
    if (countAssignable) {
        size_t count = dvmCountAssignableInstancesOfClass(clazz);
        RETURN_LONG((long long)count);
    } else {
        size_t count = dvmCountInstancesOfClass(clazz);
        RETURN_LONG((long long)count);
    }
}

const DalvikNativeMethod dvm_dalvik_system_VMDebug[] = {
    { "getVmFeatureList",           "()[Ljava/lang/String;",
        Dalvik_dalvik_system_VMDebug_getVmFeatureList },
    { "getAllocCount",              "(I)I",
        Dalvik_dalvik_system_VMDebug_getAllocCount },
    { "resetAllocCount",            "(I)V",
        Dalvik_dalvik_system_VMDebug_resetAllocCount },
    { "startAllocCounting",         "()V",
        Dalvik_dalvik_system_VMDebug_startAllocCounting },
    { "stopAllocCounting",          "()V",
        Dalvik_dalvik_system_VMDebug_stopAllocCounting },
    { "startMethodTracingNative",   "(Ljava/lang/String;Ljava/io/FileDescriptor;II)V",
        Dalvik_dalvik_system_VMDebug_startMethodTracingNative },
    { "isMethodTracingActive",      "()Z",
        Dalvik_dalvik_system_VMDebug_isMethodTracingActive },
    { "stopMethodTracing",          "()V",
        Dalvik_dalvik_system_VMDebug_stopMethodTracing },
    { "startEmulatorTracing",       "()V",
        Dalvik_dalvik_system_VMDebug_startEmulatorTracing },
    { "stopEmulatorTracing",        "()V",
        Dalvik_dalvik_system_VMDebug_stopEmulatorTracing },
    { "setAllocationLimit",         "(I)I",
        Dalvik_dalvik_system_VMDebug_setAllocationLimit },
    { "setGlobalAllocationLimit",   "(I)I",
        Dalvik_dalvik_system_VMDebug_setGlobalAllocationLimit },
    { "startInstructionCounting",   "()V",
        Dalvik_dalvik_system_VMDebug_startInstructionCounting },
    { "stopInstructionCounting",    "()V",
        Dalvik_dalvik_system_VMDebug_stopInstructionCounting },
    { "resetInstructionCount",      "()V",
        Dalvik_dalvik_system_VMDebug_resetInstructionCount },
    { "getInstructionCount",        "([I)V",
        Dalvik_dalvik_system_VMDebug_getInstructionCount },
    { "isDebuggerConnected",        "()Z",
        Dalvik_dalvik_system_VMDebug_isDebuggerConnected },
    { "isDebuggingEnabled",         "()Z",
        Dalvik_dalvik_system_VMDebug_isDebuggingEnabled },
    { "lastDebuggerActivity",       "()J",
        Dalvik_dalvik_system_VMDebug_lastDebuggerActivity },
    { "printLoadedClasses",         "(I)V",
        Dalvik_dalvik_system_VMDebug_printLoadedClasses },
    { "getLoadedClassCount",        "()I",
        Dalvik_dalvik_system_VMDebug_getLoadedClassCount },
    { "threadCpuTimeNanos",         "()J",
        Dalvik_dalvik_system_VMDebug_threadCpuTimeNanos },
    { "dumpHprofData",              "(Ljava/lang/String;Ljava/io/FileDescriptor;)V",
        Dalvik_dalvik_system_VMDebug_dumpHprofData },
    { "dumpHprofDataDdms",          "()V",
        Dalvik_dalvik_system_VMDebug_dumpHprofDataDdms },
    { "cacheRegisterMap",           "(Ljava/lang/String;)Z",
        Dalvik_dalvik_system_VMDebug_cacheRegisterMap },
    { "dumpReferenceTables",        "()V",
        Dalvik_dalvik_system_VMDebug_dumpReferenceTables },
    { "crash",                      "()V",
        Dalvik_dalvik_system_VMDebug_crash },
    { "infopoint",                 "(I)V",
        Dalvik_dalvik_system_VMDebug_infopoint },
    { "countInstancesOfClass",     "(Ljava/lang/Class;Z)J",
        Dalvik_dalvik_system_VMDebug_countInstancesOfClass },
    { NULL, NULL, NULL },
};