/*
* 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);
}
}