| /* |
| * 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. |
| */ |
| |
| /* |
| * Verifier basic block functions. |
| */ |
| #include "Dalvik.h" |
| #include "analysis/VfyBasicBlock.h" |
| #include "analysis/CodeVerify.h" |
| #include "analysis/VerifySubs.h" |
| #include "libdex/DexCatch.h" |
| #include "libdex/InstrUtils.h" |
| |
| |
| /* |
| * Extract the list of catch handlers from "pTry" into "addrBuf". |
| * |
| * Returns the size of the catch handler list. If the return value |
| * exceeds "addrBufSize", the items at the end of the list will not be |
| * represented in the output array, and this function should be called |
| * again with a larger buffer. |
| */ |
| static u4 extractCatchHandlers(const DexCode* pCode, const DexTry* pTry, |
| u4* addrBuf, size_t addrBufSize) |
| { |
| DexCatchIterator iterator; |
| unsigned int idx = 0; |
| |
| dexCatchIteratorInit(&iterator, pCode, pTry->handlerOff); |
| while (true) { |
| DexCatchHandler* handler = dexCatchIteratorNext(&iterator); |
| if (handler == NULL) |
| break; |
| |
| if (idx < addrBufSize) { |
| addrBuf[idx] = handler->address; |
| } |
| idx++; |
| } |
| |
| return idx; |
| } |
| |
| /* |
| * Returns "true" if the instruction represents a data chunk, such as a |
| * switch statement block. |
| */ |
| static bool isDataChunk(u2 insn) |
| { |
| return (insn == kPackedSwitchSignature || |
| insn == kSparseSwitchSignature || |
| insn == kArrayDataSignature); |
| } |
| |
| /* |
| * Alloc a basic block in the specified slot. The storage will be |
| * initialized. |
| */ |
| static VfyBasicBlock* allocVfyBasicBlock(VerifierData* vdata, u4 idx) |
| { |
| VfyBasicBlock* newBlock = (VfyBasicBlock*) calloc(1, sizeof(VfyBasicBlock)); |
| if (newBlock == NULL) |
| return NULL; |
| |
| /* |
| * TODO: there is no good default size here -- the problem is that most |
| * addresses will only have one predecessor, but a fair number will |
| * have 10+, and a few will have 100+ (e.g. the synthetic "finally" |
| * in a large synchronized method). We probably want to use a small |
| * base allocation (perhaps two) and then have the first overflow |
| * allocation jump dramatically (to 32 or thereabouts). |
| */ |
| newBlock->predecessors = dvmPointerSetAlloc(32); |
| if (newBlock->predecessors == NULL) { |
| free(newBlock); |
| return NULL; |
| } |
| |
| newBlock->firstAddr = (u4) -1; // DEBUG |
| |
| newBlock->liveRegs = dvmAllocBitVector(vdata->insnRegCount, false); |
| if (newBlock->liveRegs == NULL) { |
| dvmPointerSetFree(newBlock->predecessors); |
| free(newBlock); |
| return NULL; |
| } |
| |
| return newBlock; |
| } |
| |
| /* |
| * Add "curBlock" to the predecessor list in "targetIdx". |
| */ |
| static bool addToPredecessor(VerifierData* vdata, VfyBasicBlock* curBlock, |
| u4 targetIdx) |
| { |
| assert(targetIdx < vdata->insnsSize); |
| |
| /* |
| * Allocate the target basic block if necessary. This will happen |
| * on e.g. forward branches. |
| * |
| * We can't fill in all the fields, but that will happen automatically |
| * when we get to that part of the code. |
| */ |
| VfyBasicBlock* targetBlock = vdata->basicBlocks[targetIdx]; |
| if (targetBlock == NULL) { |
| targetBlock = allocVfyBasicBlock(vdata, targetIdx); |
| if (targetBlock == NULL) |
| return false; |
| vdata->basicBlocks[targetIdx] = targetBlock; |
| } |
| |
| PointerSet* preds = targetBlock->predecessors; |
| bool added = dvmPointerSetAddEntry(preds, curBlock); |
| if (!added) { |
| /* |
| * This happens sometimes for packed-switch instructions, where |
| * the same target address appears more than once. Also, a |
| * (pointless) conditional branch to the next instruction will |
| * trip over this. |
| */ |
| ALOGV("ODD: point set for targ=0x%04x (%p) already had block " |
| "fir=0x%04x (%p)", |
| targetIdx, targetBlock, curBlock->firstAddr, curBlock); |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Add ourselves to the predecessor list in all blocks we might transfer |
| * control to. |
| * |
| * There are four ways to proceed to a new instruction: |
| * (1) continue to the following instruction |
| * (2) [un]conditionally branch to a specific location |
| * (3) conditionally branch through a "switch" statement |
| * (4) throw an exception |
| * |
| * Returning from the method (via a return statement or an uncaught |
| * exception) are not interesting for liveness analysis. |
| */ |
| static bool setPredecessors(VerifierData* vdata, VfyBasicBlock* curBlock, |
| u4 curIdx, OpcodeFlags opFlags, u4 nextIdx, u4* handlerList, |
| size_t numHandlers) |
| { |
| const InsnFlags* insnFlags = vdata->insnFlags; |
| const Method* meth = vdata->method; |
| |
| unsigned int handlerIdx; |
| for (handlerIdx = 0; handlerIdx < numHandlers; handlerIdx++) { |
| if (!addToPredecessor(vdata, curBlock, handlerList[handlerIdx])) |
| return false; |
| } |
| |
| if ((opFlags & kInstrCanContinue) != 0) { |
| if (!addToPredecessor(vdata, curBlock, nextIdx)) |
| return false; |
| } |
| if ((opFlags & kInstrCanBranch) != 0) { |
| bool unused, gotBranch; |
| s4 branchOffset, absOffset; |
| |
| gotBranch = dvmGetBranchOffset(meth, insnFlags, curIdx, |
| &branchOffset, &unused); |
| assert(gotBranch); |
| absOffset = curIdx + branchOffset; |
| assert(absOffset >= 0 && (u4) absOffset < vdata->insnsSize); |
| |
| if (!addToPredecessor(vdata, curBlock, absOffset)) |
| return false; |
| } |
| |
| if ((opFlags & kInstrCanSwitch) != 0) { |
| const u2* curInsn = &meth->insns[curIdx]; |
| const u2* dataPtr; |
| |
| /* these values have already been verified, so we can trust them */ |
| s4 offsetToData = curInsn[1] | ((s4) curInsn[2]) << 16; |
| dataPtr = curInsn + offsetToData; |
| |
| /* |
| * dataPtr points to the start of the switch data. The first |
| * item is the NOP+magic, the second is the number of entries in |
| * the switch table. |
| */ |
| u2 switchCount = dataPtr[1]; |
| |
| /* |
| * Skip past the ident field, size field, and the first_key field |
| * (for packed) or the key list (for sparse). |
| */ |
| if (dexOpcodeFromCodeUnit(meth->insns[curIdx]) == OP_PACKED_SWITCH) { |
| dataPtr += 4; |
| } else { |
| assert(dexOpcodeFromCodeUnit(meth->insns[curIdx]) == |
| OP_SPARSE_SWITCH); |
| dataPtr += 2 + 2 * switchCount; |
| } |
| |
| u4 switchIdx; |
| for (switchIdx = 0; switchIdx < switchCount; switchIdx++) { |
| s4 offset, absOffset; |
| |
| offset = (s4) dataPtr[switchIdx*2] | |
| (s4) (dataPtr[switchIdx*2 +1] << 16); |
| absOffset = curIdx + offset; |
| assert(absOffset >= 0 && (u4) absOffset < vdata->insnsSize); |
| |
| if (!addToPredecessor(vdata, curBlock, absOffset)) |
| return false; |
| } |
| } |
| |
| if (false) { |
| if (dvmPointerSetGetCount(curBlock->predecessors) > 256) { |
| ALOGI("Lots of preds at 0x%04x in %s.%s:%s", curIdx, |
| meth->clazz->descriptor, meth->name, meth->shorty); |
| } |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Dump the contents of the basic blocks. |
| */ |
| static void dumpBasicBlocks(const VerifierData* vdata) |
| { |
| char printBuf[256]; |
| unsigned int idx; |
| int count; |
| |
| ALOGI("Basic blocks for %s.%s:%s", vdata->method->clazz->descriptor, |
| vdata->method->name, vdata->method->shorty); |
| for (idx = 0; idx < vdata->insnsSize; idx++) { |
| VfyBasicBlock* block = vdata->basicBlocks[idx]; |
| if (block == NULL) |
| continue; |
| |
| assert(block->firstAddr == idx); |
| count = snprintf(printBuf, sizeof(printBuf), " %04x-%04x ", |
| block->firstAddr, block->lastAddr); |
| |
| PointerSet* preds = block->predecessors; |
| size_t numPreds = dvmPointerSetGetCount(preds); |
| |
| if (numPreds > 0) { |
| count += snprintf(printBuf + count, sizeof(printBuf) - count, |
| "preds:"); |
| |
| unsigned int predIdx; |
| for (predIdx = 0; predIdx < numPreds; predIdx++) { |
| if (count >= (int) sizeof(printBuf)) |
| break; |
| const VfyBasicBlock* pred = |
| (const VfyBasicBlock*) dvmPointerSetGetEntry(preds, predIdx); |
| count += snprintf(printBuf + count, sizeof(printBuf) - count, |
| "%04x(%p),", pred->firstAddr, pred); |
| } |
| } else { |
| count += snprintf(printBuf + count, sizeof(printBuf) - count, |
| "(no preds)"); |
| } |
| |
| printBuf[sizeof(printBuf)-2] = '!'; |
| printBuf[sizeof(printBuf)-1] = '\0'; |
| |
| ALOGI("%s", printBuf); |
| } |
| |
| usleep(100 * 1000); /* ugh...let logcat catch up */ |
| } |
| |
| |
| /* |
| * Generate a list of basic blocks and related information. |
| * |
| * On success, returns "true" with vdata->basicBlocks initialized. |
| */ |
| bool dvmComputeVfyBasicBlocks(VerifierData* vdata) |
| { |
| const InsnFlags* insnFlags = vdata->insnFlags; |
| const Method* meth = vdata->method; |
| const u4 insnsSize = vdata->insnsSize; |
| const DexCode* pCode = dvmGetMethodCode(meth); |
| const DexTry* pTries = NULL; |
| const size_t kHandlerStackAllocSize = 16; /* max seen so far is 7 */ |
| u4 handlerAddrs[kHandlerStackAllocSize]; |
| u4* handlerListAlloc = NULL; |
| u4* handlerList = NULL; |
| size_t numHandlers = 0; |
| u4 idx, blockStartAddr; |
| bool result = false; |
| |
| bool verbose = false; //dvmWantVerboseVerification(meth); |
| if (verbose) { |
| ALOGI("Basic blocks for %s.%s:%s", |
| meth->clazz->descriptor, meth->name, meth->shorty); |
| } |
| |
| /* |
| * Allocate a data structure that allows us to map from an address to |
| * the corresponding basic block. Initially all pointers are NULL. |
| * They are populated on demand as we proceed (either when we reach a |
| * new BB, or when we need to add an item to the predecessor list in |
| * a not-yet-reached BB). |
| * |
| * Only the first instruction in the block points to the BB structure; |
| * the rest remain NULL. |
| */ |
| vdata->basicBlocks = |
| (VfyBasicBlock**) calloc(insnsSize, sizeof(VfyBasicBlock*)); |
| if (vdata->basicBlocks == NULL) |
| return false; |
| |
| /* |
| * The "tries" list is a series of non-overlapping regions with a list |
| * of "catch" handlers. Rather than do the "find a matching try block" |
| * computation at each step, we just walk the "try" list in parallel. |
| * |
| * Not all methods have "try" blocks. If this one does, we init tryEnd |
| * to zero, so that the (exclusive bound) range check trips immediately. |
| */ |
| u4 tryIndex = 0, tryStart = 0, tryEnd = 0; |
| if (pCode->triesSize != 0) { |
| pTries = dexGetTries(pCode); |
| } |
| |
| u4 debugBBIndex = 0; |
| |
| /* |
| * The address associated with a basic block is the start address. |
| */ |
| blockStartAddr = 0; |
| |
| for (idx = 0; idx < insnsSize; ) { |
| /* |
| * Make sure we're pointing at the right "try" block. It should |
| * not be possible to "jump over" a block, so if we're no longer |
| * in the correct one we can just advance to the next. |
| */ |
| if (pTries != NULL && idx >= tryEnd) { |
| if (tryIndex == pCode->triesSize) { |
| /* no more try blocks in this method */ |
| pTries = NULL; |
| numHandlers = 0; |
| } else { |
| /* |
| * Extract the set of handlers. We want to avoid doing |
| * this for each block, so we copy them to local storage. |
| * If it doesn't fit in the small stack area, we'll use |
| * the heap instead. |
| * |
| * It's rare to encounter a method with more than half a |
| * dozen possible handlers. |
| */ |
| tryStart = pTries[tryIndex].startAddr; |
| tryEnd = tryStart + pTries[tryIndex].insnCount; |
| |
| if (handlerListAlloc != NULL) { |
| free(handlerListAlloc); |
| handlerListAlloc = NULL; |
| } |
| numHandlers = extractCatchHandlers(pCode, &pTries[tryIndex], |
| handlerAddrs, kHandlerStackAllocSize); |
| assert(numHandlers > 0); // TODO make sure this is verified |
| if (numHandlers <= kHandlerStackAllocSize) { |
| handlerList = handlerAddrs; |
| } else { |
| ALOGD("overflow, numHandlers=%d", numHandlers); |
| handlerListAlloc = (u4*) malloc(sizeof(u4) * numHandlers); |
| if (handlerListAlloc == NULL) |
| return false; |
| extractCatchHandlers(pCode, &pTries[tryIndex], |
| handlerListAlloc, numHandlers); |
| handlerList = handlerListAlloc; |
| } |
| |
| ALOGV("+++ start=%x end=%x numHan=%d", |
| tryStart, tryEnd, numHandlers); |
| |
| tryIndex++; |
| } |
| } |
| |
| /* |
| * Check the current instruction, and possibly aspects of the |
| * next instruction, to see if this instruction ends the current |
| * basic block. |
| * |
| * Instructions that can throw only end the block if there is the |
| * possibility of a local handler catching the exception. |
| */ |
| Opcode opcode = dexOpcodeFromCodeUnit(meth->insns[idx]); |
| OpcodeFlags opFlags = dexGetFlagsFromOpcode(opcode); |
| size_t nextIdx = idx + dexGetWidthFromInstruction(&meth->insns[idx]); |
| bool endBB = false; |
| bool ignoreInstr = false; |
| |
| if ((opFlags & kInstrCanContinue) == 0) { |
| /* does not continue */ |
| endBB = true; |
| } else if ((opFlags & (kInstrCanBranch | kInstrCanSwitch)) != 0) { |
| /* conditionally branches elsewhere */ |
| endBB = true; |
| } else if ((opFlags & kInstrCanThrow) != 0 && |
| dvmInsnIsInTry(insnFlags, idx)) |
| { |
| /* throws an exception that might be caught locally */ |
| endBB = true; |
| } else if (isDataChunk(meth->insns[idx])) { |
| /* |
| * If this is a data chunk (e.g. switch data) we want to skip |
| * over it entirely. Set endBB so we don't carry this along as |
| * the start of a block, and ignoreInstr so we don't try to |
| * open a basic block for this instruction. |
| */ |
| endBB = ignoreInstr = true; |
| } else if (dvmInsnIsBranchTarget(insnFlags, nextIdx)) { |
| /* |
| * We also need to end it if the next instruction is a branch |
| * target. Note we've tagged exception catch blocks as such. |
| * |
| * If we're this far along in the "else" chain, we know that |
| * this isn't a data-chunk NOP, and control can continue to |
| * the next instruction, so we're okay examining "nextIdx". |
| */ |
| assert(nextIdx < insnsSize); |
| endBB = true; |
| } else if (opcode == OP_NOP && isDataChunk(meth->insns[nextIdx])) { |
| /* |
| * Handle an odd special case: if this is NOP padding before a |
| * data chunk, also treat it as "ignore". Otherwise it'll look |
| * like a block that starts and doesn't end. |
| */ |
| endBB = ignoreInstr = true; |
| } else { |
| /* check: return ops should be caught by absence of can-continue */ |
| assert((opFlags & kInstrCanReturn) == 0); |
| } |
| |
| if (verbose) { |
| char btc = dvmInsnIsBranchTarget(insnFlags, idx) ? '>' : ' '; |
| char tryc = |
| (pTries != NULL && idx >= tryStart && idx < tryEnd) ? 't' : ' '; |
| bool startBB = (idx == blockStartAddr); |
| const char* startEnd; |
| |
| |
| if (ignoreInstr) |
| startEnd = "IGNORE"; |
| else if (startBB && endBB) |
| startEnd = "START/END"; |
| else if (startBB) |
| startEnd = "START"; |
| else if (endBB) |
| startEnd = "END"; |
| else |
| startEnd = "-"; |
| |
| ALOGI("%04x: %c%c%s #%d", idx, tryc, btc, startEnd, debugBBIndex); |
| |
| if (pTries != NULL && idx == tryStart) { |
| assert(numHandlers > 0); |
| ALOGI(" EXC block: [%04x, %04x) %d:(%04x...)", |
| tryStart, tryEnd, numHandlers, handlerList[0]); |
| } |
| } |
| |
| if (idx != blockStartAddr) { |
| /* should not be a basic block struct associated with this addr */ |
| assert(vdata->basicBlocks[idx] == NULL); |
| } |
| if (endBB) { |
| if (!ignoreInstr) { |
| /* |
| * Create a new BB if one doesn't already exist. |
| */ |
| VfyBasicBlock* curBlock = vdata->basicBlocks[blockStartAddr]; |
| if (curBlock == NULL) { |
| curBlock = allocVfyBasicBlock(vdata, blockStartAddr); |
| if (curBlock == NULL) |
| return false; |
| vdata->basicBlocks[blockStartAddr] = curBlock; |
| } |
| |
| curBlock->firstAddr = blockStartAddr; |
| curBlock->lastAddr = idx; |
| |
| if (!setPredecessors(vdata, curBlock, idx, opFlags, nextIdx, |
| handlerList, numHandlers)) |
| { |
| goto bail; |
| } |
| } |
| |
| blockStartAddr = nextIdx; |
| debugBBIndex++; |
| } |
| |
| idx = nextIdx; |
| } |
| |
| assert(idx == insnsSize); |
| |
| result = true; |
| |
| if (verbose) |
| dumpBasicBlocks(vdata); |
| |
| bail: |
| free(handlerListAlloc); |
| return result; |
| } |
| |
| /* |
| * Free the storage used by basic blocks. |
| */ |
| void dvmFreeVfyBasicBlocks(VerifierData* vdata) |
| { |
| unsigned int idx; |
| |
| if (vdata->basicBlocks == NULL) |
| return; |
| |
| for (idx = 0; idx < vdata->insnsSize; idx++) { |
| VfyBasicBlock* block = vdata->basicBlocks[idx]; |
| if (block == NULL) |
| continue; |
| |
| dvmPointerSetFree(block->predecessors); |
| free(block); |
| } |
| } |