C++程序  |  2218行  |  63.24 KB

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

/*
 * Handle messages from debugger.
 *
 * GENERAL NOTE: we're not currently testing the message length for
 * correctness.  This is usually a bad idea, but here we can probably
 * get away with it so long as the debugger isn't broken.  We can
 * change the "read" macros to use "dataLen" to avoid wandering into
 * bad territory, and have a single "is dataLen correct" check at the
 * end of each function.  Not needed at this time.
 */
#include "jdwp/JdwpPriv.h"
#include "jdwp/JdwpHandler.h"
#include "jdwp/JdwpEvent.h"
#include "jdwp/JdwpConstants.h"
#include "jdwp/ExpandBuf.h"

#include "Bits.h"
#include "Atomic.h"
#include "DalvikVersion.h"

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

#if 0
#include <time.h>
#include <sys/time.h>
static void showTime(const char* label)
{
    struct timeval tv;
    int min, sec, msec;

    gettimeofday(&tv, NULL);
    min = (tv.tv_sec / 60) % 60;
    sec = tv.tv_sec % 60;
    msec = tv.tv_usec / 1000;

    LOGI("%02d:%02d.%03d %s\n", min, sec, msec, label);
}
#endif

/*
 * Helper function: read a "location" from an input buffer.
 */
static void jdwpReadLocation(const u1** pBuf, JdwpLocation* pLoc)
{
    memset(pLoc, 0, sizeof(*pLoc));     /* allows memcmp() later */
    pLoc->typeTag = read1(pBuf);
    pLoc->classId = dvmReadObjectId(pBuf);
    pLoc->methodId = dvmReadMethodId(pBuf);
    pLoc->idx = read8BE(pBuf);
}

/*
 * Helper function: write a "location" into the reply buffer.
 */
void dvmJdwpAddLocation(ExpandBuf* pReply, const JdwpLocation* pLoc)
{
    expandBufAdd1(pReply, pLoc->typeTag);
    expandBufAddObjectId(pReply, pLoc->classId);
    expandBufAddMethodId(pReply, pLoc->methodId);
    expandBufAdd8BE(pReply, pLoc->idx);
}

/*
 * Helper function: read a variable-width value from the input buffer.
 */
static u8 jdwpReadValue(const u1** pBuf, int width)
{
    u8 value;

    switch (width) {
    case 1:     value = read1(pBuf);                break;
    case 2:     value = read2BE(pBuf);              break;
    case 4:     value = read4BE(pBuf);              break;
    case 8:     value = read8BE(pBuf);              break;
    default:    value = (u8) -1; assert(false);     break;
    }

    return value;
}

/*
 * Helper function: write a variable-width value into the output input buffer.
 */
static void jdwpWriteValue(ExpandBuf* pReply, int width, u8 value)
{
    switch (width) {
    case 1:     expandBufAdd1(pReply, value);       break;
    case 2:     expandBufAdd2BE(pReply, value);     break;
    case 4:     expandBufAdd4BE(pReply, value);     break;
    case 8:     expandBufAdd8BE(pReply, value);     break;
    default:    assert(false);                      break;
    }
}

/*
 * Common code for *_InvokeMethod requests.
 *
 * If "isConstructor" is set, this returns "objectId" rather than the
 * expected-to-be-void return value of the called function.
 */
static JdwpError finishInvoke(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply,
    ObjectId threadId, ObjectId objectId, RefTypeId classId, MethodId methodId,
    bool isConstructor)
{
    JdwpError err = ERR_NONE;
    u8* argArray = NULL;
    u4 numArgs;
    u4 options;     /* enum InvokeOptions bit flags */
    int i;

    assert(!isConstructor || objectId != 0);

    numArgs = read4BE(&buf);

    LOGV("    --> threadId=%llx objectId=%llx\n", threadId, objectId);
    LOGV("        classId=%llx methodId=%x %s.%s\n",
        classId, methodId,
        dvmDbgGetClassDescriptor(classId),
        dvmDbgGetMethodName(classId, methodId));
    LOGV("        %d args:\n", numArgs);

    if (numArgs > 0)
        argArray = (ObjectId*) malloc(sizeof(ObjectId) * numArgs);

    for (i = 0; i < (int) numArgs; i++) {
        u1 typeTag;
        u8 value;
        int width;

        typeTag = read1(&buf);
        width = dvmDbgGetTagWidth(typeTag);
        value = jdwpReadValue(&buf, width);

        LOGV("          '%c'(%d): 0x%llx\n", typeTag, width, value);
        argArray[i] = value;
    }

    options = read4BE(&buf);
    LOGV("        options=0x%04x%s%s\n", options,
        (options & INVOKE_SINGLE_THREADED) ? " (SINGLE_THREADED)" : "",
        (options & INVOKE_NONVIRTUAL) ? " (NONVIRTUAL)" : "");


    u1 resultTag;
    u8 resultValue;
    ObjectId exceptObjId;

    err = dvmDbgInvokeMethod(threadId, objectId, classId, methodId,
            numArgs, argArray, options,
            &resultTag, &resultValue, &exceptObjId);
    if (err != ERR_NONE)
        goto bail;

    if (err == ERR_NONE) {
        if (isConstructor) {
            expandBufAdd1(pReply, JT_OBJECT);
            expandBufAddObjectId(pReply, objectId);
        } else {
            int width = dvmDbgGetTagWidth(resultTag);

            expandBufAdd1(pReply, resultTag);
            if (width != 0)
                jdwpWriteValue(pReply, width, resultValue);
        }
        expandBufAdd1(pReply, JT_OBJECT);
        expandBufAddObjectId(pReply, exceptObjId);

        LOGV("  --> returned '%c' 0x%llx (except=%08llx)\n",
            resultTag, resultValue, exceptObjId);

        /* show detailed debug output */
        if (resultTag == JT_STRING && exceptObjId == 0) {
            if (resultValue != 0) {
                char* str = dvmDbgStringToUtf8(resultValue);
                LOGV("      string '%s'\n", str);
                free(str);
            } else {
                LOGV("      string (null)\n");
            }
        }
    }

bail:
    free(argArray);
    return err;
}


/*
 * Request for version info.
 */
static JdwpError handleVM_Version(JdwpState* state, const u1* buf,
    int dataLen, ExpandBuf* pReply)
{
    char tmpBuf[128];

    /* text information on VM version */
    sprintf(tmpBuf, "Android DalvikVM %d.%d.%d",
        DALVIK_MAJOR_VERSION, DALVIK_MINOR_VERSION, DALVIK_BUG_VERSION);
    expandBufAddUtf8String(pReply, (const u1*) tmpBuf);
    /* JDWP version numbers */
    expandBufAdd4BE(pReply, 1);        // major
    expandBufAdd4BE(pReply, 5);        // minor
    /* VM JRE version */
    expandBufAddUtf8String(pReply, (const u1*) "1.5.0");  /* e.g. 1.5.0_04 */
    /* target VM name */
    expandBufAddUtf8String(pReply, (const u1*) "DalvikVM");

    return ERR_NONE;
}

/*
 * Given a class JNI signature (e.g. "Ljava/lang/Error;"), return the
 * referenceTypeID.  We need to send back more than one if the class has
 * been loaded by multiple class loaders.
 */
static JdwpError handleVM_ClassesBySignature(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    char* classDescriptor = NULL;
    u4 numClasses;
    size_t strLen;
    RefTypeId refTypeId;
    int i;

    classDescriptor = readNewUtf8String(&buf, &strLen);
    LOGV("  Req for class by signature '%s'\n", classDescriptor);

    /*
     * TODO: if a class with the same name has been loaded multiple times
     * (by different class loaders), we're supposed to return each of them.
     *
     * NOTE: this may mangle "className".
     */
    if (!dvmDbgFindLoadedClassBySignature(classDescriptor, &refTypeId)) {
        /* not currently loaded */
        LOGV("    --> no match!\n");
        numClasses = 0;
    } else {
        /* just the one */
        numClasses = 1;
    }

    expandBufAdd4BE(pReply, numClasses);

    if (numClasses > 0) {
        u1 typeTag;
        u4 status;

        /* get class vs. interface and status flags */
        dvmDbgGetClassInfo(refTypeId, &typeTag, &status, NULL);

        expandBufAdd1(pReply, typeTag);
        expandBufAddRefTypeId(pReply, refTypeId);
        expandBufAdd4BE(pReply, status);
    }

    free(classDescriptor);

    return ERR_NONE;
}

