예제 #1
0
/*
 * Send a notification when a thread's name changes.
 */
void dvmDdmSendThreadNameChange(int threadId, StringObject* newName)
{
    if (!gDvm.ddmThreadNotification)
        return;

    size_t stringLen = dvmStringLen(newName);
    const u2* chars = dvmStringChars(newName);

    /*
     * Output format:
     *  (4b) thread ID
     *  (4b) stringLen
     *  (xb) string chars
     */
    int bufLen = 4 + 4 + (stringLen * 2);
    u1 buf[bufLen];

    set4BE(&buf[0x00], threadId);
    set4BE(&buf[0x04], stringLen);
    u2* outChars = (u2*) &buf[0x08];
    while (stringLen--)
        set2BE((u1*) (outChars++), *chars++);

    dvmDbgDdmSendChunk(CHUNK_TYPE("THNM"), bufLen, buf);
}
예제 #2
0
/*
 * Generate the contents of a THST chunk.  The data encompasses all known
 * threads.
 *
 * Response has:
 *  (1b) header len
 *  (1b) bytes per entry
 *  (2b) thread count
 * Then, for each thread:
 *  (4b) threadId
 *  (1b) thread status
 *  (4b) tid
 *  (4b) utime 
 *  (4b) stime 
 *  (1b) is daemon?
 *
 * The length fields exist in anticipation of adding additional fields
 * without wanting to break ddms or bump the full protocol version.  I don't
 * think it warrants full versioning.  They might be extraneous and could
 * be removed from a future version.
 *
 * Returns a new byte[] with the data inside, or NULL on failure.  The
 * caller must call dvmReleaseTrackedAlloc() on the array.
 */
ArrayObject* dvmDdmGenerateThreadStats(void)
{
    const int kHeaderLen = 4;
    const int kBytesPerEntry = 18;

    dvmLockThreadList(NULL);

    Thread* thread;
    int threadCount = 0;
    for (thread = gDvm.threadList; thread != NULL; thread = thread->next)
        threadCount++;

    /*
     * Create a temporary buffer.  We can't perform heap allocation with
     * the thread list lock held (could cause a GC).  The output is small
     * enough to sit on the stack.
     */
    int bufLen = kHeaderLen + threadCount * kBytesPerEntry;
    u1 tmpBuf[bufLen];
    u1* buf = tmpBuf;

    set1(buf+0, kHeaderLen);
    set1(buf+1, kBytesPerEntry);
    set2BE(buf+2, (u2) threadCount);
    buf += kHeaderLen;

    pid_t pid = getpid();
    for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
        unsigned long utime, stime;
        bool isDaemon;

        if (!getThreadStats(pid, thread->systemTid, &utime, &stime)) {
            // failed; drop in empty values
            utime = stime = 0;
        }

        isDaemon = dvmGetFieldBoolean(thread->threadObj,
                        gDvm.offJavaLangThread_daemon);

        set4BE(buf+0, thread->threadId);
        set1(buf+4, thread->status);
        set4BE(buf+5, thread->systemTid);
        set4BE(buf+9, utime);
        set4BE(buf+13, stime);
        set1(buf+17, isDaemon);

        buf += kBytesPerEntry;
    }
    dvmUnlockThreadList();


    /*
     * Create a byte array to hold the data.
     */
    ArrayObject* arrayObj = dvmAllocPrimitiveArray('B', bufLen, ALLOC_DEFAULT);
    if (arrayObj != NULL)
        memcpy(arrayObj->contents, tmpBuf, bufLen);
    return arrayObj;
}
예제 #3
0
static void append_chunk(HeapChunkContext *ctx, u1 state, void* ptr, size_t length) {
    /* Make sure there's enough room left in the buffer.
     * We need to use two bytes for every fractional 256
     * allocation units used by the chunk and 17 bytes for
     * any header.
     */
    {
        size_t needed = (((length/ALLOCATION_UNIT_SIZE + 255) / 256) * 2) + 17;
        size_t bytesLeft = ctx->bufLen - (size_t)(ctx->p - ctx->buf);
        if (bytesLeft < needed) {
            flush_hpsg_chunk(ctx);
        }
        bytesLeft = ctx->bufLen - (size_t)(ctx->p - ctx->buf);
        if (bytesLeft < needed) {
            ALOGW("chunk is too big to transmit (length=%zd, %zd bytes)",
                  length, needed);
            return;
        }
    }
    if (ctx->needHeader) {
        /*
         * Start a new HPSx chunk.
         */

        /* [u4]: heap ID */
        set4BE(ctx->p, DEFAULT_HEAP_ID); ctx->p += 4;

        /* [u1]: size of allocation unit, in bytes */
        *ctx->p++ = 8;

        /* [u4]: virtual address of segment start */
        set4BE(ctx->p, (uintptr_t)ptr); ctx->p += 4;

        /* [u4]: offset of this piece (relative to the virtual address) */
        set4BE(ctx->p, 0); ctx->p += 4;

        /* [u4]: length of piece, in allocation units
         * We won't know this until we're done, so save the offset
         * and stuff in a dummy value.
         */
        ctx->pieceLenField = ctx->p;
        set4BE(ctx->p, 0x55555555); ctx->p += 4;

        ctx->needHeader = false;
    }
    /* Write out the chunk description.
     */
    length /= ALLOCATION_UNIT_SIZE;   // convert to allocation units
    ctx->totalAllocationUnits += length;
    while (length > 256) {
        *ctx->p++ = state | HPSG_PARTIAL;
        *ctx->p++ = 255;     // length - 1
        length -= 256;
    }
    *ctx->p++ = state;
    *ctx->p++ = length - 1;
}
예제 #4
0
/*
 * Send a notification when a thread starts or stops.
 *
 * Because we broadcast the full set of threads when the notifications are
 * first enabled, it's possible for "thread" to be actively executing.
 */
