C++程序  |  825行  |  23.02 KB

/*
 * Copyright (C) 2010 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.
 */

/*
 * Liveness analysis for Dalvik bytecode.
 */
#include "Dalvik.h"
#include "analysis/Liveness.h"
#include "analysis/CodeVerify.h"

static bool processInstruction(VerifierData* vdata, u4 curIdx,
    BitVector* workBits);
static bool markDebugLocals(VerifierData* vdata);
static void dumpLiveState(const VerifierData* vdata, u4 curIdx,
    const BitVector* workBits);


/*
 * Create a table of instruction widths that indicate the width of the
 * *previous* instruction.  The values are copied from the width table
 * in "vdata", not derived from the instruction stream.
 *
 * Caller must free the return value.
 */
static InstructionWidth* createBackwardWidthTable(VerifierData* vdata)
{
    InstructionWidth* widths;

    widths = (InstructionWidth*)
            calloc(vdata->insnsSize, sizeof(InstructionWidth));
    if (widths == NULL)
        return NULL;

    u4 insnWidth = 0;
    for (u4 idx = 0; idx < vdata->insnsSize; ) {
        widths[idx] = insnWidth;
        insnWidth = dvmInsnGetWidth(vdata->insnFlags, idx);
        idx += insnWidth;
    }

    return widths;
}

/*
 * Compute the "liveness" of every register at all GC points.
 */
