/*
* 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.
*/
/*
* The "dexdump" tool is intended to mimic "objdump". When possible, use
* similar command-line arguments.
*
* TODO: rework the "plain" output format to be more regexp-friendly
*
* Differences between XML output and the "current.xml" file:
* - classes in same package are not all grouped together; generally speaking
* nothing is sorted
* - no "deprecated" on fields and methods
* - no "value" on fields
* - no parameter names
* - no generic signatures on parameters, e.g. type="java.lang.Class<?>"
* - class shows declared fields and methods; does not show inherited fields
*/
#include "libdex/DexFile.h"
#include "libdex/CmdUtils.h"
#include "libdex/DexCatch.h"
#include "libdex/DexClass.h"
#include "libdex/DexDebugInfo.h"
#include "libdex/DexOpcodes.h"
#include "libdex/DexProto.h"
#include "libdex/InstrUtils.h"
#include "libdex/SysUtil.h"
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>
#include <inttypes.h>
static const char* gProgName = "dexdump";
enum OutputFormat {
OUTPUT_PLAIN = 0, /* default */
OUTPUT_XML, /* fancy */
};
/* command-line options */
struct Options {
bool checksumOnly;
bool disassemble;
bool showFileHeaders;
bool showSectionHeaders;
bool ignoreBadChecksum;
bool dumpRegisterMaps;
OutputFormat outputFormat;
const char* tempFileName;
bool exportsOnly;
bool verbose;
};
struct Options gOptions;
/* basic info about a field or method */
struct FieldMethodInfo {
const char* classDescriptor;
const char* name;
const char* signature;
};
/* basic info about a prototype */
struct ProtoInfo {
char* parameterTypes; // dynamically allocated with malloc
const char* returnType;
};
/*
* Get 2 little-endian bytes.
*/
static inline u2 get2LE(unsigned char const* pSrc)
{
return pSrc[0] | (pSrc[1] << 8);
}
/*
* Get 4 little-endian bytes.
*/
static inline u4 get4LE(unsigned char const* pSrc)
{
return pSrc[0] | (pSrc[1] << 8) | (pSrc[2] << 16) | (pSrc[3] << 24);
}
/*
* Converts a single-character primitive type into its human-readable
* equivalent.
*/
static const char* primitiveTypeLabel(char typeChar)
{
switch (typeChar) {
case 'B': return "byte";
case 'C': return "char";
case 'D': return "double";
case 'F': return "float";
case 'I': return "int";
case 'J': return "long";
case 'S': return "short";
case 'V': return "void";
case 'Z': return "boolean";
default:
return "UNKNOWN";
}
}
/*
* Converts a type descriptor to human-readable "dotted" form. For
* example, "Ljava/lang/String;" becomes "java.lang.String", and
* "[I" becomes "int[]". Also converts '$' to '.', which means this
* form can't be converted back to a descriptor.
*/
static char* descriptorToDot(const char* str)
{
int targetLen = strlen(str);
int offset = 0;
int arrayDepth = 0;
char* newStr;
/* strip leading [s; will be added to end */
while (targetLen > 1 && str[offset] == '[') {
offset++;
targetLen--;
}
arrayDepth = offset;
if (targetLen == 1) {
/* primitive type */
str = primitiveTypeLabel(str[offset]);
offset = 0;
targetLen = strlen(str);
} else {
/* account for leading 'L' and trailing ';' */
if (targetLen >= 2 && str[offset] == 'L' &&
str[offset+targetLen-1] == ';')
{
targetLen -= 2;
offset++;
}
}
newStr = (char*)malloc(targetLen + arrayDepth * 2 +1);
/* copy class name over */
int i;
for (i = 0; i < targetLen; i++) {
char ch = str[offset + i];
newStr[i] = (ch == '/' || ch == '$') ? '.' : ch;
}
/* add the appropriate number of brackets for arrays */
while (arrayDepth-- > 0) {
newStr[i++] = '[';
newStr[i++] = ']';
}
newStr[i] = '\0';
assert(i == targetLen + arrayDepth * 2);
return newStr;
}
/*
* Converts the class name portion of a type descriptor to human-readable
* "dotted" form.
*
* Returns a newly-allocated string.
*/
static char* descriptorClassToDot(const char* str)
{
const char* lastSlash;
char* newStr;
char* cp;
/* reduce to just the class name, trimming trailing ';' */
lastSlash = strrchr(str, '/');
if (lastSlash == NULL)
lastSlash = str + 1; /* start past 'L' */
else
lastSlash++; /* start past '/' */
newStr = strdup(lastSlash);
newStr[strlen(lastSlash)-1] = '\0';
for (cp = newStr; *cp != '\0'; cp++) {
if (*cp == '$')
*cp = '.';
}
return newStr;
}
/*
* Returns a quoted string representing the boolean value.
*/
static const char* quotedBool(bool val)
{
if (val)
return "\"true\"";
else
return "\"false\"";
}
static const char* quotedVisibility(u4 accessFlags)
{
if ((accessFlags & ACC_PUBLIC) != 0)
return "\"public\"";
else if ((accessFlags & ACC_PROTECTED) != 0)
return "\"protected\"";
else if ((accessFlags & ACC_PRIVATE) != 0)
return "\"private\"";
else
return "\"package\"";
}
/*
* Count the number of '1' bits in a word.
*/
static int countOnes(u4 val)
{
int count = 0;
val = val - ((val >> 1) & 0x55555555);
val = (val & 0x33333333) + ((val >> 2) & 0x33333333);
count = (((val + (val >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
return count;
}
/*
* Flag for use with createAccessFlagStr().
*/
enum AccessFor {
kAccessForClass = 0, kAccessForMethod = 1, kAccessForField = 2,
kAccessForMAX
};
/*
* Create a new string with human-readable access flags.
*
* In the base language the access_flags fields are type u2; in Dalvik
* they're u4.
*/
static char* createAccessFlagStr(u4 flags, AccessFor forWhat)
{
#define NUM_FLAGS 18
static const char* kAccessStrings[kAccessForMAX][NUM_FLAGS] = {
{
/* class, inner class */
"PUBLIC", /* 0x0001 */
"PRIVATE", /* 0x0002 */
"PROTECTED", /* 0x0004 */
"STATIC", /* 0x0008 */
"FINAL", /* 0x0010 */
"?", /* 0x0020 */
"?", /* 0x0040 */
"?", /* 0x0080 */
"?", /* 0x0100 */
"INTERFACE", /* 0x0200 */
"ABSTRACT", /* 0x0400 */
"?", /* 0x0800 */
"SYNTHETIC", /* 0x1000 */
"ANNOTATION", /* 0x2000 */
"ENUM", /* 0x4000 */
"?", /* 0x8000 */
"VERIFIED", /* 0x10000 */
"OPTIMIZED", /* 0x20000 */
},
{
/* method */
"PUBLIC", /* 0x0001 */
"PRIVATE", /* 0x0002 */
"PROTECTED", /* 0x0004 */
"STATIC", /* 0x0008 */
"FINAL", /* 0x0010 */
"SYNCHRONIZED", /* 0x0020 */
"BRIDGE", /* 0x0040 */
"VARARGS", /* 0x0080 */
"NATIVE", /* 0x0100 */
"?", /* 0x0200 */
"ABSTRACT", /* 0x0400 */
"STRICT", /* 0x0800 */
"SYNTHETIC", /* 0x1000 */
"?", /* 0x2000 */
"?", /* 0x4000 */
"MIRANDA", /* 0x8000 */
"CONSTRUCTOR", /* 0x10000 */
"DECLARED_SYNCHRONIZED", /* 0x20000 */
},
{
/* field */
"PUBLIC", /* 0x0001 */
"PRIVATE", /* 0x0002 */
"PROTECTED", /* 0x0004 */
"STATIC", /* 0x0008 */
"FINAL", /* 0x0010 */
"?", /* 0x0020 */
"VOLATILE", /* 0x0040 */
"TRANSIENT", /* 0x0080 */
"?", /* 0x0100 */
"?", /* 0x0200 */
"?", /* 0x0400 */
"?", /* 0x0800 */
"SYNTHETIC", /* 0x1000 */
"?", /* 0x2000 */
"ENUM", /* 0x4000 */
"?", /* 0x8000 */
"?", /* 0x10000 */
"?", /* 0x20000 */
},
};
const int kLongest = 21; /* strlen of longest string above */
int i, count;
char* str;
char* cp;
/*
* Allocate enough storage to hold the expected number of strings,
* plus a space between each. We over-allocate, using the longest
* string above as the base metric.
*/
count = countOnes(flags);
cp = str = (char*) malloc(count * (kLongest+1) +1);
for (i = 0; i < NUM_FLAGS; i++) {
if (flags & 0x01) {
const char* accessStr = kAccessStrings[forWhat][i];
int len = strlen(accessStr);
if (cp != str)
*cp++ = ' ';
memcpy(cp, accessStr, len);
cp += len;
}
flags >>= 1;
}
*cp = '\0';
return str;
}
/*
* Copy character data from "data" to "out", converting non-ASCII values
* to printf format chars or an ASCII filler ('.' or '?').
*
* The output buffer must be able to hold (2*len)+1 bytes. The result is
* NUL-terminated.
*/
static void asciify(char* out, const unsigned char* data, size_t len)
{
while (len--) {
if (*data < 0x20) {
/* could do more here, but we don't need them yet */
switch (*data) {
case '\0':
*out++ = '\\';
*out++ = '0';
break;
case '\n':
*out++ = '\\';
*out++ = 'n';
break;
default:
*out++ = '.';
break;
}
} else if (*data >= 0x80) {
*out++ = '?';
} else {
*out++ = *data;
}
data++;
}
*out = '\0';
}
/*
* Dump the file header.
*/
void dumpFileHeader(const DexFile* pDexFile)
{
const DexOptHeader* pOptHeader = pDexFile->pOptHeader;
const DexHeader* pHeader = pDexFile->pHeader;
char sanitized[sizeof(pHeader->magic)*2 +1];
assert(sizeof(pHeader->magic) == sizeof(pOptHeader->magic));
if (pOptHeader != NULL) {
printf("Optimized DEX file header:\n");
asciify(sanitized, pOptHeader->magic, sizeof(pOptHeader->magic));
printf("magic : '%s'\n", sanitized);
printf("dex_offset : %d (0x%06x)\n",
pOptHeader->dexOffset, pOptHeader->dexOffset);
printf("dex_length : %d\n", pOptHeader->dexLength);
printf("deps_offset : %d (0x%06x)\n",
pOptHeader->depsOffset, pOptHeader->depsOffset);
printf("deps_length : %d\n", pOptHeader->depsLength);
printf("opt_offset : %d (0x%06x)\n",
pOptHeader->optOffset, pOptHeader->optOffset);
printf("opt_length : %d\n", pOptHeader->optLength);
printf("flags : %08x\n", pOptHeader->flags);
printf("checksum : %08x\n", pOptHeader->checksum);
printf("\n");
}
printf("DEX file header:\n");
asciify(sanitized, pHeader->magic, sizeof(pHeader->magic));
printf("magic : '%s'\n", sanitized);
printf("checksum : %08x\n", pHeader->checksum);
printf("signature : %02x%02x...%02x%02x\n",
pHeader->signature[0], pHeader->signature[1],
pHeader->signature[kSHA1DigestLen-2],
pHeader->signature[kSHA1DigestLen-1]);
printf("file_size : %d\n", pHeader->fileSize);
printf("header_size : %d\n", pHeader->headerSize);
printf("link_size : %d\n", pHeader->linkSize);
printf("link_off : %d (0x%06x)\n",
pHeader->linkOff, pHeader->linkOff);
printf("string_ids_size : %d\n", pHeader->stringIdsSize);
printf("string_ids_off : %d (0x%06x)\n",
pHeader->stringIdsOff, pHeader->stringIdsOff);
printf("type_ids_size : %d\n", pHeader->typeIdsSize);
printf("type_ids_off : %d (0x%06x)\n",
pHeader->typeIdsOff, pHeader->typeIdsOff);
printf("proto_ids_size : %d\n", pHeader->protoIdsSize);
printf("proto_ids_off : %d (0x%06x)\n",
pHeader->protoIdsOff, pHeader->protoIdsOff);
printf("field_ids_size : %d\n", pHeader->fieldIdsSize);
printf("field_ids_off : %d (0x%06x)\n",
pHeader->fieldIdsOff, pHeader->fieldIdsOff);
printf("method_ids_size : %d\n", pHeader->methodIdsSize);
printf("method_ids_off : %d (0x%06x)\n",
pHeader->methodIdsOff, pHeader->methodIdsOff);
printf("class_defs_size : %d\n", pHeader->classDefsSize);
printf("class_defs_off : %d (0x%06x)\n",
pHeader->classDefsOff, pHeader->classDefsOff);
printf("data_size : %d\n", pHeader->dataSize);
printf("data_off : %d (0x%06x)\n",
pHeader->dataOff, pHeader->dataOff);
printf("\n");
}
/*
* Dump the "table of contents" for the opt area.
*/
void dumpOptDirectory(const DexFile* pDexFile)
{
const DexOptHeader* pOptHeader = pDexFile->pOptHeader;
if (pOptHeader == NULL)
return;
printf("OPT section contents:\n");
const u4* pOpt = (const u4*) ((u1*) pOptHeader + pOptHeader->optOffset);
if (*pOpt == 0) {
printf("(1.0 format, only class lookup table is present)\n\n");
return;
}
/*
* The "opt" section is in "chunk" format: a 32-bit identifier, a 32-bit
* length, then the data. Chunks start on 64-bit boundaries.
*/
while (*pOpt != kDexChunkEnd) {
const char* verboseStr;
u4 size = *(pOpt+1);
switch (*pOpt) {
case kDexChunkClassLookup:
verboseStr = "class lookup hash table";
break;
case kDexChunkRegisterMaps:
verboseStr = "register maps";
break;
default:
verboseStr = "(unknown chunk type)";
break;
}
printf("Chunk %08x (%c%c%c%c) - %s (%d bytes)\n", *pOpt,
*pOpt >> 24, (char)(*pOpt >> 16), (char)(*pOpt >> 8), (char)*pOpt,
verboseStr, size);
size = (size + 8 + 7) & ~7;
pOpt += size / sizeof(u4);
}
printf("\n");
}
/*
* Dump a class_def_item.
*/
void dumpClassDef(DexFile* pDexFile, int idx)
{
const DexClassDef* pClassDef;
const u1* pEncodedData;
DexClassData* pClassData;
pClassDef = dexGetClassDef(pDexFile, idx);
pEncodedData = dexGetClassData(pDexFile, pClassDef);
pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL);
if (pClassData == NULL) {
fprintf(stderr, "Trouble reading class data\n");
return;
}
printf("Class #%d header:\n", idx);
printf("class_idx : %d\n", pClassDef->classIdx);
printf("access_flags : %d (0x%04x)\n",
pClassDef->accessFlags, pClassDef->accessFlags);
printf("superclass_idx : %d\n", pClassDef->superclassIdx);
printf("interfaces_off : %d (0x%06x)\n",
pClassDef->interfacesOff, pClassDef->interfacesOff);
printf("source_file_idx : %d\n", pClassDef->sourceFileIdx);
printf("annotations_off : %d (0x%06x)\n",
pClassDef->annotationsOff, pClassDef->annotationsOff);
printf("class_data_off : %d (0x%06x)\n",
pClassDef->classDataOff, pClassDef->classDataOff);
printf("static_fields_size : %d\n", pClassData->header.staticFieldsSize);
printf("instance_fields_size: %d\n",
pClassData->header.instanceFieldsSize);
printf("direct_methods_size : %d\n", pClassData->header.directMethodsSize);
printf("virtual_methods_size: %d\n",
pClassData->header.virtualMethodsSize);
printf("\n");
free(pClassData);
}
/*
* Dump an interface that a class declares to implement.
*/
void dumpInterface(const DexFile* pDexFile, const DexTypeItem* pTypeItem,
int i)
{
const char* interfaceName =
dexStringByTypeIdx(pDexFile, pTypeItem->typeIdx);
if (gOptions.outputFormat == OUTPUT_PLAIN) {
printf(" #%d : '%s'\n", i, interfaceName);
} else {
char* dotted = descriptorToDot(interfaceName);
printf("<implements name=\"%s\">\n</implements>\n", dotted);
free(dotted);
}
}
/*
* Dump the catches table associated with the code.
*/
void dumpCatches(DexFile* pDexFile, const DexCode* pCode)
{
u4 triesSize = pCode->triesSize;
if (triesSize == 0) {
printf(" catches : (none)\n");
return;
}
printf(" catches : %d\n", triesSize);
const DexTry* pTries = dexGetTries(pCode);
u4 i;
for (i = 0; i < triesSize; i++) {
const DexTry* pTry = &pTries[i];
u4 start = pTry->startAddr;
u4 end = start + pTry->insnCount;
DexCatchIterator iterator;
printf(" 0x%04x - 0x%04x\n", start, end);
dexCatchIteratorInit(&iterator, pCode, pTry->handlerOff);
for (;;) {
DexCatchHandler* handler = dexCatchIteratorNext(&iterator);
const char* descriptor;
if (handler == NULL) {
break;
}
descriptor = (handler->typeIdx == kDexNoIndex) ? "<any>" :
dexStringByTypeIdx(pDexFile, handler->typeIdx);
printf(" %s -> 0x%04x\n", descriptor,
handler->address);
}
}
}
static int dumpPositionsCb(void * /* cnxt */, u4 address, u4 lineNum)
{
printf(" 0x%04x line=%d\n", address, lineNum);
return 0;
}
/*
* Dump the positions list.
*/
void dumpPositions(DexFile* pDexFile, const DexCode* pCode,
const DexMethod *pDexMethod)
{
printf(" positions : \n");
const DexMethodId *pMethodId
= dexGetMethodId(pDexFile, pDexMethod->methodIdx);
const char *classDescriptor
= dexStringByTypeIdx(pDexFile, pMethodId->classIdx);
dexDecodeDebugInfo(pDexFile, pCode, classDescriptor, pMethodId->protoIdx,
pDexMethod->accessFlags, dumpPositionsCb, NULL, NULL);
}
static void dumpLocalsCb(void * /* cnxt */, u2 reg, u4 startAddress,
u4 endAddress, const char *name, const char *descriptor,
const char *signature)
{
printf(" 0x%04x - 0x%04x reg=%d %s %s %s\n",
startAddress, endAddress, reg, name, descriptor,
signature);
}
/*
* Dump the locals list.
*/
void dumpLocals(DexFile* pDexFile, const DexCode* pCode,
const DexMethod *pDexMethod)
{
printf(" locals : \n");
const DexMethodId *pMethodId
= dexGetMethodId(pDexFile, pDexMethod->methodIdx);
const char *classDescriptor
= dexStringByTypeIdx(pDexFile, pMethodId->classIdx);
dexDecodeDebugInfo(pDexFile, pCode, classDescriptor, pMethodId->protoIdx,
pDexMethod->accessFlags, NULL, dumpLocalsCb, NULL);
}
/*
* Get information about a method.
*/
bool getMethodInfo(DexFile* pDexFile, u4 methodIdx, FieldMethodInfo* pMethInfo)
{
const DexMethodId* pMethodId;
if (methodIdx >= pDexFile->pHeader->methodIdsSize)
return false;
pMethodId = dexGetMethodId(pDexFile, methodIdx);
pMethInfo->name = dexStringById(pDexFile, pMethodId->nameIdx);
pMethInfo->signature = dexCopyDescriptorFromMethodId(pDexFile, pMethodId);
pMethInfo->classDescriptor =
dexStringByTypeIdx(pDexFile, pMethodId->classIdx);
return true;
}
/*
* Get information about a field.
*/
bool getFieldInfo(DexFile* pDexFile, u4 fieldIdx, FieldMethodInfo* pFieldInfo)
{
const DexFieldId* pFieldId;
if (fieldIdx >= pDexFile->pHeader->fieldIdsSize)
return false;
pFieldId = dexGetFieldId(pDexFile, fieldIdx);
pFieldInfo->name = dexStringById(pDexFile, pFieldId->nameIdx);
pFieldInfo->signature = dexStringByTypeIdx(pDexFile, pFieldId->typeIdx);
pFieldInfo->classDescriptor =
dexStringByTypeIdx(pDexFile, pFieldId->classIdx);
return true;
}
/*
* Get information about a ProtoId.
*/
bool getProtoInfo(DexFile* pDexFile, u4 protoIdx, ProtoInfo* pProtoInfo)
{
if (protoIdx >= pDexFile->pHeader->protoIdsSize) {
return false;
}
const DexProtoId* protoId = dexGetProtoId(pDexFile, protoIdx);
// Get string for return type.
if (protoId->returnTypeIdx >= pDexFile->pHeader->typeIdsSize) {
return false;
}
pProtoInfo->returnType = dexStringByTypeIdx(pDexFile, protoId->returnTypeIdx);
// Build string for parameter types.
size_t bufSize = 1;
char* buf = (char*)malloc(bufSize);
if (buf == NULL) {
return false;
}
buf[0] = '\0';
size_t bufUsed = 1;
const DexTypeList* paramTypes = dexGetProtoParameters(pDexFile, protoId);
if (paramTypes == NULL) {
// No parameters.
pProtoInfo->parameterTypes = buf;
return true;
}
for (u4 i = 0; i < paramTypes->size; ++i) {
if (paramTypes->list[i].typeIdx >= pDexFile->pHeader->typeIdsSize) {
free(buf);
return false;
}
const char* param = dexStringByTypeIdx(pDexFile, paramTypes->list[i].typeIdx);
size_t paramLen = strlen(param);
size_t newUsed = bufUsed + paramLen;
if (newUsed > bufSize) {
char* newBuf = (char*)realloc(buf, newUsed);
if (newBuf == NULL) {
free(buf);
return false;
}
buf = newBuf;
bufSize = newUsed;
}
memcpy(buf + bufUsed - 1, param, paramLen + 1);
bufUsed = newUsed;
}
pProtoInfo->parameterTypes = buf;
return true;
}
/*
* Look up a class' descriptor.
*/
const char* getClassDescriptor(DexFile* pDexFile, u4 classIdx)
{
return dexStringByTypeIdx(pDexFile, classIdx);
}
/*
* Helper for dumpInstruction(), which builds the string
* representation for the index in the given instruction. This will
* first try to use the given buffer, but if the result won't fit,
* then this will allocate a new buffer to hold the result. A pointer
* to the buffer which holds the full result is always returned, and
* this can be compared with the one passed in, to see if the result
* needs to be free()d.
*/
static char* indexString(DexFile* pDexFile, const DecodedInstruction* pDecInsn, size_t bufSize)
{
char* buf = (char*)malloc(bufSize);
if (buf == NULL) {
return NULL;
}
int outSize;
u4 index;
u4 secondaryIndex = 0;
u4 width;
/* TODO: Make the index *always* be in field B, to simplify this code. */
switch (dexGetFormatFromOpcode(pDecInsn->opcode)) {
case kFmt20bc:
case kFmt21c:
case kFmt35c:
case kFmt35ms:
case kFmt3rc:
case kFmt3rms:
case kFmt35mi:
case kFmt3rmi:
index = pDecInsn->vB;
width = 4;
break;
case kFmt31c:
index = pDecInsn->vB;
width = 8;
break;
case kFmt22c:
case kFmt22cs:
index = pDecInsn->vC;
width = 4;
break;
case kFmt45cc:
case kFmt4rcc:
index = pDecInsn->vB; // method index
secondaryIndex = pDecInsn->arg[4]; // proto index
width = 4;
break;
default:
index = 0;
width = 4;
break;
}
switch (pDecInsn->indexType) {
case kIndexUnknown:
/*
* This function shouldn't ever get called for this type, but do
* something sensible here, just to help with debugging.
*/
outSize = snprintf(buf, bufSize, "<unknown-index>");
break;
case kIndexNone:
/*
* This function shouldn't ever get called for this type, but do
* something sensible here, just to help with debugging.
*/
outSize = snprintf(buf, bufSize, "<no-index>");
break;
case kIndexVaries:
/*
* This one should never show up in a dexdump, so no need to try
* to get fancy here.
*/
outSize = snprintf(buf, bufSize, "<index-varies> // thing@%0*x",
width, index);
break;
case kIndexTypeRef:
if (index < pDexFile->pHeader->typeIdsSize) {
outSize = snprintf(buf, bufSize, "%s // type@%0*x",
getClassDescriptor(pDexFile, index), width, index);
} else {
outSize = snprintf(buf, bufSize, "<type?> // type@%0*x", width, index);
}
break;
case kIndexStringRef:
if (index < pDexFile->pHeader->stringIdsSize) {
outSize = snprintf(buf, bufSize, "\"%s\" // string@%0*x",
dexStringById(pDexFile, index), width, index);
} else {
outSize = snprintf(buf, bufSize, "<string?> // string@%0*x",
width, index);
}
break;
case kIndexMethodRef:
{
FieldMethodInfo methInfo;
if (getMethodInfo(pDexFile, index, &methInfo)) {
outSize = snprintf(buf, bufSize, "%s.%s:%s // method@%0*x",
methInfo.classDescriptor, methInfo.name,
methInfo.signature, width, index);
free((void *) methInfo.signature);
} else {
outSize = snprintf(buf, bufSize, "<method?> // method@%0*x",
width, index);
}
}
break;
case kIndexFieldRef:
{
FieldMethodInfo fieldInfo;
if (getFieldInfo(pDexFile, index, &fieldInfo)) {
outSize = snprintf(buf, bufSize, "%s.%s:%s // field@%0*x",
fieldInfo.classDescriptor, fieldInfo.name,
fieldInfo.signature, width, index);
} else {
outSize = snprintf(buf, bufSize, "<field?> // field@%0*x",
width, index);
}
}
break;
case kIndexInlineMethod:
outSize = snprintf(buf, bufSize, "[%0*x] // inline #%0*x",
width, index, width, index);
break;
case kIndexVtableOffset:
outSize = snprintf(buf, bufSize, "[%0*x] // vtable #%0*x",
width, index, width, index);
break;
case kIndexFieldOffset:
outSize = snprintf(buf, bufSize, "[obj+%0*x]", width, index);
break;
case kIndexMethodAndProtoRef:
{
FieldMethodInfo methInfo;
ProtoInfo protoInfo;
protoInfo.parameterTypes = NULL;
if (getMethodInfo(pDexFile, index, &methInfo) &&
getProtoInfo(pDexFile, secondaryIndex, &protoInfo)) {
outSize = snprintf(buf, bufSize, "%s.%s:%s, (%s)%s // method@%0*x, proto@%0*x",
methInfo.classDescriptor, methInfo.name, methInfo.signature,
protoInfo.parameterTypes, protoInfo.returnType,
width, index, width, secondaryIndex);
} else {
outSize = snprintf(buf, bufSize, "<method?>, <proto?> // method@%0*x, proto@%0*x",
width, index, width, secondaryIndex);
}
free(protoInfo.parameterTypes);
}
break;
case kIndexCallSiteRef:
outSize = snprintf(buf, bufSize, "call_site@%0*x", width, index);
break;
case kIndexMethodHandleRef:
outSize = snprintf(buf, bufSize, "methodhandle@%0*x", width, index);
break;
case kIndexProtoRef:
{
ProtoInfo protoInfo;
if (getProtoInfo(pDexFile, index, &protoInfo)) {
outSize = snprintf(buf, bufSize, "(%s)%s // proto@%0*x",
protoInfo.parameterTypes, protoInfo.returnType,
width, index);
} else {
outSize = snprintf(buf, bufSize, "<proto?> // proto@%0*x",
width, secondaryIndex);
}
free(protoInfo.parameterTypes);
}
break;
default:
outSize = snprintf(buf, bufSize, "<?>");
break;
}
if (outSize >= (int) bufSize) {
/*
* The buffer wasn't big enough; allocate and retry. Note:
* snprintf() doesn't count the '\0' as part of its returned
* size, so we add explicit space for it here.
*/
free(buf);
return indexString(pDexFile, pDecInsn, outSize + 1);
} else {
return buf;
}
}
/*
* Dump a single instruction.
*/
void dumpInstruction(DexFile* pDexFile, const DexCode* pCode, int insnIdx,
int insnWidth, const DecodedInstruction* pDecInsn)
{
const u2* insns = pCode->insns;
int i;
// Address of instruction (expressed as byte offset).
printf("%06zx:", ((u1*)insns - pDexFile->baseAddr) + insnIdx*2);
for (i = 0; i < 8; i++) {
if (i < insnWidth) {
if (i == 7) {
printf(" ... ");
} else {
/* print 16-bit value in little-endian order */
const u1* bytePtr = (const u1*) &insns[insnIdx+i];
printf(" %02x%02x", bytePtr[0], bytePtr[1]);
}
} else {
fputs(" ", stdout);
}
}
if (pDecInsn->opcode == OP_NOP) {
u2 instr = get2LE((const u1*) &insns[insnIdx]);
if (instr == kPackedSwitchSignature) {
printf("|%04x: packed-switch-data (%d units)",
insnIdx, insnWidth);
} else if (instr == kSparseSwitchSignature) {
printf("|%04x: sparse-switch-data (%d units)",
insnIdx, insnWidth);
} else if (instr == kArrayDataSignature) {
printf("|%04x: array-data (%d units)",
insnIdx, insnWidth);
} else {
printf("|%04x: nop // spacer", insnIdx);
}
} else {
printf("|%04x: %s", insnIdx, dexGetOpcodeName(pDecInsn->opcode));
}
// Provide an initial buffer that usually suffices, although indexString()
// may reallocate the buffer if more space is needed.
char* indexBuf = NULL;
if (pDecInsn->indexType != kIndexNone) {
indexBuf = indexString(pDexFile, pDecInsn, 200);
}
switch (dexGetFormatFromOpcode(pDecInsn->opcode)) {
case kFmt10x: // op
break;
case kFmt12x: // op vA, vB
printf(" v%d, v%d", pDecInsn->vA, pDecInsn->vB);
break;
case kFmt11n: // op vA, #+B
printf(" v%d, #int %d // #%x",
pDecInsn->vA, (s4)pDecInsn->vB, (u1)pDecInsn->vB);
break;
case kFmt11x: // op vAA
printf(" v%d", pDecInsn->vA);
break;
case kFmt10t: // op +AA
case kFmt20t: // op +AAAA
{
s4 targ = (s4) pDecInsn->vA;
printf(" %04x // %c%04x",
insnIdx + targ,
(targ < 0) ? '-' : '+',
(targ < 0) ? -targ : targ);
}
break;
case kFmt22x: // op vAA, vBBBB
printf(" v%d, v%d", pDecInsn->vA, pDecInsn->vB);
break;
case kFmt21t: // op vAA, +BBBB
{
s4 targ = (s4) pDecInsn->vB;
printf(" v%d, %04x // %c%04x", pDecInsn->vA,
insnIdx + targ,
(targ < 0) ? '-' : '+',
(targ < 0) ? -targ : targ);
}
break;
case kFmt21s: // op vAA, #+BBBB
printf(" v%d, #int %d // #%x",
pDecInsn->vA, (s4)pDecInsn->vB, (u2)pDecInsn->vB);
break;
case kFmt21h: // op vAA, #+BBBB0000[00000000]
// The printed format varies a bit based on the actual opcode.
if (pDecInsn->opcode == OP_CONST_HIGH16) {
s4 value = pDecInsn->vB << 16;
printf(" v%d, #int %d // #%x",
pDecInsn->vA, value, (u2)pDecInsn->vB);
} else {
s8 value = ((s8) pDecInsn->vB) << 48;
printf(" v%d, #long %" PRId64 " // #%x",
pDecInsn->vA, value, (u2)pDecInsn->vB);
}
break;
case kFmt21c: // op vAA, thing@BBBB
case kFmt31c: // op vAA, thing@BBBBBBBB
printf(" v%d, %s", pDecInsn->vA, indexBuf);
break;
case kFmt23x: // op vAA, vBB, vCC
printf(" v%d, v%d, v%d", pDecInsn->vA, pDecInsn->vB, pDecInsn->vC);
break;
case kFmt22b: // op vAA, vBB, #+CC
printf(" v%d, v%d, #int %d // #%02x",
pDecInsn->vA, pDecInsn->vB, (s4)pDecInsn->vC, (u1)pDecInsn->vC);
break;
case kFmt22t: // op vA, vB, +CCCC
{
s4 targ = (s4) pDecInsn->vC;
printf(" v%d, v%d, %04x // %c%04x", pDecInsn->vA, pDecInsn->vB,
insnIdx + targ,
(targ < 0) ? '-' : '+',
(targ < 0) ? -targ : targ);
}
break;
case kFmt22s: // op vA, vB, #+CCCC
printf(" v%d, v%d, #int %d // #%04x",
pDecInsn->vA, pDecInsn->vB, (s4)pDecInsn->vC, (u2)pDecInsn->vC);
break;
case kFmt22c: // op vA, vB, thing@CCCC
case kFmt22cs: // [opt] op vA, vB, field offset CCCC
printf(" v%d, v%d, %s", pDecInsn->vA, pDecInsn->vB, indexBuf);
break;
case kFmt30t:
printf(" #%08x", pDecInsn->vA);
break;
case kFmt31i: // op vAA, #+BBBBBBBB
{
/* this is often, but not always, a float */
union {
float f;
u4 i;
} conv;
conv.i = pDecInsn->vB;
printf(" v%d, #float %f // #%08x",
pDecInsn->vA, conv.f, pDecInsn->vB);
}
break;
case kFmt31t: // op vAA, offset +BBBBBBBB
printf(" v%d, %08x // +%08x",
pDecInsn->vA, insnIdx + pDecInsn->vB, pDecInsn->vB);
break;
case kFmt32x: // op vAAAA, vBBBB
printf(" v%d, v%d", pDecInsn->vA, pDecInsn->vB);
break;
case kFmt35c: // op {vC, vD, vE, vF, vG}, thing@BBBB
case kFmt35ms: // [opt] invoke-virtual+super
case kFmt35mi: // [opt] inline invoke
{
fputs(" {", stdout);
for (i = 0; i < (int) pDecInsn->vA; i++) {
if (i == 0)
printf("v%d", pDecInsn->arg[i]);
else
printf(", v%d", pDecInsn->arg[i]);
}
printf("}, %s", indexBuf);
}
break;
case kFmt3rc: // op {vCCCC .. v(CCCC+AA-1)}, thing@BBBB
case kFmt3rms: // [opt] invoke-virtual+super/range
case kFmt3rmi: // [opt] execute-inline/range
{
/*
* This doesn't match the "dx" output when some of the args are
* 64-bit values -- dx only shows the first register.
*/
fputs(" {", stdout);
for (i = 0; i < (int) pDecInsn->vA; i++) {
if (i == 0)
printf("v%d", pDecInsn->vC + i);
else
printf(", v%d", pDecInsn->vC + i);
}
printf("}, %s", indexBuf);
}
break;
case kFmt51l: // op vAA, #+BBBBBBBBBBBBBBBB
{
/* this is often, but not always, a double */
union {
double d;
u8 j;
} conv;
conv.j = pDecInsn->vB_wide;
printf(" v%d, #double %f // #%016" PRIx64,
pDecInsn->vA, conv.d, pDecInsn->vB_wide);
}
break;
case kFmt00x: // unknown op or breakpoint
break;
case kFmt45cc:
{
fputs(" {", stdout);
printf("v%d", pDecInsn->vC);
for (int i = 0; i < (int) pDecInsn->vA - 1; ++i) {
printf(", v%d", pDecInsn->arg[i]);
}
printf("}, %s", indexBuf);
}
break;
case kFmt4rcc:
{
fputs(" {", stdout);
printf("v%d", pDecInsn->vC);
for (int i = 1; i < (int) pDecInsn->vA; ++i) {
printf(", v%d", pDecInsn->vC + i);
}
printf("}, %s", indexBuf);
}
break;
default:
printf(" ???");
break;
}
putchar('\n');
free(indexBuf);
}
/*
* Dump a bytecode disassembly.
*/
void dumpBytecodes(DexFile* pDexFile, const DexMethod* pDexMethod)
{
const DexCode* pCode = dexGetCode(pDexFile, pDexMethod);
const u2* insns;
int insnIdx;
FieldMethodInfo methInfo;
int startAddr;
char* className = NULL;
assert(pCode->insnsSize > 0);
insns = pCode->insns;
methInfo.classDescriptor =
methInfo.name =
methInfo.signature = NULL;
getMethodInfo(pDexFile, pDexMethod->methodIdx, &methInfo);
startAddr = ((u1*)pCode - pDexFile->baseAddr);
className = descriptorToDot(methInfo.classDescriptor);
printf("%06x: |[%06x] %s.%s:%s\n",
startAddr, startAddr,
className, methInfo.name, methInfo.signature);
free((void *) methInfo.signature);
insnIdx = 0;
while (insnIdx < (int) pCode->insnsSize) {
int insnWidth;
DecodedInstruction decInsn;
u2 instr;
/*
* Note: This code parallels the function
* dexGetWidthFromInstruction() in InstrUtils.c, but this version
* can deal with data in either endianness.
*
* TODO: Figure out if this really matters, and possibly change
* this to just use dexGetWidthFromInstruction().
*/
instr = get2LE((const u1*)insns);
if (instr == kPackedSwitchSignature) {
insnWidth = 4 + get2LE((const u1*)(insns+1)) * 2;
} else if (instr == kSparseSwitchSignature) {
insnWidth = 2 + get2LE((const u1*)(insns+1)) * 4;
} else if (instr == kArrayDataSignature) {
int width = get2LE((const u1*)(insns+1));
int size = get2LE((const u1*)(insns+2)) |
(get2LE((const u1*)(insns+3))<<16);
// The plus 1 is to round up for odd size and width.
insnWidth = 4 + ((size * width) + 1) / 2;
} else {
Opcode opcode = dexOpcodeFromCodeUnit(instr);
insnWidth = dexGetWidthFromOpcode(opcode);
if (insnWidth == 0) {
fprintf(stderr,
"GLITCH: zero-width instruction at idx=0x%04x\n", insnIdx);
break;
}
}
dexDecodeInstruction(insns, &decInsn);
dumpInstruction(pDexFile, pCode, insnIdx, insnWidth, &decInsn);
insns += insnWidth;
insnIdx += insnWidth;
}
free(className);
}
/*
* Dump a "code" struct.
*/
void dumpCode(DexFile* pDexFile, const DexMethod* pDexMethod)
{
const DexCode* pCode = dexGetCode(pDexFile, pDexMethod);
printf(" registers : %d\n", pCode->registersSize);
printf(" ins : %d\n", pCode->insSize);
printf(" outs : %d\n", pCode->outsSize);
printf(" insns size : %d 16-bit code units\n", pCode->insnsSize);
if (gOptions.disassemble)
dumpBytecodes(pDexFile, pDexMethod);
dumpCatches(pDexFile, pCode);
/* both of these are encoded in debug info */
dumpPositions(pDexFile, pCode, pDexMethod);
dumpLocals(pDexFile, pCode, pDexMethod);
}
/*
* Dump a method.
*/
void dumpMethod(DexFile* pDexFile, const DexMethod* pDexMethod, int i)
{
const DexMethodId* pMethodId;
const char* backDescriptor;
const char* name;
char* typeDescriptor = NULL;
char* accessStr = NULL;
if (gOptions.exportsOnly &&
(pDexMethod->accessFlags & (ACC_PUBLIC | ACC_PROTECTED)) == 0)
{
return;
}
pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
name = dexStringById(pDexFile, pMethodId->nameIdx);
typeDescriptor = dexCopyDescriptorFromMethodId(pDexFile, pMethodId);
backDescriptor = dexStringByTypeIdx(pDexFile, pMethodId->classIdx);
accessStr = createAccessFlagStr(pDexMethod->accessFlags,
kAccessForMethod);
if (gOptions.outputFormat == OUTPUT_PLAIN) {
printf(" #%d : (in %s)\n", i, backDescriptor);
printf(" name : '%s'\n", name);
printf(" type : '%s'\n", typeDescriptor);
printf(" access : 0x%04x (%s)\n",
pDexMethod->accessFlags, accessStr);
if (pDexMethod->codeOff == 0) {
printf(" code : (none)\n");
} else {
printf(" code -\n");
dumpCode(pDexFile, pDexMethod);
}
if (gOptions.disassemble)
putchar('\n');
} else if (gOptions.outputFormat == OUTPUT_XML) {
bool constructor = (name[0] == '<');
if (constructor) {
char* tmp;
tmp = descriptorClassToDot(backDescriptor);
printf("<constructor name=\"%s\"\n", tmp);
free(tmp);
tmp = descriptorToDot(backDescriptor);
printf(" type=\"%s\"\n", tmp);
free(tmp);
} else {
printf("<method name=\"%s\"\n", name);
const char* returnType = strrchr(typeDescriptor, ')');
if (returnType == NULL) {
fprintf(stderr, "bad method type descriptor '%s'\n",
typeDescriptor);
goto bail;
}
char* tmp = descriptorToDot(returnType+1);
printf(" return=\"%s\"\n", tmp);
free(tmp);
printf(" abstract=%s\n",
quotedBool((pDexMethod->accessFlags & ACC_ABSTRACT) != 0));
printf(" native=%s\n",
quotedBool((pDexMethod->accessFlags & ACC_NATIVE) != 0));
bool isSync =
(pDexMethod->accessFlags & ACC_SYNCHRONIZED) != 0 ||
(pDexMethod->accessFlags & ACC_DECLARED_SYNCHRONIZED) != 0;
printf(" synchronized=%s\n", quotedBool(isSync));
}
printf(" static=%s\n",
quotedBool((pDexMethod->accessFlags & ACC_STATIC) != 0));
printf(" final=%s\n",
quotedBool((pDexMethod->accessFlags & ACC_FINAL) != 0));
// "deprecated=" not knowable w/o parsing annotations
printf(" visibility=%s\n",
quotedVisibility(pDexMethod->accessFlags));
printf(">\n");
/*
* Parameters.
*/
if (typeDescriptor[0] != '(') {
fprintf(stderr, "ERROR: bad descriptor '%s'\n", typeDescriptor);
goto bail;
}
char tmpBuf[strlen(typeDescriptor)+1]; /* more than big enough */
int argNum = 0;
const char* base = typeDescriptor+1;
while (*base != ')') {
char* cp = tmpBuf;
while (*base == '[')
*cp++ = *base++;
if (*base == 'L') {
/* copy through ';' */
do {
*cp = *base++;
} while (*cp++ != ';');
} else {
/* primitive char, copy it */
if (strchr("ZBCSIFJD", *base) == NULL) {
fprintf(stderr, "ERROR: bad method signature '%s'\n", base);
goto bail;
}
*cp++ = *base++;
}
/* null terminate and display */
*cp++ = '\0';
char* tmp = descriptorToDot(tmpBuf);
printf("<parameter name=\"arg%d\" type=\"%s\">\n</parameter>\n",
argNum++, tmp);
free(tmp);
}
if (constructor)
printf("</constructor>\n");
else
printf("</method>\n");
}
bail:
free(typeDescriptor);
free(accessStr);
}
/*
* Dump a static (class) field.
*/
void dumpSField(const DexFile* pDexFile, const DexField* pSField, int i)
{
const DexFieldId* pFieldId;
const char* backDescriptor;
const char* name;
const char* typeDescriptor;
char* accessStr;
if (gOptions.exportsOnly &&
(pSField->accessFlags & (ACC_PUBLIC | ACC_PROTECTED)) == 0)
{
return;
}
pFieldId = dexGetFieldId(pDexFile, pSField->fieldIdx);
name = dexStringById(pDexFile, pFieldId->nameIdx);
typeDescriptor = dexStringByTypeIdx(pDexFile, pFieldId->typeIdx);
backDescriptor = dexStringByTypeIdx(pDexFile, pFieldId->classIdx);
accessStr = createAccessFlagStr(pSField->accessFlags, kAccessForField);
if (gOptions.outputFormat == OUTPUT_PLAIN) {
printf(" #%d : (in %s)\n", i, backDescriptor);
printf(" name : '%s'\n", name);
printf(" type : '%s'\n", typeDescriptor);
printf(" access : 0x%04x (%s)\n",
pSField->accessFlags, accessStr);
} else if (gOptions.outputFormat == OUTPUT_XML) {
char* tmp;
printf("<field name=\"%s\"\n", name);
tmp = descriptorToDot(typeDescriptor);
printf(" type=\"%s\"\n", tmp);
free(tmp);
printf(" transient=%s\n",
quotedBool((pSField->accessFlags & ACC_TRANSIENT) != 0));
printf(" volatile=%s\n",
quotedBool((pSField->accessFlags & ACC_VOLATILE) != 0));
// "value=" not knowable w/o parsing annotations
printf(" static=%s\n",
quotedBool((pSField->accessFlags & ACC_STATIC) != 0));
printf(" final=%s\n",
quotedBool((pSField->accessFlags & ACC_FINAL) != 0));
// "deprecated=" not knowable w/o parsing annotations
printf(" visibility=%s\n",
quotedVisibility(pSField->accessFlags));
printf(">\n</field>\n");
}
free(accessStr);
}
/*
* Dump an instance field.
*/
void dumpIField(const DexFile* pDexFile, const DexField* pIField, int i)
{
dumpSField(pDexFile, pIField, i);
}
/*
* Dump the class.
*
* Note "idx" is a DexClassDef index, not a DexTypeId index.
*
* If "*pLastPackage" is NULL or does not match the current class' package,
* the value will be replaced with a newly-allocated string.
*/
void dumpClass(DexFile* pDexFile, int idx, char** pLastPackage)
{
const DexTypeList* pInterfaces;
const DexClassDef* pClassDef;
DexClassData* pClassData = NULL;
const u1* pEncodedData;
const char* fileName;
const char* classDescriptor;
const char* superclassDescriptor;
char* accessStr = NULL;
int i;
pClassDef = dexGetClassDef(pDexFile, idx);
if (gOptions.exportsOnly && (pClassDef->accessFlags & ACC_PUBLIC) == 0) {
//printf("<!-- omitting non-public class %s -->\n",
// classDescriptor);
goto bail;
}
pEncodedData = dexGetClassData(pDexFile, pClassDef);
pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL);
if (pClassData == NULL) {
printf("Trouble reading class data (#%d)\n", idx);
goto bail;
}
classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
/*
* For the XML output, show the package name. Ideally we'd gather
* up the classes, sort them, and dump them alphabetically so the
* package name wouldn't jump around, but that's not a great plan
* for something that needs to run on the device.
*/
if (!(classDescriptor[0] == 'L' &&
classDescriptor[strlen(classDescriptor)-1] == ';'))
{
/* arrays and primitives should not be defined explicitly */
fprintf(stderr, "Malformed class name '%s'\n", classDescriptor);
/* keep going? */
} else if (gOptions.outputFormat == OUTPUT_XML) {
char* mangle;
char* lastSlash;
char* cp;
mangle = strdup(classDescriptor + 1);
mangle[strlen(mangle)-1] = '\0';
/* reduce to just the package name */
lastSlash = strrchr(mangle, '/');
if (lastSlash != NULL) {
*lastSlash = '\0';
} else {
*mangle = '\0';
}
for (cp = mangle; *cp != '\0'; cp++) {
if (*cp == '/')
*cp = '.';
}
if (*pLastPackage == NULL || strcmp(mangle, *pLastPackage) != 0) {
/* start of a new package */
if (*pLastPackage != NULL)
printf("</package>\n");
printf("<package name=\"%s\"\n>\n", mangle);
free(*pLastPackage);
*pLastPackage = mangle;
} else {
free(mangle);
}
}
accessStr = createAccessFlagStr(pClassDef->accessFlags, kAccessForClass);
if (pClassDef->superclassIdx == kDexNoIndex) {
superclassDescriptor = NULL;
} else {
superclassDescriptor =
dexStringByTypeIdx(pDexFile, pClassDef->superclassIdx);
}
if (gOptions.outputFormat == OUTPUT_PLAIN) {
printf("Class #%d -\n", idx);
printf(" Class descriptor : '%s'\n", classDescriptor);
printf(" Access flags : 0x%04x (%s)\n",
pClassDef->accessFlags, accessStr);
if (superclassDescriptor != NULL)
printf(" Superclass : '%s'\n", superclassDescriptor);
printf(" Interfaces -\n");
} else {
char* tmp;
tmp = descriptorClassToDot(classDescriptor);
printf("<class name=\"%s\"\n", tmp);
free(tmp);
if (superclassDescriptor != NULL) {
tmp = descriptorToDot(superclassDescriptor);
printf(" extends=\"%s\"\n", tmp);
free(tmp);
}
printf(" abstract=%s\n",
quotedBool((pClassDef->accessFlags & ACC_ABSTRACT) != 0));
printf(" static=%s\n",
quotedBool((pClassDef->accessFlags & ACC_STATIC) != 0));
printf(" final=%s\n",
quotedBool((pClassDef->accessFlags & ACC_FINAL) != 0));
// "deprecated=" not knowable w/o parsing annotations
printf(" visibility=%s\n",
quotedVisibility(pClassDef->accessFlags));
printf(">\n");
}
pInterfaces = dexGetInterfacesList(pDexFile, pClassDef);
if (pInterfaces != NULL) {
for (i = 0; i < (int) pInterfaces->size; i++)
dumpInterface(pDexFile, dexGetTypeItem(pInterfaces, i), i);
}
if (gOptions.outputFormat == OUTPUT_PLAIN)
printf(" Static fields -\n");
for (i = 0; i < (int) pClassData->header.staticFieldsSize; i++) {
dumpSField(pDexFile, &pClassData->staticFields[i], i);
}
if (gOptions.outputFormat == OUTPUT_PLAIN)
printf(" Instance fields -\n");
for (i = 0; i < (int) pClassData->header.instanceFieldsSize; i++) {
dumpIField(pDexFile, &pClassData->instanceFields[i], i);
}
if (gOptions.outputFormat == OUTPUT_PLAIN)
printf(" Direct methods -\n");
for (i = 0; i < (int) pClassData->header.directMethodsSize; i++) {
dumpMethod(pDexFile, &pClassData->directMethods[i], i);
}
if (gOptions.outputFormat == OUTPUT_PLAIN)
printf(" Virtual methods -\n");
for (i = 0; i < (int) pClassData->header.virtualMethodsSize; i++) {
dumpMethod(pDexFile, &pClassData->virtualMethods[i], i);
}
// TODO: Annotations.
if (pClassDef->sourceFileIdx != kDexNoIndex)
fileName = dexStringById(pDexFile, pClassDef->sourceFileIdx);
else
fileName = "unknown";
if (gOptions.outputFormat == OUTPUT_PLAIN) {
printf(" source_file_idx : %d (%s)\n",
pClassDef->sourceFileIdx, fileName);
printf("\n");
}
if (gOptions.outputFormat == OUTPUT_XML) {
printf("</class>\n");
}
bail:
free(pClassData);
free(accessStr);
}
/*
* Dump a map in the "differential" format.
*
* TODO: show a hex dump of the compressed data. (We can show the
* uncompressed data if we move the compression code to libdex; otherwise
* it's too complex to merit a fast & fragile implementation here.)
*/
void dumpDifferentialCompressedMap(const u1** pData)
{
const u1* data = *pData;
const u1* dataStart = data -1; // format byte already removed
u1 regWidth;
u2 numEntries;
/* standard header */
regWidth = *data++;
numEntries = *data++;
numEntries |= (*data++) << 8;
/* compressed data begins with the compressed data length */
int compressedLen = readUnsignedLeb128(&data);
int addrWidth = 1;
if ((*data & 0x80) != 0)
addrWidth++;
int origLen = 4 + (addrWidth + regWidth) * numEntries;
int compLen = (data - dataStart) + compressedLen;
printf(" (differential compression %d -> %d [%d -> %d])\n",
origLen, compLen,
(addrWidth + regWidth) * numEntries, compressedLen);
/* skip past end of entry */
data += compressedLen;
*pData = data;
}
/*
* Dump register map contents of the current method.
*
* "*pData" should point to the start of the register map data. Advances
* "*pData" to the start of the next map.
*/
void dumpMethodMap(DexFile* pDexFile, const DexMethod* pDexMethod, int idx,
const u1** pData)
{
const u1* data = *pData;
const DexMethodId* pMethodId;
const char* name;
int offset = data - (u1*) pDexFile->pOptHeader;
pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
name = dexStringById(pDexFile, pMethodId->nameIdx);
printf(" #%d: 0x%08x %s\n", idx, offset, name);
u1 format;
int addrWidth;
format = *data++;
if (format == 1) { /* kRegMapFormatNone */
/* no map */
printf(" (no map)\n");
addrWidth = 0;
} else if (format == 2) { /* kRegMapFormatCompact8 */
addrWidth = 1;
} else if (format == 3) { /* kRegMapFormatCompact16 */
addrWidth = 2;
} else if (format == 4) { /* kRegMapFormatDifferential */
dumpDifferentialCompressedMap(&data);
goto bail;
} else {
printf(" (unknown format %d!)\n", format);
/* don't know how to skip data; failure will cascade to end of class */
goto bail;
}
if (addrWidth > 0) {
u1 regWidth;
u2 numEntries;
int idx, addr, byte;
regWidth = *data++;
numEntries = *data++;
numEntries |= (*data++) << 8;
for (idx = 0; idx < numEntries; idx++) {
addr = *data++;
if (addrWidth > 1)
addr |= (*data++) << 8;
printf(" %4x:", addr);
for (byte = 0; byte < regWidth; byte++) {
printf(" %02x", *data++);
}
printf("\n");
}
}
bail:
//if (addrWidth >= 0)
// *pData = align32(data);
*pData = data;
}
/*
* Dump the contents of the register map area.
*
* These are only present in optimized DEX files, and the structure is
* not really exposed to other parts of the VM itself. We're going to
* dig through them here, but this is pretty fragile. DO NOT rely on
* this or derive other code from it.
*/
void dumpRegisterMaps(DexFile* pDexFile)
{
const u1* pClassPool = (const u1*)pDexFile->pRegisterMapPool;
const u4* classOffsets;
const u1* ptr;
u4 numClasses;
int baseFileOffset = (u1*) pClassPool - (u1*) pDexFile->pOptHeader;
int idx;
if (pClassPool == NULL) {
printf("No register maps found\n");
return;
}
ptr = pClassPool;
numClasses = get4LE(ptr);
ptr += sizeof(u4);
classOffsets = (const u4*) ptr;
printf("RMAP begins at offset 0x%07x\n", baseFileOffset);
printf("Maps for %d classes\n", numClasses);
for (idx = 0; idx < (int) numClasses; idx++) {
const DexClassDef* pClassDef;
const char* classDescriptor;
pClassDef = dexGetClassDef(pDexFile, idx);
classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
printf("%4d: +%d (0x%08x) %s\n", idx, classOffsets[idx],
baseFileOffset + classOffsets[idx], classDescriptor);
if (classOffsets[idx] == 0)
continue;
/*
* What follows is a series of RegisterMap entries, one for every
* direct method, then one for every virtual method.
*/
DexClassData* pClassData;
const u1* pEncodedData;
const u1* data = (u1*) pClassPool + classOffsets[idx];
u2 methodCount;
int i;
pEncodedData = dexGetClassData(pDexFile, pClassDef);
pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL);
if (pClassData == NULL) {
fprintf(stderr, "Trouble reading class data\n");
continue;
}
methodCount = *data++;
methodCount |= (*data++) << 8;
data += 2; /* two pad bytes follow methodCount */
if (methodCount != pClassData->header.directMethodsSize
+ pClassData->header.virtualMethodsSize)
{
printf("NOTE: method count discrepancy (%d != %d + %d)\n",
methodCount, pClassData->header.directMethodsSize,
pClassData->header.virtualMethodsSize);
/* this is bad, but keep going anyway */
}
printf(" direct methods: %d\n",
pClassData->header.directMethodsSize);
for (i = 0; i < (int) pClassData->header.directMethodsSize; i++) {
dumpMethodMap(pDexFile, &pClassData->directMethods[i], i, &data);
}
printf(" virtual methods: %d\n",
pClassData->header.virtualMethodsSize);
for (i = 0; i < (int) pClassData->header.virtualMethodsSize; i++) {
dumpMethodMap(pDexFile, &pClassData->virtualMethods[i], i, &data);
}
free(pClassData);
}
}
static const DexMapItem* findMapItem(const DexFile* pDexFile, u4 type)
{
const u4 offset = pDexFile->pHeader->mapOff;
const DexMapList* list = (const DexMapList*)(pDexFile->baseAddr + offset);
for (u4 i = 0; i < list->size; ++i) {
if (list->list[i].type == type) {
return &list->list[i];
}
}
return nullptr;
}
static void dumpMethodHandles(DexFile* pDexFile)
{
const DexMapItem* item = findMapItem(pDexFile, kDexTypeMethodHandleItem);
if (item == nullptr) return;
const DexMethodHandleItem* method_handles =
(const DexMethodHandleItem*)(pDexFile->baseAddr + item->offset);
for (u4 i = 0; i < item->size; ++i) {
const DexMethodHandleItem& mh = method_handles[i];
const char* type;
bool is_invoke;
bool is_static;
switch ((MethodHandleType) mh.methodHandleType) {
case MethodHandleType::STATIC_PUT:
type = "put-static";
is_invoke = false;
is_static = true;
break;
case MethodHandleType::STATIC_GET:
type = "get-static";
is_invoke = false;
is_static = true;
break;
case MethodHandleType::INSTANCE_PUT:
type = "put-instance";
is_invoke = false;
is_static = false;
break;
case MethodHandleType::INSTANCE_GET:
type = "get-instance";
is_invoke = false;
is_static = false;
break;
case MethodHandleType::INVOKE_STATIC:
type = "invoke-static";
is_invoke = true;
is_static = true;
break;
case MethodHandleType::INVOKE_INSTANCE:
type = "invoke-instance";
is_invoke = true;
is_static = false;
break;
case MethodHandleType::INVOKE_CONSTRUCTOR:
type = "invoke-constructor";
is_invoke = true;
is_static = false;
break;
case MethodHandleType::INVOKE_DIRECT:
type = "invoke-direct";
is_invoke = true;
is_static = false;
break;
case MethodHandleType::INVOKE_INTERFACE:
type = "invoke-interface";
is_invoke = true;
is_static = false;
break;
default:
printf("Unknown method handle type 0x%02x, skipped.", mh.methodHandleType);
continue;
}
FieldMethodInfo info;
if (is_invoke) {
if (!getMethodInfo(pDexFile, mh.fieldOrMethodIdx, &info)) {
printf("Unknown method handle target method@%04x, skipped.", mh.fieldOrMethodIdx);
continue;
}
} else {
if (!getFieldInfo(pDexFile, mh.fieldOrMethodIdx, &info)) {
printf("Unknown method handle target field@%04x, skipped.", mh.fieldOrMethodIdx);
continue;
}
}
const char* instance = is_static ? "" : info.classDescriptor;
if (gOptions.outputFormat == OUTPUT_XML) {
printf("<method_handle index index=\"%u\"\n", i);
printf(" type=\"%s\"\n", type);
printf(" target_class=\"%s\"\n", info.classDescriptor);
printf(" target_member=\"%s\"\n", info.name);
printf(" target_member_type=\"%c%s%s\"\n",
info.signature[0], instance, info.signature + 1);
printf("</method_handle>\n");
} else {
printf("Method Handle #%u:\n", i);
printf(" type : %s\n", type);
printf(" target : %s %s\n", info.classDescriptor, info.name);
printf(" target_type : %c%s%s\n", info.signature[0], instance, info.signature + 1);
}
}
}
/* Helper for dumpCallSites(), which reads a 1- to 8- byte signed
* little endian value. */
static u8 readSignedLittleEndian(const u1** pData, u4 size) {
const u1* data = *pData;
u8 result = 0;
u4 i;
for (i = 0; i < size; i++) {
result = (result >> 8) | (((int64_t)*data++) << 56);
}
result >>= (8 - size) * 8;
*pData = data;
return result;
}
/* Helper for dumpCallSites(), which reads a 1- to 8- byte unsigned
* little endian value. */
static u8 readUnsignedLittleEndian(const u1** pData, u4 size, bool fillOnRight = false) {
const u1* data = *pData;
u8 result = 0;
u4 i;
for (i = 0; i < size; i++) {
result = (result >> 8) | (((u8)*data++) << 56);
}
if (!fillOnRight) {
result >>= (8u - size) * 8;
}
*pData = data;
return result;
}
static void dumpCallSites(DexFile* pDexFile)
{
const DexMapItem* item = findMapItem(pDexFile, kDexTypeCallSiteIdItem);
if (item == nullptr) return;
const DexCallSiteId* ids = (const DexCallSiteId*)(pDexFile->baseAddr + item->offset);
for (u4 index = 0; index < item->size; ++index) {
bool doXml = (gOptions.outputFormat == OUTPUT_XML);
printf(doXml ? "<call_site index=\"%u\" offset=\"%u\">\n" : "Call Site #%u // offset %u\n",
index, ids[index].callSiteOff);
const u1* data = pDexFile->baseAddr + ids[index].callSiteOff;
u4 count = readUnsignedLeb128(&data);
for (u4 i = 0; i < count; ++i) {
printf(doXml ? "<link_argument index=\"%u\" " : " link_argument[%u] : ", i);
u1 headerByte = *data++;
u4 valueType = headerByte & kDexAnnotationValueTypeMask;
u4 valueArg = headerByte >> kDexAnnotationValueArgShift;
switch (valueType) {
case kDexAnnotationByte: {
printf(doXml ? "type=\"byte\" value=\"%d\"/>" : "%d (byte)", (int)*data++);
break;
}
case kDexAnnotationShort: {
printf(doXml ? "type=\"short\" value=\"%d\"/>" : "%d (short)",
(int) readSignedLittleEndian(&data, valueArg + 1));
break;
}
case kDexAnnotationChar: {
printf(doXml ? "type=\"short\" value=\"%u\"/>" : "%u (char)",
(u2) readUnsignedLittleEndian(&data, valueArg + 1));
break;
}
case kDexAnnotationInt: {
printf(doXml ? "type=\"int\" value=\"%d\"/>" : "%d (int)",
(int) readSignedLittleEndian(&data, valueArg + 1));
break;
}
case kDexAnnotationLong: {
printf(doXml ? "type=\"long\" value=\"%" PRId64 "\"/>" : "%" PRId64 " (long)",
(int64_t) readSignedLittleEndian(&data, valueArg + 1));
break;
}
case kDexAnnotationFloat: {
u4 rawValue = (u4) (readUnsignedLittleEndian(&data, valueArg + 1, true) >> 32);
printf(doXml ? "type=\"float\" value=\"%g\"/>" : "%g (float)",
*((float*) &rawValue));
break;
}
case kDexAnnotationDouble: {
u8 rawValue = readUnsignedLittleEndian(&data, valueArg + 1, true);
printf(doXml ? "type=\"double\" value=\"%g\"/>" : "%g (double)",
*((double*) &rawValue));
break;
}
case kDexAnnotationMethodType: {
u4 idx = (u4) readUnsignedLittleEndian(&data, valueArg + 1);
ProtoInfo protoInfo;
memset(&protoInfo, 0, sizeof(protoInfo));
getProtoInfo(pDexFile, idx, &protoInfo);
printf(doXml ? "type=\"MethodType\" value=\"(%s)%s\"/>" : "(%s)%s (MethodType)",
protoInfo.parameterTypes, protoInfo.returnType);
free(protoInfo.parameterTypes);
break;
}
case kDexAnnotationMethodHandle: {
u4 idx = (u4) readUnsignedLittleEndian(&data, valueArg + 1);
printf(doXml ? "type=\"MethodHandle\" value=\"%u\"/>" : "%u (MethodHandle)",
idx);
break;
}
case kDexAnnotationString: {
u4 idx = (u4) readUnsignedLittleEndian(&data, valueArg + 1);
printf(doXml ? "type=\"String\" value=\"%s\"/>" : "%s (String)",
dexStringById(pDexFile, idx));
break;
}
case kDexAnnotationType: {
u4 idx = (u4) readUnsignedLittleEndian(&data, valueArg + 1);
printf(doXml ? "type=\"Class\" value=\"%s\"/>" : "%s (Class)",
dexStringByTypeIdx(pDexFile, idx));
break;
}
case kDexAnnotationNull: {
printf(doXml ? "type=\"null\" value=\"null\"/>" : "null (null)");
break;
}
case kDexAnnotationBoolean: {
printf(doXml ? "type=\"boolean\" value=\"%s\"/>" : "%s (boolean)",
(valueArg & 1) == 0 ? "false" : "true");
break;
}
default:
// Other types are not anticipated being reached here.
printf("Unexpected type found, bailing on call site info.\n");
i = count;
break;
}
printf("\n");
}
if (doXml) {
printf("</callsite>\n");
}
}
}
/*
* Dump the requested sections of the file.
*/
void processDexFile(const char* fileName, DexFile* pDexFile)
{
char* package = NULL;
int i;
if (gOptions.verbose) {
printf("Opened '%s', DEX version '%.3s'\n", fileName,
pDexFile->pHeader->magic +4);
}
if (gOptions.dumpRegisterMaps) {
dumpRegisterMaps(pDexFile);
return;
}
if (gOptions.showFileHeaders) {
dumpFileHeader(pDexFile);
dumpOptDirectory(pDexFile);
}
if (gOptions.outputFormat == OUTPUT_XML)
printf("<api>\n");
for (i = 0; i < (int) pDexFile->pHeader->classDefsSize; i++) {
if (gOptions.showSectionHeaders)
dumpClassDef(pDexFile, i);
dumpClass(pDexFile, i, &package);
}
dumpMethodHandles(pDexFile);
dumpCallSites(pDexFile);
/* free the last one allocated */
if (package != NULL) {
printf("</package>\n");
free(package);
}
if (gOptions.outputFormat == OUTPUT_XML)
printf("</api>\n");
}
/*
* Process one file.
*/
int process(const char* fileName)
{
DexFile* pDexFile = NULL;
MemMapping map;
bool mapped = false;
int result = -1;
if (gOptions.verbose)
printf("Processing '%s'...\n", fileName);
if (dexOpenAndMap(fileName, gOptions.tempFileName, &map, false) != 0) {
return result;
}
mapped = true;
int flags = kDexParseVerifyChecksum;
if (gOptions.ignoreBadChecksum)
flags |= kDexParseContinueOnError;
pDexFile = dexFileParse((u1*)map.addr, map.length, flags);
if (pDexFile == NULL) {
fprintf(stderr, "ERROR: DEX parse failed\n");
goto bail;
}
if (gOptions.checksumOnly) {
printf("Checksum verified\n");
} else {
processDexFile(fileName, pDexFile);
}
result = 0;
bail:
if (mapped)
sysReleaseShmem(&map);
if (pDexFile != NULL)
dexFileFree(pDexFile);
return result;
}
/*
* Show usage.
*/
void usage(void)
{
fprintf(stderr, "Copyright (C) 2007 The Android Open Source Project\n\n");
fprintf(stderr,
"%s: [-c] [-d] [-f] [-h] [-i] [-l layout] [-m] [-t tempfile] dexfile...\n",
gProgName);
fprintf(stderr, "\n");
fprintf(stderr, " -c : verify checksum and exit\n");
fprintf(stderr, " -d : disassemble code sections\n");
fprintf(stderr, " -f : display summary information from file header\n");
fprintf(stderr, " -h : display file header details\n");
fprintf(stderr, " -i : ignore checksum failures\n");
fprintf(stderr, " -l : output layout, either 'plain' or 'xml'\n");
fprintf(stderr, " -m : dump register maps (and nothing else)\n");
fprintf(stderr, " -t : temp file name (defaults to /sdcard/dex-temp-*)\n");
}
/*
* Parse args.
*
* I'm not using getopt_long() because we may not have it in libc.
*/
int main(int argc, char* const argv[])
{
bool wantUsage = false;
int ic;
memset(&gOptions, 0, sizeof(gOptions));
gOptions.verbose = true;
while (1) {
ic = getopt(argc, argv, "cdfhil:mt:");
if (ic < 0)
break;
switch (ic) {
case 'c': // verify the checksum then exit
gOptions.checksumOnly = true;
break;
case 'd': // disassemble Dalvik instructions
gOptions.disassemble = true;
break;
case 'f': // dump outer file header
gOptions.showFileHeaders = true;
break;
case 'h': // dump section headers, i.e. all meta-data
gOptions.showSectionHeaders = true;
break;
case 'i': // continue even if checksum is bad
gOptions.ignoreBadChecksum = true;
break;
case 'l': // layout
if (strcmp(optarg, "plain") == 0) {
gOptions.outputFormat = OUTPUT_PLAIN;
} else if (strcmp(optarg, "xml") == 0) {
gOptions.outputFormat = OUTPUT_XML;
gOptions.verbose = false;
gOptions.exportsOnly = true;
} else {
wantUsage = true;
}
break;
case 'm': // dump register maps only
gOptions.dumpRegisterMaps = true;
break;
case 't': // temp file, used when opening compressed Jar
gOptions.tempFileName = optarg;
break;
default:
wantUsage = true;
break;
}
}
if (optind == argc) {
fprintf(stderr, "%s: no file specified\n", gProgName);
wantUsage = true;
}
if (gOptions.checksumOnly && gOptions.ignoreBadChecksum) {
fprintf(stderr, "Can't specify both -c and -i\n");
wantUsage = true;
}
if (wantUsage) {
usage();
return 2;
}
int result = 0;
while (optind < argc) {
result |= process(argv[optind++]);
}
return (result != 0);
}