void dvmDdmSendThreadNotification(Thread* thread, bool started)
{
    if (!gDvm.ddmThreadNotification)
        return;

    StringObject* nameObj = NULL;
    Object* threadObj = thread->threadObj;

    if (threadObj != NULL) {
        nameObj = (StringObject*)
            dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_name);
    }

    int type, len;
    u1 buf[256];

    if (started) {
        const u2* chars;
        u2* outChars;
        size_t stringLen;

        type = CHUNK_TYPE("THCR");

        if (nameObj != NULL) {
            stringLen = dvmStringLen(nameObj);
            chars = dvmStringChars(nameObj);
        } else {
            stringLen = 0;
            chars = NULL;
        }

        /* leave room for the two integer fields */
        if (stringLen > (sizeof(buf) - sizeof(u4)*2) / 2)
            stringLen = (sizeof(buf) - sizeof(u4)*2) / 2;
        len = stringLen*2 + sizeof(u4)*2;

        set4BE(&buf[0x00], thread->threadId);
        set4BE(&buf[0x04], stringLen);

        /* copy the UTF-16 string, transforming to big-endian */
        outChars = (u2*) &buf[0x08];
        while (stringLen--)
            set2BE((u1*) (outChars++), *chars++);
    } else {
        type = CHUNK_TYPE("THDE");

        len = 4;

        set4BE(&buf[0x00], thread->threadId);
    }

    dvmDbgDdmSendChunk(type, len, buf);
}
예제 #5
0
파일: JdwpEvent.cpp 프로젝트: handgod/soma
/*
 * Write the header into the buffer and send the packet off to the debugger.
 *
 * Takes ownership of "pReq" (currently discards it).
 */
