/* * 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/DexCatch.h" #include "libdex/DexClass.h" #include "libdex/DexProto.h" #include "libdex/InstrUtils.h" #include "libdex/SysUtil.h" #include "libdex/CmdUtils.h" #include "dexdump/OpCodeNames.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> static const char* gProgName = "dexdump"; static InstructionWidth* gInstrWidth; static InstructionFormat* gInstrFormat; typedef enum OutputFormat { OUTPUT_PLAIN = 0, /* default */ OUTPUT_XML, /* fancy */ } OutputFormat; /* command-line options */ struct { bool disassemble; bool showFileHeaders; bool showSectionHeaders; bool ignoreBadChecksum; OutputFormat outputFormat; const char* tempFileName; bool exportsOnly; bool verbose; } gOptions; /* basic info about a field or method */ typedef struct FieldMethodInfo { const char* classDescriptor; const char* name; const char* signature; } FieldMethodInfo; /* * Get 2 little-endian bytes. */ static inline u2 get2LE(unsigned char const* pSrc) { return pSrc[0] | (pSrc[1] << 8); } /* * 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 = 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. * * Having completed this, I'm ready for an interview at Google. * * TODO? there's a parallel version w/o loops. Performance not currently * important. */ static int countOnes(u4 val) { int count = 0; while (val != 0) { val &= val-1; count++; } return count; } /* * Flag for use with createAccessFlagStr(). */ typedef enum AccessFor { kAccessForClass = 0, kAccessForMethod = 1, kAccessForField = 2, kAccessForMAX } AccessFor; /* * 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; } /* * Dump the file header. */ void dumpFileHeader(const DexFile* pDexFile) { const DexHeader* pHeader = pDexFile->pHeader; printf("DEX file header:\n"); printf("magic : '%.8s'\n", pHeader->magic); 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("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 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; } /* * Look up a class' descriptor. */ const char* getClassDescriptor(DexFile* pDexFile, u4 classIdx) { return dexStringByTypeIdx(pDexFile, classIdx); } /* * Dump a single instruction. */ void dumpInstruction(DexFile* pDexFile, const DexCode* pCode, int insnIdx, int insnWidth, const DecodedInstruction* pDecInsn) { static const float gSpecialTab[16] = { -2.0f, -1.0f, -0.5f, -0.25f, -0.1f, 0.1f, 0.25f, 0.5f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 10.0f, 100.0f, 1000.0f }; const u2* insns = pCode->insns; int i; printf("%06x:", ((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, getOpcodeName(pDecInsn->opCode)); } switch (dexGetInstrFormat(gInstrFormat, 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 %lld // #%x", pDecInsn->vA, value, (u2)pDecInsn->vB); } break; case kFmt21c: // op vAA, thing@BBBB if (pDecInsn->opCode == OP_CONST_STRING) { printf(" v%d, \"%s\" // string@%04x", pDecInsn->vA, dexStringById(pDexFile, pDecInsn->vB), pDecInsn->vB); } else if (pDecInsn->opCode == OP_CHECK_CAST || pDecInsn->opCode == OP_NEW_INSTANCE || pDecInsn->opCode == OP_CONST_CLASS) { printf(" v%d, %s // class@%04x", pDecInsn->vA, getClassDescriptor(pDexFile, pDecInsn->vB), pDecInsn->vB); } else /* OP_SGET* */ { FieldMethodInfo fieldInfo; if (getFieldInfo(pDexFile, pDecInsn->vB, &fieldInfo)) { printf(" v%d, %s.%s:%s // field@%04x", pDecInsn->vA, fieldInfo.classDescriptor, fieldInfo.name, fieldInfo.signature, pDecInsn->vB); } else { printf(" v%d, ??? // field@%04x", pDecInsn->vA, pDecInsn->vB); } } 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 if (pDecInsn->opCode >= OP_IGET && pDecInsn->opCode <= OP_IPUT_SHORT) { FieldMethodInfo fieldInfo; if (getFieldInfo(pDexFile, pDecInsn->vC, &fieldInfo)) { printf(" v%d, v%d, %s.%s:%s // field@%04x", pDecInsn->vA, pDecInsn->vB, fieldInfo.classDescriptor, fieldInfo.name, fieldInfo.signature, pDecInsn->vC); } else { printf(" v%d, v%d, ??? // field@%04x", pDecInsn->vA, pDecInsn->vB, pDecInsn->vC); } } else { printf(" v%d, v%d, %s // class@%04x", pDecInsn->vA, pDecInsn->vB, getClassDescriptor(pDexFile, pDecInsn->vC), pDecInsn->vC); } break; case kFmt22cs: // [opt] op vA, vB, field offset CCCC printf(" v%d, v%d, [obj+%04x]", pDecInsn->vA, pDecInsn->vB, pDecInsn->vC); 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 kFmt31c: // op vAA, thing@BBBBBBBB printf(" v%d, \"%s\" // string@%08x", pDecInsn->vA, dexStringById(pDexFile, pDecInsn->vB), 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 vB, {vD, vE, vF, vG, vA}, thing@CCCC { /* NOTE: decoding of 35c doesn't quite match spec */ 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]); } if (pDecInsn->opCode == OP_FILLED_NEW_ARRAY) { printf("}, %s // class@%04x", getClassDescriptor(pDexFile, pDecInsn->vB), pDecInsn->vB); } else { FieldMethodInfo methInfo; if (getMethodInfo(pDexFile, pDecInsn->vB, &methInfo)) { printf("}, %s.%s:%s // method@%04x", methInfo.classDescriptor, methInfo.name, methInfo.signature, pDecInsn->vB); } else { printf("}, ??? // method@%04x", pDecInsn->vB); } } } break; case kFmt35ms: // [opt] invoke-virtual+super case kFmt35fs: // [opt] invoke-interface { 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("}, [%04x] // vtable #%04x", pDecInsn->vB, pDecInsn->vB); } break; case kFmt3rc: // op {vCCCC .. v(CCCC+AA-1)}, meth@BBBB { /* * 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); } if (pDecInsn->opCode == OP_FILLED_NEW_ARRAY_RANGE) { printf("}, %s // class@%04x", getClassDescriptor(pDexFile, pDecInsn->vB), pDecInsn->vB); } else { FieldMethodInfo methInfo; if (getMethodInfo(pDexFile, pDecInsn->vB, &methInfo)) { printf("}, %s.%s:%s // method@%04x", methInfo.classDescriptor, methInfo.name, methInfo.signature, pDecInsn->vB); } else { printf("}, ??? // method@%04x", pDecInsn->vB); } } } break; case kFmt3rms: // [opt] invoke-virtual+super/range case kFmt3rfs: // [opt] invoke-interface/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("}, [%04x] // vtable #%04x", pDecInsn->vB, pDecInsn->vB); } break; case kFmt3inline: // [opt] inline invoke { #if 0 const InlineOperation* inlineOpsTable = dvmGetInlineOpsTable(); u4 tableLen = dvmGetInlineOpsTableLength(); #endif 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]); } #if 0 if (pDecInsn->vB < tableLen) { printf("}, %s.%s:%s // inline #%04x", inlineOpsTable[pDecInsn->vB].classDescriptor, inlineOpsTable[pDecInsn->vB].methodName, inlineOpsTable[pDecInsn->vB].methodSignature, pDecInsn->vB); } else { #endif printf("}, [%04x] // inline #%04x", pDecInsn->vB, pDecInsn->vB); #if 0 } #endif } 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 // #%016llx", pDecInsn->vA, conv.d, pDecInsn->vB_wide); } break; case kFmtUnknown: break; default: printf(" ???"); break; } putchar('\n'); } /* * 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; 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); insnIdx = 0; while (insnIdx < (int) pCode->insnsSize) { int insnWidth; OpCode opCode; DecodedInstruction decInsn; u2 instr; 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 = instr & 0xff; insnWidth = dexGetInstrWidthAbs(gInstrWidth, opCode); if (insnWidth == 0) { fprintf(stderr, "GLITCH: zero-width instruction at idx=0x%04x\n", insnIdx); break; } } dexDecodeInstruction(gInstrFormat, 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. * * 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 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.showFileHeaders) dumpFileHeader(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); } /* 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) goto bail; mapped = true; int flags = kDexParseVerifyChecksum; if (gOptions.ignoreBadChecksum) flags |= kDexParseContinueOnError; pDexFile = dexFileParse(map.addr, map.length, flags); if (pDexFile == NULL) { fprintf(stderr, "ERROR: DEX parse failed\n"); goto bail; } 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: [-d] [-f] [-h] [-i] [-l layout] [-t tempfile] dexfile...\n", gProgName); fprintf(stderr, "\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, " -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, "dfhil:t:"); if (ic < 0) break; switch (ic) { 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 '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; } /* initialize some VM tables */ gInstrWidth = dexCreateInstrWidthTable(); gInstrFormat = dexCreateInstrFormatTable(); if (wantUsage) { usage(); return 2; } while (optind < argc) process(argv[optind++]); free(gInstrWidth); free(gInstrFormat); return 0; }