/*
 * Handle request for the thread IDs of all running threads.
 *
 * We exclude ourselves from the list, because we don't allow ourselves
 * to be suspended, and that violates some JDWP expectations.
 */
static JdwpError handleVM_AllThreads(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    u4 threadCount;
    ObjectId* pThreadIds;
    ObjectId* walker;
    int i;

    dvmDbgGetAllThreads(&pThreadIds, &threadCount);

    expandBufAdd4BE(pReply, threadCount);

    walker = pThreadIds;
    for (i = 0; i < (int) threadCount; i++) {
        expandBufAddObjectId(pReply, *walker++);
    }

    free(pThreadIds);

    return ERR_NONE;
}

/*
 * List all thread groups that do not have a parent.
 */
static JdwpError handleVM_TopLevelThreadGroups(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    u4 groups;
    ObjectId threadGroupId;

    /*
     * TODO: maintain a list of parentless thread groups in the VM.
     *
     * For now, just return "system".  Application threads are created
     * in "main", which is a child of "system".
     */
    groups = 1;
    expandBufAdd4BE(pReply, groups);
    //threadGroupId = debugGetMainThreadGroup();
    //expandBufAdd8BE(pReply, threadGroupId);
    threadGroupId = dvmDbgGetSystemThreadGroupId();
    expandBufAddObjectId(pReply, threadGroupId);

    return ERR_NONE;
}

/*
 * Respond with the sizes of the basic debugger types.
 *
 * All IDs are 8 bytes.
 */
static JdwpError handleVM_IDSizes(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    expandBufAdd4BE(pReply, sizeof(FieldId));
    expandBufAdd4BE(pReply, sizeof(MethodId));
    expandBufAdd4BE(pReply, sizeof(ObjectId));
    expandBufAdd4BE(pReply, sizeof(RefTypeId));
    expandBufAdd4BE(pReply, sizeof(FrameId));
    return ERR_NONE;
}

/*
 * The debugger is politely asking to disconnect.  We're good with that.
 *
 * We could resume threads and clean up pinned references, but we can do
 * that when the TCP connection drops.
 */
static JdwpError handleVM_Dispose(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    return ERR_NONE;
}

/*
 * Suspend the execution of the application running in the VM (i.e. suspend
 * all threads).
 *
 * This needs to increment the "suspend count" on all threads.
 */
static JdwpError handleVM_Suspend(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    dvmDbgSuspendVM(false);
    return ERR_NONE;
}

/*
 * Resume execution.  Decrements the "suspend count" of all threads.
 */
static JdwpError handleVM_Resume(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    dvmDbgResumeVM();
    return ERR_NONE;
}

/*
 * The debugger wants the entire VM to exit.
 */
static JdwpError handleVM_Exit(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    u4 exitCode;

    exitCode = get4BE(buf);

    LOGW("Debugger is telling the VM to exit with code=%d\n", exitCode);

    dvmDbgExit(exitCode);
    return ERR_NOT_IMPLEMENTED;     // shouldn't get here
}

/*
 * Create a new string in the VM and return its ID.
 *
 * (Ctrl-Shift-I in Eclipse on an array of objects causes it to create the
 * string "java.util.Arrays".)
 */
static JdwpError handleVM_CreateString(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    char* str;
    size_t strLen;
    ObjectId stringId;

    str = readNewUtf8String(&buf, &strLen);

    LOGV("  Req to create string '%s'\n", str);

    stringId = dvmDbgCreateString(str);
    if (stringId == 0)
        return ERR_OUT_OF_MEMORY;

    expandBufAddObjectId(pReply, stringId);
    return ERR_NONE;
}

/*
 * Tell the debugger what we are capable of.
 */
static JdwpError handleVM_Capabilities(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    int i;

    expandBufAdd1(pReply, false);   /* canWatchFieldModification */
    expandBufAdd1(pReply, false);   /* canWatchFieldAccess */
    expandBufAdd1(pReply, false);   /* canGetBytecodes */
    expandBufAdd1(pReply, false);   /* canGetSyntheticAttribute */
    expandBufAdd1(pReply, false);   /* canGetOwnedMonitorInfo */
    expandBufAdd1(pReply, false);   /* canGetCurrentContendedMonitor */
    expandBufAdd1(pReply, false);   /* canGetMonitorInfo */
    return ERR_NONE;
}

/*
 * Return classpath and bootclasspath.
 */
static JdwpError handleVM_ClassPaths(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    char baseDir[2] = "/";
    u4 classPaths;
    u4 bootClassPaths;
    int i;

    /*
     * TODO: make this real.  Not important for remote debugging, but
     * might be useful for local debugging.
     */
    classPaths = 1;
    bootClassPaths = 0;

    expandBufAddUtf8String(pReply, (const u1*) baseDir);
    expandBufAdd4BE(pReply, classPaths);
    for (i = 0; i < (int) classPaths; i++) {
        expandBufAddUtf8String(pReply, (const u1*) ".");
    }

    expandBufAdd4BE(pReply, bootClassPaths);
    for (i = 0; i < (int) classPaths; i++) {
        /* add bootclasspath components as strings */
    }

    return ERR_NONE;
}

/*
 * Release a list of object IDs.  (Seen in jdb.)
 *
 * Currently does nothing.
 */
static JdwpError HandleVM_DisposeObjects(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    return ERR_NONE;
}

/*
 * Tell the debugger what we are capable of.
 */
static JdwpError handleVM_CapabilitiesNew(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    int i;

    expandBufAdd1(pReply, false);   /* canWatchFieldModification */
    expandBufAdd1(pReply, false);   /* canWatchFieldAccess */
    expandBufAdd1(pReply, false);   /* canGetBytecodes */
    expandBufAdd1(pReply, false);   /* canGetSyntheticAttribute */
    expandBufAdd1(pReply, false);   /* canGetOwnedMonitorInfo */
    expandBufAdd1(pReply, false);   /* canGetCurrentContendedMonitor */
    expandBufAdd1(pReply, false);   /* canGetMonitorInfo */
    expandBufAdd1(pReply, false);   /* canRedefineClasses */
    expandBufAdd1(pReply, false);   /* canAddMethod */
    expandBufAdd1(pReply, false);   /* canUnrestrictedlyRedefineClasses */
    expandBufAdd1(pReply, false);   /* canPopFrames */
    expandBufAdd1(pReply, false);   /* canUseInstanceFilters */
    expandBufAdd1(pReply, false);   /* canGetSourceDebugExtension */
    expandBufAdd1(pReply, false);   /* canRequestVMDeathEvent */
    expandBufAdd1(pReply, false);   /* canSetDefaultStratum */
    expandBufAdd1(pReply, false);   /* 1.6: canGetInstanceInfo */
    expandBufAdd1(pReply, false);   /* 1.6: canRequestMonitorEvents */
    expandBufAdd1(pReply, false);   /* 1.6: canGetMonitorFrameInfo */
    expandBufAdd1(pReply, false);   /* 1.6: canUseSourceNameFilters */
    expandBufAdd1(pReply, false);   /* 1.6: canGetConstantPool */
    expandBufAdd1(pReply, false);   /* 1.6: canForceEarlyReturn */

    /* fill in reserved22 through reserved32; note count started at 1 */
    for (i = 22; i <= 32; i++)
        expandBufAdd1(pReply, false);   /* reservedN */
    return ERR_NONE;
}

/*
 * Cough up the complete list of classes.
 */
static JdwpError handleVM_AllClassesWithGeneric(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    u4 numClasses = 0;
    RefTypeId* classRefBuf = NULL;
    int i;

    dvmDbgGetClassList(&numClasses, &classRefBuf);

    expandBufAdd4BE(pReply, numClasses);

    for (i = 0; i < (int) numClasses; i++) {
        static const u1 genericSignature[1] = "";
        u1 refTypeTag;
        char* signature;
        u4 status;

        dvmDbgGetClassInfo(classRefBuf[i], &refTypeTag, &status, &signature);

        expandBufAdd1(pReply, refTypeTag);
        expandBufAddRefTypeId(pReply, classRefBuf[i]);
        expandBufAddUtf8String(pReply, (const u1*) signature);
        expandBufAddUtf8String(pReply, genericSignature);
        expandBufAdd4BE(pReply, status);

        free(signature);
    }

    free(classRefBuf);

    return ERR_NONE;
}

/*
 * Given a referenceTypeID, return a string with the JNI reference type
 * signature (e.g. "Ljava/lang/Error;").
 */