bool dvmComputeLiveness(VerifierData* vdata)
{
    const InsnFlags* insnFlags = vdata->insnFlags;
    InstructionWidth* backwardWidth;
    VfyBasicBlock* startGuess = NULL;
    BitVector* workBits = NULL;
    bool result = false;

    bool verbose = false; //= dvmWantVerboseVerification(vdata->method);
    if (verbose) {
        const Method* meth = vdata->method;
        ALOGI("Computing liveness for %s.%s:%s",
            meth->clazz->descriptor, meth->name, meth->shorty);
    }

    assert(vdata->registerLines != NULL);

    backwardWidth = createBackwardWidthTable(vdata);
    if (backwardWidth == NULL)
        goto bail;

    /*
     * Allocate space for intra-block work set.  Does not include space
     * for method result "registers", which aren't visible to the GC.
     * (They would be made live by move-result and then die on the
     * instruction immediately before it.)
     */
    workBits = dvmAllocBitVector(vdata->insnRegCount, false);
    if (workBits == NULL)
        goto bail;

    /*
     * We continue until all blocks have been visited, and no block
     * requires further attention ("visited" is set and "changed" is
     * clear).
     *
     * TODO: consider creating a "dense" array of basic blocks to make
     * the walking faster.
     */
    for (int iter = 0;;) {
        VfyBasicBlock* workBlock = NULL;

        if (iter++ > 100000) {
            LOG_VFY_METH(vdata->method, "oh dear");
            dvmAbort();
        }

        /*
         * If a block is marked "changed", we stop and handle it.  If it
         * just hasn't been visited yet, we remember it but keep searching
         * for one that has been changed.
         *
         * The thought here is that this is more likely to let us work
         * from end to start, which reduces the amount of re-evaluation
         * required (both by using "changed" as a work list, and by picking
         * un-visited blocks from the tail end of the method).
         */
        if (startGuess != NULL) {
            assert(startGuess->changed);
            workBlock = startGuess;
        } else {
            for (u4 idx = 0; idx < vdata->insnsSize; idx++) {
                VfyBasicBlock* block = vdata->basicBlocks[idx];
                if (block == NULL)
                    continue;

                if (block->changed) {
                    workBlock = block;
                    break;
                } else if (!block->visited) {
                    workBlock = block;
                }
            }
        }

        if (workBlock == NULL) {
            /* all done */
            break;
        }

        assert(workBlock->changed || !workBlock->visited);
        startGuess = NULL;

        /*
         * Load work bits.  These represent the liveness of registers
         * after the last instruction in the block has finished executing.
         */
        assert(workBlock->liveRegs != NULL);
        dvmCopyBitVector(workBits, workBlock->liveRegs);
        if (verbose) {
            ALOGI("Loaded work bits from last=0x%04x", workBlock->lastAddr);
            dumpLiveState(vdata, 0xfffd, workBlock->liveRegs);
            dumpLiveState(vdata, 0xffff, workBits);
        }

        /*
         * Process a single basic block.
         *
         * If this instruction is a GC point, we want to save the result
         * in the RegisterLine.
         *
         * We don't break basic blocks on every GC point -- in particular,
         * instructions that might throw but have no "try" block don't
         * end a basic block -- so there could be more than one GC point
         * in a given basic block.
         *
         * We could change this, but it turns out to be not all that useful.
         * At first glance it appears that we could share the liveness bit
         * vector between the basic block struct and the register line,
         * but the basic block needs to reflect the state *after* the
         * instruction has finished, while the GC points need to describe
         * the state before the instruction starts.
         */
        u4 curIdx = workBlock->lastAddr;
        while (true) {
            if (!processInstruction(vdata, curIdx, workBits))
                goto bail;

            if (verbose) {
                dumpLiveState(vdata, curIdx + 0x8000, workBits);
            }

            if (dvmInsnIsGcPoint(insnFlags, curIdx)) {
                BitVector* lineBits = vdata->registerLines[curIdx].liveRegs;
                if (lineBits == NULL) {
                    lineBits = vdata->registerLines[curIdx].liveRegs =
                        dvmAllocBitVector(vdata->insnRegCount, false);
                }
                dvmCopyBitVector(lineBits, workBits);
            }

            if (curIdx == workBlock->firstAddr)
                break;
            assert(curIdx >= backwardWidth[curIdx]);
            curIdx -= backwardWidth[curIdx];
        }

        workBlock->visited = true;
        workBlock->changed = false;

        if (verbose) {
            dumpLiveState(vdata, curIdx, workBits);
        }

        /*
         * Merge changes to all predecessors.  If the new bits don't match
         * the old bits, set the "changed" flag.
         */
        PointerSet* preds = workBlock->predecessors;
        size_t numPreds = dvmPointerSetGetCount(preds);
        unsigned int predIdx;

        for (predIdx = 0; predIdx < numPreds; predIdx++) {
            VfyBasicBlock* pred =
                    (VfyBasicBlock*) dvmPointerSetGetEntry(preds, predIdx);

            pred->changed = dvmCheckMergeBitVectors(pred->liveRegs, workBits);
            if (verbose) {
                ALOGI("merging cur=%04x into pred last=%04x (ch=%d)",
                    curIdx, pred->lastAddr, pred->changed);
                dumpLiveState(vdata, 0xfffa, pred->liveRegs);
                dumpLiveState(vdata, 0xfffb, workBits);
            }

            /*
             * We want to set the "changed" flag on unvisited predecessors
             * as a way of guiding the verifier through basic blocks in
             * a reasonable order.  We can't count on variable liveness
             * changing, so we force "changed" to true even if it hasn't.
             */
            if (!pred->visited)
                pred->changed = true;

            /*
             * Keep track of one of the changed blocks so we can start
             * there instead of having to scan through the list.
             */
            if (pred->changed)
                startGuess = pred;
        }
    }

#ifndef NDEBUG
    /*
     * Sanity check: verify that all GC point register lines have a
     * liveness bit vector allocated.  Also, we're not expecting non-GC
     * points to have them.
     */
    u4 checkIdx;
    for (checkIdx = 0; checkIdx < vdata->insnsSize; ) {
        if (dvmInsnIsGcPoint(insnFlags, checkIdx)) {
            if (vdata->registerLines[checkIdx].liveRegs == NULL) {
                LOG_VFY_METH(vdata->method,
                    "GLITCH: no liveRegs for GC point 0x%04x", checkIdx);
                dvmAbort();
            }
        } else if (vdata->registerLines[checkIdx].liveRegs != NULL) {
            LOG_VFY_METH(vdata->method,
                "GLITCH: liveRegs for non-GC point 0x%04x", checkIdx);
            dvmAbort();
        }
        u4 insnWidth = dvmInsnGetWidth(insnFlags, checkIdx);
        checkIdx += insnWidth;
    }
#endif

    /*
     * Factor in the debug info, if any.
     */
    if (!markDebugLocals(vdata))
        goto bail;

    result = true;

bail:
    free(backwardWidth);
    dvmFreeBitVector(workBits);
    return result;
}