static void eventFinish(JdwpState* state, ExpandBuf* pReq)
{
    u1* buf = expandBufGetBuffer(pReq);

    set4BE(buf, expandBufGetLength(pReq));
    set4BE(buf+4, dvmJdwpNextRequestSerial(state));
    set1(buf+8, 0);     /* flags */
    set1(buf+9, kJdwpEventCommandSet);
    set1(buf+10, kJdwpCompositeCommand);

    dvmJdwpSendRequest(state, pReq);

    expandBufFree(pReq);
}
예제 #6
0
void
dvmDdmSendHeapSegments(bool shouldLock, bool native)
{
    u1 heapId[sizeof(u4)];
    GcHeap *gcHeap = gDvm.gcHeap;
    int when, what;
    bool merge;

    /* Don't even grab the lock if there's nothing to do when we're called.
     */
    if (!native) {
        when = gcHeap->ddmHpsgWhen;
        what = gcHeap->ddmHpsgWhat;
        if (when == HPSG_WHEN_NEVER) {
            return;
        }
    } else {
        when = gcHeap->ddmNhsgWhen;
        what = gcHeap->ddmNhsgWhat;
        if (when == HPSG_WHEN_NEVER) {
            return;
        }
    }
    if (shouldLock && !dvmLockHeap()) {
        LOGW("Can't lock heap for DDM HPSx dump\n");
        return;
    }

    /* Figure out what kind of chunks we'll be sending.
     */
    if (what == HPSG_WHAT_MERGED_OBJECTS) {
        merge = true;
    } else if (what == HPSG_WHAT_DISTINCT_OBJECTS) {
        merge = false;
    } else {
        assert(!"bad HPSG.what value");
        return;
    }

    /* First, send a heap start chunk.
     */
    set4BE(heapId, DEFAULT_HEAP_ID);
    dvmDbgDdmSendChunk(native ? CHUNK_TYPE("NHST") : CHUNK_TYPE("HPST"),
        sizeof(u4), heapId);

    /* Send a series of heap segment chunks.
     */
    walkHeap(merge, native);

    /* Finally, send a heap end chunk.
     */
    dvmDbgDdmSendChunk(native ? CHUNK_TYPE("NHEN") : CHUNK_TYPE("HPEN"),
        sizeof(u4), heapId);

    if (shouldLock) {
        dvmUnlockHeap();
    }
}
예제 #7
0
파일: JdwpEvent.cpp 프로젝트: handgod/soma
/*
 * Send up a chunk of DDM data.
 *
 * While this takes the form of a JDWP "event", it doesn't interact with
 * other debugger traffic, and can't suspend the VM, so we skip all of
 * the fun event token gymnastics.
 */
