/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Dalvik verification subroutines. */ #include "Dalvik.h" #include "analysis/CodeVerify.h" #include "libdex/DexCatch.h" #include "libdex/InstrUtils.h" /* * Compute the width of the instruction at each address in the instruction * stream. Addresses that are in the middle of an instruction, or that * are part of switch table data, are not set (so the caller should probably * initialize "insnFlags" to zero). * * If "pNewInstanceCount" is not NULL, it will be set to the number of * new-instance instructions in the method. * * Performs some static checks, notably: * - opcode of first instruction begins at index 0 * - only documented instructions may appear * - each instruction follows the last * - last byte of last instruction is at (code_length-1) * * Logs an error and returns "false" on failure. */ bool dvmComputeCodeWidths(const Method* meth, InsnFlags* insnFlags, int* pNewInstanceCount) { size_t insnCount = dvmGetMethodInsnsSize(meth); const u2* insns = meth->insns; bool result = false; int newInstanceCount = 0; int i; for (i = 0; i < (int) insnCount; /**/) { size_t width = dexGetInstrOrTableWidthAbs(gDvm.instrWidth, insns); if (width == 0) { LOG_VFY_METH(meth, "VFY: invalid post-opt instruction (0x%04x)\n", *insns); goto bail; } if ((*insns & 0xff) == OP_NEW_INSTANCE) newInstanceCount++; if (width > 65535) { LOG_VFY_METH(meth, "VFY: insane width %d\n", width); goto bail; } insnFlags[i] |= width; i += width; insns += width; } if (i != (int) dvmGetMethodInsnsSize(meth)) { LOG_VFY_METH(meth, "VFY: code did not end where expected (%d vs. %d)\n", i, dvmGetMethodInsnsSize(meth)); goto bail; } result = true; if (pNewInstanceCount != NULL) *pNewInstanceCount = newInstanceCount; bail: return result; } /* * Set the "in try" flags for all instructions protected by "try" statements. * Also sets the "branch target" flags for exception handlers. * * Call this after widths have been set in "insnFlags". * * Returns "false" if something in the exception table looks fishy, but * we're expecting the exception table to be somewhat sane. */ bool dvmSetTryFlags(const Method* meth, InsnFlags* insnFlags) { u4 insnsSize = dvmGetMethodInsnsSize(meth); const DexCode* pCode = dvmGetMethodCode(meth); u4 triesSize = pCode->triesSize; const DexTry* pTries; u4 handlersSize; u4 offset; u4 i; if (triesSize == 0) { return true; } pTries = dexGetTries(pCode); handlersSize = dexGetHandlersSize(pCode); for (i = 0; i < triesSize; i++) { const DexTry* pTry = &pTries[i]; u4 start = pTry->startAddr; u4 end = start + pTry->insnCount; u4 addr; if ((start >= end) || (start >= insnsSize) || (end > insnsSize)) { LOG_VFY_METH(meth, "VFY: bad exception entry: startAddr=%d endAddr=%d (size=%d)\n", start, end, insnsSize); return false; } if (dvmInsnGetWidth(insnFlags, start) == 0) { LOG_VFY_METH(meth, "VFY: 'try' block starts inside an instruction (%d)\n", start); return false; } for (addr = start; addr < end; addr += dvmInsnGetWidth(insnFlags, addr)) { assert(dvmInsnGetWidth(insnFlags, addr) != 0); dvmInsnSetInTry(insnFlags, addr, true); } } /* Iterate over each of the handlers to verify target addresses. */ offset = dexGetFirstHandlerOffset(pCode); for (i = 0; i < handlersSize; i++) { DexCatchIterator iterator; dexCatchIteratorInit(&iterator, pCode, offset); for (;;) { DexCatchHandler* handler = dexCatchIteratorNext(&iterator); u4 addr; if (handler == NULL) { break; } addr = handler->address; if (dvmInsnGetWidth(insnFlags, addr) == 0) { LOG_VFY_METH(meth, "VFY: exception handler starts at bad address (%d)\n", addr); return false; } dvmInsnSetBranchTarget(insnFlags, addr, true); } offset = dexCatchIteratorGetEndOffset(&iterator, pCode); } return true; } /* * Verify a switch table. "curOffset" is the offset of the switch * instruction. */ bool dvmCheckSwitchTargets(const Method* meth, InsnFlags* insnFlags, int curOffset) { const s4 insnCount = dvmGetMethodInsnsSize(meth); const u2* insns = meth->insns + curOffset; const u2* switchInsns; u2 expectedSignature; u4 switchCount, tableSize; s4 offsetToSwitch, offsetToKeys, offsetToTargets; s4 offset, absOffset; u4 targ; assert(curOffset >= 0 && curOffset < insnCount); /* make sure the start of the switch is in range */ offsetToSwitch = insns[1] | ((s4) insns[2]) << 16; if (curOffset + offsetToSwitch < 0 || curOffset + offsetToSwitch + 2 >= insnCount) { LOG_VFY_METH(meth, "VFY: invalid switch start: at %d, switch offset %d, count %d\n", curOffset, offsetToSwitch, insnCount); return false; } /* offset to switch table is a relative branch-style offset */ switchInsns = insns + offsetToSwitch; /* make sure the table is 32-bit aligned */ if ((((u4) switchInsns) & 0x03) != 0) { LOG_VFY_METH(meth, "VFY: unaligned switch table: at %d, switch offset %d\n", curOffset, offsetToSwitch); return false; } switchCount = switchInsns[1]; if ((*insns & 0xff) == OP_PACKED_SWITCH) { /* 0=sig, 1=count, 2/3=firstKey */ offsetToTargets = 4; offsetToKeys = -1; expectedSignature = kPackedSwitchSignature; } else { /* 0=sig, 1=count, 2..count*2 = keys */ offsetToKeys = 2; offsetToTargets = 2 + 2*switchCount; expectedSignature = kSparseSwitchSignature; } tableSize = offsetToTargets + switchCount*2; if (switchInsns[0] != expectedSignature) { LOG_VFY_METH(meth, "VFY: wrong signature for switch table (0x%04x, wanted 0x%04x)\n", switchInsns[0], expectedSignature); return false; } /* make sure the end of the switch is in range */ if (curOffset + offsetToSwitch + tableSize > (u4) insnCount) { LOG_VFY_METH(meth, "VFY: invalid switch end: at %d, switch offset %d, end %d, count %d\n", curOffset, offsetToSwitch, curOffset + offsetToSwitch + tableSize, insnCount); return false; } /* for a sparse switch, verify the keys are in ascending order */ if (offsetToKeys > 0 && switchCount > 1) { s4 lastKey; lastKey = switchInsns[offsetToKeys] | (switchInsns[offsetToKeys+1] << 16); for (targ = 1; targ < switchCount; targ++) { s4 key = (s4) switchInsns[offsetToKeys + targ*2] | (s4) (switchInsns[offsetToKeys + targ*2 +1] << 16); if (key <= lastKey) { LOG_VFY_METH(meth, "VFY: invalid packed switch: last key=%d, this=%d\n", lastKey, key); return false; } lastKey = key; } } /* verify each switch target */ for (targ = 0; targ < switchCount; targ++) { offset = (s4) switchInsns[offsetToTargets + targ*2] | (s4) (switchInsns[offsetToTargets + targ*2 +1] << 16); absOffset = curOffset + offset; if (absOffset < 0 || absOffset >= insnCount || !dvmInsnIsOpcode(insnFlags, absOffset)) { LOG_VFY_METH(meth, "VFY: invalid switch target %d (-> 0x%x) at 0x%x[%d]\n", offset, absOffset, curOffset, targ); return false; } dvmInsnSetBranchTarget(insnFlags, absOffset, true); } return true; } /* * Verify that the target of a branch instruction is valid. * * We don't expect code to jump directly into an exception handler, but * it's valid to do so as long as the target isn't a "move-exception" * instruction. We verify that in a later stage. * * The VM spec doesn't forbid an instruction from branching to itself, * but the Dalvik spec declares that only certain instructions can do so. */ bool dvmCheckBranchTarget(const Method* meth, InsnFlags* insnFlags, int curOffset, bool selfOkay) { const int insnCount = dvmGetMethodInsnsSize(meth); int offset, absOffset; bool isConditional; if (!dvmGetBranchTarget(meth, insnFlags, curOffset, &offset, &isConditional)) return false; if (!selfOkay && offset == 0) { LOG_VFY_METH(meth, "VFY: branch offset of zero not allowed at 0x%x\n", curOffset); return false; } /* * Check for 32-bit overflow. This isn't strictly necessary if we can * depend on the VM to have identical "wrap-around" behavior, but * it's unwise to depend on that. */ if (((s8) curOffset + (s8) offset) != (s8)(curOffset + offset)) { LOG_VFY_METH(meth, "VFY: branch target overflow 0x%x +%d\n", curOffset, offset); return false; } absOffset = curOffset + offset; if (absOffset < 0 || absOffset >= insnCount || !dvmInsnIsOpcode(insnFlags, absOffset)) { LOG_VFY_METH(meth, "VFY: invalid branch target %d (-> 0x%x) at 0x%x\n", offset, absOffset, curOffset); return false; } dvmInsnSetBranchTarget(insnFlags, absOffset, true); return true; } /* * Output a code verifier warning message. For the pre-verifier it's not * a big deal if something fails (and it may even be expected), but if * we're doing just-in-time verification it's significant. */ void dvmLogVerifyFailure(const Method* meth, const char* format, ...) { va_list ap; int logLevel; if (gDvm.optimizing) { return; //logLevel = ANDROID_LOG_DEBUG; } else { logLevel = ANDROID_LOG_WARN; } va_start(ap, format); LOG_PRI_VA(logLevel, LOG_TAG, format, ap); if (meth != NULL) { char* desc = dexProtoCopyMethodDescriptor(&meth->prototype); LOG_PRI(logLevel, LOG_TAG, "VFY: rejected %s.%s %s\n", meth->clazz->descriptor, meth->name, desc); free(desc); } } /* * Show a relatively human-readable message describing the failure to * resolve a class. * * TODO: this is somewhat misleading when resolution fails because of * illegal access rather than nonexistent class. */ void dvmLogUnableToResolveClass(const char* missingClassDescr, const Method* meth) { if (gDvm.optimizing) return; char* dotMissingClass = dvmDescriptorToDot(missingClassDescr); char* dotFromClass = dvmDescriptorToDot(meth->clazz->descriptor); //char* methodDescr = dexProtoCopyMethodDescriptor(&meth->prototype); LOGE("Could not find class '%s', referenced from method %s.%s\n", dotMissingClass, dotFromClass, meth->name/*, methodDescr*/); free(dotMissingClass); free(dotFromClass); //free(methodDescr); } /* * Extract the relative offset from a branch instruction. * * Returns "false" on failure (e.g. this isn't a branch instruction). */ bool dvmGetBranchTarget(const Method* meth, InsnFlags* insnFlags, int curOffset, int* pOffset, bool* pConditional) { const u2* insns = meth->insns + curOffset; switch (*insns & 0xff) { case OP_GOTO: *pOffset = ((s2) *insns) >> 8; *pConditional = false; break; case OP_GOTO_32: *pOffset = insns[1] | (((u4) insns[2]) << 16); *pConditional = false; break; case OP_GOTO_16: *pOffset = (s2) insns[1]; *pConditional = false; 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_IF_EQZ: case OP_IF_NEZ: case OP_IF_LTZ: case OP_IF_GEZ: case OP_IF_GTZ: case OP_IF_LEZ: *pOffset = (s2) insns[1]; *pConditional = true; break; default: return false; break; } return true; } /* * Given a 32-bit constant, return the most-restricted RegType enum entry * that can hold the value. */ char dvmDetermineCat1Const(s4 value) { if (value < -32768) return kRegTypeInteger; else if (value < -128) return kRegTypeShort; else if (value < 0) return kRegTypeByte; else if (value == 0) return kRegTypeZero; else if (value == 1) return kRegTypeOne; else if (value < 128) return kRegTypePosByte; else if (value < 32768) return kRegTypePosShort; else if (value < 65536) return kRegTypeChar; else return kRegTypeInteger; }