static JdwpError handleRT_Signature(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    char* signature;
    RefTypeId refTypeId;

    refTypeId = dvmReadRefTypeId(&buf);

    LOGV("  Req for signature of refTypeId=0x%llx\n", refTypeId);
    signature = dvmDbgGetSignature(refTypeId);
    expandBufAddUtf8String(pReply, (const u1*) signature);
    free(signature);

    return ERR_NONE;
}

/*
 * Return the modifiers (a/k/a access flags) for a reference type.
 */
static JdwpError handleRT_Modifiers(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId refTypeId;
    u4 modBits;

    refTypeId = dvmReadRefTypeId(&buf);
    modBits = dvmDbgGetAccessFlags(refTypeId);

    expandBufAdd4BE(pReply, modBits);

    return ERR_NONE;
}

/*
 * Get values from static fields in a reference type.
 */
static JdwpError handleRT_GetValues(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId refTypeId;
    u4 numFields;
    int i;

    refTypeId = dvmReadRefTypeId(&buf);
    numFields = read4BE(&buf);

    expandBufAdd4BE(pReply, numFields);
    for (i = 0; i < (int) numFields; i++) {
        FieldId fieldId;
        u1 fieldTag;
        int width;
        u1* ptr;

        fieldId = dvmReadFieldId(&buf);
        fieldTag = dvmDbgGetFieldTag(refTypeId, fieldId);
        width = dvmDbgGetTagWidth(fieldTag);

        expandBufAdd1(pReply, fieldTag);
        ptr = expandBufAddSpace(pReply, width);
        dvmDbgGetStaticFieldValue(refTypeId, fieldId, ptr, width);
    }

    return ERR_NONE;
}

/*
 * Get the name of the source file in which a reference type was declared.
 */
static JdwpError handleRT_SourceFile(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId refTypeId;
    const char* fileName;

    refTypeId = dvmReadRefTypeId(&buf);

    fileName = dvmDbgGetSourceFile(refTypeId);
    if (fileName != NULL) {
        expandBufAddUtf8String(pReply, (const u1*) fileName);
        return ERR_NONE;
    } else {
        return ERR_ABSENT_INFORMATION;
    }
}

/*
 * Return the current status of the reference type.
 */
static JdwpError handleRT_Status(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId refTypeId;
    u1 typeTag;
    u4 status;

    refTypeId = dvmReadRefTypeId(&buf);

    /* get status flags */
    dvmDbgGetClassInfo(refTypeId, &typeTag, &status, NULL);
    expandBufAdd4BE(pReply, status);
    return ERR_NONE;
}

/*
 * Return interfaces implemented directly by this class.
 */
static JdwpError handleRT_Interfaces(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId refTypeId;

    refTypeId = dvmReadRefTypeId(&buf);

    LOGV("  Req for interfaces in %llx (%s)\n", refTypeId,
        dvmDbgGetClassDescriptor(refTypeId));

    dvmDbgOutputAllInterfaces(refTypeId, pReply);

    return ERR_NONE;
}

/*
 * Return the class object corresponding to this type.
 */
static JdwpError handleRT_ClassObject(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId refTypeId;
    ObjectId classObjId;

    refTypeId = dvmReadRefTypeId(&buf);
    classObjId = dvmDbgGetClassObject(refTypeId);

    LOGV("  RefTypeId %llx -> ObjectId %llx\n", refTypeId, classObjId);

    expandBufAddObjectId(pReply, classObjId);

    return ERR_NONE;
}

/*
 * Returns the value of the SourceDebugExtension attribute.
 *
 * JDB seems interested, but DEX files don't currently support this.
 */
static JdwpError handleRT_SourceDebugExtension(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    /* referenceTypeId in, string out */
    return ERR_ABSENT_INFORMATION;
}

/*
 * Like RT_Signature but with the possibility of a "generic signature".
 */
static JdwpError handleRT_SignatureWithGeneric(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    static const u1 genericSignature[1] = "";
    char* signature;
    RefTypeId refTypeId;

    refTypeId = dvmReadRefTypeId(&buf);

    LOGV("  Req for signature of refTypeId=0x%llx\n", refTypeId);
    signature = dvmDbgGetSignature(refTypeId);
    if (signature != NULL)
        expandBufAddUtf8String(pReply, (const u1*) signature);
    else
        expandBufAddUtf8String(pReply, (const u1*) "Lunknown;");  /* native? */
    expandBufAddUtf8String(pReply, genericSignature);
    free(signature);

    return ERR_NONE;
}

/*
 * Return the instance of java.lang.ClassLoader that loaded the specified
 * reference type, or null if it was loaded by the system loader.
 */
static JdwpError handleRT_ClassLoader(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId refTypeId;
    ObjectId classLoaderId;

    refTypeId = dvmReadRefTypeId(&buf);

    expandBufAddObjectId(pReply, dvmDbgGetClassLoader(refTypeId));

    return ERR_NONE;
}

/*
 * Given a referenceTypeId, return a block of stuff that describes the
 * fields declared by a class.
 */
static JdwpError handleRT_FieldsWithGeneric(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId refTypeId;
    int i, numFields;

    refTypeId = dvmReadRefTypeId(&buf);
    LOGV("  Req for fields in refTypeId=0x%llx\n", refTypeId);
    {
        char* tmp = dvmDbgGetSignature(refTypeId);
        LOGV("  --> '%s'\n", tmp);
        free(tmp);
    }

    dvmDbgOutputAllFields(refTypeId, true, pReply);

    return ERR_NONE;
}

/*
 * Given a referenceTypeID, return a block of goodies describing the
 * methods declared by a class.
 */
static JdwpError handleRT_MethodsWithGeneric(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId refTypeId;
    int i;

    refTypeId = dvmReadRefTypeId(&buf);

    LOGV("  Req for methods in refTypeId=0x%llx\n", refTypeId);
    {
        char* tmp = dvmDbgGetSignature(refTypeId);
        LOGV("  --> '%s'\n", tmp);
        free(tmp);
    }

    dvmDbgOutputAllMethods(refTypeId, true, pReply);

    return ERR_NONE;
}

/*
 * Return the immediate superclass of a class.
 */
static JdwpError handleCT_Superclass(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId classId;
    RefTypeId superClassId;

    classId = dvmReadRefTypeId(&buf);

    superClassId = dvmDbgGetSuperclass(classId);

    expandBufAddRefTypeId(pReply, superClassId);

    return ERR_NONE;
}

/*
 * Set static class values.
 */
static JdwpError handleCT_SetValues(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId classId;
    u4 values;
    int i;

    classId = dvmReadRefTypeId(&buf);
    values = read4BE(&buf);

    LOGV("  Req to set %d values in classId=%llx\n", values, classId);

    for (i = 0; i < (int) values; i++) {
        FieldId fieldId;
        u1 fieldTag;
        u8 value;
        int width;

        fieldId = dvmReadFieldId(&buf);
        fieldTag = dvmDbgGetStaticFieldTag(classId, fieldId);
        width = dvmDbgGetTagWidth(fieldTag);
        value = jdwpReadValue(&buf, width);

        LOGV("    --> field=%x tag=%c -> %lld\n", fieldId, fieldTag, value);
        dvmDbgSetStaticFieldValue(classId, fieldId, value, width);
    }

    return ERR_NONE;
}

/*
 * Invoke a static method.
 *
 * Example: Eclipse sometimes uses java/lang/Class.forName(String s) on
 * values in the "variables" display.
 */
static JdwpError handleCT_InvokeMethod(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId classId;
    ObjectId threadId;
    MethodId methodId;

    classId = dvmReadRefTypeId(&buf);
    threadId = dvmReadObjectId(&buf);
    methodId = dvmReadMethodId(&buf);

    return finishInvoke(state, buf, dataLen, pReply,
            threadId, 0, classId, methodId, false);
}

/*
 * Create a new object of the requested type, and invoke the specified
 * constructor.
 *
 * Example: in IntelliJ, create a watch on "new String(myByteArray)" to
 * see the contents of a byte[] as a string.
 */
static JdwpError handleCT_NewInstance(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId classId;
    ObjectId threadId;
    MethodId methodId;
    ObjectId objectId;
    u4 numArgs;

    classId = dvmReadRefTypeId(&buf);
    threadId = dvmReadObjectId(&buf);
    methodId = dvmReadMethodId(&buf);

    LOGV("Creating instance of %s\n", dvmDbgGetClassDescriptor(classId));
    objectId = dvmDbgCreateObject(classId);
    if (objectId == 0)
        return ERR_OUT_OF_MEMORY;

    return finishInvoke(state, buf, dataLen, pReply,
            threadId, objectId, classId, methodId, true);
}