void dvmJdwpDdmSendChunkV(JdwpState* state, int type, const struct iovec* iov,
    int iovcnt)
{
    u1 header[kJDWPHeaderLen + 8];
    size_t dataLen = 0;

    assert(iov != NULL);
    assert(iovcnt > 0 && iovcnt < 10);

    /*
     * "Wrap" the contents of the iovec with a JDWP/DDMS header.  We do
     * this by creating a new copy of the vector with space for the header.
     */
    struct iovec wrapiov[iovcnt+1];
    for (int i = 0; i < iovcnt; i++) {
        wrapiov[i+1].iov_base = iov[i].iov_base;
        wrapiov[i+1].iov_len = iov[i].iov_len;
        dataLen += iov[i].iov_len;
    }

    /* form the header (JDWP plus DDMS) */
    set4BE(header, sizeof(header) + dataLen);
    set4BE(header+4, dvmJdwpNextRequestSerial(state));
    set1(header+8, 0);     /* flags */
    set1(header+9, kJDWPDdmCmdSet);
    set1(header+10, kJDWPDdmCmd);
    set4BE(header+11, type);
    set4BE(header+15, dataLen);

    wrapiov[0].iov_base = header;
    wrapiov[0].iov_len = sizeof(header);

    /*
     * Make sure we're in VMWAIT in case the write blocks.
     */
    int oldStatus = dvmDbgThreadWaiting();
    dvmJdwpSendBufferedRequest(state, wrapiov, iovcnt+1);
    dvmDbgThreadContinuing(oldStatus);
}
예제 #8
0
static void
flush_hpsg_chunk(HeapChunkContext *ctx)
{
    /* Patch the "length of piece" field.
     */
    assert(ctx->buf <= ctx->pieceLenField &&
            ctx->pieceLenField <= ctx->p);
    set4BE(ctx->pieceLenField, ctx->totalAllocationUnits);

    /* Send the chunk.
     */
    dvmDbgDdmSendChunk(ctx->type, ctx->p - ctx->buf, ctx->buf);

    /* Reset the context.
     */
    ctx->p = ctx->buf;
    ctx->totalAllocationUnits = 0;
    ctx->needHeader = true;
    ctx->pieceLenField = NULL;
}
예제 #9
0
void dvmDdmSendHeapInfo(int reason, bool shouldLock)
{
    struct timeval now;
    u8 nowMs;
    u1 *buf, *b;

    buf = (u1 *)malloc(HPIF_SIZE(1));
    if (buf == NULL) {
        return;
    }
    b = buf;

    /* If there's a one-shot 'when', reset it.
     */
    if (reason == gDvm.gcHeap->ddmHpifWhen) {
        if (shouldLock && ! dvmLockHeap()) {
            ALOGW("%s(): can't lock heap to clear when", __func__);
            goto skip_when;
        }
        if (reason == gDvm.gcHeap->ddmHpifWhen) {
            if (gDvm.gcHeap->ddmHpifWhen == HPIF_WHEN_NEXT_GC) {
                gDvm.gcHeap->ddmHpifWhen = HPIF_WHEN_NEVER;
            }
        }
        if (shouldLock) {
            dvmUnlockHeap();
        }
    }
skip_when:

    /* The current time, in milliseconds since 0:00 GMT, 1/1/70.
     */
    if (gettimeofday(&now, NULL) < 0) {
        nowMs = 0;
    } else {
        nowMs = (u8)now.tv_sec * 1000 + now.tv_usec / 1000;
    }

    /* number of heaps */
    set4BE(b, 1); b += 4;

    /* For each heap (of which there is one) */
    {
        /* heap ID */
        set4BE(b, DEFAULT_HEAP_ID); b += 4;

        /* timestamp */
        set8BE(b, nowMs); b += 8;

        /* 'when' value */
        *b++ = (u1)reason;

        /* max allowed heap size in bytes */
        set4BE(b, dvmHeapSourceGetMaximumSize()); b += 4;

        /* current heap size in bytes */
        set4BE(b, dvmHeapSourceGetValue(HS_FOOTPRINT, NULL, 0)); b += 4;

        /* number of bytes allocated */
        set4BE(b, dvmHeapSourceGetValue(HS_BYTES_ALLOCATED, NULL, 0)); b += 4;

        /* number of objects allocated */
        set4BE(b, dvmHeapSourceGetValue(HS_OBJECTS_ALLOCATED, NULL, 0)); b += 4;
    }
    assert((intptr_t)b == (intptr_t)buf + (intptr_t)HPIF_SIZE(1));

    dvmDbgDdmSendChunk(CHUNK_TYPE("HPIF"), b - buf, buf);
}
예제 #10
0
static void
heap_chunk_callback(const void *chunkptr, size_t chunklen,
                    const void *userptr, size_t userlen, void *arg)
{
    HeapChunkContext *ctx = (HeapChunkContext *)arg;
    u1 state;

    UNUSED_PARAMETER(userlen);

    assert((chunklen & (ALLOCATION_UNIT_SIZE-1)) == 0);

    /* Make sure there's enough room left in the buffer.
     * We need to use two bytes for every fractional 256
     * allocation units used by the chunk.
     */
    {
        size_t needed = (((chunklen/ALLOCATION_UNIT_SIZE + 255) / 256) * 2);
        size_t bytesLeft = ctx->bufLen - (size_t)(ctx->p - ctx->buf);
        if (bytesLeft < needed) {
            flush_hpsg_chunk(ctx);
        }

        bytesLeft = ctx->bufLen - (size_t)(ctx->p - ctx->buf);
        if (bytesLeft < needed) {
            LOGW("chunk is too big to transmit (chunklen=%zd, %zd bytes)\n",
                chunklen, needed);
            return;
        }
    }

//TODO: notice when there's a gap and start a new heap, or at least a new range.
    if (ctx->needHeader) {
        /*
         * Start a new HPSx chunk.
         */

        /* [u4]: heap ID */
        set4BE(ctx->p, DEFAULT_HEAP_ID); ctx->p += 4;

        /* [u1]: size of allocation unit, in bytes */
        *ctx->p++ = 8;

        /* [u4]: virtual address of segment start */
        set4BE(ctx->p, (uintptr_t)chunkptr); ctx->p += 4;

        /* [u4]: offset of this piece (relative to the virtual address) */
        set4BE(ctx->p, 0); ctx->p += 4;

        /* [u4]: length of piece, in allocation units
         * We won't know this until we're done, so save the offset
         * and stuff in a dummy value.
         */
        ctx->pieceLenField = ctx->p;
        set4BE(ctx->p, 0x55555555); ctx->p += 4;

        ctx->needHeader = false;
    }

    /* Determine the type of this chunk.
     */
    if (userptr == NULL) {
        /* It's a free chunk.
         */
        state = HPSG_STATE(SOLIDITY_FREE, 0);
    } else {
        const DvmHeapChunk *hc = (const DvmHeapChunk *)userptr;
        const Object *obj = chunk2ptr(hc);
        /* If we're looking at the native heap, we'll just return 
         * (SOLIDITY_HARD, KIND_NATIVE) for all allocated chunks
         */
        bool native = ctx->type == CHUNK_TYPE("NHSG");

        /* It's an allocated chunk.  Figure out what it is.
         */
//TODO: if ctx.merge, see if this chunk is different from the last chunk.
//      If it's the same, we should combine them.
        if (!native && dvmIsValidObject(obj)) {
            ClassObject *clazz = obj->clazz;
            if (clazz == NULL) {
                /* The object was probably just created
                 * but hasn't been initialized yet.
                 */
                state = HPSG_STATE(SOLIDITY_HARD, KIND_OBJECT);
            } else if (clazz == gDvm.unlinkedJavaLangClass ||
                       clazz == gDvm.classJavaLangClass)
            {
                state = HPSG_STATE(SOLIDITY_HARD, KIND_CLASS_OBJECT);
            } else if (IS_CLASS_FLAG_SET(clazz, CLASS_ISARRAY)) {
                if (IS_CLASS_FLAG_SET(clazz, CLASS_ISOBJECTARRAY)) {
                    state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_4);
                } else {
                    switch (clazz->elementClass->primitiveType) {
                    case PRIM_BOOLEAN:
                    case PRIM_BYTE:
                        state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_1);
                        break;
                    case PRIM_CHAR:
                    case PRIM_SHORT:
                        state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_2);
                        break;
                    case PRIM_INT:
                    case PRIM_FLOAT:
                        state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_4);
                        break;
                    case PRIM_DOUBLE:
                    case PRIM_LONG:
                        state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_8);
                        break;
                    default:
                        assert(!"Unknown GC heap object type");
                        state = HPSG_STATE(SOLIDITY_HARD, KIND_UNKNOWN);
                        break;
                    }
                }
            } else {
                state = HPSG_STATE(SOLIDITY_HARD, KIND_OBJECT);
            }
        } else {
            obj = NULL; // it's not actually an object
            state = HPSG_STATE(SOLIDITY_HARD, KIND_NATIVE);
        }
    }

    /* Write out the chunk description.
     */
    chunklen /= ALLOCATION_UNIT_SIZE;   // convert to allocation units
    ctx->totalAllocationUnits += chunklen;
    while (chunklen > 256) {
        *ctx->p++ = state | HPSG_PARTIAL;
        *ctx->p++ = 255;     // length - 1
        chunklen -= 256;
    }
    *ctx->p++ = state;
    *ctx->p++ = chunklen - 1;
}
예제 #11
0
파일: ExpandBuf.cpp 프로젝트: hhhaiai/JNI
/*
 * Append four big-endian bytes.
 */