/*
 * Add a register to the LIVE set.
 */
static inline void GEN(BitVector* workBits, u4 regIndex)
{
    dvmSetBit(workBits, regIndex);
}

/*
 * Add a register pair to the LIVE set.
 */
static inline void GENW(BitVector* workBits, u4 regIndex)
{
    dvmSetBit(workBits, regIndex);
    dvmSetBit(workBits, regIndex+1);
}

/*
 * Remove a register from the LIVE set.
 */
static inline void KILL(BitVector* workBits, u4 regIndex)
{
    dvmClearBit(workBits, regIndex);
}

/*
 * Remove a register pair from the LIVE set.
 */
static inline void KILLW(BitVector* workBits, u4 regIndex)
{
    dvmClearBit(workBits, regIndex);
    dvmClearBit(workBits, regIndex+1);
}

/*
 * Process a single instruction.
 *
 * Returns "false" if something goes fatally wrong.
 */
static bool processInstruction(VerifierData* vdata, u4 insnIdx,
    BitVector* workBits)
{
    const Method* meth = vdata->method;
    const u2* insns = meth->insns + insnIdx;
    DecodedInstruction decInsn;

    dexDecodeInstruction(insns, &decInsn);

    /*
     * Add registers to the "GEN" or "KILL" sets.  We want to do KILL
     * before GEN to handle cases where the source and destination
     * register is the same.
     */
    switch (decInsn.opcode) {
    case OP_NOP:
    case OP_RETURN_VOID:
    case OP_GOTO:
    case OP_GOTO_16:
    case OP_GOTO_32:
        /* no registers are used */
        break;

    case OP_RETURN:
    case OP_RETURN_OBJECT:
    case OP_MONITOR_ENTER:
    case OP_MONITOR_EXIT:
    case OP_CHECK_CAST:
    case OP_THROW:
    case OP_PACKED_SWITCH:
    case OP_SPARSE_SWITCH:
    case OP_FILL_ARRAY_DATA:
    case OP_IF_EQZ:
    case OP_IF_NEZ:
    case OP_IF_LTZ:
    case OP_IF_GEZ:
    case OP_IF_GTZ:
    case OP_IF_LEZ:
    case OP_SPUT:
    case OP_SPUT_BOOLEAN:
    case OP_SPUT_BYTE:
    case OP_SPUT_CHAR:
    case OP_SPUT_SHORT:
    case OP_SPUT_OBJECT:
        /* action <- vA */
        GEN(workBits, decInsn.vA);
        break;

    case OP_RETURN_WIDE:
    case OP_SPUT_WIDE:
        /* action <- vA(wide) */
        GENW(workBits, decInsn.vA);
        break;

    case OP_IF_EQ:
    case OP_IF_NE:
    case OP_IF_LT:
    case OP_IF_GE:
    case OP_IF_GT:
    case OP_IF_LE:
    case OP_IPUT:
    case OP_IPUT_BOOLEAN:
    case OP_IPUT_BYTE:
    case OP_IPUT_CHAR:
    case OP_IPUT_SHORT:
    case OP_IPUT_OBJECT:
        /* action <- vA, vB */
        GEN(workBits, decInsn.vA);
        GEN(workBits, decInsn.vB);
        break;

    case OP_IPUT_WIDE:
        /* action <- vA(wide), vB */
        GENW(workBits, decInsn.vA);
        GEN(workBits, decInsn.vB);
        break;

    case OP_APUT:
    case OP_APUT_BOOLEAN:
    case OP_APUT_BYTE:
    case OP_APUT_CHAR:
    case OP_APUT_SHORT:
    case OP_APUT_OBJECT:
        /* action <- vA, vB, vC */
        GEN(workBits, decInsn.vA);
        GEN(workBits, decInsn.vB);
        GEN(workBits, decInsn.vC);
        break;

    case OP_APUT_WIDE:
        /* action <- vA(wide), vB, vC */
        GENW(workBits, decInsn.vA);
        GEN(workBits, decInsn.vB);
        GEN(workBits, decInsn.vC);
        break;

    case OP_FILLED_NEW_ARRAY:
    case OP_INVOKE_VIRTUAL:
    case OP_INVOKE_SUPER:
    case OP_INVOKE_DIRECT:
    case OP_INVOKE_STATIC:
    case OP_INVOKE_INTERFACE:
        /* action <- vararg */
        {
            unsigned int idx;
            for (idx = 0; idx < decInsn.vA; idx++) {
                GEN(workBits, decInsn.arg[idx]);
            }
        }
        break;

    case OP_FILLED_NEW_ARRAY_RANGE:
    case OP_INVOKE_VIRTUAL_RANGE:
    case OP_INVOKE_SUPER_RANGE:
    case OP_INVOKE_DIRECT_RANGE:
    case OP_INVOKE_STATIC_RANGE:
    case OP_INVOKE_INTERFACE_RANGE:
        /* action <- vararg/range */
        {
            unsigned int idx;
            for (idx = 0; idx < decInsn.vA; idx++) {
                GEN(workBits, decInsn.vC + idx);
            }
        }
        break;

    case OP_MOVE_RESULT:
    case OP_MOVE_RESULT_WIDE:
    case OP_MOVE_RESULT_OBJECT:
    case OP_MOVE_EXCEPTION:
    case OP_CONST_4:
    case OP_CONST_16:
    case OP_CONST:
    case OP_CONST_HIGH16:
    case OP_CONST_STRING:
    case OP_CONST_STRING_JUMBO:
    case OP_CONST_CLASS:
    case OP_NEW_INSTANCE:
    case OP_SGET:
    case OP_SGET_BOOLEAN:
    case OP_SGET_BYTE:
    case OP_SGET_CHAR:
    case OP_SGET_SHORT:
    case OP_SGET_OBJECT:
        /* vA <- value */
        KILL(workBits, decInsn.vA);
        break;

    case OP_CONST_WIDE_16:
    case OP_CONST_WIDE_32:
    case OP_CONST_WIDE:
    case OP_CONST_WIDE_HIGH16:
    case OP_SGET_WIDE:
        /* vA(wide) <- value */
        KILLW(workBits, decInsn.vA);
        break;

    case OP_MOVE:
    case OP_MOVE_FROM16:
    case OP_MOVE_16:
    case OP_MOVE_OBJECT:
    case OP_MOVE_OBJECT_FROM16:
    case OP_MOVE_OBJECT_16:
    case OP_INSTANCE_OF:
    case OP_ARRAY_LENGTH:
    case OP_NEW_ARRAY:
    case OP_IGET:
    case OP_IGET_BOOLEAN:
    case OP_IGET_BYTE:
    case OP_IGET_CHAR:
    case OP_IGET_SHORT:
    case OP_IGET_OBJECT:
    case OP_NEG_INT:
    case OP_NOT_INT:
    case OP_NEG_FLOAT:
    case OP_INT_TO_FLOAT:
    case OP_FLOAT_TO_INT:
    case OP_INT_TO_BYTE:
    case OP_INT_TO_CHAR:
    case OP_INT_TO_SHORT:
    case OP_ADD_INT_LIT16:
    case OP_RSUB_INT:
    case OP_MUL_INT_LIT16:
    case OP_DIV_INT_LIT16:
    case OP_REM_INT_LIT16:
    case OP_AND_INT_LIT16:
    case OP_OR_INT_LIT16:
    case OP_XOR_INT_LIT16:
    case OP_ADD_INT_LIT8:
    case OP_RSUB_INT_LIT8:
    case OP_MUL_INT_LIT8:
    case OP_DIV_INT_LIT8:
    case OP_REM_INT_LIT8:
    case OP_SHL_INT_LIT8:
    case OP_SHR_INT_LIT8:
    case OP_USHR_INT_LIT8:
    case OP_AND_INT_LIT8:
    case OP_OR_INT_LIT8:
    case OP_XOR_INT_LIT8:
        /* vA <- vB */
        KILL(workBits, decInsn.vA);
        GEN(workBits, decInsn.vB);
        break;

    case OP_IGET_WIDE:
    case OP_INT_TO_LONG:
    case OP_INT_TO_DOUBLE:
    case OP_FLOAT_TO_LONG:
    case OP_FLOAT_TO_DOUBLE:
        /* vA(wide) <- vB */
        KILLW(workBits, decInsn.vA);
        GEN(workBits, decInsn.vB);
        break;

    case OP_LONG_TO_INT:
    case OP_LONG_TO_FLOAT:
    case OP_DOUBLE_TO_INT:
    case OP_DOUBLE_TO_FLOAT:
        /* vA <- vB(wide) */
        KILL(workBits, decInsn.vA);
        GENW(workBits, decInsn.vB);
        break;

    case OP_MOVE_WIDE:
    case OP_MOVE_WIDE_FROM16:
    case OP_MOVE_WIDE_16:
    case OP_NEG_LONG:
    case OP_NOT_LONG:
    case OP_NEG_DOUBLE:
    case OP_LONG_TO_DOUBLE:
    case OP_DOUBLE_TO_LONG:
        /* vA(wide) <- vB(wide) */
        KILLW(workBits, decInsn.vA);
        GENW(workBits, decInsn.vB);
        break;

    case OP_CMPL_FLOAT:
    case OP_CMPG_FLOAT:
    case OP_AGET:
    case OP_AGET_BOOLEAN:
    case OP_AGET_BYTE:
    case OP_AGET_CHAR:
    case OP_AGET_SHORT:
    case OP_AGET_OBJECT:
    case OP_ADD_INT:
    case OP_SUB_INT:
    case OP_MUL_INT:
    case OP_REM_INT:
    case OP_DIV_INT:
    case OP_AND_INT:
    case OP_OR_INT:
    case OP_XOR_INT:
    case OP_SHL_INT:
    case OP_SHR_INT:
    case OP_USHR_INT:
    case OP_ADD_FLOAT:
    case OP_SUB_FLOAT:
    case OP_MUL_FLOAT:
    case OP_DIV_FLOAT:
    case OP_REM_FLOAT:
        /* vA <- vB, vC */
        KILL(workBits, decInsn.vA);
        GEN(workBits, decInsn.vB);
        GEN(workBits, decInsn.vC);
        break;

    case OP_AGET_WIDE:
        /* vA(wide) <- vB, vC */
        KILLW(workBits, decInsn.vA);
        GEN(workBits, decInsn.vB);
        GEN(workBits, decInsn.vC);
        break;

    case OP_CMPL_DOUBLE:
    case OP_CMPG_DOUBLE:
    case OP_CMP_LONG:
        /* vA <- vB(wide), vC(wide) */
        KILL(workBits, decInsn.vA);
        GENW(workBits, decInsn.vB);
        GENW(workBits, decInsn.vC);
        break;

    case OP_SHL_LONG:
    case OP_SHR_LONG:
    case OP_USHR_LONG:
        /* vA(wide) <- vB(wide), vC */
        KILLW(workBits, decInsn.vA);
        GENW(workBits, decInsn.vB);
        GEN(workBits, decInsn.vC);
        break;

    case OP_ADD_LONG:
    case OP_SUB_LONG:
    case OP_MUL_LONG:
    case OP_DIV_LONG:
    case OP_REM_LONG:
    case OP_AND_LONG:
    case OP_OR_LONG:
    case OP_XOR_LONG:
    case OP_ADD_DOUBLE:
    case OP_SUB_DOUBLE:
    case OP_MUL_DOUBLE:
    case OP_DIV_DOUBLE:
    case OP_REM_DOUBLE:
        /* vA(wide) <- vB(wide), vC(wide) */
        KILLW(workBits, decInsn.vA);
        GENW(workBits, decInsn.vB);
        GENW(workBits, decInsn.vC);
        break;

    case OP_ADD_INT_2ADDR:
    case OP_SUB_INT_2ADDR:
    case OP_MUL_INT_2ADDR:
    case OP_REM_INT_2ADDR:
    case OP_SHL_INT_2ADDR:
    case OP_SHR_INT_2ADDR:
    case OP_USHR_INT_2ADDR:
    case OP_AND_INT_2ADDR:
    case OP_OR_INT_2ADDR:
    case OP_XOR_INT_2ADDR:
    case OP_DIV_INT_2ADDR:
        /* vA <- vA, vB */
        /* KILL(workBits, decInsn.vA); */
        GEN(workBits, decInsn.vA);
        GEN(workBits, decInsn.vB);
        break;

    case OP_SHL_LONG_2ADDR:
    case OP_SHR_LONG_2ADDR:
    case OP_USHR_LONG_2ADDR:
        /* vA(wide) <- vA(wide), vB */
        /* KILLW(workBits, decInsn.vA); */
        GENW(workBits, decInsn.vA);
        GEN(workBits, decInsn.vB);
        break;

    case OP_ADD_LONG_2ADDR:
    case OP_SUB_LONG_2ADDR:
    case OP_MUL_LONG_2ADDR:
    case OP_DIV_LONG_2ADDR:
    case OP_REM_LONG_2ADDR:
    case OP_AND_LONG_2ADDR:
    case OP_OR_LONG_2ADDR:
    case OP_XOR_LONG_2ADDR:
    case OP_ADD_FLOAT_2ADDR:
    case OP_SUB_FLOAT_2ADDR:
    case OP_MUL_FLOAT_2ADDR:
    case OP_DIV_FLOAT_2ADDR:
    case OP_REM_FLOAT_2ADDR:
    case OP_ADD_DOUBLE_2ADDR:
    case OP_SUB_DOUBLE_2ADDR:
    case OP_MUL_DOUBLE_2ADDR:
    case OP_DIV_DOUBLE_2ADDR:
    case OP_REM_DOUBLE_2ADDR:
        /* vA(wide) <- vA(wide), vB(wide) */
        /* KILLW(workBits, decInsn.vA); */
        GENW(workBits, decInsn.vA);
        GENW(workBits, decInsn.vB);
        break;

    /* we will only see this if liveness analysis is done after general vfy */
    case OP_THROW_VERIFICATION_ERROR:
        /* no registers used */
        break;

    /* quickened instructions, not expected to appear */
    case OP_EXECUTE_INLINE:
    case OP_EXECUTE_INLINE_RANGE:
    case OP_IGET_QUICK:
    case OP_IGET_WIDE_QUICK:
    case OP_IGET_OBJECT_QUICK:
    case OP_IPUT_QUICK:
    case OP_IPUT_WIDE_QUICK:
    case OP_IPUT_OBJECT_QUICK:
    case OP_INVOKE_VIRTUAL_QUICK:
    case OP_INVOKE_VIRTUAL_QUICK_RANGE:
    case OP_INVOKE_SUPER_QUICK:
    case OP_INVOKE_SUPER_QUICK_RANGE:
        /* fall through to failure */

    /* correctness fixes, not expected to appear */
    case OP_INVOKE_OBJECT_INIT_RANGE:
    case OP_RETURN_VOID_BARRIER:
    case OP_SPUT_VOLATILE:
    case OP_SPUT_OBJECT_VOLATILE:
    case OP_SPUT_WIDE_VOLATILE:
    case OP_IPUT_VOLATILE:
    case OP_IPUT_OBJECT_VOLATILE:
    case OP_IPUT_WIDE_VOLATILE:
    case OP_SGET_VOLATILE:
    case OP_SGET_OBJECT_VOLATILE:
    case OP_SGET_WIDE_VOLATILE:
    case OP_IGET_VOLATILE:
    case OP_IGET_OBJECT_VOLATILE:
    case OP_IGET_WIDE_VOLATILE:
        /* fall through to failure */

    /* these should never appear during verification */
    case OP_UNUSED_3E:
    case OP_UNUSED_3F:
    case OP_UNUSED_40:
    case OP_UNUSED_41:
    case OP_UNUSED_42:
    case OP_UNUSED_43:
    case OP_UNUSED_73:
    case OP_UNUSED_79:
    case OP_UNUSED_7A:
    case OP_BREAKPOINT:
    case OP_UNUSED_FF:
        return false;
    }

    return true;
}