/*
 * Return line number information for the method, if present.
 */
static JdwpError handleM_LineTable(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId refTypeId;
    MethodId methodId;

    refTypeId = dvmReadRefTypeId(&buf);
    methodId = dvmReadMethodId(&buf);

    LOGV("  Req for line table in %s.%s\n",
        dvmDbgGetClassDescriptor(refTypeId),
        dvmDbgGetMethodName(refTypeId,methodId));

    dvmDbgOutputLineTable(refTypeId, methodId, pReply);

    return ERR_NONE;
}

/*
 * Pull out the LocalVariableTable goodies.
 */
static JdwpError handleM_VariableTableWithGeneric(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId classId;
    MethodId methodId;

    classId = dvmReadRefTypeId(&buf);
    methodId = dvmReadMethodId(&buf);

    LOGV("  Req for LocalVarTab in class=%s method=%s\n",
        dvmDbgGetClassDescriptor(classId),
        dvmDbgGetMethodName(classId, methodId));

    /*
     * We could return ERR_ABSENT_INFORMATION here if the DEX file was
     * built without local variable information.  That will cause Eclipse
     * to make a best-effort attempt at displaying local variables
     * anonymously.  However, the attempt isn't very good, so we're probably
     * better off just not showing anything.
     */
    dvmDbgOutputVariableTable(classId, methodId, true, pReply);
    return ERR_NONE;
}

/*
 * Given an object reference, return the runtime type of the object
 * (class or array).
 *
 * This can get called on different things, e.g. threadId gets
 * passed in here.
 */
static JdwpError handleOR_ReferenceType(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId objectId;
    u1 refTypeTag;
    RefTypeId typeId;

    objectId = dvmReadObjectId(&buf);
    LOGV("  Req for type of objectId=0x%llx\n", objectId);

    dvmDbgGetObjectType(objectId, &refTypeTag, &typeId);

    expandBufAdd1(pReply, refTypeTag);
    expandBufAddRefTypeId(pReply, typeId);

    return ERR_NONE;
}

/*
 * Get values from the fields of an object.
 */
static JdwpError handleOR_GetValues(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId objectId;
    u4 numFields;
    int i;

    objectId = dvmReadObjectId(&buf);
    numFields = read4BE(&buf);

    LOGV("  Req for %d fields from objectId=0x%llx\n", numFields, objectId);

    expandBufAdd4BE(pReply, numFields);

    for (i = 0; i < (int) numFields; i++) {
        FieldId fieldId;
        u1 fieldTag;
        int width;
        u1* ptr;
        const char* fieldName;

        fieldId = dvmReadFieldId(&buf);

        fieldTag = dvmDbgGetFieldTag(objectId, fieldId);
        width = dvmDbgGetTagWidth(fieldTag);

        LOGV("    --> fieldId %x --> tag '%c'(%d)\n",
            fieldId, fieldTag, width);

        expandBufAdd1(pReply, fieldTag);
        ptr = expandBufAddSpace(pReply, width);
        dvmDbgGetFieldValue(objectId, fieldId, ptr, width);
    }

    return ERR_NONE;
}

/*
 * Set values in the fields of an object.
 */
static JdwpError handleOR_SetValues(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId objectId;
    u4 numFields;
    int i;

    objectId = dvmReadObjectId(&buf);
    numFields = read4BE(&buf);

    LOGV("  Req to set %d fields in objectId=0x%llx\n", numFields, objectId);

    for (i = 0; i < (int) numFields; i++) {
        FieldId fieldId;
        u1 fieldTag;
        int width;
        u8 value;

        fieldId = dvmReadFieldId(&buf);

        fieldTag = dvmDbgGetFieldTag(objectId, fieldId);
        width = dvmDbgGetTagWidth(fieldTag);
        value = jdwpReadValue(&buf, width);

        LOGV("    --> fieldId=%x tag='%c'(%d) value=%lld\n",
            fieldId, fieldTag, width, value);

        dvmDbgSetFieldValue(objectId, fieldId, value, width);
    }

    return ERR_NONE;
}

/*
 * Invoke an instance method.  The invocation must occur in the specified
 * thread, which must have been suspended by an event.
 *
 * The call is synchronous.  All threads in the VM are resumed, unless the
 * SINGLE_THREADED flag is set.
 *
 * If you ask Eclipse to "inspect" an object (or ask JDB to "print" an
 * object), it will try to invoke the object's toString() function.  This
 * feature becomes crucial when examining ArrayLists with Eclipse.
 */
static JdwpError handleOR_InvokeMethod(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId objectId;
    ObjectId threadId;
    RefTypeId classId;
    MethodId methodId;

    objectId = dvmReadObjectId(&buf);
    threadId = dvmReadObjectId(&buf);
    classId = dvmReadRefTypeId(&buf);
    methodId = dvmReadMethodId(&buf);

    return finishInvoke(state, buf, dataLen, pReply,
            threadId, objectId, classId, methodId, false);
}

/*
 * Disable garbage collection of the specified object.
 */
static JdwpError handleOR_DisableCollection(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    // this is currently a no-op
    return ERR_NONE;
}

/*
 * Enable garbage collection of the specified object.
 */
static JdwpError handleOR_EnableCollection(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    // this is currently a no-op
    return ERR_NONE;
}

/*
 * Determine whether an object has been garbage collected.
 */
static JdwpError handleOR_IsCollected(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId objectId;

    objectId = dvmReadObjectId(&buf);

    LOGV("  Req IsCollected(0x%llx)\n", objectId);

    // TODO: currently returning false; must integrate with GC
    expandBufAdd1(pReply, 0);

    return ERR_NONE;
}

/*
 * Return the string value in a string object.
 */
static JdwpError handleSR_Value(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId stringObject;
    char* str;

    stringObject = dvmReadObjectId(&buf);
    str = dvmDbgStringToUtf8(stringObject);

    LOGV("  Req for str %llx --> '%s'\n", stringObject, str);

    expandBufAddUtf8String(pReply, (u1*) str);
    free(str);

    return ERR_NONE;
}

/*
 * Return a thread's name.
 */
static JdwpError handleTR_Name(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadId;
    char* name;

    threadId = dvmReadObjectId(&buf);

    LOGV("  Req for name of thread 0x%llx\n", threadId);
    name = dvmDbgGetThreadName(threadId);
    if (name == NULL)
        return ERR_INVALID_THREAD;

    expandBufAddUtf8String(pReply, (u1*) name);
    free(name);

    return ERR_NONE;
}

/*
 * Suspend the specified thread.
 *
 * It's supposed to remain suspended even if interpreted code wants to
 * resume it; only the JDI is allowed to resume it.
 */
static JdwpError handleTR_Suspend(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadId;

    threadId = dvmReadObjectId(&buf);

    if (threadId == dvmDbgGetThreadSelfId()) {
        LOGI("  Warning: ignoring request to suspend self\n");
        return ERR_THREAD_NOT_SUSPENDED;
    }
    LOGV("  Req to suspend thread 0x%llx\n", threadId);

    dvmDbgSuspendThread(threadId);

    return ERR_NONE;
}

/*
 * Resume the specified thread.
 */
static JdwpError handleTR_Resume(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadId;

    threadId = dvmReadObjectId(&buf);

    if (threadId == dvmDbgGetThreadSelfId()) {
        LOGI("  Warning: ignoring request to resume self\n");
        return ERR_NONE;
    }
    LOGV("  Req to resume thread 0x%llx\n", threadId);

    dvmDbgResumeThread(threadId);

    return ERR_NONE;
}

/*
 * Return status of specified thread.
 */
static JdwpError handleTR_Status(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadId;
    u4 threadStatus;
    u4 suspendStatus;

    threadId = dvmReadObjectId(&buf);

    LOGV("  Req for status of thread 0x%llx\n", threadId);

    if (!dvmDbgGetThreadStatus(threadId, &threadStatus, &suspendStatus))
        return ERR_INVALID_THREAD;

    LOGV("    --> %s, %s\n", dvmJdwpThreadStatusStr(threadStatus),
        dvmJdwpSuspendStatusStr(suspendStatus));

    expandBufAdd4BE(pReply, threadStatus);
    expandBufAdd4BE(pReply, suspendStatus);

    return ERR_NONE;
}