void expandBufAdd4BE(ExpandBuf *pBuf, u4 val) {
    ensureSpace(pBuf, sizeof(val));
    set4BE(pBuf->storage + pBuf->curLen, val);
    pBuf->curLen += sizeof(val);
}
예제 #12
0
/*
 * "buf" contains a full JDWP packet, possibly with multiple chunks.  We
 * need to process each, accumulate the replies, and ship the whole thing
 * back.
 *
 * Returns "true" if we have a reply.  The reply buffer is newly allocated,
 * and includes the chunk type/length, followed by the data.
 *
 * TODO: we currently assume that the request and reply include a single
 * chunk.  If this becomes inconvenient we will need to adapt.
 */
bool dvmDdmHandlePacket(const u1* buf, int dataLen, u1** pReplyBuf,
    int* pReplyLen)
{
    Thread* self = dvmThreadSelf();
    const int kChunkHdrLen = 8;
    ArrayObject* dataArray = NULL;
    bool result = false;

    assert(dataLen >= 0);

    /*
     * Prep DdmServer.  We could throw this in gDvm.
     */
    ClassObject* ddmServerClass;
    Method* dispatch;

    ddmServerClass =
        dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/DdmServer;", NULL);
    if (ddmServerClass == NULL) {
        LOGW("Unable to find org.apache.harmony.dalvik.ddmc.DdmServer\n");
        goto bail;
    }
    dispatch = dvmFindDirectMethodByDescriptor(ddmServerClass, "dispatch",
                    "(I[BII)Lorg/apache/harmony/dalvik/ddmc/Chunk;");
    if (dispatch == NULL) {
        LOGW("Unable to find DdmServer.dispatch\n");
        goto bail;
    }

    /*
     * Prep Chunk.
     */
    int chunkTypeOff, chunkDataOff, chunkOffsetOff, chunkLengthOff;
    ClassObject* chunkClass;
    chunkClass = dvmFindClass("Lorg/apache/harmony/dalvik/ddmc/Chunk;", NULL);
    if (chunkClass == NULL) {
        LOGW("Unable to find org.apache.harmony.dalvik.ddmc.Chunk\n");
        goto bail;
    }
    chunkTypeOff = dvmFindFieldOffset(chunkClass, "type", "I");
    chunkDataOff = dvmFindFieldOffset(chunkClass, "data", "[B");
    chunkOffsetOff = dvmFindFieldOffset(chunkClass, "offset", "I");
    chunkLengthOff = dvmFindFieldOffset(chunkClass, "length", "I");
    if (chunkTypeOff < 0 || chunkDataOff < 0 ||
        chunkOffsetOff < 0 || chunkLengthOff < 0)
    {
        LOGW("Unable to find all chunk fields\n");
        goto bail;
    }

    /*
     * The chunk handlers are written in the Java programming language, so
     * we need to convert the buffer to a byte array.
     */
    dataArray = dvmAllocPrimitiveArray('B', dataLen, ALLOC_DEFAULT);
    if (dataArray == NULL) {
        LOGW("array alloc failed (%d)\n", dataLen);
        dvmClearException(self);
        goto bail;
    }
    memcpy(dataArray->contents, buf, dataLen);

    /*
     * Run through and find all chunks.  [Currently just find the first.]
     */
    unsigned int offset, length, type;
    type = get4BE((u1*)dataArray->contents + 0);
    length = get4BE((u1*)dataArray->contents + 4);
    offset = kChunkHdrLen;
    if (offset+length > (unsigned int) dataLen) {
        LOGW("WARNING: bad chunk found (len=%u pktLen=%d)\n", length, dataLen);
        goto bail;
    }

    /*
     * Call the handler.
     */
    JValue callRes;
    dvmCallMethod(self, dispatch, NULL, &callRes, type, dataArray, offset,
        length);
    if (dvmCheckException(self)) {
        LOGI("Exception thrown by dispatcher for 0x%08x\n", type);
        dvmLogExceptionStackTrace();
        dvmClearException(self);
        goto bail;
    }

    Object* chunk;
    ArrayObject* replyData;
    chunk = (Object*) callRes.l;
    if (chunk == NULL)
        goto bail;

    /*
     * Pull the pieces out of the chunk.  We copy the results into a
     * newly-allocated buffer that the caller can free.  We don't want to
     * continue using the Chunk object because nothing has a reference to it.
     * (If we do an alloc in here, we need to dvmAddTrackedAlloc it.)
     *
     * We could avoid this by returning type/data/offset/length and having
     * the caller be aware of the object lifetime issues, but that
     * integrates the JDWP code more tightly into the VM, and doesn't work
     * if we have responses for multiple chunks.
     *
     * So we're pretty much stuck with copying data around multiple times.
     */
    type = dvmGetFieldInt(chunk, chunkTypeOff);
    replyData = (ArrayObject*) dvmGetFieldObject(chunk, chunkDataOff);
    offset = dvmGetFieldInt(chunk, chunkOffsetOff);
    length = dvmGetFieldInt(chunk, chunkLengthOff);

    LOGV("DDM reply: type=0x%08x data=%p offset=%d length=%d\n",
        type, replyData, offset, length);

    if (length == 0 || replyData == NULL)
        goto bail;
    if (offset + length > replyData->length) {
        LOGW("WARNING: chunk off=%d len=%d exceeds reply array len %d\n",
            offset, length, replyData->length);
        goto bail;
    }

    u1* reply;
    reply = (u1*) malloc(length + kChunkHdrLen);
    if (reply == NULL) {
        LOGW("malloc %d failed\n", length+kChunkHdrLen);
        goto bail;
    }
    set4BE(reply + 0, type);
    set4BE(reply + 4, length);
    memcpy(reply+kChunkHdrLen, (const u1*)replyData->contents + offset, length);

    *pReplyBuf = reply;
    *pReplyLen = length + kChunkHdrLen;
    result = true;

    LOGV("dvmHandleDdm returning type=%.4s buf=%p len=%d\n",
        (char*) reply, reply, length);

bail:
    dvmReleaseTrackedAlloc((Object*) dataArray, NULL);
    return result;
}