/*
 * This is a dexDecodeDebugInfo callback, used by markDebugLocals().
 */
static void markLocalsCb(void* ctxt, u2 reg, u4 startAddress, u4 endAddress,
    const char* name, const char* descriptor, const char* signature)
{
    VerifierData* vdata = (VerifierData*) ctxt;
    bool verbose = dvmWantVerboseVerification(vdata->method);

    if (verbose) {
        ALOGI("%04x-%04x %2d (%s %s)",
            startAddress, endAddress, reg, name, descriptor);
    }

    bool wide = (descriptor[0] == 'D' || descriptor[0] == 'J');
    assert(reg <= vdata->insnRegCount + (wide ? 1 : 0));

    /*
     * Set the bit in all GC point instructions in the range
     * [startAddress, endAddress).
     */
    unsigned int idx;
    for (idx = startAddress; idx < endAddress; idx++) {
        BitVector* liveRegs = vdata->registerLines[idx].liveRegs;
        if (liveRegs != NULL) {
            if (wide) {
                GENW(liveRegs, reg);
            } else {
                GEN(liveRegs, reg);
            }
        }
    }
}

/*
 * Mark all debugger-visible locals as live.
 *
 * The "locals" table describes the positions of the various locals in the
 * stack frame based on the current execution address.  If the debugger
 * wants to display one, it issues a request by "slot number".  We need
 * to ensure that references in stack slots that might be queried by the
 * debugger aren't GCed.
 *
 * (If the GC had some way to mark the slot as invalid we wouldn't have
 * to do this.  We could also have the debugger interface check the
 * register map and simply refuse to return a "dead" value, but that's
 * potentially confusing since the referred-to object might actually be
 * alive, and being able to see it without having to hunt around for a
 * "live" stack frame is useful.)
 */