/*
 * Return the thread group that the specified thread is a member of.
 */
static JdwpError handleTR_ThreadGroup(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadId;
    ObjectId threadGroupId;

    threadId = dvmReadObjectId(&buf);

    /* currently not handling these */
    threadGroupId = dvmDbgGetThreadGroup(threadId);
    expandBufAddObjectId(pReply, threadGroupId);

    return ERR_NONE;
}

/*
 * Return the current call stack of a suspended thread.
 *
 * If the thread isn't suspended, the error code isn't defined, but should
 * be THREAD_NOT_SUSPENDED.
 */
static JdwpError handleTR_Frames(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadId;
    u4 startFrame, length, frames;
    int i, frameCount;

    threadId = dvmReadObjectId(&buf);
    startFrame = read4BE(&buf);
    length = read4BE(&buf);

    if (!dvmDbgThreadExists(threadId))
        return ERR_INVALID_THREAD;
    if (!dvmDbgIsSuspended(threadId)) {
        LOGV("  Rejecting req for frames in running thread '%s' (%llx)\n",
            dvmDbgGetThreadName(threadId), threadId);
        return ERR_THREAD_NOT_SUSPENDED;
    }

    frameCount = dvmDbgGetThreadFrameCount(threadId);

    LOGV("  Request for frames: threadId=%llx start=%d length=%d [count=%d]\n",
        threadId, startFrame, length, frameCount);
    if (frameCount <= 0)
        return ERR_THREAD_NOT_SUSPENDED;    /* == 0 means 100% native */

    if (length == (u4) -1)
        length = frameCount;
    assert((int) startFrame >= 0 && (int) startFrame < frameCount);
    assert((int) (startFrame + length) <= frameCount);

    frames = length;
    expandBufAdd4BE(pReply, frames);
    for (i = startFrame; i < (int) (startFrame+length); i++) {
        FrameId frameId;
        JdwpLocation loc;

        dvmDbgGetThreadFrame(threadId, i, &frameId, &loc);

        expandBufAdd8BE(pReply, frameId);
        dvmJdwpAddLocation(pReply, &loc);

        LOGVV("    Frame %d: id=%llx loc={type=%d cls=%llx mth=%x loc=%llx}\n",
            i, frameId, loc.typeTag, loc.classId, loc.methodId, loc.idx);
    }

    return ERR_NONE;
}

/*
 * Returns the #of frames on the specified thread, which must be suspended.
 */
static JdwpError handleTR_FrameCount(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadId;
    int frameCount;

    threadId = dvmReadObjectId(&buf);

    if (!dvmDbgThreadExists(threadId))
        return ERR_INVALID_THREAD;
    if (!dvmDbgIsSuspended(threadId)) {
        LOGV("  Rejecting req for frames in running thread '%s' (%llx)\n",
            dvmDbgGetThreadName(threadId), threadId);
        return ERR_THREAD_NOT_SUSPENDED;
    }

    frameCount = dvmDbgGetThreadFrameCount(threadId);
    if (frameCount < 0)
        return ERR_INVALID_THREAD;
    expandBufAdd4BE(pReply, (u4)frameCount);

    return ERR_NONE;
}

/*
 * Get the monitor that the thread is waiting on.
 */
static JdwpError handleTR_CurrentContendedMonitor(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadId;

    threadId = dvmReadObjectId(&buf);

    // TODO: create an Object to represent the monitor (we're currently
    // just using a raw Monitor struct in the VM)

    return ERR_NOT_IMPLEMENTED;
}

/*
 * Return the suspend count for the specified thread.
 *
 * (The thread *might* still be running -- it might not have examined
 * its suspend count recently.)
 */
static JdwpError handleTR_SuspendCount(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadId;
    u4 suspendCount;

    threadId = dvmReadObjectId(&buf);

    suspendCount = dvmDbgGetThreadSuspendCount(threadId);
    expandBufAdd4BE(pReply, suspendCount);

    return ERR_NONE;
}

/*
 * Return the name of a thread group.
 *
 * The Eclipse debugger recognizes "main" and "system" as special.
 */
static JdwpError handleTGR_Name(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadGroupId;
    char* name = NULL;

    threadGroupId = dvmReadObjectId(&buf);
    LOGV("  Req for name of threadGroupId=0x%llx\n", threadGroupId);

    name = dvmDbgGetThreadGroupName(threadGroupId);
    if (name != NULL)
        expandBufAddUtf8String(pReply, (u1*) name);
    else {
        expandBufAddUtf8String(pReply, (u1*) "BAD-GROUP-ID");
        LOGW("bad thread group ID\n");
    }

    free(name);

    return ERR_NONE;
}

/*
 * Returns the thread group -- if any -- that contains the specified
 * thread group.
 */
static JdwpError handleTGR_Parent(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId groupId;
    ObjectId parentGroup;

    groupId = dvmReadObjectId(&buf);

    parentGroup = dvmDbgGetThreadGroupParent(groupId);
    expandBufAddObjectId(pReply, parentGroup);

    return ERR_NONE;
}

/*
 * Return the active threads and thread groups that are part of the
 * specified thread group.
 */
static JdwpError handleTGR_Children(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadGroupId;
    u4 threadCount;
    ObjectId threadId;
    ObjectId* pThreadIds;
    ObjectId* walker;
    int i;

    threadGroupId = dvmReadObjectId(&buf);
    LOGV("  Req for threads in threadGroupId=0x%llx\n", threadGroupId);

    dvmDbgGetThreadGroupThreads(threadGroupId, &pThreadIds, &threadCount);

    expandBufAdd4BE(pReply, threadCount);

    walker = pThreadIds;
    for (i = 0; i < (int) threadCount; i++)
        expandBufAddObjectId(pReply, pThreadIds[i]);
    free(pThreadIds);

    /*
     * TODO: finish support for child groups
     *
     * For now, just show that "main" is a child of "system".
     */
    if (threadGroupId == dvmDbgGetSystemThreadGroupId()) {
        expandBufAdd4BE(pReply, 1);
        expandBufAddObjectId(pReply, dvmDbgGetMainThreadGroupId());
    } else {
        expandBufAdd4BE(pReply, 0);
    }

    return ERR_NONE;
}

/*
 * Return the #of components in the array.
 */
static JdwpError handleAR_Length(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId arrayId;
    u4 arrayLength;

    arrayId = dvmReadObjectId(&buf);
    LOGV("  Req for length of array 0x%llx\n", arrayId);

    arrayLength = dvmDbgGetArrayLength(arrayId);

    LOGV("    --> %d\n", arrayLength);

    expandBufAdd4BE(pReply, arrayLength);

    return ERR_NONE;
}

/*
 * Return the values from an array.
 */
static JdwpError handleAR_GetValues(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId arrayId;
    u4 firstIndex;
    u4 length;
    u1 tag;

    arrayId = dvmReadObjectId(&buf);
    firstIndex = read4BE(&buf);
    length = read4BE(&buf);

    tag = dvmDbgGetArrayElementTag(arrayId);
    LOGV("  Req for array values 0x%llx first=%d len=%d (elem tag=%c)\n",
        arrayId, firstIndex, length, tag);

    expandBufAdd1(pReply, tag);
    expandBufAdd4BE(pReply, length);

    if (!dvmDbgOutputArray(arrayId, firstIndex, length, pReply))
        return ERR_INVALID_LENGTH;

    return ERR_NONE;
}

/*
 * Set values in an array.
 */
static JdwpError handleAR_SetValues(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId arrayId;
    u4 firstIndex;
    u4 values;
    u1 tag;
    int i;

    arrayId = dvmReadObjectId(&buf);
    firstIndex = read4BE(&buf);
    values = read4BE(&buf);

    LOGV("  Req to set array values 0x%llx first=%d count=%d\n",
        arrayId, firstIndex, values);

    if (!dvmDbgSetArrayElements(arrayId, firstIndex, values, buf))
        return ERR_INVALID_LENGTH;

    return ERR_NONE;
}

/*
 * Return the set of classes visible to a class loader.  All classes which
 * have the class loader as a defining or initiating loader are returned.
 */
