/* * Copyright (C) 2015 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. * * Implementation file of the dexdump utility. * * This is a re-implementation of the original dexdump utility that was * based on Dalvik functions in libdex into a new dexdump that is now * based on Art functions in libart instead. The output is very similar to * to the original for correct DEX files. Error messages may differ, however. * Also, ODEX files are no longer supported. * * The dexdump tool is intended to mimic objdump. When possible, use * similar command-line arguments. * * Differences between XML output and the "current.xml" file: * - classes in same package are not all grouped together; nothing is sorted * - no "deprecated" on fields and methods * - 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 "dexdump.h" #include <inttypes.h> #include <stdio.h> #include <iostream> #include <memory> #include <sstream> #include <vector> #include "android-base/stringprintf.h" #include "dexdump_cfg.h" #include "dex_file-inl.h" #include "dex_file_types.h" #include "dex_instruction-inl.h" namespace art { /* * Options parsed in main driver. */ struct Options gOptions; /* * Output file. Defaults to stdout. */ FILE* gOutFile = stdout; /* * Data types that match the definitions in the VM specification. */ typedef uint8_t u1; typedef uint16_t u2; typedef uint32_t u4; typedef uint64_t u8; typedef int8_t s1; typedef int16_t s2; typedef int32_t s4; typedef int64_t s8; /* * Basic information about a field or a method. */ struct FieldMethodInfo { const char* classDescriptor; const char* name; const char* signature; }; /* * Flags for use with createAccessFlagStr(). */ enum AccessFor { kAccessForClass = 0, kAccessForMethod = 1, kAccessForField = 2, kAccessForMAX }; const int kNumFlags = 18; /* * Gets 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 human-readable form. */ 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"; } // switch } /* * 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 std::unique_ptr<char[]> descriptorToDot(const char* str) { int targetLen = strlen(str); int offset = 0; // Strip leading [s; will be added to end. while (targetLen > 1 && str[offset] == '[') { offset++; targetLen--; } // while const int 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++; } } // Copy class name over. std::unique_ptr<char[]> newStr(new char[targetLen + arrayDepth * 2 + 1]); int i = 0; for (; i < targetLen; i++) { const char ch = str[offset + i]; newStr[i] = (ch == '/' || ch == '$') ? '.' : ch; } // for // Add the appropriate number of brackets for arrays. for (int j = 0; j < arrayDepth; j++) { newStr[i++] = '['; newStr[i++] = ']'; } // for newStr[i] = '\0'; return newStr; } /* * Converts the class name portion of a type descriptor to human-readable * "dotted" form. For example, "Ljava/lang/String;" becomes "String". */ static std::unique_ptr<char[]> descriptorClassToDot(const char* str) { // Reduce to just the class name prefix. const char* lastSlash = strrchr(str, '/'); if (lastSlash == nullptr) { lastSlash = str + 1; // start past 'L' } else { lastSlash++; // start past '/' } // Copy class name over, trimming trailing ';'. const int targetLen = strlen(lastSlash); std::unique_ptr<char[]> newStr(new char[targetLen]); for (int i = 0; i < targetLen - 1; i++) { const char ch = lastSlash[i]; newStr[i] = ch == '$' ? '.' : ch; } // for newStr[targetLen - 1] = '\0'; return newStr; } /* * Returns string representing the boolean value. */ static const char* strBool(bool val) { return val ? "true" : "false"; } /* * Returns a quoted string representing the boolean value. */ static const char* quotedBool(bool val) { return val ? "\"true\"" : "\"false\""; } /* * Returns a quoted string representing the access flags. */ static const char* quotedVisibility(u4 accessFlags) { if (accessFlags & kAccPublic) { return "\"public\""; } else if (accessFlags & kAccProtected) { return "\"protected\""; } else if (accessFlags & kAccPrivate) { return "\"private\""; } else { return "\"package\""; } } /* * Counts the number of '1' bits in a word. */ static int countOnes(u4 val) { val = val - ((val >> 1) & 0x55555555); val = (val & 0x33333333) + ((val >> 2) & 0x33333333); return (((val + (val >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; } /* * Creates 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) { static const char* kAccessStrings[kAccessForMAX][kNumFlags] = { { "PUBLIC", /* 0x00001 */ "PRIVATE", /* 0x00002 */ "PROTECTED", /* 0x00004 */ "STATIC", /* 0x00008 */ "FINAL", /* 0x00010 */ "?", /* 0x00020 */ "?", /* 0x00040 */ "?", /* 0x00080 */ "?", /* 0x00100 */ "INTERFACE", /* 0x00200 */ "ABSTRACT", /* 0x00400 */ "?", /* 0x00800 */ "SYNTHETIC", /* 0x01000 */ "ANNOTATION", /* 0x02000 */ "ENUM", /* 0x04000 */ "?", /* 0x08000 */ "VERIFIED", /* 0x10000 */ "OPTIMIZED", /* 0x20000 */ }, { "PUBLIC", /* 0x00001 */ "PRIVATE", /* 0x00002 */ "PROTECTED", /* 0x00004 */ "STATIC", /* 0x00008 */ "FINAL", /* 0x00010 */ "SYNCHRONIZED", /* 0x00020 */ "BRIDGE", /* 0x00040 */ "VARARGS", /* 0x00080 */ "NATIVE", /* 0x00100 */ "?", /* 0x00200 */ "ABSTRACT", /* 0x00400 */ "STRICT", /* 0x00800 */ "SYNTHETIC", /* 0x01000 */ "?", /* 0x02000 */ "?", /* 0x04000 */ "MIRANDA", /* 0x08000 */ "CONSTRUCTOR", /* 0x10000 */ "DECLARED_SYNCHRONIZED", /* 0x20000 */ }, { "PUBLIC", /* 0x00001 */ "PRIVATE", /* 0x00002 */ "PROTECTED", /* 0x00004 */ "STATIC", /* 0x00008 */ "FINAL", /* 0x00010 */ "?", /* 0x00020 */ "VOLATILE", /* 0x00040 */ "TRANSIENT", /* 0x00080 */ "?", /* 0x00100 */ "?", /* 0x00200 */ "?", /* 0x00400 */ "?", /* 0x00800 */ "SYNTHETIC", /* 0x01000 */ "?", /* 0x02000 */ "ENUM", /* 0x04000 */ "?", /* 0x08000 */ "?", /* 0x10000 */ "?", /* 0x20000 */ }, }; // 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. const int kLongest = 21; // The strlen of longest string above. const int count = countOnes(flags); char* str; char* cp; cp = str = reinterpret_cast<char*>(malloc(count * (kLongest + 1) + 1)); for (int i = 0; i < kNumFlags; i++) { if (flags & 0x01) { const char* accessStr = kAccessStrings[forWhat][i]; const int len = strlen(accessStr); if (cp != str) { *cp++ = ' '; } memcpy(cp, accessStr, len); cp += len; } flags >>= 1; } // for *cp = '\0'; return str; } /* * Copies character data from "data" to "out", converting non-ASCII values * to fprintf format chars or an ASCII filler ('.' or '?'). * * The output buffer must be able to hold (2*len)+1 bytes. The result is * NULL-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; } // switch } else if (*data >= 0x80) { *out++ = '?'; } else { *out++ = *data; } data++; } // while *out = '\0'; } /* * Dumps a string value with some escape characters. */ static void dumpEscapedString(const char* p) { fputs("\"", gOutFile); for (; *p; p++) { switch (*p) { case '\\': fputs("\\\\", gOutFile); break; case '\"': fputs("\\\"", gOutFile); break; case '\t': fputs("\\t", gOutFile); break; case '\n': fputs("\\n", gOutFile); break; case '\r': fputs("\\r", gOutFile); break; default: putc(*p, gOutFile); } // switch } // for fputs("\"", gOutFile); } /* * Dumps a string as an XML attribute value. */ static void dumpXmlAttribute(const char* p) { for (; *p; p++) { switch (*p) { case '&': fputs("&", gOutFile); break; case '<': fputs("<", gOutFile); break; case '>': fputs(">", gOutFile); break; case '"': fputs(""", gOutFile); break; case '\t': fputs("	", gOutFile); break; case '\n': fputs("
", gOutFile); break; case '\r': fputs("
", gOutFile); break; default: putc(*p, gOutFile); } // switch } // for } /* * Reads variable width value, possibly sign extended at the last defined byte. */ static u8 readVarWidth(const u1** data, u1 arg, bool sign_extend) { u8 value = 0; for (u4 i = 0; i <= arg; i++) { value |= static_cast<u8>(*(*data)++) << (i * 8); } if (sign_extend) { int shift = (7 - arg) * 8; return (static_cast<s8>(value) << shift) >> shift; } return value; } /* * Dumps encoded value. */ static void dumpEncodedValue(const DexFile* pDexFile, const u1** data); // forward static void dumpEncodedValue(const DexFile* pDexFile, const u1** data, u1 type, u1 arg) { switch (type) { case DexFile::kDexAnnotationByte: fprintf(gOutFile, "%" PRId8, static_cast<s1>(readVarWidth(data, arg, false))); break; case DexFile::kDexAnnotationShort: fprintf(gOutFile, "%" PRId16, static_cast<s2>(readVarWidth(data, arg, true))); break; case DexFile::kDexAnnotationChar: fprintf(gOutFile, "%" PRIu16, static_cast<u2>(readVarWidth(data, arg, false))); break; case DexFile::kDexAnnotationInt: fprintf(gOutFile, "%" PRId32, static_cast<s4>(readVarWidth(data, arg, true))); break; case DexFile::kDexAnnotationLong: fprintf(gOutFile, "%" PRId64, static_cast<s8>(readVarWidth(data, arg, true))); break; case DexFile::kDexAnnotationFloat: { // Fill on right. union { float f; u4 data; } conv; conv.data = static_cast<u4>(readVarWidth(data, arg, false)) << (3 - arg) * 8; fprintf(gOutFile, "%g", conv.f); break; } case DexFile::kDexAnnotationDouble: { // Fill on right. union { double d; u8 data; } conv; conv.data = readVarWidth(data, arg, false) << (7 - arg) * 8; fprintf(gOutFile, "%g", conv.d); break; } case DexFile::kDexAnnotationString: { const u4 idx = static_cast<u4>(readVarWidth(data, arg, false)); if (gOptions.outputFormat == OUTPUT_PLAIN) { dumpEscapedString(pDexFile->StringDataByIdx(dex::StringIndex(idx))); } else { dumpXmlAttribute(pDexFile->StringDataByIdx(dex::StringIndex(idx))); } break; } case DexFile::kDexAnnotationType: { const u4 str_idx = static_cast<u4>(readVarWidth(data, arg, false)); fputs(pDexFile->StringByTypeIdx(dex::TypeIndex(str_idx)), gOutFile); break; } case DexFile::kDexAnnotationField: case DexFile::kDexAnnotationEnum: { const u4 field_idx = static_cast<u4>(readVarWidth(data, arg, false)); const DexFile::FieldId& pFieldId = pDexFile->GetFieldId(field_idx); fputs(pDexFile->StringDataByIdx(pFieldId.name_idx_), gOutFile); break; } case DexFile::kDexAnnotationMethod: { const u4 method_idx = static_cast<u4>(readVarWidth(data, arg, false)); const DexFile::MethodId& pMethodId = pDexFile->GetMethodId(method_idx); fputs(pDexFile->StringDataByIdx(pMethodId.name_idx_), gOutFile); break; } case DexFile::kDexAnnotationArray: { fputc('{', gOutFile); // Decode and display all elements. const u4 size = DecodeUnsignedLeb128(data); for (u4 i = 0; i < size; i++) { fputc(' ', gOutFile); dumpEncodedValue(pDexFile, data); } fputs(" }", gOutFile); break; } case DexFile::kDexAnnotationAnnotation: { const u4 type_idx = DecodeUnsignedLeb128(data); fputs(pDexFile->StringByTypeIdx(dex::TypeIndex(type_idx)), gOutFile); // Decode and display all name=value pairs. const u4 size = DecodeUnsignedLeb128(data); for (u4 i = 0; i < size; i++) { const u4 name_idx = DecodeUnsignedLeb128(data); fputc(' ', gOutFile); fputs(pDexFile->StringDataByIdx(dex::StringIndex(name_idx)), gOutFile); fputc('=', gOutFile); dumpEncodedValue(pDexFile, data); } break; } case DexFile::kDexAnnotationNull: fputs("null", gOutFile); break; case DexFile::kDexAnnotationBoolean: fputs(strBool(arg), gOutFile); break; default: fputs("????", gOutFile); break; } // switch } /* * Dumps encoded value with prefix. */ static void dumpEncodedValue(const DexFile* pDexFile, const u1** data) { const u1 enc = *(*data)++; dumpEncodedValue(pDexFile, data, enc & 0x1f, enc >> 5); } /* * Dumps the file header. */ static void dumpFileHeader(const DexFile* pDexFile) { const DexFile::Header& pHeader = pDexFile->GetHeader(); char sanitized[sizeof(pHeader.magic_) * 2 + 1]; fprintf(gOutFile, "DEX file header:\n"); asciify(sanitized, pHeader.magic_, sizeof(pHeader.magic_)); fprintf(gOutFile, "magic : '%s'\n", sanitized); fprintf(gOutFile, "checksum : %08x\n", pHeader.checksum_); fprintf(gOutFile, "signature : %02x%02x...%02x%02x\n", pHeader.signature_[0], pHeader.signature_[1], pHeader.signature_[DexFile::kSha1DigestSize - 2], pHeader.signature_[DexFile::kSha1DigestSize - 1]); fprintf(gOutFile, "file_size : %d\n", pHeader.file_size_); fprintf(gOutFile, "header_size : %d\n", pHeader.header_size_); fprintf(gOutFile, "link_size : %d\n", pHeader.link_size_); fprintf(gOutFile, "link_off : %d (0x%06x)\n", pHeader.link_off_, pHeader.link_off_); fprintf(gOutFile, "string_ids_size : %d\n", pHeader.string_ids_size_); fprintf(gOutFile, "string_ids_off : %d (0x%06x)\n", pHeader.string_ids_off_, pHeader.string_ids_off_); fprintf(gOutFile, "type_ids_size : %d\n", pHeader.type_ids_size_); fprintf(gOutFile, "type_ids_off : %d (0x%06x)\n", pHeader.type_ids_off_, pHeader.type_ids_off_); fprintf(gOutFile, "proto_ids_size : %d\n", pHeader.proto_ids_size_); fprintf(gOutFile, "proto_ids_off : %d (0x%06x)\n", pHeader.proto_ids_off_, pHeader.proto_ids_off_); fprintf(gOutFile, "field_ids_size : %d\n", pHeader.field_ids_size_); fprintf(gOutFile, "field_ids_off : %d (0x%06x)\n", pHeader.field_ids_off_, pHeader.field_ids_off_); fprintf(gOutFile, "method_ids_size : %d\n", pHeader.method_ids_size_); fprintf(gOutFile, "method_ids_off : %d (0x%06x)\n", pHeader.method_ids_off_, pHeader.method_ids_off_); fprintf(gOutFile, "class_defs_size : %d\n", pHeader.class_defs_size_); fprintf(gOutFile, "class_defs_off : %d (0x%06x)\n", pHeader.class_defs_off_, pHeader.class_defs_off_); fprintf(gOutFile, "data_size : %d\n", pHeader.data_size_); fprintf(gOutFile, "data_off : %d (0x%06x)\n\n", pHeader.data_off_, pHeader.data_off_); } /* * Dumps a class_def_item. */ static void dumpClassDef(const DexFile* pDexFile, int idx) { // General class information. const DexFile::ClassDef& pClassDef = pDexFile->GetClassDef(idx); fprintf(gOutFile, "Class #%d header:\n", idx); fprintf(gOutFile, "class_idx : %d\n", pClassDef.class_idx_.index_); fprintf(gOutFile, "access_flags : %d (0x%04x)\n", pClassDef.access_flags_, pClassDef.access_flags_); fprintf(gOutFile, "superclass_idx : %d\n", pClassDef.superclass_idx_.index_); fprintf(gOutFile, "interfaces_off : %d (0x%06x)\n", pClassDef.interfaces_off_, pClassDef.interfaces_off_); fprintf(gOutFile, "source_file_idx : %d\n", pClassDef.source_file_idx_.index_); fprintf(gOutFile, "annotations_off : %d (0x%06x)\n", pClassDef.annotations_off_, pClassDef.annotations_off_); fprintf(gOutFile, "class_data_off : %d (0x%06x)\n", pClassDef.class_data_off_, pClassDef.class_data_off_); // Fields and methods. const u1* pEncodedData = pDexFile->GetClassData(pClassDef); if (pEncodedData != nullptr) { ClassDataItemIterator pClassData(*pDexFile, pEncodedData); fprintf(gOutFile, "static_fields_size : %d\n", pClassData.NumStaticFields()); fprintf(gOutFile, "instance_fields_size: %d\n", pClassData.NumInstanceFields()); fprintf(gOutFile, "direct_methods_size : %d\n", pClassData.NumDirectMethods()); fprintf(gOutFile, "virtual_methods_size: %d\n", pClassData.NumVirtualMethods()); } else { fprintf(gOutFile, "static_fields_size : 0\n"); fprintf(gOutFile, "instance_fields_size: 0\n"); fprintf(gOutFile, "direct_methods_size : 0\n"); fprintf(gOutFile, "virtual_methods_size: 0\n"); } fprintf(gOutFile, "\n"); } /** * Dumps an annotation set item. */ static void dumpAnnotationSetItem(const DexFile* pDexFile, const DexFile::AnnotationSetItem* set_item) { if (set_item == nullptr || set_item->size_ == 0) { fputs(" empty-annotation-set\n", gOutFile); return; } for (u4 i = 0; i < set_item->size_; i++) { const DexFile::AnnotationItem* annotation = pDexFile->GetAnnotationItem(set_item, i); if (annotation == nullptr) { continue; } fputs(" ", gOutFile); switch (annotation->visibility_) { case DexFile::kDexVisibilityBuild: fputs("VISIBILITY_BUILD ", gOutFile); break; case DexFile::kDexVisibilityRuntime: fputs("VISIBILITY_RUNTIME ", gOutFile); break; case DexFile::kDexVisibilitySystem: fputs("VISIBILITY_SYSTEM ", gOutFile); break; default: fputs("VISIBILITY_UNKNOWN ", gOutFile); break; } // switch // Decode raw bytes in annotation. const u1* rData = annotation->annotation_; dumpEncodedValue(pDexFile, &rData, DexFile::kDexAnnotationAnnotation, 0); fputc('\n', gOutFile); } } /* * Dumps class annotations. */ static void dumpClassAnnotations(const DexFile* pDexFile, int idx) { const DexFile::ClassDef& pClassDef = pDexFile->GetClassDef(idx); const DexFile::AnnotationsDirectoryItem* dir = pDexFile->GetAnnotationsDirectory(pClassDef); if (dir == nullptr) { return; // none } fprintf(gOutFile, "Class #%d annotations:\n", idx); const DexFile::AnnotationSetItem* class_set_item = pDexFile->GetClassAnnotationSet(dir); const DexFile::FieldAnnotationsItem* fields = pDexFile->GetFieldAnnotations(dir); const DexFile::MethodAnnotationsItem* methods = pDexFile->GetMethodAnnotations(dir); const DexFile::ParameterAnnotationsItem* pars = pDexFile->GetParameterAnnotations(dir); // Annotations on the class itself. if (class_set_item != nullptr) { fprintf(gOutFile, "Annotations on class\n"); dumpAnnotationSetItem(pDexFile, class_set_item); } // Annotations on fields. if (fields != nullptr) { for (u4 i = 0; i < dir->fields_size_; i++) { const u4 field_idx = fields[i].field_idx_; const DexFile::FieldId& pFieldId = pDexFile->GetFieldId(field_idx); const char* field_name = pDexFile->StringDataByIdx(pFieldId.name_idx_); fprintf(gOutFile, "Annotations on field #%u '%s'\n", field_idx, field_name); dumpAnnotationSetItem(pDexFile, pDexFile->GetFieldAnnotationSetItem(fields[i])); } } // Annotations on methods. if (methods != nullptr) { for (u4 i = 0; i < dir->methods_size_; i++) { const u4 method_idx = methods[i].method_idx_; const DexFile::MethodId& pMethodId = pDexFile->GetMethodId(method_idx); const char* method_name = pDexFile->StringDataByIdx(pMethodId.name_idx_); fprintf(gOutFile, "Annotations on method #%u '%s'\n", method_idx, method_name); dumpAnnotationSetItem(pDexFile, pDexFile->GetMethodAnnotationSetItem(methods[i])); } } // Annotations on method parameters. if (pars != nullptr) { for (u4 i = 0; i < dir->parameters_size_; i++) { const u4 method_idx = pars[i].method_idx_; const DexFile::MethodId& pMethodId = pDexFile->GetMethodId(method_idx); const char* method_name = pDexFile->StringDataByIdx(pMethodId.name_idx_); fprintf(gOutFile, "Annotations on method #%u '%s' parameters\n", method_idx, method_name); const DexFile::AnnotationSetRefList* list = pDexFile->GetParameterAnnotationSetRefList(&pars[i]); if (list != nullptr) { for (u4 j = 0; j < list->size_; j++) { fprintf(gOutFile, "#%u\n", j); dumpAnnotationSetItem(pDexFile, pDexFile->GetSetRefItemItem(&list->list_[j])); } } } } fputc('\n', gOutFile); } /* * Dumps an interface that a class declares to implement. */ static void dumpInterface(const DexFile* pDexFile, const DexFile::TypeItem& pTypeItem, int i) { const char* interfaceName = pDexFile->StringByTypeIdx(pTypeItem.type_idx_); if (gOptions.outputFormat == OUTPUT_PLAIN) { fprintf(gOutFile, " #%d : '%s'\n", i, interfaceName); } else { std::unique_ptr<char[]> dot(descriptorToDot(interfaceName)); fprintf(gOutFile, "<implements name=\"%s\">\n</implements>\n", dot.get()); } } /* * Dumps the catches table associated with the code. */ static void dumpCatches(const DexFile* pDexFile, const DexFile::CodeItem* pCode) { const u4 triesSize = pCode->tries_size_; // No catch table. if (triesSize == 0) { fprintf(gOutFile, " catches : (none)\n"); return; } // Dump all table entries. fprintf(gOutFile, " catches : %d\n", triesSize); for (u4 i = 0; i < triesSize; i++) { const DexFile::TryItem* pTry = pDexFile->GetTryItems(*pCode, i); const u4 start = pTry->start_addr_; const u4 end = start + pTry->insn_count_; fprintf(gOutFile, " 0x%04x - 0x%04x\n", start, end); for (CatchHandlerIterator it(*pCode, *pTry); it.HasNext(); it.Next()) { const dex::TypeIndex tidx = it.GetHandlerTypeIndex(); const char* descriptor = (!tidx.IsValid()) ? "<any>" : pDexFile->StringByTypeIdx(tidx); fprintf(gOutFile, " %s -> 0x%04x\n", descriptor, it.GetHandlerAddress()); } // for } // for } /* * Callback for dumping each positions table entry. */ static bool dumpPositionsCb(void* /*context*/, const DexFile::PositionInfo& entry) { fprintf(gOutFile, " 0x%04x line=%d\n", entry.address_, entry.line_); return false; } /* * Callback for dumping locals table entry. */ static void dumpLocalsCb(void* /*context*/, const DexFile::LocalInfo& entry) { const char* signature = entry.signature_ != nullptr ? entry.signature_ : ""; fprintf(gOutFile, " 0x%04x - 0x%04x reg=%d %s %s %s\n", entry.start_address_, entry.end_address_, entry.reg_, entry.name_, entry.descriptor_, signature); } /* * Helper for dumpInstruction(), which builds the string * representation for the index in the given instruction. * Returns a pointer to a buffer of sufficient size. */ static std::unique_ptr<char[]> indexString(const DexFile* pDexFile, const Instruction* pDecInsn, size_t bufSize) { static const u4 kInvalidIndex = std::numeric_limits<u4>::max(); std::unique_ptr<char[]> buf(new char[bufSize]); // Determine index and width of the string. u4 index = 0; u4 secondary_index = kInvalidIndex; u4 width = 4; switch (Instruction::FormatOf(pDecInsn->Opcode())) { // SOME NOT SUPPORTED: // case Instruction::k20bc: case Instruction::k21c: case Instruction::k35c: // case Instruction::k35ms: case Instruction::k3rc: // case Instruction::k3rms: // case Instruction::k35mi: // case Instruction::k3rmi: index = pDecInsn->VRegB(); width = 4; break; case Instruction::k31c: index = pDecInsn->VRegB(); width = 8; break; case Instruction::k22c: // case Instruction::k22cs: index = pDecInsn->VRegC(); width = 4; break; case Instruction::k45cc: case Instruction::k4rcc: index = pDecInsn->VRegB(); secondary_index = pDecInsn->VRegH(); width = 4; break; default: break; } // switch // Determine index type. size_t outSize = 0; switch (Instruction::IndexTypeOf(pDecInsn->Opcode())) { case Instruction::kIndexUnknown: // This function should never get called for this type, but do // something sensible here, just to help with debugging. outSize = snprintf(buf.get(), bufSize, "<unknown-index>"); break; case Instruction::kIndexNone: // This function should never get called for this type, but do // something sensible here, just to help with debugging. outSize = snprintf(buf.get(), bufSize, "<no-index>"); break; case Instruction::kIndexTypeRef: if (index < pDexFile->GetHeader().type_ids_size_) { const char* tp = pDexFile->StringByTypeIdx(dex::TypeIndex(index)); outSize = snprintf(buf.get(), bufSize, "%s // type@%0*x", tp, width, index); } else { outSize = snprintf(buf.get(), bufSize, "<type?> // type@%0*x", width, index); } break; case Instruction::kIndexStringRef: if (index < pDexFile->GetHeader().string_ids_size_) { const char* st = pDexFile->StringDataByIdx(dex::StringIndex(index)); outSize = snprintf(buf.get(), bufSize, "\"%s\" // string@%0*x", st, width, index); } else { outSize = snprintf(buf.get(), bufSize, "<string?> // string@%0*x", width, index); } break; case Instruction::kIndexMethodRef: if (index < pDexFile->GetHeader().method_ids_size_) { const DexFile::MethodId& pMethodId = pDexFile->GetMethodId(index); const char* name = pDexFile->StringDataByIdx(pMethodId.name_idx_); const Signature signature = pDexFile->GetMethodSignature(pMethodId); const char* backDescriptor = pDexFile->StringByTypeIdx(pMethodId.class_idx_); outSize = snprintf(buf.get(), bufSize, "%s.%s:%s // method@%0*x", backDescriptor, name, signature.ToString().c_str(), width, index); } else { outSize = snprintf(buf.get(), bufSize, "<method?> // method@%0*x", width, index); } break; case Instruction::kIndexFieldRef: if (index < pDexFile->GetHeader().field_ids_size_) { const DexFile::FieldId& pFieldId = pDexFile->GetFieldId(index); const char* name = pDexFile->StringDataByIdx(pFieldId.name_idx_); const char* typeDescriptor = pDexFile->StringByTypeIdx(pFieldId.type_idx_); const char* backDescriptor = pDexFile->StringByTypeIdx(pFieldId.class_idx_); outSize = snprintf(buf.get(), bufSize, "%s.%s:%s // field@%0*x", backDescriptor, name, typeDescriptor, width, index); } else { outSize = snprintf(buf.get(), bufSize, "<field?> // field@%0*x", width, index); } break; case Instruction::kIndexVtableOffset: outSize = snprintf(buf.get(), bufSize, "[%0*x] // vtable #%0*x", width, index, width, index); break; case Instruction::kIndexFieldOffset: outSize = snprintf(buf.get(), bufSize, "[obj+%0*x]", width, index); break; case Instruction::kIndexMethodAndProtoRef: { std::string method("<method?>"); std::string proto("<proto?>"); if (index < pDexFile->GetHeader().method_ids_size_) { const DexFile::MethodId& pMethodId = pDexFile->GetMethodId(index); const char* name = pDexFile->StringDataByIdx(pMethodId.name_idx_); const Signature signature = pDexFile->GetMethodSignature(pMethodId); const char* backDescriptor = pDexFile->StringByTypeIdx(pMethodId.class_idx_); method = android::base::StringPrintf("%s.%s:%s", backDescriptor, name, signature.ToString().c_str()); } if (secondary_index < pDexFile->GetHeader().proto_ids_size_) { const DexFile::ProtoId& protoId = pDexFile->GetProtoId(secondary_index); const Signature signature = pDexFile->GetProtoSignature(protoId); proto = signature.ToString(); } outSize = snprintf(buf.get(), bufSize, "%s, %s // method@%0*x, proto@%0*x", method.c_str(), proto.c_str(), width, index, width, secondary_index); break; } case Instruction::kIndexCallSiteRef: // Call site information is too large to detail in disassembly so just output the index. outSize = snprintf(buf.get(), bufSize, "call_site@%0*x", width, index); break; // SOME NOT SUPPORTED: // case Instruction::kIndexVaries: // case Instruction::kIndexInlineMethod: default: outSize = snprintf(buf.get(), bufSize, "<?>"); break; } // switch // Determine success of string construction. if (outSize >= bufSize) { // The buffer wasn't big enough; retry with computed size. Note: snprintf() // doesn't count/ the '\0' as part of its returned size, so we add explicit // space for it here. return indexString(pDexFile, pDecInsn, outSize + 1); } return buf; } /* * Dumps a single instruction. */ static void dumpInstruction(const DexFile* pDexFile, const DexFile::CodeItem* pCode, u4 codeOffset, u4 insnIdx, u4 insnWidth, const Instruction* pDecInsn) { // Address of instruction (expressed as byte offset). fprintf(gOutFile, "%06x:", codeOffset + 0x10 + insnIdx * 2); // Dump (part of) raw bytes. const u2* insns = pCode->insns_; for (u4 i = 0; i < 8; i++) { if (i < insnWidth) { if (i == 7) { fprintf(gOutFile, " ... "); } else { // Print 16-bit value in little-endian order. const u1* bytePtr = (const u1*) &insns[insnIdx + i]; fprintf(gOutFile, " %02x%02x", bytePtr[0], bytePtr[1]); } } else { fputs(" ", gOutFile); } } // for // Dump pseudo-instruction or opcode. if (pDecInsn->Opcode() == Instruction::NOP) { const u2 instr = get2LE((const u1*) &insns[insnIdx]); if (instr == Instruction::kPackedSwitchSignature) { fprintf(gOutFile, "|%04x: packed-switch-data (%d units)", insnIdx, insnWidth); } else if (instr == Instruction::kSparseSwitchSignature) { fprintf(gOutFile, "|%04x: sparse-switch-data (%d units)", insnIdx, insnWidth); } else if (instr == Instruction::kArrayDataSignature) { fprintf(gOutFile, "|%04x: array-data (%d units)", insnIdx, insnWidth); } else { fprintf(gOutFile, "|%04x: nop // spacer", insnIdx); } } else { fprintf(gOutFile, "|%04x: %s", insnIdx, pDecInsn->Name()); } // Set up additional argument. std::unique_ptr<char[]> indexBuf; if (Instruction::IndexTypeOf(pDecInsn->Opcode()) != Instruction::kIndexNone) { indexBuf = indexString(pDexFile, pDecInsn, 200); } // Dump the instruction. // // NOTE: pDecInsn->DumpString(pDexFile) differs too much from original. // switch (Instruction::FormatOf(pDecInsn->Opcode())) { case Instruction::k10x: // op break; case Instruction::k12x: // op vA, vB fprintf(gOutFile, " v%d, v%d", pDecInsn->VRegA(), pDecInsn->VRegB()); break; case Instruction::k11n: // op vA, #+B fprintf(gOutFile, " v%d, #int %d // #%x", pDecInsn->VRegA(), (s4) pDecInsn->VRegB(), (u1)pDecInsn->VRegB()); break; case Instruction::k11x: // op vAA fprintf(gOutFile, " v%d", pDecInsn->VRegA()); break; case Instruction::k10t: // op +AA case Instruction::k20t: { // op +AAAA const s4 targ = (s4) pDecInsn->VRegA(); fprintf(gOutFile, " %04x // %c%04x", insnIdx + targ, (targ < 0) ? '-' : '+', (targ < 0) ? -targ : targ); break; } case Instruction::k22x: // op vAA, vBBBB fprintf(gOutFile, " v%d, v%d", pDecInsn->VRegA(), pDecInsn->VRegB()); break; case Instruction::k21t: { // op vAA, +BBBB const s4 targ = (s4) pDecInsn->VRegB(); fprintf(gOutFile, " v%d, %04x // %c%04x", pDecInsn->VRegA(), insnIdx + targ, (targ < 0) ? '-' : '+', (targ < 0) ? -targ : targ); break; } case Instruction::k21s: // op vAA, #+BBBB fprintf(gOutFile, " v%d, #int %d // #%x", pDecInsn->VRegA(), (s4) pDecInsn->VRegB(), (u2)pDecInsn->VRegB()); break; case Instruction::k21h: // op vAA, #+BBBB0000[00000000] // The printed format varies a bit based on the actual opcode. if (pDecInsn->Opcode() == Instruction::CONST_HIGH16) { const s4 value = pDecInsn->VRegB() << 16; fprintf(gOutFile, " v%d, #int %d // #%x", pDecInsn->VRegA(), value, (u2) pDecInsn->VRegB()); } else { const s8 value = ((s8) pDecInsn->VRegB()) << 48; fprintf(gOutFile, " v%d, #long %" PRId64 " // #%x", pDecInsn->VRegA(), value, (u2) pDecInsn->VRegB()); } break; case Instruction::k21c: // op vAA, thing@BBBB case Instruction::k31c: // op vAA, thing@BBBBBBBB fprintf(gOutFile, " v%d, %s", pDecInsn->VRegA(), indexBuf.get()); break; case Instruction::k23x: // op vAA, vBB, vCC fprintf(gOutFile, " v%d, v%d, v%d", pDecInsn->VRegA(), pDecInsn->VRegB(), pDecInsn->VRegC()); break; case Instruction::k22b: // op vAA, vBB, #+CC fprintf(gOutFile, " v%d, v%d, #int %d // #%02x", pDecInsn->VRegA(), pDecInsn->VRegB(), (s4) pDecInsn->VRegC(), (u1) pDecInsn->VRegC()); break; case Instruction::k22t: { // op vA, vB, +CCCC const s4 targ = (s4) pDecInsn->VRegC(); fprintf(gOutFile, " v%d, v%d, %04x // %c%04x", pDecInsn->VRegA(), pDecInsn->VRegB(), insnIdx + targ, (targ < 0) ? '-' : '+', (targ < 0) ? -targ : targ); break; } case Instruction::k22s: // op vA, vB, #+CCCC fprintf(gOutFile, " v%d, v%d, #int %d // #%04x", pDecInsn->VRegA(), pDecInsn->VRegB(), (s4) pDecInsn->VRegC(), (u2) pDecInsn->VRegC()); break; case Instruction::k22c: // op vA, vB, thing@CCCC // NOT SUPPORTED: // case Instruction::k22cs: // [opt] op vA, vB, field offset CCCC fprintf(gOutFile, " v%d, v%d, %s", pDecInsn->VRegA(), pDecInsn->VRegB(), indexBuf.get()); break; case Instruction::k30t: fprintf(gOutFile, " #%08x", pDecInsn->VRegA()); break; case Instruction::k31i: { // op vAA, #+BBBBBBBB // This is often, but not always, a float. union { float f; u4 i; } conv; conv.i = pDecInsn->VRegB(); fprintf(gOutFile, " v%d, #float %g // #%08x", pDecInsn->VRegA(), conv.f, pDecInsn->VRegB()); break; } case Instruction::k31t: // op vAA, offset +BBBBBBBB fprintf(gOutFile, " v%d, %08x // +%08x", pDecInsn->VRegA(), insnIdx + pDecInsn->VRegB(), pDecInsn->VRegB()); break; case Instruction::k32x: // op vAAAA, vBBBB fprintf(gOutFile, " v%d, v%d", pDecInsn->VRegA(), pDecInsn->VRegB()); break; case Instruction::k35c: // op {vC, vD, vE, vF, vG}, thing@BBBB case Instruction::k45cc: { // op {vC, vD, vE, vF, vG}, method@BBBB, proto@HHHH // NOT SUPPORTED: // case Instruction::k35ms: // [opt] invoke-virtual+super // case Instruction::k35mi: // [opt] inline invoke u4 arg[Instruction::kMaxVarArgRegs]; pDecInsn->GetVarArgs(arg); fputs(" {", gOutFile); for (int i = 0, n = pDecInsn->VRegA(); i < n; i++) { if (i == 0) { fprintf(gOutFile, "v%d", arg[i]); } else { fprintf(gOutFile, ", v%d", arg[i]); } } // for fprintf(gOutFile, "}, %s", indexBuf.get()); break; } case Instruction::k3rc: // op {vCCCC .. v(CCCC+AA-1)}, thing@BBBB case Instruction::k4rcc: { // op {vCCCC .. v(CCCC+AA-1)}, method@BBBB, proto@HHHH // NOT SUPPORTED: // case Instruction::k3rms: // [opt] invoke-virtual+super/range // case Instruction::k3rmi: // [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(" {", gOutFile); for (int i = 0, n = pDecInsn->VRegA(); i < n; i++) { if (i == 0) { fprintf(gOutFile, "v%d", pDecInsn->VRegC() + i); } else { fprintf(gOutFile, ", v%d", pDecInsn->VRegC() + i); } } // for fprintf(gOutFile, "}, %s", indexBuf.get()); } break; case Instruction::k51l: { // op vAA, #+BBBBBBBBBBBBBBBB // This is often, but not always, a double. union { double d; u8 j; } conv; conv.j = pDecInsn->WideVRegB(); fprintf(gOutFile, " v%d, #double %g // #%016" PRIx64, pDecInsn->VRegA(), conv.d, pDecInsn->WideVRegB()); break; } // NOT SUPPORTED: // case Instruction::k00x: // unknown op or breakpoint // break; default: fprintf(gOutFile, " ???"); break; } // switch fputc('\n', gOutFile); } /* * Dumps a bytecode disassembly. */ static void dumpBytecodes(const DexFile* pDexFile, u4 idx, const DexFile::CodeItem* pCode, u4 codeOffset) { const DexFile::MethodId& pMethodId = pDexFile->GetMethodId(idx); const char* name = pDexFile->StringDataByIdx(pMethodId.name_idx_); const Signature signature = pDexFile->GetMethodSignature(pMethodId); const char* backDescriptor = pDexFile->StringByTypeIdx(pMethodId.class_idx_); // Generate header. std::unique_ptr<char[]> dot(descriptorToDot(backDescriptor)); fprintf(gOutFile, "%06x: |[%06x] %s.%s:%s\n", codeOffset, codeOffset, dot.get(), name, signature.ToString().c_str()); // Iterate over all instructions. const u2* insns = pCode->insns_; for (u4 insnIdx = 0; insnIdx < pCode->insns_size_in_code_units_;) { const Instruction* instruction = Instruction::At(&insns[insnIdx]); const u4 insnWidth = instruction->SizeInCodeUnits(); if (insnWidth == 0) { fprintf(stderr, "GLITCH: zero-width instruction at idx=0x%04x\n", insnIdx); break; } dumpInstruction(pDexFile, pCode, codeOffset, insnIdx, insnWidth, instruction); insnIdx += insnWidth; } // for } /* * Dumps code of a method. */ static void dumpCode(const DexFile* pDexFile, u4 idx, u4 flags, const DexFile::CodeItem* pCode, u4 codeOffset) { fprintf(gOutFile, " registers : %d\n", pCode->registers_size_); fprintf(gOutFile, " ins : %d\n", pCode->ins_size_); fprintf(gOutFile, " outs : %d\n", pCode->outs_size_); fprintf(gOutFile, " insns size : %d 16-bit code units\n", pCode->insns_size_in_code_units_); // Bytecode disassembly, if requested. if (gOptions.disassemble) { dumpBytecodes(pDexFile, idx, pCode, codeOffset); } // Try-catch blocks. dumpCatches(pDexFile, pCode); // Positions and locals table in the debug info. bool is_static = (flags & kAccStatic) != 0; fprintf(gOutFile, " positions : \n"); pDexFile->DecodeDebugPositionInfo(pCode, dumpPositionsCb, nullptr); fprintf(gOutFile, " locals : \n"); pDexFile->DecodeDebugLocalInfo(pCode, is_static, idx, dumpLocalsCb, nullptr); } /* * Dumps a method. */ static void dumpMethod(const DexFile* pDexFile, u4 idx, u4 flags, const DexFile::CodeItem* pCode, u4 codeOffset, int i) { // Bail for anything private if export only requested. if (gOptions.exportsOnly && (flags & (kAccPublic | kAccProtected)) == 0) { return; } const DexFile::MethodId& pMethodId = pDexFile->GetMethodId(idx); const char* name = pDexFile->StringDataByIdx(pMethodId.name_idx_); const Signature signature = pDexFile->GetMethodSignature(pMethodId); char* typeDescriptor = strdup(signature.ToString().c_str()); const char* backDescriptor = pDexFile->StringByTypeIdx(pMethodId.class_idx_); char* accessStr = createAccessFlagStr(flags, kAccessForMethod); if (gOptions.outputFormat == OUTPUT_PLAIN) { fprintf(gOutFile, " #%d : (in %s)\n", i, backDescriptor); fprintf(gOutFile, " name : '%s'\n", name); fprintf(gOutFile, " type : '%s'\n", typeDescriptor); fprintf(gOutFile, " access : 0x%04x (%s)\n", flags, accessStr); if (pCode == nullptr) { fprintf(gOutFile, " code : (none)\n"); } else { fprintf(gOutFile, " code -\n"); dumpCode(pDexFile, idx, flags, pCode, codeOffset); } if (gOptions.disassemble) { fputc('\n', gOutFile); } } else if (gOptions.outputFormat == OUTPUT_XML) { const bool constructor = (name[0] == '<'); // Method name and prototype. if (constructor) { std::unique_ptr<char[]> dot(descriptorClassToDot(backDescriptor)); fprintf(gOutFile, "<constructor name=\"%s\"\n", dot.get()); dot = descriptorToDot(backDescriptor); fprintf(gOutFile, " type=\"%s\"\n", dot.get()); } else { fprintf(gOutFile, "<method name=\"%s\"\n", name); const char* returnType = strrchr(typeDescriptor, ')'); if (returnType == nullptr) { fprintf(stderr, "bad method type descriptor '%s'\n", typeDescriptor); goto bail; } std::unique_ptr<char[]> dot(descriptorToDot(returnType + 1)); fprintf(gOutFile, " return=\"%s\"\n", dot.get()); fprintf(gOutFile, " abstract=%s\n", quotedBool((flags & kAccAbstract) != 0)); fprintf(gOutFile, " native=%s\n", quotedBool((flags & kAccNative) != 0)); fprintf(gOutFile, " synchronized=%s\n", quotedBool( (flags & (kAccSynchronized | kAccDeclaredSynchronized)) != 0)); } // Additional method flags. fprintf(gOutFile, " static=%s\n", quotedBool((flags & kAccStatic) != 0)); fprintf(gOutFile, " final=%s\n", quotedBool((flags & kAccFinal) != 0)); // The "deprecated=" not knowable w/o parsing annotations. fprintf(gOutFile, " visibility=%s\n>\n", quotedVisibility(flags)); // Parameters. if (typeDescriptor[0] != '(') { fprintf(stderr, "ERROR: bad descriptor '%s'\n", typeDescriptor); goto bail; } char* tmpBuf = reinterpret_cast<char*>(malloc(strlen(typeDescriptor) + 1)); const char* base = typeDescriptor + 1; int argNum = 0; 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) == nullptr) { fprintf(stderr, "ERROR: bad method signature '%s'\n", base); break; // while } *cp++ = *base++; } // Null terminate and display. *cp++ = '\0'; std::unique_ptr<char[]> dot(descriptorToDot(tmpBuf)); fprintf(gOutFile, "<parameter name=\"arg%d\" type=\"%s\">\n" "</parameter>\n", argNum++, dot.get()); } // while free(tmpBuf); if (constructor) { fprintf(gOutFile, "</constructor>\n"); } else { fprintf(gOutFile, "</method>\n"); } } bail: free(typeDescriptor); free(accessStr); } /* * Dumps a static (class) field. */ static void dumpSField(const DexFile* pDexFile, u4 idx, u4 flags, int i, const u1** data) { // Bail for anything private if export only requested. if (gOptions.exportsOnly && (flags & (kAccPublic | kAccProtected)) == 0) { return; } const DexFile::FieldId& pFieldId = pDexFile->GetFieldId(idx); const char* name = pDexFile->StringDataByIdx(pFieldId.name_idx_); const char* typeDescriptor = pDexFile->StringByTypeIdx(pFieldId.type_idx_); const char* backDescriptor = pDexFile->StringByTypeIdx(pFieldId.class_idx_); char* accessStr = createAccessFlagStr(flags, kAccessForField); if (gOptions.outputFormat == OUTPUT_PLAIN) { fprintf(gOutFile, " #%d : (in %s)\n", i, backDescriptor); fprintf(gOutFile, " name : '%s'\n", name); fprintf(gOutFile, " type : '%s'\n", typeDescriptor); fprintf(gOutFile, " access : 0x%04x (%s)\n", flags, accessStr); if (data != nullptr) { fputs(" value : ", gOutFile); dumpEncodedValue(pDexFile, data); fputs("\n", gOutFile); } } else if (gOptions.outputFormat == OUTPUT_XML) { fprintf(gOutFile, "<field name=\"%s\"\n", name); std::unique_ptr<char[]> dot(descriptorToDot(typeDescriptor)); fprintf(gOutFile, " type=\"%s\"\n", dot.get()); fprintf(gOutFile, " transient=%s\n", quotedBool((flags & kAccTransient) != 0)); fprintf(gOutFile, " volatile=%s\n", quotedBool((flags & kAccVolatile) != 0)); // The "value=" is not knowable w/o parsing annotations. fprintf(gOutFile, " static=%s\n", quotedBool((flags & kAccStatic) != 0)); fprintf(gOutFile, " final=%s\n", quotedBool((flags & kAccFinal) != 0)); // The "deprecated=" is not knowable w/o parsing annotations. fprintf(gOutFile, " visibility=%s\n", quotedVisibility(flags)); if (data != nullptr) { fputs(" value=\"", gOutFile); dumpEncodedValue(pDexFile, data); fputs("\"\n", gOutFile); } fputs(">\n</field>\n", gOutFile); } free(accessStr); } /* * Dumps an instance field. */ static void dumpIField(const DexFile* pDexFile, u4 idx, u4 flags, int i) { dumpSField(pDexFile, idx, flags, i, nullptr); } /* * Dumping a CFG. Note that this will do duplicate work. utils.h doesn't expose the code-item * version, so the DumpMethodCFG code will have to iterate again to find it. But dexdump is a * tool, so this is not performance-critical. */ static void dumpCfg(const DexFile* dex_file, u4 dex_method_idx, const DexFile::CodeItem* code_item) { if (code_item != nullptr) { std::ostringstream oss; DumpMethodCFG(dex_file, dex_method_idx, oss); fputs(oss.str().c_str(), gOutFile); } } static void dumpCfg(const DexFile* dex_file, int idx) { const DexFile::ClassDef& class_def = dex_file->GetClassDef(idx); const u1* class_data = dex_file->GetClassData(class_def); if (class_data == nullptr) { // empty class such as a marker interface? return; } ClassDataItemIterator it(*dex_file, class_data); while (it.HasNextStaticField()) { it.Next(); } while (it.HasNextInstanceField()) { it.Next(); } while (it.HasNextDirectMethod()) { dumpCfg(dex_file, it.GetMemberIndex(), it.GetMethodCodeItem()); it.Next(); } while (it.HasNextVirtualMethod()) { dumpCfg(dex_file, it.GetMemberIndex(), it.GetMethodCodeItem()); it.Next(); } } /* * Dumps the class. * * Note "idx" is a DexClassDef index, not a DexTypeId index. * * If "*pLastPackage" is nullptr or does not match the current class' package, * the value will be replaced with a newly-allocated string. */ static void dumpClass(const DexFile* pDexFile, int idx, char** pLastPackage) { const DexFile::ClassDef& pClassDef = pDexFile->GetClassDef(idx); // Omitting non-public class. if (gOptions.exportsOnly && (pClassDef.access_flags_ & kAccPublic) == 0) { return; } if (gOptions.showSectionHeaders) { dumpClassDef(pDexFile, idx); } if (gOptions.showAnnotations) { dumpClassAnnotations(pDexFile, idx); } if (gOptions.showCfg) { dumpCfg(pDexFile, idx); return; } // 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. const char* classDescriptor = pDexFile->StringByTypeIdx(pClassDef.class_idx_); if (!(classDescriptor[0] == 'L' && classDescriptor[strlen(classDescriptor)-1] == ';')) { // Arrays and primitives should not be defined explicitly. Keep going? fprintf(stderr, "Malformed class name '%s'\n", classDescriptor); } else if (gOptions.outputFormat == OUTPUT_XML) { char* mangle = strdup(classDescriptor + 1); mangle[strlen(mangle)-1] = '\0'; // Reduce to just the package name. char* lastSlash = strrchr(mangle, '/'); if (lastSlash != nullptr) { *lastSlash = '\0'; } else { *mangle = '\0'; } for (char* cp = mangle; *cp != '\0'; cp++) { if (*cp == '/') { *cp = '.'; } } // for if (*pLastPackage == nullptr || strcmp(mangle, *pLastPackage) != 0) { // Start of a new package. if (*pLastPackage != nullptr) { fprintf(gOutFile, "</package>\n"); } fprintf(gOutFile, "<package name=\"%s\"\n>\n", mangle); free(*pLastPackage); *pLastPackage = mangle; } else { free(mangle); } } // General class information. char* accessStr = createAccessFlagStr(pClassDef.access_flags_, kAccessForClass); const char* superclassDescriptor; if (!pClassDef.superclass_idx_.IsValid()) { superclassDescriptor = nullptr; } else { superclassDescriptor = pDexFile->StringByTypeIdx(pClassDef.superclass_idx_); } if (gOptions.outputFormat == OUTPUT_PLAIN) { fprintf(gOutFile, "Class #%d -\n", idx); fprintf(gOutFile, " Class descriptor : '%s'\n", classDescriptor); fprintf(gOutFile, " Access flags : 0x%04x (%s)\n", pClassDef.access_flags_, accessStr); if (superclassDescriptor != nullptr) { fprintf(gOutFile, " Superclass : '%s'\n", superclassDescriptor); } fprintf(gOutFile, " Interfaces -\n"); } else { std::unique_ptr<char[]> dot(descriptorClassToDot(classDescriptor)); fprintf(gOutFile, "<class name=\"%s\"\n", dot.get()); if (superclassDescriptor != nullptr) { dot = descriptorToDot(superclassDescriptor); fprintf(gOutFile, " extends=\"%s\"\n", dot.get()); } fprintf(gOutFile, " interface=%s\n", quotedBool((pClassDef.access_flags_ & kAccInterface) != 0)); fprintf(gOutFile, " abstract=%s\n", quotedBool((pClassDef.access_flags_ & kAccAbstract) != 0)); fprintf(gOutFile, " static=%s\n", quotedBool((pClassDef.access_flags_ & kAccStatic) != 0)); fprintf(gOutFile, " final=%s\n", quotedBool((pClassDef.access_flags_ & kAccFinal) != 0)); // The "deprecated=" not knowable w/o parsing annotations. fprintf(gOutFile, " visibility=%s\n", quotedVisibility(pClassDef.access_flags_)); fprintf(gOutFile, ">\n"); } // Interfaces. const DexFile::TypeList* pInterfaces = pDexFile->GetInterfacesList(pClassDef); if (pInterfaces != nullptr) { for (u4 i = 0; i < pInterfaces->Size(); i++) { dumpInterface(pDexFile, pInterfaces->GetTypeItem(i), i); } // for } // Fields and methods. const u1* pEncodedData = pDexFile->GetClassData(pClassDef); if (pEncodedData == nullptr) { if (gOptions.outputFormat == OUTPUT_PLAIN) { fprintf(gOutFile, " Static fields -\n"); fprintf(gOutFile, " Instance fields -\n"); fprintf(gOutFile, " Direct methods -\n"); fprintf(gOutFile, " Virtual methods -\n"); } } else { ClassDataItemIterator pClassData(*pDexFile, pEncodedData); // Prepare data for static fields. const u1* sData = pDexFile->GetEncodedStaticFieldValuesArray(pClassDef); const u4 sSize = sData != nullptr ? DecodeUnsignedLeb128(&sData) : 0; // Static fields. if (gOptions.outputFormat == OUTPUT_PLAIN) { fprintf(gOutFile, " Static fields -\n"); } for (u4 i = 0; pClassData.HasNextStaticField(); i++, pClassData.Next()) { dumpSField(pDexFile, pClassData.GetMemberIndex(), pClassData.GetRawMemberAccessFlags(), i, i < sSize ? &sData : nullptr); } // for // Instance fields. if (gOptions.outputFormat == OUTPUT_PLAIN) { fprintf(gOutFile, " Instance fields -\n"); } for (u4 i = 0; pClassData.HasNextInstanceField(); i++, pClassData.Next()) { dumpIField(pDexFile, pClassData.GetMemberIndex(), pClassData.GetRawMemberAccessFlags(), i); } // for // Direct methods. if (gOptions.outputFormat == OUTPUT_PLAIN) { fprintf(gOutFile, " Direct methods -\n"); } for (int i = 0; pClassData.HasNextDirectMethod(); i++, pClassData.Next()) { dumpMethod(pDexFile, pClassData.GetMemberIndex(), pClassData.GetRawMemberAccessFlags(), pClassData.GetMethodCodeItem(), pClassData.GetMethodCodeItemOffset(), i); } // for // Virtual methods. if (gOptions.outputFormat == OUTPUT_PLAIN) { fprintf(gOutFile, " Virtual methods -\n"); } for (int i = 0; pClassData.HasNextVirtualMethod(); i++, pClassData.Next()) { dumpMethod(pDexFile, pClassData.GetMemberIndex(), pClassData.GetRawMemberAccessFlags(), pClassData.GetMethodCodeItem(), pClassData.GetMethodCodeItemOffset(), i); } // for } // End of class. if (gOptions.outputFormat == OUTPUT_PLAIN) { const char* fileName; if (pClassDef.source_file_idx_.IsValid()) { fileName = pDexFile->StringDataByIdx(pClassDef.source_file_idx_); } else { fileName = "unknown"; } fprintf(gOutFile, " source_file_idx : %d (%s)\n\n", pClassDef.source_file_idx_.index_, fileName); } else if (gOptions.outputFormat == OUTPUT_XML) { fprintf(gOutFile, "</class>\n"); } free(accessStr); } static void dumpMethodHandle(const DexFile* pDexFile, u4 idx) { const DexFile::MethodHandleItem& mh = pDexFile->GetMethodHandle(idx); bool is_invoke = false; const char* type; switch (static_cast<DexFile::MethodHandleType>(mh.method_handle_type_)) { case DexFile::MethodHandleType::kStaticPut: type = "put-static"; break; case DexFile::MethodHandleType::kStaticGet: type = "get-static"; break; case DexFile::MethodHandleType::kInstancePut: type = "put-instance"; break; case DexFile::MethodHandleType::kInstanceGet: type = "get-instance"; break; case DexFile::MethodHandleType::kInvokeStatic: type = "invoke-static"; is_invoke = true; break; case DexFile::MethodHandleType::kInvokeInstance: type = "invoke-instance"; is_invoke = true; break; case DexFile::MethodHandleType::kInvokeConstructor: type = "invoke-constructor"; is_invoke = true; break; } const char* declaring_class; const char* member; std::string member_type; if (is_invoke) { const DexFile::MethodId& method_id = pDexFile->GetMethodId(mh.field_or_method_idx_); declaring_class = pDexFile->GetMethodDeclaringClassDescriptor(method_id); member = pDexFile->GetMethodName(method_id); member_type = pDexFile->GetMethodSignature(method_id).ToString(); } else { const DexFile::FieldId& field_id = pDexFile->GetFieldId(mh.field_or_method_idx_); declaring_class = pDexFile->GetFieldDeclaringClassDescriptor(field_id); member = pDexFile->GetFieldName(field_id); member_type = pDexFile->GetFieldTypeDescriptor(field_id); } if (gOptions.outputFormat == OUTPUT_PLAIN) { fprintf(gOutFile, "Method handle #%u:\n", idx); fprintf(gOutFile, " type : %s\n", type); fprintf(gOutFile, " target : %s %s\n", declaring_class, member); fprintf(gOutFile, " target_type : %s\n", member_type.c_str()); } else { fprintf(gOutFile, "<method_handle index=\"%u\"\n", idx); fprintf(gOutFile, " type=\"%s\"\n", type); fprintf(gOutFile, " target_class=\"%s\"\n", declaring_class); fprintf(gOutFile, " target_member=\"%s\"\n", member); fprintf(gOutFile, " target_member_type="); dumpEscapedString(member_type.c_str()); fprintf(gOutFile, "\n>\n</method_handle>\n"); } } static void dumpCallSite(const DexFile* pDexFile, u4 idx) { const DexFile::CallSiteIdItem& call_site_id = pDexFile->GetCallSiteId(idx); CallSiteArrayValueIterator it(*pDexFile, call_site_id); if (it.Size() < 3) { fprintf(stderr, "ERROR: Call site %u has too few values.\n", idx); return; } uint32_t method_handle_idx = static_cast<uint32_t>(it.GetJavaValue().i); it.Next(); dex::StringIndex method_name_idx = static_cast<dex::StringIndex>(it.GetJavaValue().i); const char* method_name = pDexFile->StringDataByIdx(method_name_idx); it.Next(); uint32_t method_type_idx = static_cast<uint32_t>(it.GetJavaValue().i); const DexFile::ProtoId& method_type_id = pDexFile->GetProtoId(method_type_idx); std::string method_type = pDexFile->GetProtoSignature(method_type_id).ToString(); it.Next(); if (gOptions.outputFormat == OUTPUT_PLAIN) { fprintf(gOutFile, "Call site #%u:\n", idx); fprintf(gOutFile, " link_argument[0] : %u (MethodHandle)\n", method_handle_idx); fprintf(gOutFile, " link_argument[1] : %s (String)\n", method_name); fprintf(gOutFile, " link_argument[2] : %s (MethodType)\n", method_type.c_str()); } else { fprintf(gOutFile, "<call_site index=\"%u\">\n", idx); fprintf(gOutFile, "<link_argument index=\"0\" type=\"MethodHandle\" value=\"%u\"/>\n", method_handle_idx); fprintf(gOutFile, "<link_argument index=\"1\" type=\"String\" values=\"%s\"/>\n", method_name); fprintf(gOutFile, "<link_argument index=\"2\" type=\"MethodType\" value=\"%s\"/>\n", method_type.c_str()); } size_t argument = 3; while (it.HasNext()) { const char* type; std::string value; switch (it.GetValueType()) { case EncodedArrayValueIterator::ValueType::kByte: type = "byte"; value = android::base::StringPrintf("%u", it.GetJavaValue().b); break; case EncodedArrayValueIterator::ValueType::kShort: type = "short"; value = android::base::StringPrintf("%d", it.GetJavaValue().s); break; case EncodedArrayValueIterator::ValueType::kChar: type = "char"; value = android::base::StringPrintf("%u", it.GetJavaValue().c); break; case EncodedArrayValueIterator::ValueType::kInt: type = "int"; value = android::base::StringPrintf("%d", it.GetJavaValue().i); break; case EncodedArrayValueIterator::ValueType::kLong: type = "long"; value = android::base::StringPrintf("%" PRId64, it.GetJavaValue().j); break; case EncodedArrayValueIterator::ValueType::kFloat: type = "float"; value = android::base::StringPrintf("%g", it.GetJavaValue().f); break; case EncodedArrayValueIterator::ValueType::kDouble: type = "double"; value = android::base::StringPrintf("%g", it.GetJavaValue().d); break; case EncodedArrayValueIterator::ValueType::kMethodType: { type = "MethodType"; uint32_t proto_idx = static_cast<uint32_t>(it.GetJavaValue().i); const DexFile::ProtoId& proto_id = pDexFile->GetProtoId(proto_idx); value = pDexFile->GetProtoSignature(proto_id).ToString(); break; } case EncodedArrayValueIterator::ValueType::kMethodHandle: type = "MethodHandle"; value = android::base::StringPrintf("%d", it.GetJavaValue().i); break; case EncodedArrayValueIterator::ValueType::kString: { type = "String"; dex::StringIndex string_idx = static_cast<dex::StringIndex>(it.GetJavaValue().i); value = pDexFile->StringDataByIdx(string_idx); break; } case EncodedArrayValueIterator::ValueType::kType: { type = "Class"; dex::TypeIndex type_idx = static_cast<dex::TypeIndex>(it.GetJavaValue().i); const DexFile::ClassDef* class_def = pDexFile->FindClassDef(type_idx); value = pDexFile->GetClassDescriptor(*class_def); value = descriptorClassToDot(value.c_str()).get(); break; } case EncodedArrayValueIterator::ValueType::kField: case EncodedArrayValueIterator::ValueType::kMethod: case EncodedArrayValueIterator::ValueType::kEnum: case EncodedArrayValueIterator::ValueType::kArray: case EncodedArrayValueIterator::ValueType::kAnnotation: // Unreachable based on current EncodedArrayValueIterator::Next(). UNIMPLEMENTED(FATAL) << " type " << type; UNREACHABLE(); break; case EncodedArrayValueIterator::ValueType::kNull: type = "Null"; value = "null"; break; case EncodedArrayValueIterator::ValueType::kBoolean: type = "boolean"; value = it.GetJavaValue().z ? "true" : "false"; break; } if (gOptions.outputFormat == OUTPUT_PLAIN) { fprintf(gOutFile, " link_argument[%zu] : %s (%s)\n", argument, value.c_str(), type); } else { fprintf(gOutFile, "<link_argument index=\"%zu\" type=\"%s\" value=", argument, type); dumpEscapedString(value.c_str()); fprintf(gOutFile, "/>\n"); } it.Next(); argument++; } if (gOptions.outputFormat == OUTPUT_XML) { fprintf(gOutFile, "</call_site>\n"); } } /* * Dumps the requested sections of the file. */ static void processDexFile(const char* fileName, const DexFile* pDexFile, size_t i, size_t n) { if (gOptions.verbose) { fputs("Opened '", gOutFile); fputs(fileName, gOutFile); if (n > 1) { fprintf(gOutFile, ":%s", DexFile::GetMultiDexClassesDexName(i).c_str()); } fprintf(gOutFile, "', DEX version '%.3s'\n", pDexFile->GetHeader().magic_ + 4); } // Headers. if (gOptions.showFileHeaders) { dumpFileHeader(pDexFile); } // Open XML context. if (gOptions.outputFormat == OUTPUT_XML) { fprintf(gOutFile, "<api>\n"); } // Iterate over all classes. char* package = nullptr; const u4 classDefsSize = pDexFile->GetHeader().class_defs_size_; for (u4 i = 0; i < classDefsSize; i++) { dumpClass(pDexFile, i, &package); } // for // Iterate over all method handles. for (u4 i = 0; i < pDexFile->NumMethodHandles(); ++i) { dumpMethodHandle(pDexFile, i); } // for // Iterate over all call site ids. for (u4 i = 0; i < pDexFile->NumCallSiteIds(); ++i) { dumpCallSite(pDexFile, i); } // for // Free the last package allocated. if (package != nullptr) { fprintf(gOutFile, "</package>\n"); free(package); } // Close XML context. if (gOptions.outputFormat == OUTPUT_XML) { fprintf(gOutFile, "</api>\n"); } } /* * Processes a single file (either direct .dex or indirect .zip/.jar/.apk). */ int processFile(const char* fileName) { if (gOptions.verbose) { fprintf(gOutFile, "Processing '%s'...\n", fileName); } // If the file is not a .dex file, the function tries .zip/.jar/.apk files, // all of which are Zip archives with "classes.dex" inside. const bool kVerifyChecksum = !gOptions.ignoreBadChecksum; std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files; if (!DexFile::Open(fileName, fileName, kVerifyChecksum, &error_msg, &dex_files)) { // Display returned error message to user. Note that this error behavior // differs from the error messages shown by the original Dalvik dexdump. fputs(error_msg.c_str(), stderr); fputc('\n', stderr); return -1; } // Success. Either report checksum verification or process // all dex files found in given file. if (gOptions.checksumOnly) { fprintf(gOutFile, "Checksum verified\n"); } else { for (size_t i = 0, n = dex_files.size(); i < n; i++) { processDexFile(fileName, dex_files[i].get(), i, n); } } return 0; } } // namespace art