static bool markDebugLocals(VerifierData* vdata)
{
    const Method* meth = vdata->method;

    dexDecodeDebugInfo(meth->clazz->pDvmDex->pDexFile, dvmGetMethodCode(meth),
        meth->clazz->descriptor, meth->prototype.protoIdx, meth->accessFlags,
        NULL, markLocalsCb, vdata);

    return true;
}


/*
 * Dump the liveness bits to the log.
 *
 * "curIdx" is for display only.
 */
static void dumpLiveState(const VerifierData* vdata, u4 curIdx,
    const BitVector* workBits)
{
    u4 insnRegCount = vdata->insnRegCount;
    size_t regCharSize = insnRegCount + (insnRegCount-1)/4 + 2 +1;
    char regChars[regCharSize +1];
    unsigned int idx;

    memset(regChars, ' ', regCharSize);
    regChars[0] = '[';
    if (insnRegCount == 0)
        regChars[1] = ']';
    else
        regChars[1 + (insnRegCount-1) + (insnRegCount-1)/4 +1] = ']';
    regChars[regCharSize] = '\0';

    for (idx = 0; idx < insnRegCount; idx++) {
        char ch = dvmIsBitSet(workBits, idx) ? '+' : '-';
        regChars[1 + idx + (idx/4)] = ch;
    }

    ALOGI("0x%04x %s", curIdx, regChars);
}