static JdwpError handleCLR_VisibleClasses(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId classLoaderObject;
    u4 numClasses = 0;
    RefTypeId* classRefBuf = NULL;
    int i;

    classLoaderObject = dvmReadObjectId(&buf);

    dvmDbgGetVisibleClassList(classLoaderObject, &numClasses, &classRefBuf);

    expandBufAdd4BE(pReply, numClasses);
    for (i = 0; i < (int) numClasses; i++) {
        u1 refTypeTag;

        refTypeTag = dvmDbgGetClassObjectType(classRefBuf[i]);

        expandBufAdd1(pReply, refTypeTag);
        expandBufAddRefTypeId(pReply, classRefBuf[i]);
    }

    return ERR_NONE;
}

/*
 * Set an event trigger.
 *
 * Reply with a requestID.
 */
static JdwpError handleER_Set(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    JdwpEvent* pEvent;
    JdwpError err;
    const u1* origBuf = buf;
    /*int origDataLen = dataLen;*/
    u1 eventKind;
    u1 suspendPolicy;
    u4 modifierCount;
    u4 requestId;
    int idx;

    eventKind = read1(&buf);
    suspendPolicy = read1(&buf);
    modifierCount = read4BE(&buf);

    LOGVV("  Set(kind=%s(%u) suspend=%s(%u) mods=%u)\n",
        dvmJdwpEventKindStr(eventKind), eventKind,
        dvmJdwpSuspendPolicyStr(suspendPolicy), suspendPolicy,
        modifierCount);

    assert(modifierCount < 256);    /* reasonableness check */

    pEvent = dvmJdwpEventAlloc(modifierCount);
    pEvent->eventKind = eventKind;
    pEvent->suspendPolicy = suspendPolicy;
    pEvent->modCount = modifierCount;

    /*
     * Read modifiers.  Ordering may be significant (see explanation of Count
     * mods in JDWP doc).
     */
    for (idx = 0; idx < (int) modifierCount; idx++) {
        u1 modKind;

        modKind = read1(&buf);

        pEvent->mods[idx].modKind = modKind;

        switch (modKind) {
        case MK_COUNT:          /* report once, when "--count" reaches 0 */
            {
                u4 count = read4BE(&buf);
                LOGVV("    Count: %u\n", count);
                if (count == 0)
                    return ERR_INVALID_COUNT;
                pEvent->mods[idx].count.count = count;
            }
            break;
        case MK_CONDITIONAL:    /* conditional on expression) */
            {
                u4 exprId = read4BE(&buf);
                LOGVV("    Conditional: %d\n", exprId);
                pEvent->mods[idx].conditional.exprId = exprId;
            }
            break;
        case MK_THREAD_ONLY:    /* only report events in specified thread */
            {
                ObjectId threadId = dvmReadObjectId(&buf);
                LOGVV("    ThreadOnly: %llx\n", threadId);
                pEvent->mods[idx].threadOnly.threadId = threadId;
            }
            break;
        case MK_CLASS_ONLY:     /* for ClassPrepare, MethodEntry */
            {
                RefTypeId clazzId = dvmReadRefTypeId(&buf);
                LOGVV("    ClassOnly: %llx (%s)\n",
                    clazzId, dvmDbgGetClassDescriptor(clazzId));
                pEvent->mods[idx].classOnly.referenceTypeId = clazzId;
            }
            break;
        case MK_CLASS_MATCH:    /* restrict events to matching classes */
            {
                char* pattern;
                size_t strLen;

                pattern = readNewUtf8String(&buf, &strLen);
                LOGVV("    ClassMatch: '%s'\n", pattern);
                /* pattern is "java.foo.*", we want "java/foo/ *" */
                pEvent->mods[idx].classMatch.classPattern =
                    dvmDotToSlash(pattern);
                free(pattern);
            }
            break;
        case MK_CLASS_EXCLUDE:  /* restrict events to non-matching classes */
            {
                char* pattern;
                size_t strLen;

                pattern = readNewUtf8String(&buf, &strLen);
                LOGVV("    ClassExclude: '%s'\n", pattern);
                pEvent->mods[idx].classExclude.classPattern =
                    dvmDotToSlash(pattern);
                free(pattern);
            }
            break;
        case MK_LOCATION_ONLY:  /* restrict certain events based on loc */
            {
                JdwpLocation loc;

                jdwpReadLocation(&buf, &loc);
                LOGVV("    LocationOnly: typeTag=%d classId=%llx methodId=%x idx=%llx\n",
                    loc.typeTag, loc.classId, loc.methodId, loc.idx);
                pEvent->mods[idx].locationOnly.loc = loc;
            }
            break;
        case MK_EXCEPTION_ONLY: /* modifies EK_EXCEPTION events */
            {
                RefTypeId exceptionOrNull;      /* null == all exceptions */
                u1 caught, uncaught;

                exceptionOrNull = dvmReadRefTypeId(&buf);
                caught = read1(&buf);
                uncaught = read1(&buf);
                LOGVV("    ExceptionOnly: type=%llx(%s) caught=%d uncaught=%d\n",
                    exceptionOrNull, (exceptionOrNull == 0) ? "null"
                        : dvmDbgGetClassDescriptor(exceptionOrNull),
                    caught, uncaught);

                pEvent->mods[idx].exceptionOnly.refTypeId = exceptionOrNull;
                pEvent->mods[idx].exceptionOnly.caught = caught;
                pEvent->mods[idx].exceptionOnly.uncaught = uncaught;
            }
            break;
        case MK_FIELD_ONLY:     /* for field access/mod events */
            {
                RefTypeId declaring = dvmReadRefTypeId(&buf);
                FieldId fieldId = dvmReadFieldId(&buf);
                LOGVV("    FieldOnly: %llx %x\n", declaring, fieldId);
                pEvent->mods[idx].fieldOnly.refTypeId = declaring;
                pEvent->mods[idx].fieldOnly.fieldId = fieldId;;
            }
            break;
        case MK_STEP:           /* for use with EK_SINGLE_STEP */
            {
                ObjectId threadId;
                u4 size, depth;

                threadId = dvmReadObjectId(&buf);
                size = read4BE(&buf);
                depth = read4BE(&buf);
                LOGVV("    Step: thread=%llx size=%s depth=%s\n",
                    threadId, dvmJdwpStepSizeStr(size),
                    dvmJdwpStepDepthStr(depth));

                pEvent->mods[idx].step.threadId = threadId;
                pEvent->mods[idx].step.size = size;
                pEvent->mods[idx].step.depth = depth;
            }
            break;
        case MK_INSTANCE_ONLY:  /* report events related to a specific obj */
            {
                ObjectId instance = dvmReadObjectId(&buf);
                LOGVV("    InstanceOnly: %llx\n", instance);
                pEvent->mods[idx].instanceOnly.objectId = instance;
            }
            break;
        default:
            LOGW("GLITCH: unsupported modKind=%d\n", modKind);
            break;
        }
    }

    /*
     * Make sure we consumed all data.  It is possible that the remote side
     * has sent us bad stuff, but for now we blame ourselves.
     */
    if (buf != origBuf + dataLen) {
        LOGW("GLITCH: dataLen is %d, we have consumed %d\n", dataLen,
            (int) (buf - origBuf));
    }

    /*
     * We reply with an integer "requestID".
     */
    requestId = dvmJdwpNextEventSerial(state);
    expandBufAdd4BE(pReply, requestId);

    pEvent->requestId = requestId;

    LOGV("    --> event requestId=0x%x\n", requestId);

    /* add it to the list */
    err = dvmJdwpRegisterEvent(state, pEvent);
    if (err != ERR_NONE) {
        /* registration failed, probably because event is bogus */
        dvmJdwpEventFree(pEvent);
        LOGW("WARNING: event request rejected\n");
    }
    return err;
}

/*
 * Clear an event.  Failure to find an event with a matching ID is a no-op
 * and does not return an error.
 */
static JdwpError handleER_Clear(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    u1 eventKind;
    u4 requestId;

    eventKind = read1(&buf);
    requestId = read4BE(&buf);

    LOGV("  Req to clear eventKind=%d requestId=0x%08x\n", eventKind,requestId);

    dvmJdwpUnregisterEventById(state, requestId);

    return ERR_NONE;
}

/*
 * Return the values of arguments and local variables.
 */
