/* * Copyright (C) 2011 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. */ /* * Handling of method debug info in a .dex file. */ #include "DexDebugInfo.h" #include "DexProto.h" #include "Leb128.h" #include <stdlib.h> #include <string.h> /* * Decode the arguments in a method signature, which looks something * like "(ID[Ljava/lang/String;)V". * * Returns the type signature letter for the next argument, or ')' if * there are no more args. Advances "pSig" to point to the character * after the one returned. */ static char decodeSignature(const char** pSig) { const char* sig = *pSig; if (*sig == '(') sig++; if (*sig == 'L') { /* object ref */ while (*++sig != ';') ; *pSig = sig+1; return 'L'; } if (*sig == '[') { /* array; advance past array type */ while (*++sig == '[') ; if (*sig == 'L') { while (*++sig != ';') ; } *pSig = sig+1; return '['; } if (*sig == '\0') return *sig; /* don't advance further */ *pSig = sig+1; return *sig; } /* * returns the length of a type string, given the start of the * type string. Used for the case where the debug info format * references types that are inside a method type signature. */ static int typeLength(const char *type) { // Assumes any leading '(' has already been gobbled const char *end = type; decodeSignature(&end); return end - type; } /* * Reads a string index as encoded for the debug info format, * returning a string pointer or NULL as appropriate. */ static const char* readStringIdx(const DexFile* pDexFile, const u1** pStream) { u4 stringIdx = readUnsignedLeb128(pStream); // Remember, encoded string indicies have 1 added to them. if (stringIdx == 0) { return NULL; } else { return dexStringById(pDexFile, stringIdx - 1); } } /* * Reads a type index as encoded for the debug info format, returning * a string pointer for its descriptor or NULL as appropriate. */ static const char* readTypeIdx(const DexFile* pDexFile, const u1** pStream) { u4 typeIdx = readUnsignedLeb128(pStream); // Remember, encoded type indicies have 1 added to them. if (typeIdx == 0) { return NULL; } else { return dexStringByTypeIdx(pDexFile, typeIdx - 1); } } struct LocalInfo { const char *name; const char *descriptor; const char *signature; u2 startAddress; bool live; }; static void emitLocalCbIfLive(void *cnxt, int reg, u4 endAddress, LocalInfo *localInReg, DexDebugNewLocalCb localCb) { if (localCb != NULL && localInReg[reg].live) { localCb(cnxt, reg, localInReg[reg].startAddress, endAddress, localInReg[reg].name, localInReg[reg].descriptor, localInReg[reg].signature == NULL ? "" : localInReg[reg].signature ); } } static void invalidStream(const char* classDescriptor, const DexProto* proto) { IF_LOGE() { char* methodDescriptor = dexProtoCopyMethodDescriptor(proto); LOGE("Invalid debug info stream. class %s; proto %s", classDescriptor, methodDescriptor); free(methodDescriptor); } } static void dexDecodeDebugInfo0( const DexFile* pDexFile, const DexCode* pCode, const char* classDescriptor, u4 protoIdx, u4 accessFlags, DexDebugNewPositionCb posCb, DexDebugNewLocalCb localCb, void* cnxt, const u1* stream, LocalInfo* localInReg) { DexProto proto = { pDexFile, protoIdx }; u4 insnsSize = pCode->insnsSize; u4 line = readUnsignedLeb128(&stream); u4 parametersSize = readUnsignedLeb128(&stream); u2 argReg = pCode->registersSize - pCode->insSize; u4 address = 0; if ((accessFlags & ACC_STATIC) == 0) { /* * The code is an instance method, which means that there is * an initial this parameter. Also, the proto list should * contain exactly one fewer argument word than the insSize * indicates. */ assert(pCode->insSize == (dexProtoComputeArgsSize(&proto) + 1)); localInReg[argReg].name = "this"; localInReg[argReg].descriptor = classDescriptor; localInReg[argReg].startAddress = 0; localInReg[argReg].live = true; argReg++; } else { assert(pCode->insSize == dexProtoComputeArgsSize(&proto)); } DexParameterIterator iterator; dexParameterIteratorInit(&iterator, &proto); while (parametersSize-- != 0) { const char* descriptor = dexParameterIteratorNextDescriptor(&iterator); const char *name; int reg; if ((argReg >= pCode->registersSize) || (descriptor == NULL)) { invalidStream(classDescriptor, &proto); return; } name = readStringIdx(pDexFile, &stream); reg = argReg; switch (descriptor[0]) { case 'D': case 'J': argReg += 2; break; default: argReg += 1; break; } if (name != NULL) { localInReg[reg].name = name; localInReg[reg].descriptor = descriptor; localInReg[reg].signature = NULL; localInReg[reg].startAddress = address; localInReg[reg].live = true; } } for (;;) { u1 opcode = *stream++; u2 reg; switch (opcode) { case DBG_END_SEQUENCE: return; case DBG_ADVANCE_PC: address += readUnsignedLeb128(&stream); break; case DBG_ADVANCE_LINE: line += readSignedLeb128(&stream); break; case DBG_START_LOCAL: case DBG_START_LOCAL_EXTENDED: reg = readUnsignedLeb128(&stream); if (reg > pCode->registersSize) { invalidStream(classDescriptor, &proto); return; } // Emit what was previously there, if anything emitLocalCbIfLive(cnxt, reg, address, localInReg, localCb); localInReg[reg].name = readStringIdx(pDexFile, &stream); localInReg[reg].descriptor = readTypeIdx(pDexFile, &stream); if (opcode == DBG_START_LOCAL_EXTENDED) { localInReg[reg].signature = readStringIdx(pDexFile, &stream); } else { localInReg[reg].signature = NULL; } localInReg[reg].startAddress = address; localInReg[reg].live = true; break; case DBG_END_LOCAL: reg = readUnsignedLeb128(&stream); if (reg > pCode->registersSize) { invalidStream(classDescriptor, &proto); return; } emitLocalCbIfLive (cnxt, reg, address, localInReg, localCb); localInReg[reg].live = false; break; case DBG_RESTART_LOCAL: reg = readUnsignedLeb128(&stream); if (reg > pCode->registersSize) { invalidStream(classDescriptor, &proto); return; } if (localInReg[reg].name == NULL || localInReg[reg].descriptor == NULL) { invalidStream(classDescriptor, &proto); return; } /* * If the register is live, the "restart" is superfluous, * and we don't want to mess with the existing start address. */ if (!localInReg[reg].live) { localInReg[reg].startAddress = address; localInReg[reg].live = true; } break; case DBG_SET_PROLOGUE_END: case DBG_SET_EPILOGUE_BEGIN: case DBG_SET_FILE: break; default: { int adjopcode = opcode - DBG_FIRST_SPECIAL; address += adjopcode / DBG_LINE_RANGE; line += DBG_LINE_BASE + (adjopcode % DBG_LINE_RANGE); if (posCb != NULL) { int done; done = posCb(cnxt, address, line); if (done) { // early exit return; } } break; } } } } // TODO optimize localCb == NULL case void dexDecodeDebugInfo( const DexFile* pDexFile, const DexCode* pCode, const char* classDescriptor, u4 protoIdx, u4 accessFlags, DexDebugNewPositionCb posCb, DexDebugNewLocalCb localCb, void* cnxt) { const u1* stream = dexGetDebugInfoStream(pDexFile, pCode); LocalInfo localInReg[pCode->registersSize]; memset(localInReg, 0, sizeof(LocalInfo) * pCode->registersSize); if (stream != NULL) { dexDecodeDebugInfo0(pDexFile, pCode, classDescriptor, protoIdx, accessFlags, posCb, localCb, cnxt, stream, localInReg); } for (int reg = 0; reg < pCode->registersSize; reg++) { emitLocalCbIfLive(cnxt, reg, pCode->insnsSize, localInReg, localCb); } }