static JdwpError handleSF_GetValues(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadId;
    FrameId frameId;
    u4 slots;
    int i;

    threadId = dvmReadObjectId(&buf);
    frameId = dvmReadFrameId(&buf);
    slots = read4BE(&buf);

    LOGV("  Req for %d slots in threadId=%llx frameId=%llx\n",
        slots, threadId, frameId);

    expandBufAdd4BE(pReply, slots);     /* "int values" */
    for (i = 0; i < (int) slots; i++) {
        u4 slot;
        u1 reqSigByte;
        int width;
        u1* ptr;

        slot = read4BE(&buf);
        reqSigByte = read1(&buf);

        LOGV("    --> slot %d '%c'\n", slot, reqSigByte);

        width = dvmDbgGetTagWidth(reqSigByte);
        ptr = expandBufAddSpace(pReply, width+1);
        dvmDbgGetLocalValue(threadId, frameId, slot, reqSigByte, ptr, width);
    }

    return ERR_NONE;
}

/*
 * Set the values of arguments and local variables.
 */
static JdwpError handleSF_SetValues(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadId;
    FrameId frameId;
    u4 slots;
    int i;

    threadId = dvmReadObjectId(&buf);
    frameId = dvmReadFrameId(&buf);
    slots = read4BE(&buf);

    LOGV("  Req to set %d slots in threadId=%llx frameId=%llx\n",
        slots, threadId, frameId);

    for (i = 0; i < (int) slots; i++) {
        u4 slot;
        u1 sigByte;
        u8 value;
        int width;

        slot = read4BE(&buf);
        sigByte = read1(&buf);
        width = dvmDbgGetTagWidth(sigByte);
        value = jdwpReadValue(&buf, width);

        LOGV("    --> slot %d '%c' %llx\n", slot, sigByte, value);
        dvmDbgSetLocalValue(threadId, frameId, slot, sigByte, value, width);
    }

    return ERR_NONE;
}

/*
 * Returns the value of "this" for the specified frame.
 */
static JdwpError handleSF_ThisObject(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    ObjectId threadId;
    FrameId frameId;
    u1 objectTag;
    ObjectId objectId;
    char* typeName;

    threadId = dvmReadObjectId(&buf);
    frameId = dvmReadFrameId(&buf);

    if (!dvmDbgGetThisObject(threadId, frameId, &objectId))
        return ERR_INVALID_FRAMEID;

    if (objectId == 0) {
        typeName = strdup("null");
        objectTag = 0;
    } else {
        typeName = dvmDbgGetObjectTypeName(objectId);
        objectTag = dvmDbgGetObjectTag(objectId, typeName);
    }
    LOGV("  Req for 'this' in thread=%llx frame=%llx --> %llx %s '%c'\n",
        threadId, frameId, objectId, typeName, (char)objectTag);
    free(typeName);

    expandBufAdd1(pReply, objectTag);
    expandBufAddObjectId(pReply, objectId);

    return ERR_NONE;
}

/*
 * Return the reference type reflected by this class object.
 *
 * This appears to be required because ReferenceTypeId values are NEVER
 * reused, whereas ClassIds can be recycled like any other object.  (Either
 * that, or I have no idea what this is for.)
 */
static JdwpError handleCOR_ReflectedType(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    RefTypeId classObjectId;

    classObjectId = dvmReadRefTypeId(&buf);

    LOGV("  Req for refTypeId for class=%llx (%s)\n",
        classObjectId, dvmDbgGetClassDescriptor(classObjectId));

    /* just hand the type back to them */
    if (dvmDbgIsInterface(classObjectId))
        expandBufAdd1(pReply, TT_INTERFACE);
    else
        expandBufAdd1(pReply, TT_CLASS);
    expandBufAddRefTypeId(pReply, classObjectId);

    return ERR_NONE;
}

/*
 * Handle a DDM packet with a single chunk in it.
 */
static JdwpError handleDDM_Chunk(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    u1* replyBuf = NULL;
    int replyLen = -1;

    LOGV("  Handling DDM packet (%.4s)\n", buf);

    /*
     * On first DDM packet, notify all handlers that DDM is running.
     */
    if (!state->ddmActive) {
        state->ddmActive = true;
        dvmDbgDdmConnected();
    }

    /*
     * If they want to send something back, we copy it into the buffer.
     * A no-copy approach would be nicer.
     *
     * TODO: consider altering the JDWP stuff to hold the packet header
     * in a separate buffer.  That would allow us to writev() DDM traffic
     * instead of copying it into the expanding buffer.  The reduction in
     * heap requirements is probably more valuable than the efficiency.
     */
    if (dvmDbgDdmHandlePacket(buf, dataLen, &replyBuf, &replyLen)) {
        assert(replyLen > 0 && replyLen < 1*1024*1024);
        memcpy(expandBufAddSpace(pReply, replyLen), replyBuf, replyLen);
        free(replyBuf);
    }
    return ERR_NONE;
}

/*
 * Handler map decl.
 */
typedef JdwpError (*JdwpRequestHandler)(JdwpState* state,
    const u1* buf, int dataLen, ExpandBuf* reply);

typedef struct {
    u1  cmdSet;
    u1  cmd;
    JdwpRequestHandler  func;
    const char* descr;
} JdwpHandlerMap;

/*
 * Map commands to functions.
 *
 * Command sets 0-63 are incoming requests, 64-127 are outbound requests,
 * and 128-256 are vendor-defined.
 */
static const JdwpHandlerMap gHandlerMap[] = {
    /* VirtualMachine command set (1) */
    { 1,    1,  handleVM_Version,       "VirtualMachine.Version" },
    { 1,    2,  handleVM_ClassesBySignature,
                                        "VirtualMachine.ClassesBySignature" },
    //1,    3,  VirtualMachine.AllClasses
    { 1,    4,  handleVM_AllThreads,    "VirtualMachine.AllThreads" },
    { 1,    5,  handleVM_TopLevelThreadGroups,
                                        "VirtualMachine.TopLevelThreadGroups" },
    { 1,    6,  handleVM_Dispose,       "VirtualMachine.Dispose" },
    { 1,    7,  handleVM_IDSizes,       "VirtualMachine.IDSizes" },
    { 1,    8,  handleVM_Suspend,       "VirtualMachine.Suspend" },
    { 1,    9,  handleVM_Resume,        "VirtualMachine.Resume" },
    { 1,    10, handleVM_Exit,          "VirtualMachine.Exit" },
    { 1,    11, handleVM_CreateString,  "VirtualMachine.CreateString" },
    { 1,    12, handleVM_Capabilities,  "VirtualMachine.Capabilities" },
    { 1,    13, handleVM_ClassPaths,    "VirtualMachine.ClassPaths" },
    { 1,    14, HandleVM_DisposeObjects, "VirtualMachine.DisposeObjects" },
    //1,    15, HoldEvents
    //1,    16, ReleaseEvents
    { 1,    17, handleVM_CapabilitiesNew,
                                        "VirtualMachine.CapabilitiesNew" },
    //1,    18, RedefineClasses
    //1,    19, SetDefaultStratum
    { 1,    20, handleVM_AllClassesWithGeneric,
                                        "VirtualMachine.AllClassesWithGeneric"},
    //1,    21, InstanceCounts

    /* ReferenceType command set (2) */
    { 2,    1,  handleRT_Signature,     "ReferenceType.Signature" },
    { 2,    2,  handleRT_ClassLoader,   "ReferenceType.ClassLoader" },
    { 2,    3,  handleRT_Modifiers,     "ReferenceType.Modifiers" },
    //2,    4,  Fields
    //2,    5,  Methods
    { 2,    6,  handleRT_GetValues,     "ReferenceType.GetValues" },
    { 2,    7,  handleRT_SourceFile,    "ReferenceType.SourceFile" },
    //2,    8,  NestedTypes
    { 2,    9,  handleRT_Status,        "ReferenceType.Status" },
    { 2,    10, handleRT_Interfaces,    "ReferenceType.Interfaces" },
    { 2,    11, handleRT_ClassObject,   "ReferenceType.ClassObject" },
    { 2,    12, handleRT_SourceDebugExtension,
                                        "ReferenceType.SourceDebugExtension" },
    { 2,    13, handleRT_SignatureWithGeneric,
                                        "ReferenceType.SignatureWithGeneric" },
    { 2,    14, handleRT_FieldsWithGeneric,
                                        "ReferenceType.FieldsWithGeneric" },
    { 2,    15, handleRT_MethodsWithGeneric,
                                        "ReferenceType.MethodsWithGeneric" },
    //2,    16, Instances
    //2,    17, ClassFileVersion
    //2,    18, ConstantPool

    /* ClassType command set (3) */
    { 3,    1,  handleCT_Superclass,    "ClassType.Superclass" },
    { 3,    2,  handleCT_SetValues,     "ClassType.SetValues" },
    { 3,    3,  handleCT_InvokeMethod,  "ClassType.InvokeMethod" },
    { 3,    4,  handleCT_NewInstance,   "ClassType.NewInstance" },

    /* ArrayType command set (4) */
    //4,    1,  NewInstance

    /* InterfaceType command set (5) */

    /* Method command set (6) */
    { 6,    1,  handleM_LineTable,      "Method.LineTable" },
    //6,    2,  VariableTable
    //6,    3,  Bytecodes
    //6,    4,  IsObsolete
    { 6,    5,  handleM_VariableTableWithGeneric,
                                        "Method.VariableTableWithGeneric" },

    /* Field command set (8) */

    /* ObjectReference command set (9) */
    { 9,    1,  handleOR_ReferenceType, "ObjectReference.ReferenceType" },
    { 9,    2,  handleOR_GetValues,     "ObjectReference.GetValues" },
    { 9,    3,  handleOR_SetValues,     "ObjectReference.SetValues" },
    //9,    4,  (not defined)
    //9,    5,  MonitorInfo
    { 9,    6,  handleOR_InvokeMethod,  "ObjectReference.InvokeMethod" },
    { 9,    7,  handleOR_DisableCollection,
                                        "ObjectReference.DisableCollection" },
    { 9,    8,  handleOR_EnableCollection,
                                        "ObjectReference.EnableCollection" },
    { 9,    9,  handleOR_IsCollected,   "ObjectReference.IsCollected" },
    //9,    10, ReferringObjects

    /* StringReference command set (10) */
    { 10,   1,  handleSR_Value,         "StringReference.Value" },

    /* ThreadReference command set (11) */
    { 11,   1,  handleTR_Name,          "ThreadReference.Name" },
    { 11,   2,  handleTR_Suspend,       "ThreadReference.Suspend" },
    { 11,   3,  handleTR_Resume,        "ThreadReference.Resume" },
    { 11,   4,  handleTR_Status,        "ThreadReference.Status" },
    { 11,   5,  handleTR_ThreadGroup,   "ThreadReference.ThreadGroup" },
    { 11,   6,  handleTR_Frames,        "ThreadReference.Frames" },
    { 11,   7,  handleTR_FrameCount,    "ThreadReference.FrameCount" },
    //11,   8,  OwnedMonitors
    { 11,   9,  handleTR_CurrentContendedMonitor,
                                    "ThreadReference.CurrentContendedMonitor" },
    //11,   10, Stop
    //11,   11, Interrupt
    { 11,   12, handleTR_SuspendCount,  "ThreadReference.SuspendCount" },
    //11,   13, OwnedMonitorsStackDepthInfo
    //11,   14, ForceEarlyReturn

    /* ThreadGroupReference command set (12) */
    { 12,   1,  handleTGR_Name,         "ThreadGroupReference.Name" },
    { 12,   2,  handleTGR_Parent,       "ThreadGroupReference.Parent" },
    { 12,   3,  handleTGR_Children,     "ThreadGroupReference.Children" },

    /* ArrayReference command set (13) */
    { 13,   1,  handleAR_Length,        "ArrayReference.Length" },
    { 13,   2,  handleAR_GetValues,     "ArrayReference.GetValues" },
    { 13,   3,  handleAR_SetValues,     "ArrayReference.SetValues" },

    /* ClassLoaderReference command set (14) */
    { 14,   1,  handleCLR_VisibleClasses,
                                        "ClassLoaderReference.VisibleClasses" },

    /* EventRequest command set (15) */
    { 15,   1,  handleER_Set,           "EventRequest.Set" },
    { 15,   2,  handleER_Clear,         "EventRequest.Clear" },
    //15,   3,  ClearAllBreakpoints

    /* StackFrame command set (16) */
    { 16,   1,  handleSF_GetValues,     "StackFrame.GetValues" },
    { 16,   2,  handleSF_SetValues,     "StackFrame.SetValues" },
    { 16,   3,  handleSF_ThisObject,    "StackFrame.ThisObject" },
    //16,   4,  PopFrames

    /* ClassObjectReference command set (17) */
    { 17,   1,  handleCOR_ReflectedType,"ClassObjectReference.ReflectedType" },

    /* Event command set (64) */
    //64,  100, Composite   <-- sent from VM to debugger, never received by VM

    { 199,  1,  handleDDM_Chunk,        "DDM.Chunk" },
};


/*
 * Process a request from the debugger.
 *
 * On entry, the JDWP thread is in VMWAIT.
 */
void dvmJdwpProcessRequest(JdwpState* state, const JdwpReqHeader* pHeader,
    const u1* buf, int dataLen, ExpandBuf* pReply)
{
    JdwpError result = ERR_NONE;
    int i, respLen;

    /*
     * Activity from a debugger, not merely ddms.  Mark us as having an
     * active debugger session, and zero out the last-activity timestamp.
     */
    if (pHeader->cmdSet != kJDWPDdmCmdSet) {
        dvmDbgActive();

        state->lastActivitySec = 0;
        MEM_BARRIER();
    }

    /*
     * If a debugger event has fired in another thread, wait until the
     * initiating thread has suspended itself before processing messages
     * from the debugger.  Otherwise we (the JDWP thread) could be told to
     * resume the thread before it has suspended.
     *
     * We call with an argument of zero to wait for the current event
     * thread to finish, and then clear the block.  Depending on the thread
     * suspend policy, this may allow events in other threads to fire,
     * but those events have no bearing on what the debugger has sent us
     * in the current request.
     *
     * Note that we MUST clear the event token before waking the event
     * thread up, or risk waiting for the thread to suspend after we've
     * told it to resume.
     */
    dvmJdwpSetWaitForEventThread(state, 0);

    /*
     * Tell the VM that we're running and shouldn't be interrupted by GC.
     * Do this after anything that can stall indefinitely.
     */
    dvmDbgThreadRunning();

    expandBufAddSpace(pReply, kJDWPHeaderLen);

    for (i = 0; i < (int) NELEM(gHandlerMap); i++) {
        if (gHandlerMap[i].cmdSet == pHeader->cmdSet &&
            gHandlerMap[i].cmd == pHeader->cmd)
        {
            LOGV("REQ: %s (cmd=%d/%d dataLen=%d id=0x%06x)\n",
                gHandlerMap[i].descr, pHeader->cmdSet, pHeader->cmd,
                dataLen, pHeader->id);
            result = (*gHandlerMap[i].func)(state, buf, dataLen, pReply);
            break;
        }
    }
    if (i == NELEM(gHandlerMap)) {
        LOGE("REQ: UNSUPPORTED (cmd=%d/%d dataLen=%d id=0x%06x)\n",
            pHeader->cmdSet, pHeader->cmd, dataLen, pHeader->id);
        if (dataLen > 0)
            dvmPrintHexDumpDbg(buf, dataLen, LOG_TAG);
        assert(!"command not implemented");      // make it *really* obvious
        result = ERR_NOT_IMPLEMENTED;
    }

    /*
     * Set up the reply header.
     *
     * If we encountered an error, only send the header back.
     */
    u1* replyBuf = expandBufGetBuffer(pReply);
    set4BE(replyBuf + 4, pHeader->id);
    set1(replyBuf + 8, kJDWPFlagReply);
    set2BE(replyBuf + 9, result);
    if (result == ERR_NONE)
        set4BE(replyBuf + 0, expandBufGetLength(pReply));
    else
        set4BE(replyBuf + 0, kJDWPHeaderLen);

    respLen = expandBufGetLength(pReply) - kJDWPHeaderLen;
    IF_LOG(LOG_VERBOSE, LOG_TAG) {
        LOGV("reply: dataLen=%d err=%s(%d)%s\n", respLen,
            dvmJdwpErrorStr(result), result,
            result != ERR_NONE ? " **FAILED**" : "");
        if (respLen > 0)
            dvmPrintHexDumpDbg(expandBufGetBuffer(pReply) + kJDWPHeaderLen,
                respLen, LOG_TAG);
    }

    /*
     * Update last-activity timestamp.  We really only need this during
     * the initial setup.  Only update if this is a non-DDMS packet.
     */
    if (pHeader->cmdSet != kJDWPDdmCmdSet) {
        long lastSec, lastMsec;

        dvmJdwpGetNowMsec(&lastSec, &lastMsec);
        state->lastActivityMsec = lastMsec;
        MEM_BARRIER();      // updating a 64-bit value
        state->lastActivitySec = lastSec;
    }

    /* tell the VM that GC is okay again */
    dvmDbgThreadWaiting();
}