diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2020-04-28 20:27:19 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2020-04-28 20:27:19 +0000 |
commit | f9e8e0e80832d630d3b67368ce386b60883d3016 (patch) | |
tree | 73ea7b17d529b31d2818cbdafdd46d9c83d9280b | |
parent | 064265659b79fb290b760409c18445b340202ac1 (diff) | |
parent | 9a1d3ec50905d5dabbd1ce393e5715df43cc3d1e (diff) | |
download | dalvik-android10-android13-mainline-tzdata-release.tar.gz |
Snap for 6439596 from 9a1d3ec50905d5dabbd1ce393e5715df43cc3d1e to qt-aml-tzdata-releaseq_tzdata_aml_297100400q_tzdata_aml_297100300q_tzdata_aml_297100000q_tzdata_aml_296200000q_tzdata_aml_295600118q_tzdata_aml_295600110q_tzdata_aml_295500002q_tzdata_aml_295500001q_tzdata_aml_294400310android-mainline-12.0.0_r54android-mainline-12.0.0_r111android-mainline-10.0.0_r13android-mainline-10.0.0_r12android-mainline-10.0.0_r11q_tzdata_aml_297100000android12-mainline-tzdata-releaseandroid10-mainline-tzdata-releaseandroid10-android13-mainline-tzdata-release
Change-Id: I5de0589107877e5a67a2a28acc2bb53fa64bd77a
45 files changed, 13555 insertions, 87 deletions
diff --git a/Android.bp b/Android.bp index 702d27da4..44a1433b8 100644 --- a/Android.bp +++ b/Android.bp @@ -1,4 +1,6 @@ subdirs = [ + "dexdump", "dx", + "libdex", "tools/hprof-conv", ] @@ -1,9 +1,10 @@ +benoitlamarche@google.com +jplesot@google.com +yroussel@google.com # Studio gavra@google.com cmw@google.com # ART -mathieuc@google.com narayan@google.com ngeoffray@google.com oth@google.com -sehr@google.com diff --git a/dexdump/Android.bp b/dexdump/Android.bp new file mode 100644 index 000000000..2dbb1e42d --- /dev/null +++ b/dexdump/Android.bp @@ -0,0 +1,53 @@ +// 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. + +// +// dexdump, similar in purpose to objdump. +// + +cc_binary { + name: "dexdump", + host_supported: true, + + srcs: ["DexDump.cpp"], + include_dirs: ["dalvik"], + + cflags: [ + "-Wall", + "-Werror", + ], + target: { + android: { + static_libs: [ + "libdex", + "libbase", + ], + shared_libs: [ + "libz", + "liblog", + ], + }, + host: { + static_libs: [ + "libdex", + "libbase", + "liblog", + "libz", + ], + }, + windows: { + enabled: true, + }, + }, +} diff --git a/dexdump/DexDump.cpp b/dexdump/DexDump.cpp new file mode 100644 index 000000000..9b1ac3ab1 --- /dev/null +++ b/dexdump/DexDump.cpp @@ -0,0 +1,2287 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The "dexdump" tool is intended to mimic "objdump". When possible, use + * similar command-line arguments. + * + * TODO: rework the "plain" output format to be more regexp-friendly + * + * Differences between XML output and the "current.xml" file: + * - classes in same package are not all grouped together; generally speaking + * nothing is sorted + * - no "deprecated" on fields and methods + * - no "value" on fields + * - no parameter names + * - no generic signatures on parameters, e.g. type="java.lang.Class<?>" + * - class shows declared fields and methods; does not show inherited fields + */ + +#include "libdex/DexFile.h" + +#include "libdex/CmdUtils.h" +#include "libdex/DexCatch.h" +#include "libdex/DexClass.h" +#include "libdex/DexDebugInfo.h" +#include "libdex/DexOpcodes.h" +#include "libdex/DexProto.h" +#include "libdex/InstrUtils.h" +#include "libdex/SysUtil.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> +#include <assert.h> +#include <inttypes.h> + +static const char* gProgName = "dexdump"; + +enum OutputFormat { + OUTPUT_PLAIN = 0, /* default */ + OUTPUT_XML, /* fancy */ +}; + +/* command-line options */ +struct Options { + bool checksumOnly; + bool disassemble; + bool showFileHeaders; + bool showSectionHeaders; + bool ignoreBadChecksum; + bool dumpRegisterMaps; + OutputFormat outputFormat; + const char* tempFileName; + bool exportsOnly; + bool verbose; +}; + +struct Options gOptions; + +/* basic info about a field or method */ +struct FieldMethodInfo { + const char* classDescriptor; + const char* name; + const char* signature; +}; + + +/* basic info about a prototype */ +struct ProtoInfo { + char* parameterTypes; // dynamically allocated with malloc + const char* returnType; +}; + +/* + * Get 2 little-endian bytes. + */ +static inline u2 get2LE(unsigned char const* pSrc) +{ + return pSrc[0] | (pSrc[1] << 8); +} + +/* + * Get 4 little-endian bytes. + */ +static inline u4 get4LE(unsigned char const* pSrc) +{ + return pSrc[0] | (pSrc[1] << 8) | (pSrc[2] << 16) | (pSrc[3] << 24); +} + +/* + * Converts a single-character primitive type into its human-readable + * equivalent. + */ +static const char* primitiveTypeLabel(char typeChar) +{ + switch (typeChar) { + case 'B': return "byte"; + case 'C': return "char"; + case 'D': return "double"; + case 'F': return "float"; + case 'I': return "int"; + case 'J': return "long"; + case 'S': return "short"; + case 'V': return "void"; + case 'Z': return "boolean"; + default: + return "UNKNOWN"; + } +} + +/* + * Converts a type descriptor to human-readable "dotted" form. For + * example, "Ljava/lang/String;" becomes "java.lang.String", and + * "[I" becomes "int[]". + */ +static char* descriptorToDot(const char* str) +{ + int targetLen = strlen(str); + int offset = 0; + int arrayDepth = 0; + char* newStr; + + /* strip leading [s; will be added to end */ + while (targetLen > 1 && str[offset] == '[') { + offset++; + targetLen--; + } + arrayDepth = offset; + + if (targetLen == 1) { + /* primitive type */ + str = primitiveTypeLabel(str[offset]); + offset = 0; + targetLen = strlen(str); + } else { + /* account for leading 'L' and trailing ';' */ + if (targetLen >= 2 && str[offset] == 'L' && + str[offset+targetLen-1] == ';') + { + targetLen -= 2; + offset++; + } + } + + newStr = (char*)malloc(targetLen + arrayDepth * 2 +1); + + /* copy class name over */ + int i; + for (i = 0; i < targetLen; i++) { + char ch = str[offset + i]; + newStr[i] = (ch == '/') ? '.' : ch; + } + + /* 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; +} + +/* + * Retrieves the class name portion of a type descriptor. + * + * Returns a newly-allocated string. + */ +static char* descriptorClassToName(const char* str) +{ + const char* lastSlash; + char* newStr; + + /* 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'; + + return newStr; +} + +/* + * Returns a quoted string representing the boolean value. + */ +static const char* quotedBool(bool val) +{ + if (val) + return "\"true\""; + else + return "\"false\""; +} + +static const char* quotedVisibility(u4 accessFlags) +{ + if ((accessFlags & ACC_PUBLIC) != 0) + return "\"public\""; + else if ((accessFlags & ACC_PROTECTED) != 0) + return "\"protected\""; + else if ((accessFlags & ACC_PRIVATE) != 0) + return "\"private\""; + else + return "\"package\""; +} + +/* + * Count the number of '1' bits in a word. + */ +static int countOnes(u4 val) +{ + int count = 0; + + val = val - ((val >> 1) & 0x55555555); + val = (val & 0x33333333) + ((val >> 2) & 0x33333333); + count = (((val + (val >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; + + return count; +} + +/* + * Flag for use with createAccessFlagStr(). + */ +enum AccessFor { + kAccessForClass = 0, kAccessForMethod = 1, kAccessForField = 2, + kAccessForMAX +}; + +/* + * Create a new string with human-readable access flags. + * + * In the base language the access_flags fields are type u2; in Dalvik + * they're u4. + */ +static char* createAccessFlagStr(u4 flags, AccessFor forWhat) +{ +#define NUM_FLAGS 18 + static const char* kAccessStrings[kAccessForMAX][NUM_FLAGS] = { + { + /* class, inner class */ + "PUBLIC", /* 0x0001 */ + "PRIVATE", /* 0x0002 */ + "PROTECTED", /* 0x0004 */ + "STATIC", /* 0x0008 */ + "FINAL", /* 0x0010 */ + "?", /* 0x0020 */ + "?", /* 0x0040 */ + "?", /* 0x0080 */ + "?", /* 0x0100 */ + "INTERFACE", /* 0x0200 */ + "ABSTRACT", /* 0x0400 */ + "?", /* 0x0800 */ + "SYNTHETIC", /* 0x1000 */ + "ANNOTATION", /* 0x2000 */ + "ENUM", /* 0x4000 */ + "?", /* 0x8000 */ + "VERIFIED", /* 0x10000 */ + "OPTIMIZED", /* 0x20000 */ + }, + { + /* method */ + "PUBLIC", /* 0x0001 */ + "PRIVATE", /* 0x0002 */ + "PROTECTED", /* 0x0004 */ + "STATIC", /* 0x0008 */ + "FINAL", /* 0x0010 */ + "SYNCHRONIZED", /* 0x0020 */ + "BRIDGE", /* 0x0040 */ + "VARARGS", /* 0x0080 */ + "NATIVE", /* 0x0100 */ + "?", /* 0x0200 */ + "ABSTRACT", /* 0x0400 */ + "STRICT", /* 0x0800 */ + "SYNTHETIC", /* 0x1000 */ + "?", /* 0x2000 */ + "?", /* 0x4000 */ + "MIRANDA", /* 0x8000 */ + "CONSTRUCTOR", /* 0x10000 */ + "DECLARED_SYNCHRONIZED", /* 0x20000 */ + }, + { + /* field */ + "PUBLIC", /* 0x0001 */ + "PRIVATE", /* 0x0002 */ + "PROTECTED", /* 0x0004 */ + "STATIC", /* 0x0008 */ + "FINAL", /* 0x0010 */ + "?", /* 0x0020 */ + "VOLATILE", /* 0x0040 */ + "TRANSIENT", /* 0x0080 */ + "?", /* 0x0100 */ + "?", /* 0x0200 */ + "?", /* 0x0400 */ + "?", /* 0x0800 */ + "SYNTHETIC", /* 0x1000 */ + "?", /* 0x2000 */ + "ENUM", /* 0x4000 */ + "?", /* 0x8000 */ + "?", /* 0x10000 */ + "?", /* 0x20000 */ + }, + }; + const int kLongest = 21; /* strlen of longest string above */ + int i, count; + char* str; + char* cp; + + /* + * Allocate enough storage to hold the expected number of strings, + * plus a space between each. We over-allocate, using the longest + * string above as the base metric. + */ + count = countOnes(flags); + cp = str = (char*) malloc(count * (kLongest+1) +1); + + for (i = 0; i < NUM_FLAGS; i++) { + if (flags & 0x01) { + const char* accessStr = kAccessStrings[forWhat][i]; + int len = strlen(accessStr); + if (cp != str) + *cp++ = ' '; + + memcpy(cp, accessStr, len); + cp += len; + } + flags >>= 1; + } + *cp = '\0'; + + return str; +} + + +/* + * Copy character data from "data" to "out", converting non-ASCII values + * to printf format chars or an ASCII filler ('.' or '?'). + * + * The output buffer must be able to hold (2*len)+1 bytes. The result is + * NUL-terminated. + */ +static void asciify(char* out, const unsigned char* data, size_t len) +{ + while (len--) { + if (*data < 0x20) { + /* could do more here, but we don't need them yet */ + switch (*data) { + case '\0': + *out++ = '\\'; + *out++ = '0'; + break; + case '\n': + *out++ = '\\'; + *out++ = 'n'; + break; + default: + *out++ = '.'; + break; + } + } else if (*data >= 0x80) { + *out++ = '?'; + } else { + *out++ = *data; + } + data++; + } + *out = '\0'; +} + +/* + * Dump the file header. + */ +void dumpFileHeader(const DexFile* pDexFile) +{ + const DexOptHeader* pOptHeader = pDexFile->pOptHeader; + const DexHeader* pHeader = pDexFile->pHeader; + char sanitized[sizeof(pHeader->magic)*2 +1]; + + assert(sizeof(pHeader->magic) == sizeof(pOptHeader->magic)); + + if (pOptHeader != NULL) { + printf("Optimized DEX file header:\n"); + + asciify(sanitized, pOptHeader->magic, sizeof(pOptHeader->magic)); + printf("magic : '%s'\n", sanitized); + printf("dex_offset : %d (0x%06x)\n", + pOptHeader->dexOffset, pOptHeader->dexOffset); + printf("dex_length : %d\n", pOptHeader->dexLength); + printf("deps_offset : %d (0x%06x)\n", + pOptHeader->depsOffset, pOptHeader->depsOffset); + printf("deps_length : %d\n", pOptHeader->depsLength); + printf("opt_offset : %d (0x%06x)\n", + pOptHeader->optOffset, pOptHeader->optOffset); + printf("opt_length : %d\n", pOptHeader->optLength); + printf("flags : %08x\n", pOptHeader->flags); + printf("checksum : %08x\n", pOptHeader->checksum); + printf("\n"); + } + + printf("DEX file header:\n"); + asciify(sanitized, pHeader->magic, sizeof(pHeader->magic)); + printf("magic : '%s'\n", sanitized); + printf("checksum : %08x\n", pHeader->checksum); + printf("signature : %02x%02x...%02x%02x\n", + pHeader->signature[0], pHeader->signature[1], + pHeader->signature[kSHA1DigestLen-2], + pHeader->signature[kSHA1DigestLen-1]); + printf("file_size : %d\n", pHeader->fileSize); + printf("header_size : %d\n", pHeader->headerSize); + printf("link_size : %d\n", pHeader->linkSize); + printf("link_off : %d (0x%06x)\n", + pHeader->linkOff, pHeader->linkOff); + printf("string_ids_size : %d\n", pHeader->stringIdsSize); + printf("string_ids_off : %d (0x%06x)\n", + pHeader->stringIdsOff, pHeader->stringIdsOff); + printf("type_ids_size : %d\n", pHeader->typeIdsSize); + printf("type_ids_off : %d (0x%06x)\n", + pHeader->typeIdsOff, pHeader->typeIdsOff); + printf("proto_ids_size : %d\n", pHeader->protoIdsSize); + printf("proto_ids_off : %d (0x%06x)\n", + pHeader->protoIdsOff, pHeader->protoIdsOff); + printf("field_ids_size : %d\n", pHeader->fieldIdsSize); + printf("field_ids_off : %d (0x%06x)\n", + pHeader->fieldIdsOff, pHeader->fieldIdsOff); + printf("method_ids_size : %d\n", pHeader->methodIdsSize); + printf("method_ids_off : %d (0x%06x)\n", + pHeader->methodIdsOff, pHeader->methodIdsOff); + printf("class_defs_size : %d\n", pHeader->classDefsSize); + printf("class_defs_off : %d (0x%06x)\n", + pHeader->classDefsOff, pHeader->classDefsOff); + printf("data_size : %d\n", pHeader->dataSize); + printf("data_off : %d (0x%06x)\n", + pHeader->dataOff, pHeader->dataOff); + printf("\n"); +} + +/* + * Dump the "table of contents" for the opt area. + */ +void dumpOptDirectory(const DexFile* pDexFile) +{ + const DexOptHeader* pOptHeader = pDexFile->pOptHeader; + if (pOptHeader == NULL) + return; + + printf("OPT section contents:\n"); + + const u4* pOpt = (const u4*) ((u1*) pOptHeader + pOptHeader->optOffset); + + if (*pOpt == 0) { + printf("(1.0 format, only class lookup table is present)\n\n"); + return; + } + + /* + * The "opt" section is in "chunk" format: a 32-bit identifier, a 32-bit + * length, then the data. Chunks start on 64-bit boundaries. + */ + while (*pOpt != kDexChunkEnd) { + const char* verboseStr; + + u4 size = *(pOpt+1); + + switch (*pOpt) { + case kDexChunkClassLookup: + verboseStr = "class lookup hash table"; + break; + case kDexChunkRegisterMaps: + verboseStr = "register maps"; + break; + default: + verboseStr = "(unknown chunk type)"; + break; + } + + printf("Chunk %08x (%c%c%c%c) - %s (%d bytes)\n", *pOpt, + *pOpt >> 24, (char)(*pOpt >> 16), (char)(*pOpt >> 8), (char)*pOpt, + verboseStr, size); + + size = (size + 8 + 7) & ~7; + pOpt += size / sizeof(u4); + } + printf("\n"); +} + +/* + * Dump a class_def_item. + */ +void dumpClassDef(DexFile* pDexFile, int idx) +{ + const DexClassDef* pClassDef; + const u1* pEncodedData; + DexClassData* pClassData; + + pClassDef = dexGetClassDef(pDexFile, idx); + pEncodedData = dexGetClassData(pDexFile, pClassDef); + pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL); + + if (pClassData == NULL) { + fprintf(stderr, "Trouble reading class data\n"); + return; + } + + printf("Class #%d header:\n", idx); + printf("class_idx : %d\n", pClassDef->classIdx); + printf("access_flags : %d (0x%04x)\n", + pClassDef->accessFlags, pClassDef->accessFlags); + printf("superclass_idx : %d\n", pClassDef->superclassIdx); + printf("interfaces_off : %d (0x%06x)\n", + pClassDef->interfacesOff, pClassDef->interfacesOff); + printf("source_file_idx : %d\n", pClassDef->sourceFileIdx); + printf("annotations_off : %d (0x%06x)\n", + pClassDef->annotationsOff, pClassDef->annotationsOff); + printf("class_data_off : %d (0x%06x)\n", + pClassDef->classDataOff, pClassDef->classDataOff); + printf("static_fields_size : %d\n", pClassData->header.staticFieldsSize); + printf("instance_fields_size: %d\n", + pClassData->header.instanceFieldsSize); + printf("direct_methods_size : %d\n", pClassData->header.directMethodsSize); + printf("virtual_methods_size: %d\n", + pClassData->header.virtualMethodsSize); + printf("\n"); + + free(pClassData); +} + +/* + * Dump an interface that a class declares to implement. + */ +void dumpInterface(const DexFile* pDexFile, const DexTypeItem* pTypeItem, + int i) +{ + const char* interfaceName = + dexStringByTypeIdx(pDexFile, pTypeItem->typeIdx); + + if (gOptions.outputFormat == OUTPUT_PLAIN) { + printf(" #%d : '%s'\n", i, interfaceName); + } else { + char* dotted = descriptorToDot(interfaceName); + printf("<implements name=\"%s\">\n</implements>\n", dotted); + free(dotted); + } +} + +/* + * Dump the catches table associated with the code. + */ +void dumpCatches(DexFile* pDexFile, const DexCode* pCode) +{ + u4 triesSize = pCode->triesSize; + + if (triesSize == 0) { + printf(" catches : (none)\n"); + return; + } + + printf(" catches : %d\n", triesSize); + + const DexTry* pTries = dexGetTries(pCode); + u4 i; + + for (i = 0; i < triesSize; i++) { + const DexTry* pTry = &pTries[i]; + u4 start = pTry->startAddr; + u4 end = start + pTry->insnCount; + DexCatchIterator iterator; + + printf(" 0x%04x - 0x%04x\n", start, end); + + dexCatchIteratorInit(&iterator, pCode, pTry->handlerOff); + + for (;;) { + DexCatchHandler* handler = dexCatchIteratorNext(&iterator); + const char* descriptor; + + if (handler == NULL) { + break; + } + + descriptor = (handler->typeIdx == kDexNoIndex) ? "<any>" : + dexStringByTypeIdx(pDexFile, handler->typeIdx); + + printf(" %s -> 0x%04x\n", descriptor, + handler->address); + } + } +} + +static int dumpPositionsCb(void * /* cnxt */, u4 address, u4 lineNum) +{ + printf(" 0x%04x line=%d\n", address, lineNum); + return 0; +} + +/* + * Dump the positions list. + */ +void dumpPositions(DexFile* pDexFile, const DexCode* pCode, + const DexMethod *pDexMethod) +{ + printf(" positions : \n"); + const DexMethodId *pMethodId + = dexGetMethodId(pDexFile, pDexMethod->methodIdx); + const char *classDescriptor + = dexStringByTypeIdx(pDexFile, pMethodId->classIdx); + + dexDecodeDebugInfo(pDexFile, pCode, classDescriptor, pMethodId->protoIdx, + pDexMethod->accessFlags, dumpPositionsCb, NULL, NULL); +} + +static void dumpLocalsCb(void * /* cnxt */, u2 reg, u4 startAddress, + u4 endAddress, const char *name, const char *descriptor, + const char *signature) +{ + printf(" 0x%04x - 0x%04x reg=%d %s %s %s\n", + startAddress, endAddress, reg, name, descriptor, + signature); +} + +/* + * Dump the locals list. + */ +void dumpLocals(DexFile* pDexFile, const DexCode* pCode, + const DexMethod *pDexMethod) +{ + printf(" locals : \n"); + + const DexMethodId *pMethodId + = dexGetMethodId(pDexFile, pDexMethod->methodIdx); + const char *classDescriptor + = dexStringByTypeIdx(pDexFile, pMethodId->classIdx); + + dexDecodeDebugInfo(pDexFile, pCode, classDescriptor, pMethodId->protoIdx, + pDexMethod->accessFlags, NULL, dumpLocalsCb, NULL); +} + +/* + * Get information about a method. + */ +bool getMethodInfo(DexFile* pDexFile, u4 methodIdx, FieldMethodInfo* pMethInfo) +{ + const DexMethodId* pMethodId; + + if (methodIdx >= pDexFile->pHeader->methodIdsSize) + return false; + + pMethodId = dexGetMethodId(pDexFile, methodIdx); + pMethInfo->name = dexStringById(pDexFile, pMethodId->nameIdx); + pMethInfo->signature = dexCopyDescriptorFromMethodId(pDexFile, pMethodId); + + pMethInfo->classDescriptor = + dexStringByTypeIdx(pDexFile, pMethodId->classIdx); + return true; +} + +/* + * Get information about a field. + */ +bool getFieldInfo(DexFile* pDexFile, u4 fieldIdx, FieldMethodInfo* pFieldInfo) +{ + const DexFieldId* pFieldId; + + if (fieldIdx >= pDexFile->pHeader->fieldIdsSize) + return false; + + pFieldId = dexGetFieldId(pDexFile, fieldIdx); + pFieldInfo->name = dexStringById(pDexFile, pFieldId->nameIdx); + pFieldInfo->signature = dexStringByTypeIdx(pDexFile, pFieldId->typeIdx); + pFieldInfo->classDescriptor = + dexStringByTypeIdx(pDexFile, pFieldId->classIdx); + return true; +} + +/* + * Get information about a ProtoId. + */ +bool getProtoInfo(DexFile* pDexFile, u4 protoIdx, ProtoInfo* pProtoInfo) +{ + if (protoIdx >= pDexFile->pHeader->protoIdsSize) { + return false; + } + + const DexProtoId* protoId = dexGetProtoId(pDexFile, protoIdx); + + // Get string for return type. + if (protoId->returnTypeIdx >= pDexFile->pHeader->typeIdsSize) { + return false; + } + pProtoInfo->returnType = dexStringByTypeIdx(pDexFile, protoId->returnTypeIdx); + + // Build string for parameter types. + size_t bufSize = 1; + char* buf = (char*)malloc(bufSize); + if (buf == NULL) { + return false; + } + + buf[0] = '\0'; + size_t bufUsed = 1; + + const DexTypeList* paramTypes = dexGetProtoParameters(pDexFile, protoId); + if (paramTypes == NULL) { + // No parameters. + pProtoInfo->parameterTypes = buf; + return true; + } + + for (u4 i = 0; i < paramTypes->size; ++i) { + if (paramTypes->list[i].typeIdx >= pDexFile->pHeader->typeIdsSize) { + free(buf); + return false; + } + const char* param = dexStringByTypeIdx(pDexFile, paramTypes->list[i].typeIdx); + size_t paramLen = strlen(param); + size_t newUsed = bufUsed + paramLen; + if (newUsed > bufSize) { + char* newBuf = (char*)realloc(buf, newUsed); + if (newBuf == NULL) { + free(buf); + return false; + } + buf = newBuf; + bufSize = newUsed; + } + memcpy(buf + bufUsed - 1, param, paramLen + 1); + bufUsed = newUsed; + } + + pProtoInfo->parameterTypes = buf; + return true; +} + +/* + * Look up a class' descriptor. + */ +const char* getClassDescriptor(DexFile* pDexFile, u4 classIdx) +{ + return dexStringByTypeIdx(pDexFile, classIdx); +} + +/* + * Helper for dumpInstruction(), which builds the string + * representation for the index in the given instruction. This will + * first try to use the given buffer, but if the result won't fit, + * then this will allocate a new buffer to hold the result. A pointer + * to the buffer which holds the full result is always returned, and + * this can be compared with the one passed in, to see if the result + * needs to be free()d. + */ +static char* indexString(DexFile* pDexFile, const DecodedInstruction* pDecInsn, size_t bufSize) +{ + char* buf = (char*)malloc(bufSize); + if (buf == NULL) { + return NULL; + } + + int outSize; + u4 index; + u4 secondaryIndex = 0; + u4 width; + + /* TODO: Make the index *always* be in field B, to simplify this code. */ + switch (dexGetFormatFromOpcode(pDecInsn->opcode)) { + case kFmt20bc: + case kFmt21c: + case kFmt35c: + case kFmt35ms: + case kFmt3rc: + case kFmt3rms: + case kFmt35mi: + case kFmt3rmi: + index = pDecInsn->vB; + width = 4; + break; + case kFmt31c: + index = pDecInsn->vB; + width = 8; + break; + case kFmt22c: + case kFmt22cs: + index = pDecInsn->vC; + width = 4; + break; + case kFmt45cc: + case kFmt4rcc: + index = pDecInsn->vB; // method index + secondaryIndex = pDecInsn->arg[4]; // proto index + width = 4; + break; + default: + index = 0; + width = 4; + break; + } + + switch (pDecInsn->indexType) { + case kIndexUnknown: + /* + * This function shouldn't ever get called for this type, but do + * something sensible here, just to help with debugging. + */ + outSize = snprintf(buf, bufSize, "<unknown-index>"); + break; + case kIndexNone: + /* + * This function shouldn't ever get called for this type, but do + * something sensible here, just to help with debugging. + */ + outSize = snprintf(buf, bufSize, "<no-index>"); + break; + case kIndexVaries: + /* + * This one should never show up in a dexdump, so no need to try + * to get fancy here. + */ + outSize = snprintf(buf, bufSize, "<index-varies> // thing@%0*x", + width, index); + break; + case kIndexTypeRef: + if (index < pDexFile->pHeader->typeIdsSize) { + outSize = snprintf(buf, bufSize, "%s // type@%0*x", + getClassDescriptor(pDexFile, index), width, index); + } else { + outSize = snprintf(buf, bufSize, "<type?> // type@%0*x", width, index); + } + break; + case kIndexStringRef: + if (index < pDexFile->pHeader->stringIdsSize) { + outSize = snprintf(buf, bufSize, "\"%s\" // string@%0*x", + dexStringById(pDexFile, index), width, index); + } else { + outSize = snprintf(buf, bufSize, "<string?> // string@%0*x", + width, index); + } + break; + case kIndexMethodRef: + { + FieldMethodInfo methInfo; + if (getMethodInfo(pDexFile, index, &methInfo)) { + outSize = snprintf(buf, bufSize, "%s.%s:%s // method@%0*x", + methInfo.classDescriptor, methInfo.name, + methInfo.signature, width, index); + free((void *) methInfo.signature); + } else { + outSize = snprintf(buf, bufSize, "<method?> // method@%0*x", + width, index); + } + } + break; + case kIndexFieldRef: + { + FieldMethodInfo fieldInfo; + if (getFieldInfo(pDexFile, index, &fieldInfo)) { + outSize = snprintf(buf, bufSize, "%s.%s:%s // field@%0*x", + fieldInfo.classDescriptor, fieldInfo.name, + fieldInfo.signature, width, index); + } else { + outSize = snprintf(buf, bufSize, "<field?> // field@%0*x", + width, index); + } + } + break; + case kIndexInlineMethod: + outSize = snprintf(buf, bufSize, "[%0*x] // inline #%0*x", + width, index, width, index); + break; + case kIndexVtableOffset: + outSize = snprintf(buf, bufSize, "[%0*x] // vtable #%0*x", + width, index, width, index); + break; + case kIndexFieldOffset: + outSize = snprintf(buf, bufSize, "[obj+%0*x]", width, index); + break; + case kIndexMethodAndProtoRef: + { + FieldMethodInfo methInfo; + ProtoInfo protoInfo; + protoInfo.parameterTypes = NULL; + if (getMethodInfo(pDexFile, index, &methInfo) && + getProtoInfo(pDexFile, secondaryIndex, &protoInfo)) { + outSize = snprintf(buf, bufSize, "%s.%s:%s, (%s)%s // method@%0*x, proto@%0*x", + methInfo.classDescriptor, methInfo.name, methInfo.signature, + protoInfo.parameterTypes, protoInfo.returnType, + width, index, width, secondaryIndex); + } else { + outSize = snprintf(buf, bufSize, "<method?>, <proto?> // method@%0*x, proto@%0*x", + width, index, width, secondaryIndex); + } + free(protoInfo.parameterTypes); + } + break; + case kIndexCallSiteRef: + outSize = snprintf(buf, bufSize, "call_site@%0*x", width, index); + break; + case kIndexMethodHandleRef: + outSize = snprintf(buf, bufSize, "methodhandle@%0*x", width, index); + break; + case kIndexProtoRef: + { + ProtoInfo protoInfo; + if (getProtoInfo(pDexFile, index, &protoInfo)) { + outSize = snprintf(buf, bufSize, "(%s)%s // proto@%0*x", + protoInfo.parameterTypes, protoInfo.returnType, + width, index); + + } else { + outSize = snprintf(buf, bufSize, "<proto?> // proto@%0*x", + width, secondaryIndex); + } + free(protoInfo.parameterTypes); + } + break; + default: + outSize = snprintf(buf, bufSize, "<?>"); + break; + } + + if (outSize >= (int) bufSize) { + /* + * The buffer wasn't big enough; allocate and retry. Note: + * snprintf() doesn't count the '\0' as part of its returned + * size, so we add explicit space for it here. + */ + free(buf); + return indexString(pDexFile, pDecInsn, outSize + 1); + } else { + return buf; + } +} + +/* + * Dump a single instruction. + */ +void dumpInstruction(DexFile* pDexFile, const DexCode* pCode, int insnIdx, + int insnWidth, const DecodedInstruction* pDecInsn) +{ + const u2* insns = pCode->insns; + int i; + + // Address of instruction (expressed as byte offset). + printf("%06zx:", ((u1*)insns - pDexFile->baseAddr) + insnIdx*2); + + for (i = 0; i < 8; i++) { + if (i < insnWidth) { + if (i == 7) { + printf(" ... "); + } else { + /* print 16-bit value in little-endian order */ + const u1* bytePtr = (const u1*) &insns[insnIdx+i]; + printf(" %02x%02x", bytePtr[0], bytePtr[1]); + } + } else { + fputs(" ", stdout); + } + } + + if (pDecInsn->opcode == OP_NOP) { + u2 instr = get2LE((const u1*) &insns[insnIdx]); + if (instr == kPackedSwitchSignature) { + printf("|%04x: packed-switch-data (%d units)", + insnIdx, insnWidth); + } else if (instr == kSparseSwitchSignature) { + printf("|%04x: sparse-switch-data (%d units)", + insnIdx, insnWidth); + } else if (instr == kArrayDataSignature) { + printf("|%04x: array-data (%d units)", + insnIdx, insnWidth); + } else { + printf("|%04x: nop // spacer", insnIdx); + } + } else { + printf("|%04x: %s", insnIdx, dexGetOpcodeName(pDecInsn->opcode)); + } + + // Provide an initial buffer that usually suffices, although indexString() + // may reallocate the buffer if more space is needed. + char* indexBuf = NULL; + if (pDecInsn->indexType != kIndexNone) { + indexBuf = indexString(pDexFile, pDecInsn, 200); + } + + switch (dexGetFormatFromOpcode(pDecInsn->opcode)) { + case kFmt10x: // op + break; + case kFmt12x: // op vA, vB + printf(" v%d, v%d", pDecInsn->vA, pDecInsn->vB); + break; + case kFmt11n: // op vA, #+B + printf(" v%d, #int %d // #%x", + pDecInsn->vA, (s4)pDecInsn->vB, (u1)pDecInsn->vB); + break; + case kFmt11x: // op vAA + printf(" v%d", pDecInsn->vA); + break; + case kFmt10t: // op +AA + case kFmt20t: // op +AAAA + { + s4 targ = (s4) pDecInsn->vA; + printf(" %04x // %c%04x", + insnIdx + targ, + (targ < 0) ? '-' : '+', + (targ < 0) ? -targ : targ); + } + break; + case kFmt22x: // op vAA, vBBBB + printf(" v%d, v%d", pDecInsn->vA, pDecInsn->vB); + break; + case kFmt21t: // op vAA, +BBBB + { + s4 targ = (s4) pDecInsn->vB; + printf(" v%d, %04x // %c%04x", pDecInsn->vA, + insnIdx + targ, + (targ < 0) ? '-' : '+', + (targ < 0) ? -targ : targ); + } + break; + case kFmt21s: // op vAA, #+BBBB + printf(" v%d, #int %d // #%x", + pDecInsn->vA, (s4)pDecInsn->vB, (u2)pDecInsn->vB); + break; + case kFmt21h: // op vAA, #+BBBB0000[00000000] + // The printed format varies a bit based on the actual opcode. + if (pDecInsn->opcode == OP_CONST_HIGH16) { + s4 value = pDecInsn->vB << 16; + printf(" v%d, #int %d // #%x", + pDecInsn->vA, value, (u2)pDecInsn->vB); + } else { + s8 value = ((s8) pDecInsn->vB) << 48; + printf(" v%d, #long %" PRId64 " // #%x", + pDecInsn->vA, value, (u2)pDecInsn->vB); + } + break; + case kFmt21c: // op vAA, thing@BBBB + case kFmt31c: // op vAA, thing@BBBBBBBB + printf(" v%d, %s", pDecInsn->vA, indexBuf); + break; + case kFmt23x: // op vAA, vBB, vCC + printf(" v%d, v%d, v%d", pDecInsn->vA, pDecInsn->vB, pDecInsn->vC); + break; + case kFmt22b: // op vAA, vBB, #+CC + printf(" v%d, v%d, #int %d // #%02x", + pDecInsn->vA, pDecInsn->vB, (s4)pDecInsn->vC, (u1)pDecInsn->vC); + break; + case kFmt22t: // op vA, vB, +CCCC + { + s4 targ = (s4) pDecInsn->vC; + printf(" v%d, v%d, %04x // %c%04x", pDecInsn->vA, pDecInsn->vB, + insnIdx + targ, + (targ < 0) ? '-' : '+', + (targ < 0) ? -targ : targ); + } + break; + case kFmt22s: // op vA, vB, #+CCCC + printf(" v%d, v%d, #int %d // #%04x", + pDecInsn->vA, pDecInsn->vB, (s4)pDecInsn->vC, (u2)pDecInsn->vC); + break; + case kFmt22c: // op vA, vB, thing@CCCC + case kFmt22cs: // [opt] op vA, vB, field offset CCCC + printf(" v%d, v%d, %s", pDecInsn->vA, pDecInsn->vB, indexBuf); + break; + case kFmt30t: + printf(" #%08x", pDecInsn->vA); + break; + case kFmt31i: // op vAA, #+BBBBBBBB + { + /* this is often, but not always, a float */ + union { + float f; + u4 i; + } conv; + conv.i = pDecInsn->vB; + printf(" v%d, #float %f // #%08x", + pDecInsn->vA, conv.f, pDecInsn->vB); + } + break; + case kFmt31t: // op vAA, offset +BBBBBBBB + printf(" v%d, %08x // +%08x", + pDecInsn->vA, insnIdx + pDecInsn->vB, pDecInsn->vB); + break; + case kFmt32x: // op vAAAA, vBBBB + printf(" v%d, v%d", pDecInsn->vA, pDecInsn->vB); + break; + case kFmt35c: // op {vC, vD, vE, vF, vG}, thing@BBBB + case kFmt35ms: // [opt] invoke-virtual+super + case kFmt35mi: // [opt] inline invoke + { + fputs(" {", stdout); + for (i = 0; i < (int) pDecInsn->vA; i++) { + if (i == 0) + printf("v%d", pDecInsn->arg[i]); + else + printf(", v%d", pDecInsn->arg[i]); + } + printf("}, %s", indexBuf); + } + break; + case kFmt3rc: // op {vCCCC .. v(CCCC+AA-1)}, thing@BBBB + case kFmt3rms: // [opt] invoke-virtual+super/range + case kFmt3rmi: // [opt] execute-inline/range + { + /* + * This doesn't match the "dx" output when some of the args are + * 64-bit values -- dx only shows the first register. + */ + fputs(" {", stdout); + for (i = 0; i < (int) pDecInsn->vA; i++) { + if (i == 0) + printf("v%d", pDecInsn->vC + i); + else + printf(", v%d", pDecInsn->vC + i); + } + printf("}, %s", indexBuf); + } + break; + case kFmt51l: // op vAA, #+BBBBBBBBBBBBBBBB + { + /* this is often, but not always, a double */ + union { + double d; + u8 j; + } conv; + conv.j = pDecInsn->vB_wide; + printf(" v%d, #double %f // #%016" PRIx64, + pDecInsn->vA, conv.d, pDecInsn->vB_wide); + } + break; + case kFmt00x: // unknown op or breakpoint + break; + case kFmt45cc: + { + fputs(" {", stdout); + printf("v%d", pDecInsn->vC); + for (int i = 0; i < (int) pDecInsn->vA - 1; ++i) { + printf(", v%d", pDecInsn->arg[i]); + } + printf("}, %s", indexBuf); + } + break; + case kFmt4rcc: + { + fputs(" {", stdout); + printf("v%d", pDecInsn->vC); + for (int i = 1; i < (int) pDecInsn->vA; ++i) { + printf(", v%d", pDecInsn->vC + i); + } + printf("}, %s", indexBuf); + } + break; + default: + printf(" ???"); + break; + } + + putchar('\n'); + + free(indexBuf); +} + +/* + * Dump a bytecode disassembly. + */ +void dumpBytecodes(DexFile* pDexFile, const DexMethod* pDexMethod) +{ + const DexCode* pCode = dexGetCode(pDexFile, pDexMethod); + const u2* insns; + int insnIdx; + FieldMethodInfo methInfo; + int startAddr; + char* className = NULL; + + assert(pCode->insnsSize > 0); + insns = pCode->insns; + + methInfo.classDescriptor = + methInfo.name = + methInfo.signature = NULL; + + getMethodInfo(pDexFile, pDexMethod->methodIdx, &methInfo); + startAddr = ((u1*)pCode - pDexFile->baseAddr); + className = descriptorToDot(methInfo.classDescriptor); + + printf("%06x: |[%06x] %s.%s:%s\n", + startAddr, startAddr, + className, methInfo.name, methInfo.signature); + free((void *) methInfo.signature); + + insnIdx = 0; + while (insnIdx < (int) pCode->insnsSize) { + int insnWidth; + DecodedInstruction decInsn; + u2 instr; + + /* + * Note: This code parallels the function + * dexGetWidthFromInstruction() in InstrUtils.c, but this version + * can deal with data in either endianness. + * + * TODO: Figure out if this really matters, and possibly change + * this to just use dexGetWidthFromInstruction(). + */ + instr = get2LE((const u1*)insns); + if (instr == kPackedSwitchSignature) { + insnWidth = 4 + get2LE((const u1*)(insns+1)) * 2; + } else if (instr == kSparseSwitchSignature) { + insnWidth = 2 + get2LE((const u1*)(insns+1)) * 4; + } else if (instr == kArrayDataSignature) { + int width = get2LE((const u1*)(insns+1)); + int size = get2LE((const u1*)(insns+2)) | + (get2LE((const u1*)(insns+3))<<16); + // The plus 1 is to round up for odd size and width. + insnWidth = 4 + ((size * width) + 1) / 2; + } else { + Opcode opcode = dexOpcodeFromCodeUnit(instr); + insnWidth = dexGetWidthFromOpcode(opcode); + if (insnWidth == 0) { + fprintf(stderr, + "GLITCH: zero-width instruction at idx=0x%04x\n", insnIdx); + break; + } + } + + dexDecodeInstruction(insns, &decInsn); + dumpInstruction(pDexFile, pCode, insnIdx, insnWidth, &decInsn); + + insns += insnWidth; + insnIdx += insnWidth; + } + + free(className); +} + +/* + * Dump a "code" struct. + */ +void dumpCode(DexFile* pDexFile, const DexMethod* pDexMethod) +{ + const DexCode* pCode = dexGetCode(pDexFile, pDexMethod); + + printf(" registers : %d\n", pCode->registersSize); + printf(" ins : %d\n", pCode->insSize); + printf(" outs : %d\n", pCode->outsSize); + printf(" insns size : %d 16-bit code units\n", pCode->insnsSize); + + if (gOptions.disassemble) + dumpBytecodes(pDexFile, pDexMethod); + + dumpCatches(pDexFile, pCode); + /* both of these are encoded in debug info */ + dumpPositions(pDexFile, pCode, pDexMethod); + dumpLocals(pDexFile, pCode, pDexMethod); +} + +/* + * Dump a method. + */ +void dumpMethod(DexFile* pDexFile, const DexMethod* pDexMethod, int i) +{ + const DexMethodId* pMethodId; + const char* backDescriptor; + const char* name; + char* typeDescriptor = NULL; + char* accessStr = NULL; + + if (gOptions.exportsOnly && + (pDexMethod->accessFlags & (ACC_PUBLIC | ACC_PROTECTED)) == 0) + { + return; + } + + pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx); + name = dexStringById(pDexFile, pMethodId->nameIdx); + typeDescriptor = dexCopyDescriptorFromMethodId(pDexFile, pMethodId); + + backDescriptor = dexStringByTypeIdx(pDexFile, pMethodId->classIdx); + + accessStr = createAccessFlagStr(pDexMethod->accessFlags, + kAccessForMethod); + + if (gOptions.outputFormat == OUTPUT_PLAIN) { + printf(" #%d : (in %s)\n", i, backDescriptor); + printf(" name : '%s'\n", name); + printf(" type : '%s'\n", typeDescriptor); + printf(" access : 0x%04x (%s)\n", + pDexMethod->accessFlags, accessStr); + + if (pDexMethod->codeOff == 0) { + printf(" code : (none)\n"); + } else { + printf(" code -\n"); + dumpCode(pDexFile, pDexMethod); + } + + if (gOptions.disassemble) + putchar('\n'); + } else if (gOptions.outputFormat == OUTPUT_XML) { + bool constructor = (name[0] == '<'); + + if (constructor) { + char* tmp; + + tmp = descriptorClassToName(backDescriptor); + printf("<constructor name=\"%s\"\n", tmp); + free(tmp); + + tmp = descriptorToDot(backDescriptor); + printf(" type=\"%s\"\n", tmp); + free(tmp); + } else { + printf("<method name=\"%s\"\n", name); + + const char* returnType = strrchr(typeDescriptor, ')'); + if (returnType == NULL) { + fprintf(stderr, "bad method type descriptor '%s'\n", + typeDescriptor); + goto bail; + } + + char* tmp = descriptorToDot(returnType+1); + printf(" return=\"%s\"\n", tmp); + free(tmp); + + printf(" abstract=%s\n", + quotedBool((pDexMethod->accessFlags & ACC_ABSTRACT) != 0)); + printf(" native=%s\n", + quotedBool((pDexMethod->accessFlags & ACC_NATIVE) != 0)); + + bool isSync = + (pDexMethod->accessFlags & ACC_SYNCHRONIZED) != 0 || + (pDexMethod->accessFlags & ACC_DECLARED_SYNCHRONIZED) != 0; + printf(" synchronized=%s\n", quotedBool(isSync)); + } + + printf(" static=%s\n", + quotedBool((pDexMethod->accessFlags & ACC_STATIC) != 0)); + printf(" final=%s\n", + quotedBool((pDexMethod->accessFlags & ACC_FINAL) != 0)); + // "deprecated=" not knowable w/o parsing annotations + printf(" visibility=%s\n", + quotedVisibility(pDexMethod->accessFlags)); + + printf(">\n"); + + /* + * Parameters. + */ + if (typeDescriptor[0] != '(') { + fprintf(stderr, "ERROR: bad descriptor '%s'\n", typeDescriptor); + goto bail; + } + + char tmpBuf[strlen(typeDescriptor)+1]; /* more than big enough */ + int argNum = 0; + + const char* base = typeDescriptor+1; + + while (*base != ')') { + char* cp = tmpBuf; + + while (*base == '[') + *cp++ = *base++; + + if (*base == 'L') { + /* copy through ';' */ + do { + *cp = *base++; + } while (*cp++ != ';'); + } else { + /* primitive char, copy it */ + if (strchr("ZBCSIFJD", *base) == NULL) { + fprintf(stderr, "ERROR: bad method signature '%s'\n", base); + goto bail; + } + *cp++ = *base++; + } + + /* null terminate and display */ + *cp++ = '\0'; + + char* tmp = descriptorToDot(tmpBuf); + printf("<parameter name=\"arg%d\" type=\"%s\">\n</parameter>\n", + argNum++, tmp); + free(tmp); + } + + if (constructor) + printf("</constructor>\n"); + else + printf("</method>\n"); + } + +bail: + free(typeDescriptor); + free(accessStr); +} + +/* + * Dump a static (class) field. + */ +void dumpSField(const DexFile* pDexFile, const DexField* pSField, int i) +{ + const DexFieldId* pFieldId; + const char* backDescriptor; + const char* name; + const char* typeDescriptor; + char* accessStr; + + if (gOptions.exportsOnly && + (pSField->accessFlags & (ACC_PUBLIC | ACC_PROTECTED)) == 0) + { + return; + } + + pFieldId = dexGetFieldId(pDexFile, pSField->fieldIdx); + name = dexStringById(pDexFile, pFieldId->nameIdx); + typeDescriptor = dexStringByTypeIdx(pDexFile, pFieldId->typeIdx); + backDescriptor = dexStringByTypeIdx(pDexFile, pFieldId->classIdx); + + accessStr = createAccessFlagStr(pSField->accessFlags, kAccessForField); + + if (gOptions.outputFormat == OUTPUT_PLAIN) { + printf(" #%d : (in %s)\n", i, backDescriptor); + printf(" name : '%s'\n", name); + printf(" type : '%s'\n", typeDescriptor); + printf(" access : 0x%04x (%s)\n", + pSField->accessFlags, accessStr); + } else if (gOptions.outputFormat == OUTPUT_XML) { + char* tmp; + + printf("<field name=\"%s\"\n", name); + + tmp = descriptorToDot(typeDescriptor); + printf(" type=\"%s\"\n", tmp); + free(tmp); + + printf(" transient=%s\n", + quotedBool((pSField->accessFlags & ACC_TRANSIENT) != 0)); + printf(" volatile=%s\n", + quotedBool((pSField->accessFlags & ACC_VOLATILE) != 0)); + // "value=" not knowable w/o parsing annotations + printf(" static=%s\n", + quotedBool((pSField->accessFlags & ACC_STATIC) != 0)); + printf(" final=%s\n", + quotedBool((pSField->accessFlags & ACC_FINAL) != 0)); + // "deprecated=" not knowable w/o parsing annotations + printf(" visibility=%s\n", + quotedVisibility(pSField->accessFlags)); + printf(">\n</field>\n"); + } + + free(accessStr); +} + +/* + * Dump an instance field. + */ +void dumpIField(const DexFile* pDexFile, const DexField* pIField, int i) +{ + dumpSField(pDexFile, pIField, i); +} + +/* + * Dump the class. + * + * Note "idx" is a DexClassDef index, not a DexTypeId index. + * + * If "*pLastPackage" is NULL or does not match the current class' package, + * the value will be replaced with a newly-allocated string. + */ +void dumpClass(DexFile* pDexFile, int idx, char** pLastPackage) +{ + const DexTypeList* pInterfaces; + const DexClassDef* pClassDef; + DexClassData* pClassData = NULL; + const u1* pEncodedData; + const char* fileName; + const char* classDescriptor; + const char* superclassDescriptor; + char* accessStr = NULL; + int i; + + pClassDef = dexGetClassDef(pDexFile, idx); + + if (gOptions.exportsOnly && (pClassDef->accessFlags & ACC_PUBLIC) == 0) { + //printf("<!-- omitting non-public class %s -->\n", + // classDescriptor); + goto bail; + } + + pEncodedData = dexGetClassData(pDexFile, pClassDef); + pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL); + + if (pClassData == NULL) { + printf("Trouble reading class data (#%d)\n", idx); + goto bail; + } + + classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx); + + /* + * For the XML output, show the package name. Ideally we'd gather + * up the classes, sort them, and dump them alphabetically so the + * package name wouldn't jump around, but that's not a great plan + * for something that needs to run on the device. + */ + if (!(classDescriptor[0] == 'L' && + classDescriptor[strlen(classDescriptor)-1] == ';')) + { + /* arrays and primitives should not be defined explicitly */ + fprintf(stderr, "Malformed class name '%s'\n", classDescriptor); + /* keep going? */ + } else if (gOptions.outputFormat == OUTPUT_XML) { + char* mangle; + char* lastSlash; + char* cp; + + mangle = strdup(classDescriptor + 1); + mangle[strlen(mangle)-1] = '\0'; + + /* reduce to just the package name */ + lastSlash = strrchr(mangle, '/'); + if (lastSlash != NULL) { + *lastSlash = '\0'; + } else { + *mangle = '\0'; + } + + for (cp = mangle; *cp != '\0'; cp++) { + if (*cp == '/') + *cp = '.'; + } + + if (*pLastPackage == NULL || strcmp(mangle, *pLastPackage) != 0) { + /* start of a new package */ + if (*pLastPackage != NULL) + printf("</package>\n"); + printf("<package name=\"%s\"\n>\n", mangle); + free(*pLastPackage); + *pLastPackage = mangle; + } else { + free(mangle); + } + } + + accessStr = createAccessFlagStr(pClassDef->accessFlags, kAccessForClass); + + if (pClassDef->superclassIdx == kDexNoIndex) { + superclassDescriptor = NULL; + } else { + superclassDescriptor = + dexStringByTypeIdx(pDexFile, pClassDef->superclassIdx); + } + + if (gOptions.outputFormat == OUTPUT_PLAIN) { + printf("Class #%d -\n", idx); + printf(" Class descriptor : '%s'\n", classDescriptor); + printf(" Access flags : 0x%04x (%s)\n", + pClassDef->accessFlags, accessStr); + + if (superclassDescriptor != NULL) + printf(" Superclass : '%s'\n", superclassDescriptor); + + printf(" Interfaces -\n"); + } else { + char* tmp; + + tmp = descriptorClassToName(classDescriptor); + printf("<class name=\"%s\"\n", tmp); + free(tmp); + + if (superclassDescriptor != NULL) { + tmp = descriptorToDot(superclassDescriptor); + printf(" extends=\"%s\"\n", tmp); + free(tmp); + } + printf(" abstract=%s\n", + quotedBool((pClassDef->accessFlags & ACC_ABSTRACT) != 0)); + printf(" static=%s\n", + quotedBool((pClassDef->accessFlags & ACC_STATIC) != 0)); + printf(" final=%s\n", + quotedBool((pClassDef->accessFlags & ACC_FINAL) != 0)); + // "deprecated=" not knowable w/o parsing annotations + printf(" visibility=%s\n", + quotedVisibility(pClassDef->accessFlags)); + printf(">\n"); + } + pInterfaces = dexGetInterfacesList(pDexFile, pClassDef); + if (pInterfaces != NULL) { + for (i = 0; i < (int) pInterfaces->size; i++) + dumpInterface(pDexFile, dexGetTypeItem(pInterfaces, i), i); + } + + if (gOptions.outputFormat == OUTPUT_PLAIN) + printf(" Static fields -\n"); + for (i = 0; i < (int) pClassData->header.staticFieldsSize; i++) { + dumpSField(pDexFile, &pClassData->staticFields[i], i); + } + + if (gOptions.outputFormat == OUTPUT_PLAIN) + printf(" Instance fields -\n"); + for (i = 0; i < (int) pClassData->header.instanceFieldsSize; i++) { + dumpIField(pDexFile, &pClassData->instanceFields[i], i); + } + + if (gOptions.outputFormat == OUTPUT_PLAIN) + printf(" Direct methods -\n"); + for (i = 0; i < (int) pClassData->header.directMethodsSize; i++) { + dumpMethod(pDexFile, &pClassData->directMethods[i], i); + } + + if (gOptions.outputFormat == OUTPUT_PLAIN) + printf(" Virtual methods -\n"); + for (i = 0; i < (int) pClassData->header.virtualMethodsSize; i++) { + dumpMethod(pDexFile, &pClassData->virtualMethods[i], i); + } + + // TODO: Annotations. + + if (pClassDef->sourceFileIdx != kDexNoIndex) + fileName = dexStringById(pDexFile, pClassDef->sourceFileIdx); + else + fileName = "unknown"; + + if (gOptions.outputFormat == OUTPUT_PLAIN) { + printf(" source_file_idx : %d (%s)\n", + pClassDef->sourceFileIdx, fileName); + printf("\n"); + } + + if (gOptions.outputFormat == OUTPUT_XML) { + printf("</class>\n"); + } + +bail: + free(pClassData); + free(accessStr); +} + + +/* + * Dump a map in the "differential" format. + * + * TODO: show a hex dump of the compressed data. (We can show the + * uncompressed data if we move the compression code to libdex; otherwise + * it's too complex to merit a fast & fragile implementation here.) + */ +void dumpDifferentialCompressedMap(const u1** pData) +{ + const u1* data = *pData; + const u1* dataStart = data -1; // format byte already removed + u1 regWidth; + u2 numEntries; + + /* standard header */ + regWidth = *data++; + numEntries = *data++; + numEntries |= (*data++) << 8; + + /* compressed data begins with the compressed data length */ + int compressedLen = readUnsignedLeb128(&data); + int addrWidth = 1; + if ((*data & 0x80) != 0) + addrWidth++; + + int origLen = 4 + (addrWidth + regWidth) * numEntries; + int compLen = (data - dataStart) + compressedLen; + + printf(" (differential compression %d -> %d [%d -> %d])\n", + origLen, compLen, + (addrWidth + regWidth) * numEntries, compressedLen); + + /* skip past end of entry */ + data += compressedLen; + + *pData = data; +} + +/* + * Dump register map contents of the current method. + * + * "*pData" should point to the start of the register map data. Advances + * "*pData" to the start of the next map. + */ +void dumpMethodMap(DexFile* pDexFile, const DexMethod* pDexMethod, int idx, + const u1** pData) +{ + const u1* data = *pData; + const DexMethodId* pMethodId; + const char* name; + int offset = data - (u1*) pDexFile->pOptHeader; + + pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx); + name = dexStringById(pDexFile, pMethodId->nameIdx); + printf(" #%d: 0x%08x %s\n", idx, offset, name); + + u1 format; + int addrWidth; + + format = *data++; + if (format == 1) { /* kRegMapFormatNone */ + /* no map */ + printf(" (no map)\n"); + addrWidth = 0; + } else if (format == 2) { /* kRegMapFormatCompact8 */ + addrWidth = 1; + } else if (format == 3) { /* kRegMapFormatCompact16 */ + addrWidth = 2; + } else if (format == 4) { /* kRegMapFormatDifferential */ + dumpDifferentialCompressedMap(&data); + goto bail; + } else { + printf(" (unknown format %d!)\n", format); + /* don't know how to skip data; failure will cascade to end of class */ + goto bail; + } + + if (addrWidth > 0) { + u1 regWidth; + u2 numEntries; + int idx, addr, byte; + + regWidth = *data++; + numEntries = *data++; + numEntries |= (*data++) << 8; + + for (idx = 0; idx < numEntries; idx++) { + addr = *data++; + if (addrWidth > 1) + addr |= (*data++) << 8; + + printf(" %4x:", addr); + for (byte = 0; byte < regWidth; byte++) { + printf(" %02x", *data++); + } + printf("\n"); + } + } + +bail: + //if (addrWidth >= 0) + // *pData = align32(data); + *pData = data; +} + +/* + * Dump the contents of the register map area. + * + * These are only present in optimized DEX files, and the structure is + * not really exposed to other parts of the VM itself. We're going to + * dig through them here, but this is pretty fragile. DO NOT rely on + * this or derive other code from it. + */ +void dumpRegisterMaps(DexFile* pDexFile) +{ + const u1* pClassPool = (const u1*)pDexFile->pRegisterMapPool; + const u4* classOffsets; + const u1* ptr; + u4 numClasses; + int baseFileOffset = (u1*) pClassPool - (u1*) pDexFile->pOptHeader; + int idx; + + if (pClassPool == NULL) { + printf("No register maps found\n"); + return; + } + + ptr = pClassPool; + numClasses = get4LE(ptr); + ptr += sizeof(u4); + classOffsets = (const u4*) ptr; + + printf("RMAP begins at offset 0x%07x\n", baseFileOffset); + printf("Maps for %d classes\n", numClasses); + for (idx = 0; idx < (int) numClasses; idx++) { + const DexClassDef* pClassDef; + const char* classDescriptor; + + pClassDef = dexGetClassDef(pDexFile, idx); + classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx); + + printf("%4d: +%d (0x%08x) %s\n", idx, classOffsets[idx], + baseFileOffset + classOffsets[idx], classDescriptor); + + if (classOffsets[idx] == 0) + continue; + + /* + * What follows is a series of RegisterMap entries, one for every + * direct method, then one for every virtual method. + */ + DexClassData* pClassData; + const u1* pEncodedData; + const u1* data = (u1*) pClassPool + classOffsets[idx]; + u2 methodCount; + int i; + + pEncodedData = dexGetClassData(pDexFile, pClassDef); + pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL); + if (pClassData == NULL) { + fprintf(stderr, "Trouble reading class data\n"); + continue; + } + + methodCount = *data++; + methodCount |= (*data++) << 8; + data += 2; /* two pad bytes follow methodCount */ + if (methodCount != pClassData->header.directMethodsSize + + pClassData->header.virtualMethodsSize) + { + printf("NOTE: method count discrepancy (%d != %d + %d)\n", + methodCount, pClassData->header.directMethodsSize, + pClassData->header.virtualMethodsSize); + /* this is bad, but keep going anyway */ + } + + printf(" direct methods: %d\n", + pClassData->header.directMethodsSize); + for (i = 0; i < (int) pClassData->header.directMethodsSize; i++) { + dumpMethodMap(pDexFile, &pClassData->directMethods[i], i, &data); + } + + printf(" virtual methods: %d\n", + pClassData->header.virtualMethodsSize); + for (i = 0; i < (int) pClassData->header.virtualMethodsSize; i++) { + dumpMethodMap(pDexFile, &pClassData->virtualMethods[i], i, &data); + } + + free(pClassData); + } +} + +static const DexMapItem* findMapItem(const DexFile* pDexFile, u4 type) +{ + const u4 offset = pDexFile->pHeader->mapOff; + const DexMapList* list = (const DexMapList*)(pDexFile->baseAddr + offset); + for (u4 i = 0; i < list->size; ++i) { + if (list->list[i].type == type) { + return &list->list[i]; + } + } + return nullptr; +} + +static void dumpMethodHandles(DexFile* pDexFile) +{ + const DexMapItem* item = findMapItem(pDexFile, kDexTypeMethodHandleItem); + if (item == nullptr) return; + const DexMethodHandleItem* method_handles = + (const DexMethodHandleItem*)(pDexFile->baseAddr + item->offset); + for (u4 i = 0; i < item->size; ++i) { + const DexMethodHandleItem& mh = method_handles[i]; + const char* type; + bool is_invoke; + bool is_static; + switch ((MethodHandleType) mh.methodHandleType) { + case MethodHandleType::STATIC_PUT: + type = "put-static"; + is_invoke = false; + is_static = true; + break; + case MethodHandleType::STATIC_GET: + type = "get-static"; + is_invoke = false; + is_static = true; + break; + case MethodHandleType::INSTANCE_PUT: + type = "put-instance"; + is_invoke = false; + is_static = false; + break; + case MethodHandleType::INSTANCE_GET: + type = "get-instance"; + is_invoke = false; + is_static = false; + break; + case MethodHandleType::INVOKE_STATIC: + type = "invoke-static"; + is_invoke = true; + is_static = true; + break; + case MethodHandleType::INVOKE_INSTANCE: + type = "invoke-instance"; + is_invoke = true; + is_static = false; + break; + case MethodHandleType::INVOKE_CONSTRUCTOR: + type = "invoke-constructor"; + is_invoke = true; + is_static = false; + break; + case MethodHandleType::INVOKE_DIRECT: + type = "invoke-direct"; + is_invoke = true; + is_static = false; + break; + case MethodHandleType::INVOKE_INTERFACE: + type = "invoke-interface"; + is_invoke = true; + is_static = false; + break; + default: + printf("Unknown method handle type 0x%02x, skipped.", mh.methodHandleType); + continue; + } + + FieldMethodInfo info; + if (is_invoke) { + if (!getMethodInfo(pDexFile, mh.fieldOrMethodIdx, &info)) { + printf("Unknown method handle target method@%04x, skipped.", mh.fieldOrMethodIdx); + continue; + } + } else { + if (!getFieldInfo(pDexFile, mh.fieldOrMethodIdx, &info)) { + printf("Unknown method handle target field@%04x, skipped.", mh.fieldOrMethodIdx); + continue; + } + } + + const char* instance = is_static ? "" : info.classDescriptor; + + if (gOptions.outputFormat == OUTPUT_XML) { + printf("<method_handle index index=\"%u\"\n", i); + printf(" type=\"%s\"\n", type); + printf(" target_class=\"%s\"\n", info.classDescriptor); + printf(" target_member=\"%s\"\n", info.name); + printf(" target_member_type=\"%c%s%s\"\n", + info.signature[0], instance, info.signature + 1); + printf("</method_handle>\n"); + } else { + printf("Method Handle #%u:\n", i); + printf(" type : %s\n", type); + printf(" target : %s %s\n", info.classDescriptor, info.name); + printf(" target_type : %c%s%s\n", info.signature[0], instance, info.signature + 1); + } + } +} + +/* Helper for dumpCallSites(), which reads a 1- to 8- byte signed + * little endian value. */ +static u8 readSignedLittleEndian(const u1** pData, u4 size) { + const u1* data = *pData; + u8 result = 0; + u4 i; + + for (i = 0; i < size; i++) { + result = (result >> 8) | (((int64_t)*data++) << 56); + } + + result >>= (8 - size) * 8; + *pData = data; + return result; +} + +/* Helper for dumpCallSites(), which reads a 1- to 8- byte unsigned + * little endian value. */ +static u8 readUnsignedLittleEndian(const u1** pData, u4 size, bool fillOnRight = false) { + const u1* data = *pData; + u8 result = 0; + u4 i; + + for (i = 0; i < size; i++) { + result = (result >> 8) | (((u8)*data++) << 56); + } + + if (!fillOnRight) { + result >>= (8u - size) * 8; + } + + *pData = data; + return result; +} + +static void dumpCallSites(DexFile* pDexFile) +{ + const DexMapItem* item = findMapItem(pDexFile, kDexTypeCallSiteIdItem); + if (item == nullptr) return; + const DexCallSiteId* ids = (const DexCallSiteId*)(pDexFile->baseAddr + item->offset); + for (u4 index = 0; index < item->size; ++index) { + bool doXml = (gOptions.outputFormat == OUTPUT_XML); + printf(doXml ? "<call_site index=\"%u\" offset=\"%u\">\n" : "Call Site #%u // offset %u\n", + index, ids[index].callSiteOff); + const u1* data = pDexFile->baseAddr + ids[index].callSiteOff; + u4 count = readUnsignedLeb128(&data); + for (u4 i = 0; i < count; ++i) { + printf(doXml ? "<link_argument index=\"%u\" " : " link_argument[%u] : ", i); + u1 headerByte = *data++; + u4 valueType = headerByte & kDexAnnotationValueTypeMask; + u4 valueArg = headerByte >> kDexAnnotationValueArgShift; + switch (valueType) { + case kDexAnnotationByte: { + printf(doXml ? "type=\"byte\" value=\"%d\"/>" : "%d (byte)", (int)*data++); + break; + } + case kDexAnnotationShort: { + printf(doXml ? "type=\"short\" value=\"%d\"/>" : "%d (short)", + (int) readSignedLittleEndian(&data, valueArg + 1)); + break; + } + case kDexAnnotationChar: { + printf(doXml ? "type=\"short\" value=\"%u\"/>" : "%u (char)", + (u2) readUnsignedLittleEndian(&data, valueArg + 1)); + break; + } + case kDexAnnotationInt: { + printf(doXml ? "type=\"int\" value=\"%d\"/>" : "%d (int)", + (int) readSignedLittleEndian(&data, valueArg + 1)); + break; + } + case kDexAnnotationLong: { + printf(doXml ? "type=\"long\" value=\"%" PRId64 "\"/>" : "%" PRId64 " (long)", + (int64_t) readSignedLittleEndian(&data, valueArg + 1)); + break; + } + case kDexAnnotationFloat: { + u4 rawValue = (u4) (readUnsignedLittleEndian(&data, valueArg + 1, true) >> 32); + printf(doXml ? "type=\"float\" value=\"%g\"/>" : "%g (float)", + *((float*) &rawValue)); + break; + } + case kDexAnnotationDouble: { + u8 rawValue = readUnsignedLittleEndian(&data, valueArg + 1, true); + printf(doXml ? "type=\"double\" value=\"%g\"/>" : "%g (double)", + *((double*) &rawValue)); + break; + } + case kDexAnnotationMethodType: { + u4 idx = (u4) readUnsignedLittleEndian(&data, valueArg + 1); + ProtoInfo protoInfo; + memset(&protoInfo, 0, sizeof(protoInfo)); + getProtoInfo(pDexFile, idx, &protoInfo); + printf(doXml ? "type=\"MethodType\" value=\"(%s)%s\"/>" : "(%s)%s (MethodType)", + protoInfo.parameterTypes, protoInfo.returnType); + free(protoInfo.parameterTypes); + break; + } + case kDexAnnotationMethodHandle: { + u4 idx = (u4) readUnsignedLittleEndian(&data, valueArg + 1); + printf(doXml ? "type=\"MethodHandle\" value=\"%u\"/>" : "%u (MethodHandle)", + idx); + break; + } + case kDexAnnotationString: { + u4 idx = (u4) readUnsignedLittleEndian(&data, valueArg + 1); + printf(doXml ? "type=\"String\" value=\"%s\"/>" : "%s (String)", + dexStringById(pDexFile, idx)); + break; + } + case kDexAnnotationType: { + u4 idx = (u4) readUnsignedLittleEndian(&data, valueArg + 1); + printf(doXml ? "type=\"Class\" value=\"%s\"/>" : "%s (Class)", + dexStringByTypeIdx(pDexFile, idx)); + break; + } + case kDexAnnotationNull: { + printf(doXml ? "type=\"null\" value=\"null\"/>" : "null (null)"); + break; + } + case kDexAnnotationBoolean: { + printf(doXml ? "type=\"boolean\" value=\"%s\"/>" : "%s (boolean)", + (valueArg & 1) == 0 ? "false" : "true"); + break; + } + default: + // Other types are not anticipated being reached here. + printf("Unexpected type found, bailing on call site info.\n"); + i = count; + break; + } + printf("\n"); + } + + if (doXml) { + printf("</callsite>\n"); + } + } +} + +/* + * Dump the requested sections of the file. + */ +void processDexFile(const char* fileName, DexFile* pDexFile) +{ + char* package = NULL; + int i; + + if (gOptions.verbose) { + printf("Opened '%s', DEX version '%.3s'\n", fileName, + pDexFile->pHeader->magic +4); + } + + if (gOptions.dumpRegisterMaps) { + dumpRegisterMaps(pDexFile); + return; + } + + if (gOptions.showFileHeaders) { + dumpFileHeader(pDexFile); + dumpOptDirectory(pDexFile); + } + + if (gOptions.outputFormat == OUTPUT_XML) + printf("<api>\n"); + + for (i = 0; i < (int) pDexFile->pHeader->classDefsSize; i++) { + if (gOptions.showSectionHeaders) + dumpClassDef(pDexFile, i); + + dumpClass(pDexFile, i, &package); + } + + dumpMethodHandles(pDexFile); + dumpCallSites(pDexFile); + + /* free the last one allocated */ + if (package != NULL) { + printf("</package>\n"); + free(package); + } + + if (gOptions.outputFormat == OUTPUT_XML) + printf("</api>\n"); +} + + +/* + * Process one file. + */ +int process(const char* fileName) +{ + DexFile* pDexFile = NULL; + MemMapping map; + bool mapped = false; + int result = -1; + + if (gOptions.verbose) + printf("Processing '%s'...\n", fileName); + + if (dexOpenAndMap(fileName, gOptions.tempFileName, &map, false) != 0) { + return result; + } + mapped = true; + + int flags = kDexParseVerifyChecksum; + if (gOptions.ignoreBadChecksum) + flags |= kDexParseContinueOnError; + + pDexFile = dexFileParse((u1*)map.addr, map.length, flags); + if (pDexFile == NULL) { + fprintf(stderr, "ERROR: DEX parse failed\n"); + goto bail; + } + + if (gOptions.checksumOnly) { + printf("Checksum verified\n"); + } else { + processDexFile(fileName, pDexFile); + } + + result = 0; + +bail: + if (mapped) + sysReleaseShmem(&map); + if (pDexFile != NULL) + dexFileFree(pDexFile); + return result; +} + + +/* + * Show usage. + */ +void usage(void) +{ + fprintf(stderr, "Copyright (C) 2007 The Android Open Source Project\n\n"); + fprintf(stderr, + "%s: [-c] [-d] [-f] [-h] [-i] [-l layout] [-m] [-t tempfile] dexfile...\n", + gProgName); + fprintf(stderr, "\n"); + fprintf(stderr, " -c : verify checksum and exit\n"); + fprintf(stderr, " -d : disassemble code sections\n"); + fprintf(stderr, " -f : display summary information from file header\n"); + fprintf(stderr, " -h : display file header details\n"); + fprintf(stderr, " -i : ignore checksum failures\n"); + fprintf(stderr, " -l : output layout, either 'plain' or 'xml'\n"); + fprintf(stderr, " -m : dump register maps (and nothing else)\n"); + fprintf(stderr, " -t : temp file name (defaults to /sdcard/dex-temp-*)\n"); +} + +/* + * Parse args. + * + * I'm not using getopt_long() because we may not have it in libc. + */ +int main(int argc, char* const argv[]) +{ + bool wantUsage = false; + int ic; + + memset(&gOptions, 0, sizeof(gOptions)); + gOptions.verbose = true; + + while (1) { + ic = getopt(argc, argv, "cdfhil:mt:"); + if (ic < 0) + break; + + switch (ic) { + case 'c': // verify the checksum then exit + gOptions.checksumOnly = true; + break; + case 'd': // disassemble Dalvik instructions + gOptions.disassemble = true; + break; + case 'f': // dump outer file header + gOptions.showFileHeaders = true; + break; + case 'h': // dump section headers, i.e. all meta-data + gOptions.showSectionHeaders = true; + break; + case 'i': // continue even if checksum is bad + gOptions.ignoreBadChecksum = true; + break; + case 'l': // layout + if (strcmp(optarg, "plain") == 0) { + gOptions.outputFormat = OUTPUT_PLAIN; + } else if (strcmp(optarg, "xml") == 0) { + gOptions.outputFormat = OUTPUT_XML; + gOptions.verbose = false; + gOptions.exportsOnly = true; + } else { + wantUsage = true; + } + break; + case 'm': // dump register maps only + gOptions.dumpRegisterMaps = true; + break; + case 't': // temp file, used when opening compressed Jar + gOptions.tempFileName = optarg; + break; + default: + wantUsage = true; + break; + } + } + + if (optind == argc) { + fprintf(stderr, "%s: no file specified\n", gProgName); + wantUsage = true; + } + + if (gOptions.checksumOnly && gOptions.ignoreBadChecksum) { + fprintf(stderr, "Can't specify both -c and -i\n"); + wantUsage = true; + } + + if (wantUsage) { + usage(); + return 2; + } + + int result = 0; + while (optind < argc) { + result |= process(argv[optind++]); + } + + return (result != 0); +} diff --git a/dexdump/NOTICE b/dexdump/NOTICE new file mode 100644 index 000000000..c5b1efa7a --- /dev/null +++ b/dexdump/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/dx/src/com/android/dex/DexFormat.java b/dx/src/com/android/dex/DexFormat.java index ab995667b..66b4460d7 100644 --- a/dx/src/com/android/dex/DexFormat.java +++ b/dx/src/com/android/dex/DexFormat.java @@ -23,9 +23,6 @@ package com.android.dex; public final class DexFormat { private DexFormat() {} - /** API level to target in order to allow spaces in SimpleName */ - public static final int API_SPACES_IN_SIMPLE_NAME = 10000; - /** API level to target in order to generate const-method-handle and const-method-type */ public static final int API_CONST_METHOD_HANDLE = 28; @@ -51,9 +48,6 @@ public final class DexFormat { */ public static final int API_CURRENT = API_CONST_METHOD_HANDLE; - /** dex file version number for API level 10000 and earlier */ - public static final String VERSION_FOR_API_10000 = "040"; - /** dex file version number for API level 28 and earlier */ public static final String VERSION_FOR_API_28 = "039"; @@ -133,8 +127,6 @@ public final class DexFormat { return API_METHOD_HANDLES; } else if (version.equals(VERSION_FOR_API_28)) { return API_CONST_METHOD_HANDLE; - } else if (version.equals(VERSION_FOR_API_10000)) { - return API_SPACES_IN_SIMPLE_NAME; } else if (version.equals(VERSION_CURRENT)) { return API_CURRENT; } @@ -153,8 +145,6 @@ public final class DexFormat { if (targetApiLevel >= API_CURRENT) { version = VERSION_CURRENT; - } else if (targetApiLevel >= API_SPACES_IN_SIMPLE_NAME) { - version = VERSION_FOR_API_10000; } else if (targetApiLevel >= API_CONST_METHOD_HANDLE) { version = VERSION_FOR_API_28; } else if (targetApiLevel >= API_METHOD_HANDLES) { diff --git a/dx/src/com/android/dex/Mutf8.java b/dx/src/com/android/dex/Mutf8.java index df3f3159d..c64da331b 100644 --- a/dx/src/com/android/dex/Mutf8.java +++ b/dx/src/com/android/dex/Mutf8.java @@ -21,6 +21,8 @@ import java.io.UTFDataFormatException; /** * Modified UTF-8 as described in the dex file format spec. + * + * <p>Derived from libcore's MUTF-8 encoder at java.nio.charset.ModifiedUtf8. */ public final class Mutf8 { private Mutf8() {} diff --git a/dx/tests/127-merge-stress/run b/dx/tests/127-merge-stress/run index a189cb195..40a98b487 100755 --- a/dx/tests/127-merge-stress/run +++ b/dx/tests/127-merge-stress/run @@ -58,7 +58,7 @@ for dex in $candidates; do fi checksums[$checksum]=$checksum - dexdump -c $dex >/dev/null 2>&1 + dexdump2 -c $dex >/dev/null 2>&1 if [ $? -eq 0 ]; then validdexes+=("$dex") fi diff --git a/dx/tests/137-dexmerger-dex38/run b/dx/tests/137-dexmerger-dex38/run index 65e70b7e9..af1dc9e35 100755 --- a/dx/tests/137-dexmerger-dex38/run +++ b/dx/tests/137-dexmerger-dex38/run @@ -23,4 +23,4 @@ jar xf invokecustom.jar ) java -cp $(dirname $(which dx))/../framework/dx.jar com.android.dx.merge.DexMerger \ out.dex invokecustom/*.dex >& /dev/null -dexdump -d out.dex +dexdump2 -d out.dex diff --git a/dx/tests/run-all-tests b/dx/tests/run-all-tests index 1822b22b8..3734a98f1 100755 --- a/dx/tests/run-all-tests +++ b/dx/tests/run-all-tests @@ -33,7 +33,7 @@ cd "${progdir}" progdir=`pwd` prog="${progdir}"/`basename "${prog}"` -skip_tests="127-merge-stress" +skip_tests="" # Command-line options sequential="no" diff --git a/libdex/Android.bp b/libdex/Android.bp new file mode 100644 index 000000000..737e097cd --- /dev/null +++ b/libdex/Android.bp @@ -0,0 +1,56 @@ +// 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. + +cc_library_static { + name: "libdex", + host_supported: true, + + srcs: [ + "CmdUtils.cpp", + "DexCatch.cpp", + "DexClass.cpp", + "DexDataMap.cpp", + "DexDebugInfo.cpp", + "DexFile.cpp", + "DexInlines.cpp", + "DexOptData.cpp", + "DexOpcodes.cpp", + "DexProto.cpp", + "DexSwapVerify.cpp", + "DexUtf.cpp", + "InstrUtils.cpp", + "Leb128.cpp", + "OptInvocation.cpp", + "sha1.cpp", + "SysUtil.cpp", + ], + include_dirs: [ + "dalvik", + "external/zlib", + ], + static_libs: ["liblog"], + whole_static_libs: ["libziparchive"], + + cflags: [ + "-Wall", + "-Werror", + "-Wimplicit-fallthrough", + ], + target: { + windows: { + enabled: true, + cflags: ["-Wno-unused-parameter"], + }, + }, +} diff --git a/libdex/CmdUtils.cpp b/libdex/CmdUtils.cpp new file mode 100644 index 000000000..8244a1189 --- /dev/null +++ b/libdex/CmdUtils.cpp @@ -0,0 +1,228 @@ +/* + * 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. + */ +/* + * Some utility functions for use with command-line utilities. + */ +#include "DexFile.h" +#include "ZipArchive.h" +#include "CmdUtils.h" + +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <fcntl.h> +#include <errno.h> + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +/* + * Extract "classes.dex" from archive file. + * + * If "quiet" is set, don't report common errors. + */ +UnzipToFileResult dexUnzipToFile(const char* zipFileName, + const char* outFileName, bool quiet) +{ + UnzipToFileResult result = kUTFRSuccess; + static const char* kFileToExtract = "classes.dex"; + ZipArchiveHandle archive; + ZipEntry entry; + bool unlinkOnFailure = false; + int fd = -1; + + if (dexZipOpenArchive(zipFileName, &archive) != 0) { + if (!quiet) { + fprintf(stderr, "Unable to open '%s' as zip archive\n", + zipFileName); + } + result = kUTFRNotZip; + goto bail; + } + + fd = open(outFileName, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600); + if (fd < 0) { + fprintf(stderr, "Unable to create output file '%s': %s\n", + outFileName, strerror(errno)); + result = kUTFROutputFileProblem; + goto bail; + } + + unlinkOnFailure = true; + + if (dexZipFindEntry(archive, kFileToExtract, &entry) != 0) { + if (!quiet) { + fprintf(stderr, "Unable to find '%s' in '%s'\n", + kFileToExtract, zipFileName); + } + result = kUTFRNoClassesDex; + goto bail; + } + + if (dexZipExtractEntryToFile(archive, &entry, fd) != 0) { + fprintf(stderr, "Extract of '%s' from '%s' failed\n", + kFileToExtract, zipFileName); + result = kUTFRBadZip; + goto bail; + } + +bail: + if (fd >= 0) + close(fd); + if (unlinkOnFailure && result != kUTFRSuccess) + unlink(outFileName); + dexZipCloseArchive(archive); + return result; +} + +/* + * Map the specified DEX file read-only (possibly after expanding it into a + * temp file from a Jar). Pass in a MemMapping struct to hold the info. + * If the file is an unoptimized DEX file, then byte-swapping and structural + * verification are performed on it before the memory is made read-only. + * + * The temp file is deleted after the map succeeds. + * + * This is intended for use by tools (e.g. dexdump) that need to get a + * read-only copy of a DEX file that could be in a number of different states. + * + * If "tempFileName" is NULL, a default value is used. The temp file is + * deleted after the map succeeds. + * + * If "quiet" is set, don't report common errors. + * + * Returns 0 (kUTFRSuccess) on success. + */ +UnzipToFileResult dexOpenAndMap(const char* fileName, const char* tempFileName, + MemMapping* pMap, bool quiet) +{ + UnzipToFileResult result = kUTFRGenericFailure; + int len = strlen(fileName); + char tempNameBuf[32]; + bool removeTemp = false; + int fd = -1; + + if (len < 5) { + if (!quiet) { + fprintf(stderr, + "ERROR: filename must end in .dex, .zip, .jar, or .apk\n"); + } + result = kUTFRBadArgs; + goto bail; + } + + if (strcasecmp(fileName + len -3, "dex") != 0) { + if (tempFileName == NULL) { + /* + * Try .zip/.jar/.apk, all of which are Zip archives with + * "classes.dex" inside. We need to extract the compressed + * data to a temp file, the location of which varies. + * + * On the device we must use /sdcard because most other + * directories aren't writable (either because of permissions + * or because the volume is mounted read-only). On desktop + * it's nice to use the designated temp directory. + */ + if (access("/tmp", W_OK) == 0) { + sprintf(tempNameBuf, "/tmp/dex-temp-%d", getpid()); + } else if (access("/sdcard", W_OK) == 0) { + sprintf(tempNameBuf, "/sdcard/dex-temp-%d", getpid()); + } else { + fprintf(stderr, + "NOTE: /tmp and /sdcard unavailable for temp files\n"); + sprintf(tempNameBuf, "dex-temp-%d", getpid()); + } + + tempFileName = tempNameBuf; + } + + result = dexUnzipToFile(fileName, tempFileName, quiet); + + if (result == kUTFRSuccess) { + //printf("+++ Good unzip to '%s'\n", tempFileName); + fileName = tempFileName; + removeTemp = true; + } else if (result == kUTFRNotZip) { + if (!quiet) { + fprintf(stderr, "Not Zip, retrying as DEX\n"); + } + } else { + if (!quiet && result == kUTFRNoClassesDex) { + fprintf(stderr, "Zip has no classes.dex\n"); + } + goto bail; + } + } + + result = kUTFRGenericFailure; + + /* + * Pop open the (presumed) DEX file. + */ + fd = open(fileName, O_RDONLY | O_BINARY); + if (fd < 0) { + if (!quiet) { + fprintf(stderr, "ERROR: unable to open '%s': %s\n", + fileName, strerror(errno)); + } + goto bail; + } + + if (sysMapFileInShmemWritableReadOnly(fd, pMap) != 0) { + fprintf(stderr, "ERROR: Unable to map '%s'\n", fileName); + goto bail; + } + + /* + * This call will fail if the file exists on a filesystem that + * doesn't support mprotect(). If that's the case, then the file + * will have already been mapped private-writable by the previous + * call, so we don't need to do anything special if this call + * returns non-zero. + */ + sysChangeMapAccess(pMap->addr, pMap->length, true, pMap); + + if (dexSwapAndVerifyIfNecessary((u1*) pMap->addr, pMap->length)) { + fprintf(stderr, "ERROR: Failed structural verification of '%s'\n", + fileName); + goto bail; + } + + /* + * Similar to above, this call will fail if the file wasn't ever + * read-only to begin with. This is innocuous, though it is + * undesirable from a memory hygiene perspective. + */ + sysChangeMapAccess(pMap->addr, pMap->length, false, pMap); + + /* + * Success! Close the file and return with the start/length in pMap. + */ + result = kUTFRSuccess; + +bail: + if (fd >= 0) + close(fd); + if (removeTemp) { + /* this will fail if the OS doesn't allow removal of a mapped file */ + if (unlink(tempFileName) != 0) { + fprintf(stderr, "WARNING: unable to remove temp '%s'\n", + tempFileName); + } + } + return result; +} diff --git a/libdex/CmdUtils.h b/libdex/CmdUtils.h new file mode 100644 index 000000000..887eed9ba --- /dev/null +++ b/libdex/CmdUtils.h @@ -0,0 +1,73 @@ +/* + * 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. + */ +/* + * Access .dex (Dalvik Executable Format) files. The code here assumes that + * the DEX file has been rewritten (byte-swapped, word-aligned) and that + * the contents can be directly accessed as a collection of C arrays. Please + * see docs/dalvik/dex-format.html for a detailed description. + * + * The structure and field names were chosen to match those in the DEX spec. + * + * It's generally assumed that the DEX file will be stored in shared memory, + * obviating the need to copy code and constant pool entries into newly + * allocated storage. Maintaining local pointers to items in the shared area + * is valid and encouraged. + * + * All memory-mapped structures are 32-bit aligned unless otherwise noted. + */ +#ifndef LIBDEX_CMDUTILS_H_ +#define LIBDEX_CMDUTILS_H_ + +/* encode the result of unzipping to a file */ +enum UnzipToFileResult { + kUTFRSuccess = 0, + kUTFRGenericFailure, + kUTFRBadArgs, + kUTFRNotZip, + kUTFRNoClassesDex, + kUTFROutputFileProblem, + kUTFRBadZip, +}; + +/* + * Map the specified DEX file read-only (possibly after expanding it into a + * temp file from a Jar). Pass in a MemMapping struct to hold the info. + * If the file is an unoptimized DEX file, then byte-swapping and structural + * verification are performed on it before the memory is made read-only. + * + * The temp file is deleted after the map succeeds. + * + * This is intended for use by tools (e.g. dexdump) that need to get a + * read-only copy of a DEX file that could be in a number of different states. + * + * If "tempFileName" is NULL, a default value is used. The temp file is + * deleted after the map succeeds. + * + * If "quiet" is set, don't report common errors. + * + * Returns 0 (kUTFRSuccess) on success. + */ +UnzipToFileResult dexOpenAndMap(const char* fileName, const char* tempFileName, + MemMapping* pMap, bool quiet); + +/* + * Utility function to open a Zip archive, find "classes.dex", and extract + * it to a file. + */ +UnzipToFileResult dexUnzipToFile(const char* zipFileName, + const char* outFileName, bool quiet); + +#endif // LIBDEX_CMDUTILS_H_ diff --git a/libdex/DexCatch.cpp b/libdex/DexCatch.cpp new file mode 100644 index 000000000..ed97e87d9 --- /dev/null +++ b/libdex/DexCatch.cpp @@ -0,0 +1,90 @@ +/* + * 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. + */ + +/* + * Functions for dealing with try-catch info. + */ + +#include "DexCatch.h" + +/* Get the first handler offset for the given DexCode. + * It's not 0 because the handlers list is prefixed with its size + * (in entries) as a uleb128. */ +u4 dexGetFirstHandlerOffset(const DexCode* pCode) { + if (pCode->triesSize == 0) { + return 0; + } + + const u1* baseData = dexGetCatchHandlerData(pCode); + const u1* data = baseData; + + readUnsignedLeb128(&data); + + return data - baseData; +} + +/* Get count of handler lists for the given DexCode. */ +u4 dexGetHandlersSize(const DexCode* pCode) { + if (pCode->triesSize == 0) { + return 0; + } + + const u1* data = dexGetCatchHandlerData(pCode); + + return readUnsignedLeb128(&data); +} + +/* Helper for dexFindCatchHandlerOffset(), which does an actual search + * in the tries table. Returns -1 if there is no applicable handler. */ +int dexFindCatchHandlerOffset0(u2 triesSize, const DexTry* pTries, + u4 address) { + // Note: Signed type is important for max and min. + int min = 0; + int max = triesSize - 1; + + while (max >= min) { + int guess = (min + max) >> 1; + const DexTry* pTry = &pTries[guess]; + u4 start = pTry->startAddr; + + if (address < start) { + max = guess - 1; + continue; + } + + u4 end = start + pTry->insnCount; + + if (address >= end) { + min = guess + 1; + continue; + } + + // We have a winner! + return (int) pTry->handlerOff; + } + + // No match. + return -1; +} + +/* Get the handler offset just past the end of the one just iterated over. + * This ends the iteration if it wasn't already. */ +u4 dexCatchIteratorGetEndOffset(DexCatchIterator* pIterator, + const DexCode* pCode) { + while (dexCatchIteratorNext(pIterator) != NULL) /* empty */ ; + + return (u4) (pIterator->pEncodedData - dexGetCatchHandlerData(pCode)); +} diff --git a/libdex/DexCatch.h b/libdex/DexCatch.h new file mode 100644 index 000000000..cfea2d900 --- /dev/null +++ b/libdex/DexCatch.h @@ -0,0 +1,162 @@ +/* + * 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. + */ + +/* + * Functions for dealing with try-catch info. + */ + +#ifndef LIBDEX_DEXCATCH_H_ +#define LIBDEX_DEXCATCH_H_ + +#include "DexFile.h" +#include "Leb128.h" + +/* + * Catch handler entry, used while iterating over catch_handler_items. + */ +struct DexCatchHandler { + u4 typeIdx; /* type index of the caught exception type */ + u4 address; /* handler address */ +}; + +/* Get the first handler offset for the given DexCode. + * It's not 0 because the handlers list is prefixed with its size + * (in entries) as a uleb128. */ +u4 dexGetFirstHandlerOffset(const DexCode* pCode); + +/* Get count of handler lists for the given DexCode. */ +u4 dexGetHandlersSize(const DexCode* pCode); + +/* + * Iterator over catch handler data. This structure should be treated as + * opaque. + */ +struct DexCatchIterator { + const u1* pEncodedData; + bool catchesAll; + u4 countRemaining; + DexCatchHandler handler; +}; + +/* Initialize a DexCatchIterator to emptiness. This mostly exists to + * squelch innocuous warnings. */ +DEX_INLINE void dexCatchIteratorClear(DexCatchIterator* pIterator) { + pIterator->pEncodedData = NULL; + pIterator->catchesAll = false; + pIterator->countRemaining = 0; + pIterator->handler.typeIdx = 0; + pIterator->handler.address = 0; +} + +/* Initialize a DexCatchIterator with a direct pointer to encoded handlers. */ +DEX_INLINE void dexCatchIteratorInitToPointer(DexCatchIterator* pIterator, + const u1* pEncodedData) +{ + s4 count = readSignedLeb128(&pEncodedData); + + if (count <= 0) { + pIterator->catchesAll = true; + count = -count; + } else { + pIterator->catchesAll = false; + } + + pIterator->pEncodedData = pEncodedData; + pIterator->countRemaining = count; +} + +/* Initialize a DexCatchIterator to a particular handler offset. */ +DEX_INLINE void dexCatchIteratorInit(DexCatchIterator* pIterator, + const DexCode* pCode, u4 offset) +{ + dexCatchIteratorInitToPointer(pIterator, + dexGetCatchHandlerData(pCode) + offset); +} + +/* Get the next item from a DexCatchIterator. Returns NULL if at end. */ +DEX_INLINE DexCatchHandler* dexCatchIteratorNext(DexCatchIterator* pIterator) { + if (pIterator->countRemaining == 0) { + if (! pIterator->catchesAll) { + return NULL; + } + + pIterator->catchesAll = false; + pIterator->handler.typeIdx = kDexNoIndex; + } else { + u4 typeIdx = readUnsignedLeb128(&pIterator->pEncodedData); + pIterator->handler.typeIdx = typeIdx; + pIterator->countRemaining--; + } + + pIterator->handler.address = readUnsignedLeb128(&pIterator->pEncodedData); + return &pIterator->handler; +} + +/* Get the handler offset just past the end of the one just iterated over. + * This ends the iteration if it wasn't already. */ +u4 dexCatchIteratorGetEndOffset(DexCatchIterator* pIterator, + const DexCode* pCode); + +/* Helper for dexFindCatchHandler(). Do not call directly. */ +int dexFindCatchHandlerOffset0(u2 triesSize, const DexTry* pTries, + u4 address); + +/* Find the handler associated with a given address, if any. + * Initializes the given iterator and returns true if a match is + * found. Returns false if there is no applicable handler. */ +DEX_INLINE bool dexFindCatchHandler(DexCatchIterator *pIterator, + const DexCode* pCode, u4 address) { + u2 triesSize = pCode->triesSize; + int offset = -1; + + // Short-circuit the overwhelmingly common cases. + switch (triesSize) { + case 0: { + break; + } + case 1: { + const DexTry* tries = dexGetTries(pCode); + u4 start = tries[0].startAddr; + + if (address < start) { + break; + } + + u4 end = start + tries[0].insnCount; + + if (address >= end) { + break; + } + + offset = tries[0].handlerOff; + break; + } + default: { + offset = dexFindCatchHandlerOffset0(triesSize, dexGetTries(pCode), + address); + } + } + + if (offset < 0) { + dexCatchIteratorClear(pIterator); // This squelches warnings. + return false; + } else { + dexCatchIteratorInit(pIterator, pCode, offset); + return true; + } +} + +#endif // LIBDEX_DEXCATCH_H_ diff --git a/libdex/DexClass.cpp b/libdex/DexClass.cpp new file mode 100644 index 000000000..a33906c94 --- /dev/null +++ b/libdex/DexClass.cpp @@ -0,0 +1,191 @@ +/* + * 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. + */ + +/* + * Functions to deal with class definition structures in DEX files + */ + +#include <stdlib.h> +#include <string.h> +#include "DexClass.h" +#include "Leb128.h" + +/* Helper for verification which reads and verifies a given number + * of uleb128 values. */ +static bool verifyUlebs(const u1* pData, const u1* pLimit, u4 count) { + bool okay = true; + + while (okay && (count-- != 0)) { + readAndVerifyUnsignedLeb128(&pData, pLimit, &okay); + } + + return okay; +} + +/* Read and verify the header of a class_data_item. This updates the + * given data pointer to point past the end of the read data and + * returns an "okay" flag (that is, false == failure). */ +bool dexReadAndVerifyClassDataHeader(const u1** pData, const u1* pLimit, + DexClassDataHeader *pHeader) { + if (! verifyUlebs(*pData, pLimit, 4)) { + return false; + } + + dexReadClassDataHeader(pData, pHeader); + return true; +} + +/* Read and verify an encoded_field. This updates the + * given data pointer to point past the end of the read data and + * returns an "okay" flag (that is, false == failure). + * + * The lastIndex value should be set to 0 before the first field in + * a list is read. It is updated as fields are read and used in the + * decode process. + * + * The verification done by this function is of the raw data format + * only; it does not verify that access flags or indices + * are valid. */ +bool dexReadAndVerifyClassDataField(const u1** pData, const u1* pLimit, + DexField* pField, u4* lastIndex) { + if (! verifyUlebs(*pData, pLimit, 2)) { + return false; + } + + dexReadClassDataField(pData, pField, lastIndex); + return true; +} + +/* Read and verify an encoded_method. This updates the + * given data pointer to point past the end of the read data and + * returns an "okay" flag (that is, false == failure). + * + * The lastIndex value should be set to 0 before the first method in + * a list is read. It is updated as fields are read and used in the + * decode process. + * + * The verification done by this function is of the raw data format + * only; it does not verify that access flags, indices, or offsets + * are valid. */ +bool dexReadAndVerifyClassDataMethod(const u1** pData, const u1* pLimit, + DexMethod* pMethod, u4* lastIndex) { + if (! verifyUlebs(*pData, pLimit, 3)) { + return false; + } + + dexReadClassDataMethod(pData, pMethod, lastIndex); + return true; +} + +/* Read, verify, and return an entire class_data_item. This updates + * the given data pointer to point past the end of the read data. This + * function allocates a single chunk of memory for the result, which + * must subsequently be free()d. This function returns NULL if there + * was trouble parsing the data. If this function is passed NULL, it + * returns an initialized empty DexClassData structure. + * + * The verification done by this function is of the raw data format + * only; it does not verify that access flags, indices, or offsets + * are valid. */ +DexClassData* dexReadAndVerifyClassData(const u1** pData, const u1* pLimit) { + DexClassDataHeader header; + u4 lastIndex; + + if (*pData == NULL) { + DexClassData* result = (DexClassData*) malloc(sizeof(DexClassData)); + memset(result, 0, sizeof(*result)); + return result; + } + + if (! dexReadAndVerifyClassDataHeader(pData, pLimit, &header)) { + return NULL; + } + + size_t resultSize = sizeof(DexClassData) + + (header.staticFieldsSize * sizeof(DexField)) + + (header.instanceFieldsSize * sizeof(DexField)) + + (header.directMethodsSize * sizeof(DexMethod)) + + (header.virtualMethodsSize * sizeof(DexMethod)); + + DexClassData* result = (DexClassData*) malloc(resultSize); + u1* ptr = ((u1*) result) + sizeof(DexClassData); + bool okay = true; + u4 i; + + if (result == NULL) { + return NULL; + } + + result->header = header; + + if (header.staticFieldsSize != 0) { + result->staticFields = (DexField*) ptr; + ptr += header.staticFieldsSize * sizeof(DexField); + } else { + result->staticFields = NULL; + } + + if (header.instanceFieldsSize != 0) { + result->instanceFields = (DexField*) ptr; + ptr += header.instanceFieldsSize * sizeof(DexField); + } else { + result->instanceFields = NULL; + } + + if (header.directMethodsSize != 0) { + result->directMethods = (DexMethod*) ptr; + ptr += header.directMethodsSize * sizeof(DexMethod); + } else { + result->directMethods = NULL; + } + + if (header.virtualMethodsSize != 0) { + result->virtualMethods = (DexMethod*) ptr; + } else { + result->virtualMethods = NULL; + } + + lastIndex = 0; + for (i = 0; okay && (i < header.staticFieldsSize); i++) { + okay = dexReadAndVerifyClassDataField(pData, pLimit, + &result->staticFields[i], &lastIndex); + } + + lastIndex = 0; + for (i = 0; okay && (i < header.instanceFieldsSize); i++) { + okay = dexReadAndVerifyClassDataField(pData, pLimit, + &result->instanceFields[i], &lastIndex); + } + + lastIndex = 0; + for (i = 0; okay && (i < header.directMethodsSize); i++) { + okay = dexReadAndVerifyClassDataMethod(pData, pLimit, + &result->directMethods[i], &lastIndex); + } + + lastIndex = 0; + for (i = 0; okay && (i < header.virtualMethodsSize); i++) { + okay = dexReadAndVerifyClassDataMethod(pData, pLimit, + &result->virtualMethods[i], &lastIndex); + } + + if (! okay) { + free(result); + return NULL; + } + + return result; +} diff --git a/libdex/DexClass.h b/libdex/DexClass.h new file mode 100644 index 000000000..11b3b0eee --- /dev/null +++ b/libdex/DexClass.h @@ -0,0 +1,162 @@ +/* + * 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. + */ + +/* + * Functions to deal with class definition structures in DEX files + */ + +#ifndef LIBDEX_DEXCLASS_H_ +#define LIBDEX_DEXCLASS_H_ + +#include "DexFile.h" +#include "Leb128.h" + +/* expanded form of a class_data_item header */ +struct DexClassDataHeader { + u4 staticFieldsSize; + u4 instanceFieldsSize; + u4 directMethodsSize; + u4 virtualMethodsSize; +}; + +/* expanded form of encoded_field */ +struct DexField { + u4 fieldIdx; /* index to a field_id_item */ + u4 accessFlags; +}; + +/* expanded form of encoded_method */ +struct DexMethod { + u4 methodIdx; /* index to a method_id_item */ + u4 accessFlags; + u4 codeOff; /* file offset to a code_item */ +}; + +/* expanded form of class_data_item. Note: If a particular item is + * absent (e.g., no static fields), then the corresponding pointer + * is set to NULL. */ +struct DexClassData { + DexClassDataHeader header; + DexField* staticFields; + DexField* instanceFields; + DexMethod* directMethods; + DexMethod* virtualMethods; +}; + +/* Read and verify the header of a class_data_item. This updates the + * given data pointer to point past the end of the read data and + * returns an "okay" flag (that is, false == failure). */ +bool dexReadAndVerifyClassDataHeader(const u1** pData, const u1* pLimit, + DexClassDataHeader *pHeader); + +/* Read and verify an encoded_field. This updates the + * given data pointer to point past the end of the read data and + * returns an "okay" flag (that is, false == failure). + * + * The lastIndex value should be set to 0 before the first field in + * a list is read. It is updated as fields are read and used in the + * decode process. + * + * The verification done by this function is of the raw data format + * only; it does not verify that access flags or indices + * are valid. */ +bool dexReadAndVerifyClassDataField(const u1** pData, const u1* pLimit, + DexField* pField, u4* lastIndex); + +/* Read and verify an encoded_method. This updates the + * given data pointer to point past the end of the read data and + * returns an "okay" flag (that is, false == failure). + * + * The lastIndex value should be set to 0 before the first method in + * a list is read. It is updated as fields are read and used in the + * decode process. + * + * The verification done by this function is of the raw data format + * only; it does not verify that access flags, indices, or offsets + * are valid. */ +bool dexReadAndVerifyClassDataMethod(const u1** pData, const u1* pLimit, + DexMethod* pMethod, u4* lastIndex); + +/* Read, verify, and return an entire class_data_item. This updates + * the given data pointer to point past the end of the read data. This + * function allocates a single chunk of memory for the result, which + * must subsequently be free()d. This function returns NULL if there + * was trouble parsing the data. If this function is passed NULL, it + * returns an initialized empty DexClassData structure. + * + * The verification done by this function is of the raw data format + * only; it does not verify that access flags, indices, or offsets + * are valid. */ +DexClassData* dexReadAndVerifyClassData(const u1** pData, const u1* pLimit); + +/* + * Get the DexCode for a DexMethod. Returns NULL if the class is native + * or abstract. + */ +DEX_INLINE const DexCode* dexGetCode(const DexFile* pDexFile, + const DexMethod* pDexMethod) +{ + if (pDexMethod->codeOff == 0) + return NULL; + return (const DexCode*) (pDexFile->baseAddr + pDexMethod->codeOff); +} + + +/* Read the header of a class_data_item without verification. This + * updates the given data pointer to point past the end of the read + * data. */ +DEX_INLINE void dexReadClassDataHeader(const u1** pData, + DexClassDataHeader *pHeader) { + pHeader->staticFieldsSize = readUnsignedLeb128(pData); + pHeader->instanceFieldsSize = readUnsignedLeb128(pData); + pHeader->directMethodsSize = readUnsignedLeb128(pData); + pHeader->virtualMethodsSize = readUnsignedLeb128(pData); +} + +/* Read an encoded_field without verification. This updates the + * given data pointer to point past the end of the read data. + * + * The lastIndex value should be set to 0 before the first field in + * a list is read. It is updated as fields are read and used in the + * decode process. + */ +DEX_INLINE void dexReadClassDataField(const u1** pData, DexField* pField, + u4* lastIndex) { + u4 index = *lastIndex + readUnsignedLeb128(pData); + + pField->accessFlags = readUnsignedLeb128(pData); + pField->fieldIdx = index; + *lastIndex = index; +} + +/* Read an encoded_method without verification. This updates the + * given data pointer to point past the end of the read data. + * + * The lastIndex value should be set to 0 before the first method in + * a list is read. It is updated as fields are read and used in the + * decode process. + */ +DEX_INLINE void dexReadClassDataMethod(const u1** pData, DexMethod* pMethod, + u4* lastIndex) { + u4 index = *lastIndex + readUnsignedLeb128(pData); + + pMethod->accessFlags = readUnsignedLeb128(pData); + pMethod->codeOff = readUnsignedLeb128(pData); + pMethod->methodIdx = index; + *lastIndex = index; +} + +#endif // LIBDEX_DEXCLASS_H_ diff --git a/libdex/DexDataMap.cpp b/libdex/DexDataMap.cpp new file mode 100644 index 000000000..18e4a4519 --- /dev/null +++ b/libdex/DexDataMap.cpp @@ -0,0 +1,138 @@ +/* + * 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. + */ + +/* + * Verification-time map of data section items + */ + +#include "DexDataMap.h" +#include <stdlib.h> + +/* + * Allocate and initialize a DexDataMap. Returns NULL on failure. + */ +DexDataMap* dexDataMapAlloc(u4 maxCount) { + /* + * Allocate a single chunk for the DexDataMap itself as well as the + * two arrays. + */ + size_t size = 0; + DexDataMap* map = NULL; + + const u4 sizeOfItems = (u4) (sizeof(u4) + sizeof(u2)); + if (__builtin_mul_overflow(maxCount, sizeOfItems, &size) || + __builtin_add_overflow(size, sizeof(DexDataMap), &size)) { + return NULL; + } + + map = (DexDataMap*) malloc(size); + + if (map == NULL) { + return NULL; + } + + map->count = 0; + map->max = maxCount; + map->offsets = (u4*) (map + 1); + map->types = (u2*) (map->offsets + maxCount); + + return map; +} + +/* + * Free a DexDataMap. + */ +void dexDataMapFree(DexDataMap* map) { + /* + * Since everything got allocated together, everything can be freed + * in one fell swoop. Also, free(NULL) is a nop (per spec), so we + * don't have to worry about an explicit test for that. + */ + free(map); +} + +/* + * Add a new element to the map. The offset must be greater than the + * all previously added offsets. + */ +void dexDataMapAdd(DexDataMap* map, u4 offset, u2 type) { + assert(map != NULL); + assert(map->count < map->max); + + if ((map->count != 0) && + (map->offsets[map->count - 1] >= offset)) { + ALOGE("Out-of-order data map offset: %#x then %#x", + map->offsets[map->count - 1], offset); + return; + } + + map->offsets[map->count] = offset; + map->types[map->count] = type; + map->count++; +} + +/* + * Get the type associated with the given offset. This returns -1 if + * there is no entry for the given offset. + */ +int dexDataMapGet(DexDataMap* map, u4 offset) { + assert(map != NULL); + + // Note: Signed type is important for max and min. + int min = 0; + int max = map->count - 1; + u4* offsets = map->offsets; + + while (max >= min) { + int guessIdx = (min + max) >> 1; + u4 guess = offsets[guessIdx]; + + if (offset < guess) { + max = guessIdx - 1; + } else if (offset > guess) { + min = guessIdx + 1; + } else { + // We have a winner! + return map->types[guessIdx]; + } + } + + // No match. + return -1; +} + +/* + * Verify that there is an entry in the map, mapping the given offset to + * the given type. This will return true if such an entry exists and + * return false as well as log an error if not. + */ +bool dexDataMapVerify(DexDataMap* map, u4 offset, u2 type) { + int found = dexDataMapGet(map, offset); + + if (found == type) { + return true; + } + + if (found < 0) { + ALOGE("No data map entry found @ %#x; expected %x", + offset, type); + } else { + ALOGE("Unexpected data map entry @ %#x: expected %x, found %x", + offset, type, found); + } + + return false; +} diff --git a/libdex/DexDataMap.h b/libdex/DexDataMap.h new file mode 100644 index 000000000..7e43dc934 --- /dev/null +++ b/libdex/DexDataMap.h @@ -0,0 +1,73 @@ +/* + * 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. + */ + +/* + * Verification-time map of data section items + */ + +#ifndef LIBDEX_DEXDATAMAP_H_ +#define LIBDEX_DEXDATAMAP_H_ + +#include "DexFile.h" + +struct DexDataMap { + u4 count; /* number of items currently in the map */ + u4 max; /* maximum number of items that may be held */ + u4* offsets; /* array of item offsets */ + u2* types; /* corresponding array of item types */ +}; + +/* + * Allocate and initialize a DexDataMap. Returns NULL on failure. + */ +DexDataMap* dexDataMapAlloc(u4 maxCount); + +/* + * Free a DexDataMap. + */ +void dexDataMapFree(DexDataMap* map); + +/* + * Add a new element to the map. The offset must be greater than the + * all previously added offsets. + */ +void dexDataMapAdd(DexDataMap* map, u4 offset, u2 type); + +/* + * Get the type associated with the given offset. This returns -1 if + * there is no entry for the given offset. + */ +int dexDataMapGet(DexDataMap* map, u4 offset); + +/* + * Verify that there is an entry in the map, mapping the given offset to + * the given type. This will return true if such an entry exists and + * return false as well as log an error if not. + */ +bool dexDataMapVerify(DexDataMap* map, u4 offset, u2 type); + +/* + * Like dexDataMapVerify(), but also accept a 0 offset as valid. + */ +DEX_INLINE bool dexDataMapVerify0Ok(DexDataMap* map, u4 offset, u2 type) { + if (offset == 0) { + return true; + } + + return dexDataMapVerify(map, offset, type); +} + +#endif // LIBDEX_DEXDATAMAP_H_ diff --git a/libdex/DexDebugInfo.cpp b/libdex/DexDebugInfo.cpp new file mode 100644 index 000000000..5f9ffbe0f --- /dev/null +++ b/libdex/DexDebugInfo.cpp @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Handling of method debug info in a .dex file. + */ + +#include "DexDebugInfo.h" +#include "DexProto.h" +#include "Leb128.h" + +#include <stdlib.h> +#include <string.h> + +/* + * Reads a string index as encoded for the debug info format, + * returning a string pointer or NULL as appropriate. + */ +static const char* readStringIdx(const DexFile* pDexFile, + const u1** pStream) { + u4 stringIdx = readUnsignedLeb128(pStream); + + // Remember, encoded string indicies have 1 added to them. + if (stringIdx == 0) { + return NULL; + } else { + return dexStringById(pDexFile, stringIdx - 1); + } +} + +/* + * Reads a type index as encoded for the debug info format, returning + * a string pointer for its descriptor or NULL as appropriate. + */ +static const char* readTypeIdx(const DexFile* pDexFile, + const u1** pStream) { + u4 typeIdx = readUnsignedLeb128(pStream); + + // Remember, encoded type indicies have 1 added to them. + if (typeIdx == 0) { + return NULL; + } else { + return dexStringByTypeIdx(pDexFile, typeIdx - 1); + } +} + +struct LocalInfo { + const char *name; + const char *descriptor; + const char *signature; + u2 startAddress; + bool live; +}; + +static void emitLocalCbIfLive(void *cnxt, int reg, u4 endAddress, + LocalInfo *localInReg, DexDebugNewLocalCb localCb) +{ + if (localCb != NULL && localInReg[reg].live) { + localCb(cnxt, reg, localInReg[reg].startAddress, endAddress, + localInReg[reg].name, + localInReg[reg].descriptor, + localInReg[reg].signature == NULL + ? "" : localInReg[reg].signature ); + } +} + +static void invalidStream(const char* classDescriptor, const DexProto* proto) { + IF_ALOGE() { + char* methodDescriptor = dexProtoCopyMethodDescriptor(proto); + ALOGE("Invalid debug info stream. class %s; proto %s", + classDescriptor, methodDescriptor); + free(methodDescriptor); + } +} + +static void dexDecodeDebugInfo0( + const DexFile* pDexFile, + const DexCode* pCode, + const char* classDescriptor, + u4 protoIdx, + u4 accessFlags, + DexDebugNewPositionCb posCb, DexDebugNewLocalCb localCb, + void* cnxt, + const u1* stream, + LocalInfo* localInReg) +{ + DexProto proto = { pDexFile, protoIdx }; + u4 line = readUnsignedLeb128(&stream); + u4 parametersSize = readUnsignedLeb128(&stream); + u2 argReg = pCode->registersSize - pCode->insSize; + u4 address = 0; + + if ((accessFlags & ACC_STATIC) == 0) { + /* + * The code is an instance method, which means that there is + * an initial this parameter. Also, the proto list should + * contain exactly one fewer argument word than the insSize + * indicates. + */ + assert(pCode->insSize == (dexProtoComputeArgsSize(&proto) + 1)); + localInReg[argReg].name = "this"; + localInReg[argReg].descriptor = classDescriptor; + localInReg[argReg].startAddress = 0; + localInReg[argReg].live = true; + argReg++; + } else { + assert(pCode->insSize == dexProtoComputeArgsSize(&proto)); + } + + DexParameterIterator iterator; + dexParameterIteratorInit(&iterator, &proto); + + while (parametersSize-- != 0) { + const char* descriptor = dexParameterIteratorNextDescriptor(&iterator); + const char *name; + int reg; + + if ((argReg >= pCode->registersSize) || (descriptor == NULL)) { + invalidStream(classDescriptor, &proto); + return; + } + + name = readStringIdx(pDexFile, &stream); + reg = argReg; + + switch (descriptor[0]) { + case 'D': + case 'J': + argReg += 2; + break; + default: + argReg += 1; + break; + } + + if (name != NULL) { + localInReg[reg].name = name; + localInReg[reg].descriptor = descriptor; + localInReg[reg].signature = NULL; + localInReg[reg].startAddress = address; + localInReg[reg].live = true; + } + } + + for (;;) { + u1 opcode = *stream++; + u2 reg; + + switch (opcode) { + case DBG_END_SEQUENCE: + return; + + case DBG_ADVANCE_PC: + address += readUnsignedLeb128(&stream); + break; + + case DBG_ADVANCE_LINE: + line += readSignedLeb128(&stream); + break; + + case DBG_START_LOCAL: + case DBG_START_LOCAL_EXTENDED: + reg = readUnsignedLeb128(&stream); + if (reg > pCode->registersSize) { + invalidStream(classDescriptor, &proto); + return; + } + + // Emit what was previously there, if anything + emitLocalCbIfLive(cnxt, reg, address, + localInReg, localCb); + + localInReg[reg].name = readStringIdx(pDexFile, &stream); + localInReg[reg].descriptor = readTypeIdx(pDexFile, &stream); + if (opcode == DBG_START_LOCAL_EXTENDED) { + localInReg[reg].signature + = readStringIdx(pDexFile, &stream); + } else { + localInReg[reg].signature = NULL; + } + localInReg[reg].startAddress = address; + localInReg[reg].live = true; + break; + + case DBG_END_LOCAL: + reg = readUnsignedLeb128(&stream); + if (reg > pCode->registersSize) { + invalidStream(classDescriptor, &proto); + return; + } + + emitLocalCbIfLive (cnxt, reg, address, localInReg, localCb); + localInReg[reg].live = false; + break; + + case DBG_RESTART_LOCAL: + reg = readUnsignedLeb128(&stream); + if (reg > pCode->registersSize) { + invalidStream(classDescriptor, &proto); + return; + } + + if (localInReg[reg].name == NULL + || localInReg[reg].descriptor == NULL) { + invalidStream(classDescriptor, &proto); + return; + } + + /* + * If the register is live, the "restart" is superfluous, + * and we don't want to mess with the existing start address. + */ + if (!localInReg[reg].live) { + localInReg[reg].startAddress = address; + localInReg[reg].live = true; + } + break; + + case DBG_SET_PROLOGUE_END: + case DBG_SET_EPILOGUE_BEGIN: + case DBG_SET_FILE: + break; + + default: { + int adjopcode = opcode - DBG_FIRST_SPECIAL; + + address += adjopcode / DBG_LINE_RANGE; + line += DBG_LINE_BASE + (adjopcode % DBG_LINE_RANGE); + + if (posCb != NULL) { + int done; + done = posCb(cnxt, address, line); + + if (done) { + // early exit + return; + } + } + break; + } + } + } +} + +// TODO optimize localCb == NULL case +void dexDecodeDebugInfo( + const DexFile* pDexFile, + const DexCode* pCode, + const char* classDescriptor, + u4 protoIdx, + u4 accessFlags, + DexDebugNewPositionCb posCb, DexDebugNewLocalCb localCb, + void* cnxt) +{ + const u1* stream = dexGetDebugInfoStream(pDexFile, pCode); + LocalInfo localInReg[pCode->registersSize]; + + memset(localInReg, 0, sizeof(LocalInfo) * pCode->registersSize); + + if (stream != NULL) { + dexDecodeDebugInfo0(pDexFile, pCode, classDescriptor, protoIdx, accessFlags, + posCb, localCb, cnxt, stream, localInReg); + } + + for (int reg = 0; reg < pCode->registersSize; reg++) { + emitLocalCbIfLive(cnxt, reg, pCode->insnsSize, localInReg, localCb); + } +} diff --git a/libdex/DexDebugInfo.h b/libdex/DexDebugInfo.h new file mode 100644 index 000000000..bd0954c8c --- /dev/null +++ b/libdex/DexDebugInfo.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Handling of method debug info in a .dex file. + */ + +#ifndef LIBDEX_DEXDEBUGINFO_H_ +#define LIBDEX_DEXDEBUGINFO_H_ + +#include "DexFile.h" + +/* + * Callback for "new position table entry". + * Returning non-0 causes the decoder to stop early. + */ +typedef int (*DexDebugNewPositionCb)(void *cnxt, u4 address, u4 lineNum); + +/* + * Callback for "new locals table entry". "signature" is an empty string + * if no signature is available for an entry. + */ +typedef void (*DexDebugNewLocalCb)(void *cnxt, u2 reg, u4 startAddress, + u4 endAddress, const char *name, const char *descriptor, + const char *signature); + +/* + * Decode debug info for method. + * + * posCb is called in ascending address order. + * localCb is called in order of ascending end address. + */ +void dexDecodeDebugInfo( + const DexFile* pDexFile, + const DexCode* pDexCode, + const char* classDescriptor, + u4 protoIdx, + u4 accessFlags, + DexDebugNewPositionCb posCb, DexDebugNewLocalCb localCb, + void* cnxt); + +#endif // LIBDEX_DEXDEBUGINFO_H_ diff --git a/libdex/DexFile.cpp b/libdex/DexFile.cpp new file mode 100644 index 000000000..d9acd5188 --- /dev/null +++ b/libdex/DexFile.cpp @@ -0,0 +1,539 @@ +/* + * 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. + */ + +/* + * Access the contents of a .dex file. + */ + +#include "DexFile.h" +#include "DexOptData.h" +#include "DexProto.h" +#include "DexCatch.h" +#include "Leb128.h" +#include "sha1.h" +#include "ZipArchive.h" + +#include <zlib.h> + +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> + + +/* + * Verifying checksums is good, but it slows things down and causes us to + * touch every page. In the "optimized" world, it doesn't work at all, + * because we rewrite the contents. + */ +static const bool kVerifySignature = false; + +/* (documented in header) */ +char dexGetPrimitiveTypeDescriptorChar(PrimitiveType type) { + const char* string = dexGetPrimitiveTypeDescriptor(type); + + return (string == NULL) ? '\0' : string[0]; +} + +/* (documented in header) */ +const char* dexGetPrimitiveTypeDescriptor(PrimitiveType type) { + switch (type) { + case PRIM_VOID: return "V"; + case PRIM_BOOLEAN: return "Z"; + case PRIM_BYTE: return "B"; + case PRIM_SHORT: return "S"; + case PRIM_CHAR: return "C"; + case PRIM_INT: return "I"; + case PRIM_LONG: return "J"; + case PRIM_FLOAT: return "F"; + case PRIM_DOUBLE: return "D"; + default: return NULL; + } + + return NULL; +} + +/* (documented in header) */ +const char* dexGetBoxedTypeDescriptor(PrimitiveType type) { + switch (type) { + case PRIM_VOID: return NULL; + case PRIM_BOOLEAN: return "Ljava/lang/Boolean;"; + case PRIM_BYTE: return "Ljava/lang/Byte;"; + case PRIM_SHORT: return "Ljava/lang/Short;"; + case PRIM_CHAR: return "Ljava/lang/Character;"; + case PRIM_INT: return "Ljava/lang/Integer;"; + case PRIM_LONG: return "Ljava/lang/Long;"; + case PRIM_FLOAT: return "Ljava/lang/Float;"; + case PRIM_DOUBLE: return "Ljava/lang/Double;"; + default: return NULL; + } +} + +/* (documented in header) */ +PrimitiveType dexGetPrimitiveTypeFromDescriptorChar(char descriptorChar) { + switch (descriptorChar) { + case 'V': return PRIM_VOID; + case 'Z': return PRIM_BOOLEAN; + case 'B': return PRIM_BYTE; + case 'S': return PRIM_SHORT; + case 'C': return PRIM_CHAR; + case 'I': return PRIM_INT; + case 'J': return PRIM_LONG; + case 'F': return PRIM_FLOAT; + case 'D': return PRIM_DOUBLE; + default: return PRIM_NOT; + } +} + +/* Return the UTF-8 encoded string with the specified string_id index, + * also filling in the UTF-16 size (number of 16-bit code points).*/ +const char* dexStringAndSizeById(const DexFile* pDexFile, u4 idx, + u4* utf16Size) { + const DexStringId* pStringId = dexGetStringId(pDexFile, idx); + const u1* ptr = pDexFile->baseAddr + pStringId->stringDataOff; + + *utf16Size = readUnsignedLeb128(&ptr); + return (const char*) ptr; +} + +/* + * Format an SHA-1 digest for printing. tmpBuf must be able to hold at + * least kSHA1DigestOutputLen bytes. + */ +const char* dvmSHA1DigestToStr(const unsigned char digest[], char* tmpBuf); + +/* + * Compute a SHA-1 digest on a range of bytes. + */ +static void dexComputeSHA1Digest(const unsigned char* data, size_t length, + unsigned char digest[]) +{ + SHA1_CTX context; + SHA1Init(&context); + SHA1Update(&context, data, length); + SHA1Final(digest, &context); +} + +/* + * Format the SHA-1 digest into the buffer, which must be able to hold at + * least kSHA1DigestOutputLen bytes. Returns a pointer to the buffer, + */ +static const char* dexSHA1DigestToStr(const unsigned char digest[],char* tmpBuf) +{ + static const char hexDigit[] = "0123456789abcdef"; + char* cp; + int i; + + cp = tmpBuf; + for (i = 0; i < kSHA1DigestLen; i++) { + *cp++ = hexDigit[digest[i] >> 4]; + *cp++ = hexDigit[digest[i] & 0x0f]; + } + *cp++ = '\0'; + + assert(cp == tmpBuf + kSHA1DigestOutputLen); + + return tmpBuf; +} + +/* + * Compute a hash code on a UTF-8 string, for use with internal hash tables. + * + * This may or may not be compatible with UTF-8 hash functions used inside + * the Dalvik VM. + * + * The basic "multiply by 31 and add" approach does better on class names + * than most other things tried (e.g. adler32). + */ +static u4 classDescriptorHash(const char* str) +{ + u4 hash = 1; + + while (*str != '\0') + hash = hash * 31 + *str++; + + return hash; +} + +/* + * Add an entry to the class lookup table. We hash the string and probe + * until we find an open slot. + */ +static void classLookupAdd(DexFile* pDexFile, DexClassLookup* pLookup, + int stringOff, int classDefOff, int* pNumProbes) +{ + const char* classDescriptor = + (const char*) (pDexFile->baseAddr + stringOff); + u4 hash = classDescriptorHash(classDescriptor); + int mask = pLookup->numEntries-1; + int idx = hash & mask; + + /* + * Find the first empty slot. We oversized the table, so this is + * guaranteed to finish. + */ + int probes = 0; + while (pLookup->table[idx].classDescriptorOffset != 0) { + idx = (idx + 1) & mask; + probes++; + } + //if (probes > 1) + // ALOGW("classLookupAdd: probes=%d", probes); + + pLookup->table[idx].classDescriptorHash = hash; + pLookup->table[idx].classDescriptorOffset = stringOff; + pLookup->table[idx].classDefOffset = classDefOff; + *pNumProbes = probes; +} + +/* + * Create the class lookup hash table. + * + * Returns newly-allocated storage. + */ +DexClassLookup* dexCreateClassLookup(DexFile* pDexFile) +{ + DexClassLookup* pLookup; + int allocSize; + int i, numEntries; + int numProbes, totalProbes, maxProbes; + + numProbes = totalProbes = maxProbes = 0; + + assert(pDexFile != NULL); + + /* + * Using a factor of 3 results in far less probing than a factor of 2, + * but almost doubles the flash storage requirements for the bootstrap + * DEX files. The overall impact on class loading performance seems + * to be minor. We could probably get some performance improvement by + * using a secondary hash. + */ + numEntries = dexRoundUpPower2(pDexFile->pHeader->classDefsSize * 2); + allocSize = offsetof(DexClassLookup, table) + + numEntries * sizeof(pLookup->table[0]); + + pLookup = (DexClassLookup*) calloc(1, allocSize); + if (pLookup == NULL) + return NULL; + pLookup->size = allocSize; + pLookup->numEntries = numEntries; + + for (i = 0; i < (int)pDexFile->pHeader->classDefsSize; i++) { + const DexClassDef* pClassDef; + const char* pString; + + pClassDef = dexGetClassDef(pDexFile, i); + pString = dexStringByTypeIdx(pDexFile, pClassDef->classIdx); + + classLookupAdd(pDexFile, pLookup, + (u1*)pString - pDexFile->baseAddr, + (u1*)pClassDef - pDexFile->baseAddr, &numProbes); + + if (numProbes > maxProbes) + maxProbes = numProbes; + totalProbes += numProbes; + } + + ALOGV("Class lookup: classes=%d slots=%d (%d%% occ) alloc=%d" + " total=%d max=%d", + pDexFile->pHeader->classDefsSize, numEntries, + (100 * pDexFile->pHeader->classDefsSize) / numEntries, + allocSize, totalProbes, maxProbes); + + return pLookup; +} + + +/* + * Set up the basic raw data pointers of a DexFile. This function isn't + * meant for general use. + */ +void dexFileSetupBasicPointers(DexFile* pDexFile, const u1* data) { + DexHeader *pHeader = (DexHeader*) data; + + pDexFile->baseAddr = data; + pDexFile->pHeader = pHeader; + pDexFile->pStringIds = (const DexStringId*) (data + pHeader->stringIdsOff); + pDexFile->pTypeIds = (const DexTypeId*) (data + pHeader->typeIdsOff); + pDexFile->pFieldIds = (const DexFieldId*) (data + pHeader->fieldIdsOff); + pDexFile->pMethodIds = (const DexMethodId*) (data + pHeader->methodIdsOff); + pDexFile->pProtoIds = (const DexProtoId*) (data + pHeader->protoIdsOff); + pDexFile->pClassDefs = (const DexClassDef*) (data + pHeader->classDefsOff); + pDexFile->pLinkData = (const DexLink*) (data + pHeader->linkOff); +} + +/* + * Parse an optimized or unoptimized .dex file sitting in memory. This is + * called after the byte-ordering and structure alignment has been fixed up. + * + * On success, return a newly-allocated DexFile. + */ +DexFile* dexFileParse(const u1* data, size_t length, int flags) +{ + DexFile* pDexFile = NULL; + const DexHeader* pHeader; + const u1* magic; + int result = -1; + + if (length < sizeof(DexHeader)) { + ALOGE("too short to be a valid .dex"); + goto bail; /* bad file format */ + } + + pDexFile = (DexFile*) malloc(sizeof(DexFile)); + if (pDexFile == NULL) + goto bail; /* alloc failure */ + memset(pDexFile, 0, sizeof(DexFile)); + + /* + * Peel off the optimized header. + */ + if (memcmp(data, DEX_OPT_MAGIC, 4) == 0) { + magic = data; + if (memcmp(magic+4, DEX_OPT_MAGIC_VERS, 4) != 0) { + ALOGE("bad opt version (0x%02x %02x %02x %02x)", + magic[4], magic[5], magic[6], magic[7]); + goto bail; + } + + pDexFile->pOptHeader = (const DexOptHeader*) data; + ALOGV("Good opt header, DEX offset is %d, flags=0x%02x", + pDexFile->pOptHeader->dexOffset, pDexFile->pOptHeader->flags); + + /* parse the optimized dex file tables */ + if (!dexParseOptData(data, length, pDexFile)) + goto bail; + + /* ignore the opt header and appended data from here on out */ + data += pDexFile->pOptHeader->dexOffset; + length -= pDexFile->pOptHeader->dexOffset; + if (pDexFile->pOptHeader->dexLength > length) { + ALOGE("File truncated? stored len=%d, rem len=%d", + pDexFile->pOptHeader->dexLength, (int) length); + goto bail; + } + length = pDexFile->pOptHeader->dexLength; + } + + dexFileSetupBasicPointers(pDexFile, data); + pHeader = pDexFile->pHeader; + + if (!dexHasValidMagic(pHeader)) { + goto bail; + } + + /* + * Verify the checksum(s). This is reasonably quick, but does require + * touching every byte in the DEX file. The base checksum changes after + * byte-swapping and DEX optimization. + */ + if (flags & kDexParseVerifyChecksum) { + u4 adler = dexComputeChecksum(pHeader); + if (adler != pHeader->checksum) { + ALOGE("ERROR: bad checksum (%08x vs %08x)", + adler, pHeader->checksum); + if (!(flags & kDexParseContinueOnError)) + goto bail; + } else { + ALOGV("+++ adler32 checksum (%08x) verified", adler); + } + + const DexOptHeader* pOptHeader = pDexFile->pOptHeader; + if (pOptHeader != NULL) { + adler = dexComputeOptChecksum(pOptHeader); + if (adler != pOptHeader->checksum) { + ALOGE("ERROR: bad opt checksum (%08x vs %08x)", + adler, pOptHeader->checksum); + if (!(flags & kDexParseContinueOnError)) + goto bail; + } else { + ALOGV("+++ adler32 opt checksum (%08x) verified", adler); + } + } + } + + /* + * Verify the SHA-1 digest. (Normally we don't want to do this -- + * the digest is used to uniquely identify the original DEX file, and + * can't be computed for verification after the DEX is byte-swapped + * and optimized.) + */ + if (kVerifySignature) { + unsigned char sha1Digest[kSHA1DigestLen]; + const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum) + + kSHA1DigestLen; + + dexComputeSHA1Digest(data + nonSum, length - nonSum, sha1Digest); + if (memcmp(sha1Digest, pHeader->signature, kSHA1DigestLen) != 0) { + char tmpBuf1[kSHA1DigestOutputLen]; + char tmpBuf2[kSHA1DigestOutputLen]; + ALOGE("ERROR: bad SHA1 digest (%s vs %s)", + dexSHA1DigestToStr(sha1Digest, tmpBuf1), + dexSHA1DigestToStr(pHeader->signature, tmpBuf2)); + if (!(flags & kDexParseContinueOnError)) + goto bail; + } else { + ALOGV("+++ sha1 digest verified"); + } + } + + if (pHeader->fileSize != length) { + ALOGE("ERROR: stored file size (%d) != expected (%d)", + (int) pHeader->fileSize, (int) length); + if (!(flags & kDexParseContinueOnError)) + goto bail; + } + + if (pHeader->classDefsSize == 0) { + ALOGE("ERROR: DEX file has no classes in it, failing"); + goto bail; + } + + /* + * Success! + */ + result = 0; + +bail: + if (result != 0 && pDexFile != NULL) { + dexFileFree(pDexFile); + pDexFile = NULL; + } + return pDexFile; +} + +/* + * Free up the DexFile and any associated data structures. + * + * Note we may be called with a partially-initialized DexFile. + */ +void dexFileFree(DexFile* pDexFile) +{ + if (pDexFile == NULL) + return; + + free(pDexFile); +} + +/* + * Look up a class definition entry by descriptor. + * + * "descriptor" should look like "Landroid/debug/Stuff;". + */ +const DexClassDef* dexFindClass(const DexFile* pDexFile, + const char* descriptor) +{ + const DexClassLookup* pLookup = pDexFile->pClassLookup; + u4 hash; + int idx, mask; + + hash = classDescriptorHash(descriptor); + mask = pLookup->numEntries - 1; + idx = hash & mask; + + /* + * Search until we find a matching entry or an empty slot. + */ + while (true) { + int offset; + + offset = pLookup->table[idx].classDescriptorOffset; + if (offset == 0) + return NULL; + + if (pLookup->table[idx].classDescriptorHash == hash) { + const char* str; + + str = (const char*) (pDexFile->baseAddr + offset); + if (strcmp(str, descriptor) == 0) { + return (const DexClassDef*) + (pDexFile->baseAddr + pLookup->table[idx].classDefOffset); + } + } + + idx = (idx + 1) & mask; + } +} + + +/* + * Compute the DEX file checksum for a memory-mapped DEX file. + */ +u4 dexComputeChecksum(const DexHeader* pHeader) +{ + const u1* start = (const u1*) pHeader; + + uLong adler = adler32(0L, Z_NULL, 0); + const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum); + + return (u4) adler32(adler, start + nonSum, pHeader->fileSize - nonSum); +} + +/* + * Compute the size, in bytes, of a DexCode. + */ +size_t dexGetDexCodeSize(const DexCode* pCode) +{ + /* + * The catch handler data is the last entry. It has a variable number + * of variable-size pieces, so we need to create an iterator. + */ + u4 handlersSize; + u4 offset; + u4 ui; + + if (pCode->triesSize != 0) { + handlersSize = dexGetHandlersSize(pCode); + offset = dexGetFirstHandlerOffset(pCode); + } else { + handlersSize = 0; + offset = 0; + } + + for (ui = 0; ui < handlersSize; ui++) { + DexCatchIterator iterator; + dexCatchIteratorInit(&iterator, pCode, offset); + offset = dexCatchIteratorGetEndOffset(&iterator, pCode); + } + + const u1* handlerData = dexGetCatchHandlerData(pCode); + + //ALOGD("+++ pCode=%p handlerData=%p last offset=%d", + // pCode, handlerData, offset); + + /* return the size of the catch handler + everything before it */ + return (handlerData - (u1*) pCode) + offset; +} + +/* + * Round up to the next highest power of 2. + * + * Found on http://graphics.stanford.edu/~seander/bithacks.html. + */ +u4 dexRoundUpPower2(u4 val) +{ + val--; + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + val++; + + return val; +} diff --git a/libdex/DexFile.h b/libdex/DexFile.h new file mode 100644 index 000000000..7fc7d051c --- /dev/null +++ b/libdex/DexFile.h @@ -0,0 +1,1084 @@ +/* + * 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. + */ + +/* + * Access .dex (Dalvik Executable Format) files. The code here assumes that + * the DEX file has been rewritten (byte-swapped, word-aligned) and that + * the contents can be directly accessed as a collection of C arrays. Please + * see docs/dalvik/dex-format.html for a detailed description. + * + * The structure and field names were chosen to match those in the DEX spec. + * + * It's generally assumed that the DEX file will be stored in shared memory, + * obviating the need to copy code and constant pool entries into newly + * allocated storage. Maintaining local pointers to items in the shared area + * is valid and encouraged. + * + * All memory-mapped structures are 32-bit aligned unless otherwise noted. + */ + +#ifndef LIBDEX_DEXFILE_H_ +#define LIBDEX_DEXFILE_H_ + +/* + * Annotation to tell clang that we intend to fall through from one case to + * another in a switch. Sourced from android-base/macros.h. + */ +#define FALLTHROUGH_INTENDED [[clang::fallthrough]] + +#ifndef LOG_TAG +# define LOG_TAG "libdex" +#endif +#include <log/log.h> + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <assert.h> + +/* + * If "very verbose" logging is enabled, make it equivalent to ALOGV. + * Otherwise, make it disappear. + * + * Define this above the #include "Dalvik.h" to enable for only a + * single file. + */ +/* #define VERY_VERBOSE_LOG */ +#if defined(VERY_VERBOSE_LOG) +# define LOGVV ALOGV +# define IF_LOGVV() IF_ALOGV() +#else +# define LOGVV(...) ((void)0) +# define IF_LOGVV() if (false) +#endif + +/* + * These 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; + +#include "libdex/SysUtil.h" + +/* + * gcc-style inline management -- ensures we have a copy of all functions + * in the library, so code that links against us will work whether or not + * it was built with optimizations enabled. + */ +#ifndef _DEX_GEN_INLINES /* only defined by DexInlines.c */ +# define DEX_INLINE extern __inline__ +#else +# define DEX_INLINE +#endif + +/* DEX file magic number */ +#define DEX_MAGIC "dex\n" + +/* The version for android N, encoded in 4 bytes of ASCII. This differentiates dex files that may + * use default methods. + */ +#define DEX_MAGIC_VERS_37 "037\0" + +/* The version for android O, encoded in 4 bytes of ASCII. This differentiates dex files that may + * contain invoke-custom, invoke-polymorphic, call-sites, and method handles. + */ +#define DEX_MAGIC_VERS_38 "038\0" + +/* The version for android P, encoded in 4 bytes of ASCII. This differentiates dex files that may + * contain const-method-handle and const-proto. + */ +#define DEX_MAGIC_VERS_39 "039\0" + +/* current version, encoded in 4 bytes of ASCII */ +#define DEX_MAGIC_VERS "036\0" + +/* + * older but still-recognized version (corresponding to Android API + * levels 13 and earlier + */ +#define DEX_MAGIC_VERS_API_13 "035\0" + +/* same, but for optimized DEX header */ +#define DEX_OPT_MAGIC "dey\n" +#define DEX_OPT_MAGIC_VERS "036\0" + +#define DEX_DEP_MAGIC "deps" + +/* + * 160-bit SHA-1 digest. + */ +enum { kSHA1DigestLen = 20, + kSHA1DigestOutputLen = kSHA1DigestLen*2 +1 }; + +/* general constants */ +enum { + kDexEndianConstant = 0x12345678, /* the endianness indicator */ + kDexNoIndex = 0xffffffff, /* not a valid index value */ +}; + +/* + * Enumeration of all the primitive types. + */ +enum PrimitiveType { + PRIM_NOT = 0, /* value is a reference type, not a primitive type */ + PRIM_VOID = 1, + PRIM_BOOLEAN = 2, + PRIM_BYTE = 3, + PRIM_SHORT = 4, + PRIM_CHAR = 5, + PRIM_INT = 6, + PRIM_LONG = 7, + PRIM_FLOAT = 8, + PRIM_DOUBLE = 9, +}; + +/* + * access flags and masks; the "standard" ones are all <= 0x4000 + * + * Note: There are related declarations in vm/oo/Object.h in the ClassFlags + * enum. + */ +enum { + ACC_PUBLIC = 0x00000001, // class, field, method, ic + ACC_PRIVATE = 0x00000002, // field, method, ic + ACC_PROTECTED = 0x00000004, // field, method, ic + ACC_STATIC = 0x00000008, // field, method, ic + ACC_FINAL = 0x00000010, // class, field, method, ic + ACC_SYNCHRONIZED = 0x00000020, // method (only allowed on natives) + ACC_SUPER = 0x00000020, // class (not used in Dalvik) + ACC_VOLATILE = 0x00000040, // field + ACC_BRIDGE = 0x00000040, // method (1.5) + ACC_TRANSIENT = 0x00000080, // field + ACC_VARARGS = 0x00000080, // method (1.5) + ACC_NATIVE = 0x00000100, // method + ACC_INTERFACE = 0x00000200, // class, ic + ACC_ABSTRACT = 0x00000400, // class, method, ic + ACC_STRICT = 0x00000800, // method + ACC_SYNTHETIC = 0x00001000, // field, method, ic + ACC_ANNOTATION = 0x00002000, // class, ic (1.5) + ACC_ENUM = 0x00004000, // class, field, ic (1.5) + ACC_CONSTRUCTOR = 0x00010000, // method (Dalvik only) + ACC_DECLARED_SYNCHRONIZED = + 0x00020000, // method (Dalvik only) + ACC_CLASS_MASK = + (ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT + | ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM), + ACC_INNER_CLASS_MASK = + (ACC_CLASS_MASK | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC), + ACC_FIELD_MASK = + (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL + | ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM), + ACC_METHOD_MASK = + (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL + | ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE + | ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | ACC_CONSTRUCTOR + | ACC_DECLARED_SYNCHRONIZED), +}; + +/* annotation constants */ +enum { + kDexVisibilityBuild = 0x00, /* annotation visibility */ + kDexVisibilityRuntime = 0x01, + kDexVisibilitySystem = 0x02, + + kDexAnnotationByte = 0x00, + kDexAnnotationShort = 0x02, + kDexAnnotationChar = 0x03, + kDexAnnotationInt = 0x04, + kDexAnnotationLong = 0x06, + kDexAnnotationFloat = 0x10, + kDexAnnotationDouble = 0x11, + kDexAnnotationMethodType = 0x15, + kDexAnnotationMethodHandle = 0x16, + kDexAnnotationString = 0x17, + kDexAnnotationType = 0x18, + kDexAnnotationField = 0x19, + kDexAnnotationMethod = 0x1a, + kDexAnnotationEnum = 0x1b, + kDexAnnotationArray = 0x1c, + kDexAnnotationAnnotation = 0x1d, + kDexAnnotationNull = 0x1e, + kDexAnnotationBoolean = 0x1f, + + kDexAnnotationValueTypeMask = 0x1f, /* low 5 bits */ + kDexAnnotationValueArgShift = 5, +}; + +/* map item type codes */ +enum { + kDexTypeHeaderItem = 0x0000, + kDexTypeStringIdItem = 0x0001, + kDexTypeTypeIdItem = 0x0002, + kDexTypeProtoIdItem = 0x0003, + kDexTypeFieldIdItem = 0x0004, + kDexTypeMethodIdItem = 0x0005, + kDexTypeClassDefItem = 0x0006, + kDexTypeCallSiteIdItem = 0x0007, + kDexTypeMethodHandleItem = 0x0008, + kDexTypeMapList = 0x1000, + kDexTypeTypeList = 0x1001, + kDexTypeAnnotationSetRefList = 0x1002, + kDexTypeAnnotationSetItem = 0x1003, + kDexTypeClassDataItem = 0x2000, + kDexTypeCodeItem = 0x2001, + kDexTypeStringDataItem = 0x2002, + kDexTypeDebugInfoItem = 0x2003, + kDexTypeAnnotationItem = 0x2004, + kDexTypeEncodedArrayItem = 0x2005, + kDexTypeAnnotationsDirectoryItem = 0x2006, +}; + +/* auxillary data section chunk codes */ +enum { + kDexChunkClassLookup = 0x434c4b50, /* CLKP */ + kDexChunkRegisterMaps = 0x524d4150, /* RMAP */ + + kDexChunkEnd = 0x41454e44, /* AEND */ +}; + +/* debug info opcodes and constants */ +enum { + DBG_END_SEQUENCE = 0x00, + DBG_ADVANCE_PC = 0x01, + DBG_ADVANCE_LINE = 0x02, + DBG_START_LOCAL = 0x03, + DBG_START_LOCAL_EXTENDED = 0x04, + DBG_END_LOCAL = 0x05, + DBG_RESTART_LOCAL = 0x06, + DBG_SET_PROLOGUE_END = 0x07, + DBG_SET_EPILOGUE_BEGIN = 0x08, + DBG_SET_FILE = 0x09, + DBG_FIRST_SPECIAL = 0x0a, + DBG_LINE_BASE = -4, + DBG_LINE_RANGE = 15, +}; + +/* + * Direct-mapped "header_item" struct. + */ +struct DexHeader { + u1 magic[8]; /* includes version number */ + u4 checksum; /* adler32 checksum */ + u1 signature[kSHA1DigestLen]; /* SHA-1 hash */ + u4 fileSize; /* length of entire file */ + u4 headerSize; /* offset to start of next section */ + u4 endianTag; + u4 linkSize; + u4 linkOff; + u4 mapOff; + u4 stringIdsSize; + u4 stringIdsOff; + u4 typeIdsSize; + u4 typeIdsOff; + u4 protoIdsSize; + u4 protoIdsOff; + u4 fieldIdsSize; + u4 fieldIdsOff; + u4 methodIdsSize; + u4 methodIdsOff; + u4 classDefsSize; + u4 classDefsOff; + u4 dataSize; + u4 dataOff; +}; + +/* + * Direct-mapped "map_item". + */ +struct DexMapItem { + u2 type; /* type code (see kDexType* above) */ + u2 unused; + u4 size; /* count of items of the indicated type */ + u4 offset; /* file offset to the start of data */ +}; + +/* + * Direct-mapped "map_list". + */ +struct DexMapList { + u4 size; /* #of entries in list */ + DexMapItem list[1]; /* entries */ +}; + +/* + * Direct-mapped "string_id_item". + */ +struct DexStringId { + u4 stringDataOff; /* file offset to string_data_item */ +}; + +/* + * Direct-mapped "type_id_item". + */ +struct DexTypeId { + u4 descriptorIdx; /* index into stringIds list for type descriptor */ +}; + +/* + * Direct-mapped "field_id_item". + */ +struct DexFieldId { + u2 classIdx; /* index into typeIds list for defining class */ + u2 typeIdx; /* index into typeIds for field type */ + u4 nameIdx; /* index into stringIds for field name */ +}; + +/* + * Direct-mapped "method_id_item". + */ +struct DexMethodId { + u2 classIdx; /* index into typeIds list for defining class */ + u2 protoIdx; /* index into protoIds for method prototype */ + u4 nameIdx; /* index into stringIds for method name */ +}; + +/* + * Direct-mapped "proto_id_item". + */ +struct DexProtoId { + u4 shortyIdx; /* index into stringIds for shorty descriptor */ + u4 returnTypeIdx; /* index into typeIds list for return type */ + u4 parametersOff; /* file offset to type_list for parameter types */ +}; + +/* + * Direct-mapped "class_def_item". + */ +struct DexClassDef { + u4 classIdx; /* index into typeIds for this class */ + u4 accessFlags; + u4 superclassIdx; /* index into typeIds for superclass */ + u4 interfacesOff; /* file offset to DexTypeList */ + u4 sourceFileIdx; /* index into stringIds for source file name */ + u4 annotationsOff; /* file offset to annotations_directory_item */ + u4 classDataOff; /* file offset to class_data_item */ + u4 staticValuesOff; /* file offset to DexEncodedArray */ +}; + +/* + * Direct-mapped "call_site_id_item" + */ +struct DexCallSiteId { + u4 callSiteOff; /* file offset to DexEncodedArray */ +}; + +/* + * Enumeration of method handle type codes. + */ +enum MethodHandleType { + STATIC_PUT = 0x00, + STATIC_GET = 0x01, + INSTANCE_PUT = 0x02, + INSTANCE_GET = 0x03, + INVOKE_STATIC = 0x04, + INVOKE_INSTANCE = 0x05, + INVOKE_CONSTRUCTOR = 0x06, + INVOKE_DIRECT = 0x07, + INVOKE_INTERFACE = 0x08 +}; + +/* + * Direct-mapped "method_handle_item" + */ +struct DexMethodHandleItem { + u2 methodHandleType; /* type of method handle */ + u2 reserved1; /* reserved for future use */ + u2 fieldOrMethodIdx; /* index of associated field or method */ + u2 reserved2; /* reserved for future use */ +}; + +/* + * Direct-mapped "type_item". + */ +struct DexTypeItem { + u2 typeIdx; /* index into typeIds */ +}; + +/* + * Direct-mapped "type_list". + */ +struct DexTypeList { + u4 size; /* #of entries in list */ + DexTypeItem list[1]; /* entries */ +}; + +/* + * Direct-mapped "code_item". + * + * The "catches" table is used when throwing an exception, + * "debugInfo" is used when displaying an exception stack trace or + * debugging. An offset of zero indicates that there are no entries. + */ +struct DexCode { + u2 registersSize; + u2 insSize; + u2 outsSize; + u2 triesSize; + u4 debugInfoOff; /* file offset to debug info stream */ + u4 insnsSize; /* size of the insns array, in u2 units */ + u2 insns[1]; + /* followed by optional u2 padding */ + /* followed by try_item[triesSize] */ + /* followed by uleb128 handlersSize */ + /* followed by catch_handler_item[handlersSize] */ +}; + +/* + * Direct-mapped "try_item". + */ +struct DexTry { + u4 startAddr; /* start address, in 16-bit code units */ + u2 insnCount; /* instruction count, in 16-bit code units */ + u2 handlerOff; /* offset in encoded handler data to handlers */ +}; + +/* + * Link table. Currently undefined. + */ +struct DexLink { + u1 bleargh; +}; + + +/* + * Direct-mapped "annotations_directory_item". + */ +struct DexAnnotationsDirectoryItem { + u4 classAnnotationsOff; /* offset to DexAnnotationSetItem */ + u4 fieldsSize; /* count of DexFieldAnnotationsItem */ + u4 methodsSize; /* count of DexMethodAnnotationsItem */ + u4 parametersSize; /* count of DexParameterAnnotationsItem */ + /* followed by DexFieldAnnotationsItem[fieldsSize] */ + /* followed by DexMethodAnnotationsItem[methodsSize] */ + /* followed by DexParameterAnnotationsItem[parametersSize] */ +}; + +/* + * Direct-mapped "field_annotations_item". + */ +struct DexFieldAnnotationsItem { + u4 fieldIdx; + u4 annotationsOff; /* offset to DexAnnotationSetItem */ +}; + +/* + * Direct-mapped "method_annotations_item". + */ +struct DexMethodAnnotationsItem { + u4 methodIdx; + u4 annotationsOff; /* offset to DexAnnotationSetItem */ +}; + +/* + * Direct-mapped "parameter_annotations_item". + */ +struct DexParameterAnnotationsItem { + u4 methodIdx; + u4 annotationsOff; /* offset to DexAnotationSetRefList */ +}; + +/* + * Direct-mapped "annotation_set_ref_item". + */ +struct DexAnnotationSetRefItem { + u4 annotationsOff; /* offset to DexAnnotationSetItem */ +}; + +/* + * Direct-mapped "annotation_set_ref_list". + */ +struct DexAnnotationSetRefList { + u4 size; + DexAnnotationSetRefItem list[1]; +}; + +/* + * Direct-mapped "annotation_set_item". + */ +struct DexAnnotationSetItem { + u4 size; + u4 entries[1]; /* offset to DexAnnotationItem */ +}; + +/* + * Direct-mapped "annotation_item". + * + * NOTE: this structure is byte-aligned. + */ +struct DexAnnotationItem { + u1 visibility; + u1 annotation[1]; /* data in encoded_annotation format */ +}; + +/* + * Direct-mapped "encoded_array". + * + * NOTE: this structure is byte-aligned. + */ +struct DexEncodedArray { + u1 array[1]; /* data in encoded_array format */ +}; + +/* + * Lookup table for classes. It provides a mapping from class name to + * class definition. Used by dexFindClass(). + * + * We calculate this at DEX optimization time and embed it in the file so we + * don't need the same hash table in every VM. This is slightly slower than + * a hash table with direct pointers to the items, but because it's shared + * there's less of a penalty for using a fairly sparse table. + */ +struct DexClassLookup { + int size; // total size, including "size" + int numEntries; // size of table[]; always power of 2 + struct { + u4 classDescriptorHash; // class descriptor hash code + int classDescriptorOffset; // in bytes, from start of DEX + int classDefOffset; // in bytes, from start of DEX + } table[1]; +}; + +/* + * Header added by DEX optimization pass. Values are always written in + * local byte and structure padding. The first field (magic + version) + * is guaranteed to be present and directly readable for all expected + * compiler configurations; the rest is version-dependent. + * + * Try to keep this simple and fixed-size. + */ +struct DexOptHeader { + u1 magic[8]; /* includes version number */ + + u4 dexOffset; /* file offset of DEX header */ + u4 dexLength; + u4 depsOffset; /* offset of optimized DEX dependency table */ + u4 depsLength; + u4 optOffset; /* file offset of optimized data tables */ + u4 optLength; + + u4 flags; /* some info flags */ + u4 checksum; /* adler32 checksum covering deps/opt */ + + /* pad for 64-bit alignment if necessary */ +}; + +#define DEX_OPT_FLAG_BIG (1<<1) /* swapped to big-endian */ + +#define DEX_INTERFACE_CACHE_SIZE 128 /* must be power of 2 */ + +/* + * Structure representing a DEX file. + * + * Code should regard DexFile as opaque, using the API calls provided here + * to access specific structures. + */ +struct DexFile { + /* directly-mapped "opt" header */ + const DexOptHeader* pOptHeader; + + /* pointers to directly-mapped structs and arrays in base DEX */ + const DexHeader* pHeader; + const DexStringId* pStringIds; + const DexTypeId* pTypeIds; + const DexFieldId* pFieldIds; + const DexMethodId* pMethodIds; + const DexProtoId* pProtoIds; + const DexClassDef* pClassDefs; + const DexLink* pLinkData; + + /* + * These are mapped out of the "auxillary" section, and may not be + * included in the file. + */ + const DexClassLookup* pClassLookup; + const void* pRegisterMapPool; // RegisterMapClassPool + + /* points to start of DEX file data */ + const u1* baseAddr; + + /* track memory overhead for auxillary structures */ + int overhead; + + /* additional app-specific data structures associated with the DEX */ + //void* auxData; +}; + +/* + * Utility function -- rounds up to the nearest power of 2. + */ +u4 dexRoundUpPower2(u4 val); + +/* + * Parse an optimized or unoptimized .dex file sitting in memory. + * + * On success, return a newly-allocated DexFile. + */ +DexFile* dexFileParse(const u1* data, size_t length, int flags); + +/* bit values for "flags" argument to dexFileParse */ +enum { + kDexParseDefault = 0, + kDexParseVerifyChecksum = 1, + kDexParseContinueOnError = (1 << 1), +}; + +/* + * Fix the byte ordering of all fields in the DEX file, and do + * structural verification. This is only required for code that opens + * "raw" DEX files, such as the DEX optimizer. + * + * Return 0 on success. + */ +int dexSwapAndVerify(u1* addr, int len); + +/* + * Detect the file type of the given memory buffer via magic number. + * Call dexSwapAndVerify() on an unoptimized DEX file, do nothing + * but return successfully on an optimized DEX file, and report an + * error for all other cases. + * + * Return 0 on success. + */ +int dexSwapAndVerifyIfNecessary(u1* addr, size_t len); + +/* + * Check to see if the file magic and format version in the given + * header are recognized as valid. Returns true if they are + * acceptable. + */ +bool dexHasValidMagic(const DexHeader* pHeader); + +/* + * Compute DEX checksum. + */ +u4 dexComputeChecksum(const DexHeader* pHeader); + +/* + * Free a DexFile structure, along with any associated structures. + */ +void dexFileFree(DexFile* pDexFile); + +/* + * Create class lookup table. + */ +DexClassLookup* dexCreateClassLookup(DexFile* pDexFile); + +/* + * Find a class definition by descriptor. + */ +const DexClassDef* dexFindClass(const DexFile* pFile, const char* descriptor); + +/* + * Set up the basic raw data pointers of a DexFile. This function isn't + * meant for general use. + */ +void dexFileSetupBasicPointers(DexFile* pDexFile, const u1* data); + +/* return the DexMapList of the file, if any */ +DEX_INLINE const DexMapList* dexGetMap(const DexFile* pDexFile) { + u4 mapOff = pDexFile->pHeader->mapOff; + + if (mapOff == 0) { + return NULL; + } else { + return (const DexMapList*) (pDexFile->baseAddr + mapOff); + } +} + +/* return the const char* string data referred to by the given string_id */ +DEX_INLINE const char* dexGetStringData(const DexFile* pDexFile, + const DexStringId* pStringId) { + const u1* ptr = pDexFile->baseAddr + pStringId->stringDataOff; + + // Skip the uleb128 length. + while (*(ptr++) > 0x7f) /* empty */ ; + + return (const char*) ptr; +} +/* return the StringId with the specified index */ +DEX_INLINE const DexStringId* dexGetStringId(const DexFile* pDexFile, u4 idx) { + assert(idx < pDexFile->pHeader->stringIdsSize); + return &pDexFile->pStringIds[idx]; +} +/* return the UTF-8 encoded string with the specified string_id index */ +DEX_INLINE const char* dexStringById(const DexFile* pDexFile, u4 idx) { + const DexStringId* pStringId = dexGetStringId(pDexFile, idx); + return dexGetStringData(pDexFile, pStringId); +} + +/* Return the UTF-8 encoded string with the specified string_id index, + * also filling in the UTF-16 size (number of 16-bit code points).*/ +const char* dexStringAndSizeById(const DexFile* pDexFile, u4 idx, + u4* utf16Size); + +/* return the TypeId with the specified index */ +DEX_INLINE const DexTypeId* dexGetTypeId(const DexFile* pDexFile, u4 idx) { + assert(idx < pDexFile->pHeader->typeIdsSize); + return &pDexFile->pTypeIds[idx]; +} + +/* + * Get the descriptor string associated with a given type index. + * The caller should not free() the returned string. + */ +DEX_INLINE const char* dexStringByTypeIdx(const DexFile* pDexFile, u4 idx) { + const DexTypeId* typeId = dexGetTypeId(pDexFile, idx); + return dexStringById(pDexFile, typeId->descriptorIdx); +} + +/* return the MethodId with the specified index */ +DEX_INLINE const DexMethodId* dexGetMethodId(const DexFile* pDexFile, u4 idx) { + assert(idx < pDexFile->pHeader->methodIdsSize); + return &pDexFile->pMethodIds[idx]; +} + +/* return the FieldId with the specified index */ +DEX_INLINE const DexFieldId* dexGetFieldId(const DexFile* pDexFile, u4 idx) { + assert(idx < pDexFile->pHeader->fieldIdsSize); + return &pDexFile->pFieldIds[idx]; +} + +/* return the ProtoId with the specified index */ +DEX_INLINE const DexProtoId* dexGetProtoId(const DexFile* pDexFile, u4 idx) { + assert(idx < pDexFile->pHeader->protoIdsSize); + return &pDexFile->pProtoIds[idx]; +} + +/* + * Get the parameter list from a ProtoId. The returns NULL if the ProtoId + * does not have a parameter list. + */ +DEX_INLINE const DexTypeList* dexGetProtoParameters( + const DexFile *pDexFile, const DexProtoId* pProtoId) { + if (pProtoId->parametersOff == 0) { + return NULL; + } + return (const DexTypeList*) + (pDexFile->baseAddr + pProtoId->parametersOff); +} + +/* return the ClassDef with the specified index */ +DEX_INLINE const DexClassDef* dexGetClassDef(const DexFile* pDexFile, u4 idx) { + assert(idx < pDexFile->pHeader->classDefsSize); + return &pDexFile->pClassDefs[idx]; +} + +/* given a ClassDef pointer, recover its index */ +DEX_INLINE u4 dexGetIndexForClassDef(const DexFile* pDexFile, + const DexClassDef* pClassDef) +{ + assert(pClassDef >= pDexFile->pClassDefs && + pClassDef < pDexFile->pClassDefs + pDexFile->pHeader->classDefsSize); + return pClassDef - pDexFile->pClassDefs; +} + +/* get the interface list for a DexClass */ +DEX_INLINE const DexTypeList* dexGetInterfacesList(const DexFile* pDexFile, + const DexClassDef* pClassDef) +{ + if (pClassDef->interfacesOff == 0) + return NULL; + return (const DexTypeList*) + (pDexFile->baseAddr + pClassDef->interfacesOff); +} +/* return the Nth entry in a DexTypeList. */ +DEX_INLINE const DexTypeItem* dexGetTypeItem(const DexTypeList* pList, + u4 idx) +{ + assert(idx < pList->size); + return &pList->list[idx]; +} +/* return the type_idx for the Nth entry in a TypeList */ +DEX_INLINE u4 dexTypeListGetIdx(const DexTypeList* pList, u4 idx) { + const DexTypeItem* pItem = dexGetTypeItem(pList, idx); + return pItem->typeIdx; +} + +/* get the static values list for a DexClass */ +DEX_INLINE const DexEncodedArray* dexGetStaticValuesList( + const DexFile* pDexFile, const DexClassDef* pClassDef) +{ + if (pClassDef->staticValuesOff == 0) + return NULL; + return (const DexEncodedArray*) + (pDexFile->baseAddr + pClassDef->staticValuesOff); +} + +/* get the annotations directory item for a DexClass */ +DEX_INLINE const DexAnnotationsDirectoryItem* dexGetAnnotationsDirectoryItem( + const DexFile* pDexFile, const DexClassDef* pClassDef) +{ + if (pClassDef->annotationsOff == 0) + return NULL; + return (const DexAnnotationsDirectoryItem*) + (pDexFile->baseAddr + pClassDef->annotationsOff); +} + +/* get the source file string */ +DEX_INLINE const char* dexGetSourceFile( + const DexFile* pDexFile, const DexClassDef* pClassDef) +{ + if (pClassDef->sourceFileIdx == 0xffffffff) + return NULL; + return dexStringById(pDexFile, pClassDef->sourceFileIdx); +} + +/* get the size, in bytes, of a DexCode */ +size_t dexGetDexCodeSize(const DexCode* pCode); + +/* Get the list of "tries" for the given DexCode. */ +DEX_INLINE const DexTry* dexGetTries(const DexCode* pCode) { + const u2* insnsEnd = &pCode->insns[pCode->insnsSize]; + + // Round to four bytes. + if ((((uintptr_t) insnsEnd) & 3) != 0) { + insnsEnd++; + } + + return (const DexTry*) insnsEnd; +} + +/* Get the base of the encoded data for the given DexCode. */ +DEX_INLINE const u1* dexGetCatchHandlerData(const DexCode* pCode) { + const DexTry* pTries = dexGetTries(pCode); + return (const u1*) &pTries[pCode->triesSize]; +} + +/* get a pointer to the start of the debugging data */ +DEX_INLINE const u1* dexGetDebugInfoStream(const DexFile* pDexFile, + const DexCode* pCode) +{ + if (pCode->debugInfoOff == 0) { + return NULL; + } else { + return pDexFile->baseAddr + pCode->debugInfoOff; + } +} + +/* DexClassDef convenience - get class descriptor */ +DEX_INLINE const char* dexGetClassDescriptor(const DexFile* pDexFile, + const DexClassDef* pClassDef) +{ + return dexStringByTypeIdx(pDexFile, pClassDef->classIdx); +} + +/* DexClassDef convenience - get superclass descriptor */ +DEX_INLINE const char* dexGetSuperClassDescriptor(const DexFile* pDexFile, + const DexClassDef* pClassDef) +{ + if (pClassDef->superclassIdx == 0) + return NULL; + return dexStringByTypeIdx(pDexFile, pClassDef->superclassIdx); +} + +/* DexClassDef convenience - get class_data_item pointer */ +DEX_INLINE const u1* dexGetClassData(const DexFile* pDexFile, + const DexClassDef* pClassDef) +{ + if (pClassDef->classDataOff == 0) + return NULL; + return (const u1*) (pDexFile->baseAddr + pClassDef->classDataOff); +} + +/* Get an annotation set at a particular offset. */ +DEX_INLINE const DexAnnotationSetItem* dexGetAnnotationSetItem( + const DexFile* pDexFile, u4 offset) +{ + if (offset == 0) { + return NULL; + } + return (const DexAnnotationSetItem*) (pDexFile->baseAddr + offset); +} +/* get the class' annotation set */ +DEX_INLINE const DexAnnotationSetItem* dexGetClassAnnotationSet( + const DexFile* pDexFile, const DexAnnotationsDirectoryItem* pAnnoDir) +{ + return dexGetAnnotationSetItem(pDexFile, pAnnoDir->classAnnotationsOff); +} + +/* get the class' field annotation list */ +DEX_INLINE const DexFieldAnnotationsItem* dexGetFieldAnnotations( + const DexFile* pDexFile, const DexAnnotationsDirectoryItem* pAnnoDir) +{ + (void) pDexFile; + if (pAnnoDir->fieldsSize == 0) + return NULL; + + // Skip past the header to the start of the field annotations. + return (const DexFieldAnnotationsItem*) &pAnnoDir[1]; +} + +/* get field annotation list size */ +DEX_INLINE int dexGetFieldAnnotationsSize(const DexFile* pDexFile, + const DexAnnotationsDirectoryItem* pAnnoDir) +{ + (void) pDexFile; + return pAnnoDir->fieldsSize; +} + +/* return a pointer to the field's annotation set */ +DEX_INLINE const DexAnnotationSetItem* dexGetFieldAnnotationSetItem( + const DexFile* pDexFile, const DexFieldAnnotationsItem* pItem) +{ + return dexGetAnnotationSetItem(pDexFile, pItem->annotationsOff); +} + +/* get the class' method annotation list */ +DEX_INLINE const DexMethodAnnotationsItem* dexGetMethodAnnotations( + const DexFile* pDexFile, const DexAnnotationsDirectoryItem* pAnnoDir) +{ + (void) pDexFile; + if (pAnnoDir->methodsSize == 0) + return NULL; + + /* + * Skip past the header and field annotations to the start of the + * method annotations. + */ + const u1* addr = (const u1*) &pAnnoDir[1]; + addr += pAnnoDir->fieldsSize * sizeof (DexFieldAnnotationsItem); + return (const DexMethodAnnotationsItem*) addr; +} + +/* get method annotation list size */ +DEX_INLINE int dexGetMethodAnnotationsSize(const DexFile* pDexFile, + const DexAnnotationsDirectoryItem* pAnnoDir) +{ + (void) pDexFile; + return pAnnoDir->methodsSize; +} + +/* return a pointer to the method's annotation set */ +DEX_INLINE const DexAnnotationSetItem* dexGetMethodAnnotationSetItem( + const DexFile* pDexFile, const DexMethodAnnotationsItem* pItem) +{ + return dexGetAnnotationSetItem(pDexFile, pItem->annotationsOff); +} + +/* get the class' parameter annotation list */ +DEX_INLINE const DexParameterAnnotationsItem* dexGetParameterAnnotations( + const DexFile* pDexFile, const DexAnnotationsDirectoryItem* pAnnoDir) +{ + (void) pDexFile; + if (pAnnoDir->parametersSize == 0) + return NULL; + + /* + * Skip past the header, field annotations, and method annotations + * to the start of the parameter annotations. + */ + const u1* addr = (const u1*) &pAnnoDir[1]; + addr += pAnnoDir->fieldsSize * sizeof (DexFieldAnnotationsItem); + addr += pAnnoDir->methodsSize * sizeof (DexMethodAnnotationsItem); + return (const DexParameterAnnotationsItem*) addr; +} + +/* get method annotation list size */ +DEX_INLINE int dexGetParameterAnnotationsSize(const DexFile* pDexFile, + const DexAnnotationsDirectoryItem* pAnnoDir) +{ + (void) pDexFile; + return pAnnoDir->parametersSize; +} + +/* return the parameter annotation ref list */ +DEX_INLINE const DexAnnotationSetRefList* dexGetParameterAnnotationSetRefList( + const DexFile* pDexFile, const DexParameterAnnotationsItem* pItem) +{ + if (pItem->annotationsOff == 0) { + return NULL; + } + return (const DexAnnotationSetRefList*) (pDexFile->baseAddr + pItem->annotationsOff); +} + +/* get method annotation list size */ +DEX_INLINE int dexGetParameterAnnotationSetRefSize(const DexFile* pDexFile, + const DexParameterAnnotationsItem* pItem) +{ + if (pItem->annotationsOff == 0) { + return 0; + } + return dexGetParameterAnnotationSetRefList(pDexFile, pItem)->size; +} + +/* return the Nth entry from an annotation set ref list */ +DEX_INLINE const DexAnnotationSetRefItem* dexGetParameterAnnotationSetRef( + const DexAnnotationSetRefList* pList, u4 idx) +{ + assert(idx < pList->size); + return &pList->list[idx]; +} + +/* given a DexAnnotationSetRefItem, return the DexAnnotationSetItem */ +DEX_INLINE const DexAnnotationSetItem* dexGetSetRefItemItem( + const DexFile* pDexFile, const DexAnnotationSetRefItem* pItem) +{ + return dexGetAnnotationSetItem(pDexFile, pItem->annotationsOff); +} + +/* return the Nth annotation offset from a DexAnnotationSetItem */ +DEX_INLINE u4 dexGetAnnotationOff( + const DexAnnotationSetItem* pAnnoSet, u4 idx) +{ + assert(idx < pAnnoSet->size); + return pAnnoSet->entries[idx]; +} + +/* return the Nth annotation item from a DexAnnotationSetItem */ +DEX_INLINE const DexAnnotationItem* dexGetAnnotationItem( + const DexFile* pDexFile, const DexAnnotationSetItem* pAnnoSet, u4 idx) +{ + u4 offset = dexGetAnnotationOff(pAnnoSet, idx); + if (offset == 0) { + return NULL; + } + return (const DexAnnotationItem*) (pDexFile->baseAddr + offset); +} + +/* + * Get the type descriptor character associated with a given primitive + * type. This returns '\0' if the type is invalid. + */ +char dexGetPrimitiveTypeDescriptorChar(PrimitiveType type); + +/* + * Get the type descriptor string associated with a given primitive + * type. + */ +const char* dexGetPrimitiveTypeDescriptor(PrimitiveType type); + +/* + * Get the boxed type descriptor string associated with a given + * primitive type. This returns NULL for an invalid type, including + * particularly for type "void". In the latter case, even though there + * is a class Void, there's no such thing as a boxed instance of it. + */ +const char* dexGetBoxedTypeDescriptor(PrimitiveType type); + +/* + * Get the primitive type constant from the given descriptor character. + * This returns PRIM_NOT (note: this is a 0) if the character is invalid + * as a primitive type descriptor. + */ +PrimitiveType dexGetPrimitiveTypeFromDescriptorChar(char descriptorChar); + +#endif // LIBDEX_DEXFILE_H_ diff --git a/libdex/DexInlines.cpp b/libdex/DexInlines.cpp new file mode 100644 index 000000000..cbedb6267 --- /dev/null +++ b/libdex/DexInlines.cpp @@ -0,0 +1,33 @@ +/* + * 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. + */ + +/* + * Generate non-inline copies of inline functions in header files. + */ + +#define _DEX_GEN_INLINES + +#include "DexFile.h" + +#include "DexCatch.h" +#include "DexClass.h" +#include "DexDataMap.h" +#include "DexUtf.h" +#include "DexOpcodes.h" +#include "DexProto.h" +#include "InstrUtils.h" +#include "Leb128.h" +#include "ZipArchive.h" diff --git a/libdex/DexOpcodes.cpp b/libdex/DexOpcodes.cpp new file mode 100644 index 000000000..d3db79b67 --- /dev/null +++ b/libdex/DexOpcodes.cpp @@ -0,0 +1,299 @@ +/* + * 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. + */ + +/* + * Table of Dalvik opcode names. + * + * IMPORTANT NOTE: The contents of this file are mostly generated + * automatically by the opcode-gen tool. Any edits to the generated + * sections will get wiped out the next time the tool is run. + */ + +#include "DexOpcodes.h" +#include <assert.h> + +/* + * Dalvik opcode names. + */ +static const char* gOpNames[kNumPackedOpcodes] = { + // BEGIN(libdex-opcode-names); GENERATED AUTOMATICALLY BY opcode-gen + "nop", + "move", + "move/from16", + "move/16", + "move-wide", + "move-wide/from16", + "move-wide/16", + "move-object", + "move-object/from16", + "move-object/16", + "move-result", + "move-result-wide", + "move-result-object", + "move-exception", + "return-void", + "return", + "return-wide", + "return-object", + "const/4", + "const/16", + "const", + "const/high16", + "const-wide/16", + "const-wide/32", + "const-wide", + "const-wide/high16", + "const-string", + "const-string/jumbo", + "const-class", + "monitor-enter", + "monitor-exit", + "check-cast", + "instance-of", + "array-length", + "new-instance", + "new-array", + "filled-new-array", + "filled-new-array/range", + "fill-array-data", + "throw", + "goto", + "goto/16", + "goto/32", + "packed-switch", + "sparse-switch", + "cmpl-float", + "cmpg-float", + "cmpl-double", + "cmpg-double", + "cmp-long", + "if-eq", + "if-ne", + "if-lt", + "if-ge", + "if-gt", + "if-le", + "if-eqz", + "if-nez", + "if-ltz", + "if-gez", + "if-gtz", + "if-lez", + "unused-3e", + "unused-3f", + "unused-40", + "unused-41", + "unused-42", + "unused-43", + "aget", + "aget-wide", + "aget-object", + "aget-boolean", + "aget-byte", + "aget-char", + "aget-short", + "aput", + "aput-wide", + "aput-object", + "aput-boolean", + "aput-byte", + "aput-char", + "aput-short", + "iget", + "iget-wide", + "iget-object", + "iget-boolean", + "iget-byte", + "iget-char", + "iget-short", + "iput", + "iput-wide", + "iput-object", + "iput-boolean", + "iput-byte", + "iput-char", + "iput-short", + "sget", + "sget-wide", + "sget-object", + "sget-boolean", + "sget-byte", + "sget-char", + "sget-short", + "sput", + "sput-wide", + "sput-object", + "sput-boolean", + "sput-byte", + "sput-char", + "sput-short", + "invoke-virtual", + "invoke-super", + "invoke-direct", + "invoke-static", + "invoke-interface", + "unused-73", + "invoke-virtual/range", + "invoke-super/range", + "invoke-direct/range", + "invoke-static/range", + "invoke-interface/range", + "unused-79", + "unused-7a", + "neg-int", + "not-int", + "neg-long", + "not-long", + "neg-float", + "neg-double", + "int-to-long", + "int-to-float", + "int-to-double", + "long-to-int", + "long-to-float", + "long-to-double", + "float-to-int", + "float-to-long", + "float-to-double", + "double-to-int", + "double-to-long", + "double-to-float", + "int-to-byte", + "int-to-char", + "int-to-short", + "add-int", + "sub-int", + "mul-int", + "div-int", + "rem-int", + "and-int", + "or-int", + "xor-int", + "shl-int", + "shr-int", + "ushr-int", + "add-long", + "sub-long", + "mul-long", + "div-long", + "rem-long", + "and-long", + "or-long", + "xor-long", + "shl-long", + "shr-long", + "ushr-long", + "add-float", + "sub-float", + "mul-float", + "div-float", + "rem-float", + "add-double", + "sub-double", + "mul-double", + "div-double", + "rem-double", + "add-int/2addr", + "sub-int/2addr", + "mul-int/2addr", + "div-int/2addr", + "rem-int/2addr", + "and-int/2addr", + "or-int/2addr", + "xor-int/2addr", + "shl-int/2addr", + "shr-int/2addr", + "ushr-int/2addr", + "add-long/2addr", + "sub-long/2addr", + "mul-long/2addr", + "div-long/2addr", + "rem-long/2addr", + "and-long/2addr", + "or-long/2addr", + "xor-long/2addr", + "shl-long/2addr", + "shr-long/2addr", + "ushr-long/2addr", + "add-float/2addr", + "sub-float/2addr", + "mul-float/2addr", + "div-float/2addr", + "rem-float/2addr", + "add-double/2addr", + "sub-double/2addr", + "mul-double/2addr", + "div-double/2addr", + "rem-double/2addr", + "add-int/lit16", + "rsub-int", + "mul-int/lit16", + "div-int/lit16", + "rem-int/lit16", + "and-int/lit16", + "or-int/lit16", + "xor-int/lit16", + "add-int/lit8", + "rsub-int/lit8", + "mul-int/lit8", + "div-int/lit8", + "rem-int/lit8", + "and-int/lit8", + "or-int/lit8", + "xor-int/lit8", + "shl-int/lit8", + "shr-int/lit8", + "ushr-int/lit8", + "+iget-volatile", + "+iput-volatile", + "+sget-volatile", + "+sput-volatile", + "+iget-object-volatile", + "+iget-wide-volatile", + "+iput-wide-volatile", + "+sget-wide-volatile", + "+sput-wide-volatile", + "^breakpoint", + "^throw-verification-error", + "+execute-inline", + "+execute-inline/range", + "+invoke-object-init/range", + "+return-void-barrier", + "+iget-quick", + "unused-f3", + "unused-f4", + "unused-f5", + "unused-f6", + "unused-f7", + "unused-f8", + "unused-f9", + "invoke-polymorphic", + "invoke-polymorphic/range", + "invoke-custom", + "invoke-custom/range", + "const-method-handle", + "const-method-type", + // END(libdex-opcode-names) +}; + +/* + * Return the name of an opcode. + */ +const char* dexGetOpcodeName(Opcode op) +{ + assert(op >= 0 && op < kNumPackedOpcodes); + return gOpNames[op]; +} diff --git a/libdex/DexOpcodes.h b/libdex/DexOpcodes.h new file mode 100644 index 000000000..f4dcd6b51 --- /dev/null +++ b/libdex/DexOpcodes.h @@ -0,0 +1,620 @@ +/* + * 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. + */ + +/* + * Dalvik opcode information. + * + * IMPORTANT NOTE: The contents of this file are mostly generated + * automatically by the opcode-gen tool. Any edits to the generated + * sections will get wiped out the next time the tool is run. + * + * See the file opcode-gen/README.txt for information about updating + * opcodes and instruction formats. + */ + +#ifndef LIBDEX_DEXOPCODES_H_ +#define LIBDEX_DEXOPCODES_H_ + +#include "DexFile.h" + +/* + * kMaxOpcodeValue: the highest possible raw (unpacked) opcode value + * + * kNumPackedOpcodes: the highest possible packed opcode value of a + * valid Dalvik opcode, plus one + * + * TODO: Change this once the rest of the code is prepared to deal with + * extended opcodes. + */ +// BEGIN(libdex-maximum-values); GENERATED AUTOMATICALLY BY opcode-gen +#define kMaxOpcodeValue 0xffff +#define kNumPackedOpcodes 0x100 +// END(libdex-maximum-values); GENERATED AUTOMATICALLY BY opcode-gen + +/* + * Switch table and array data signatures are a code unit consisting + * of "NOP" (0x00) in the low-order byte and a non-zero identifying + * code in the high-order byte. (A true NOP is 0x0000.) + */ +#define kPackedSwitchSignature 0x0100 +#define kSparseSwitchSignature 0x0200 +#define kArrayDataSignature 0x0300 + +/* + * Enumeration of all Dalvik opcodes, where the enumeration value + * associated with each is the corresponding packed opcode number. + * This is different than the opcode value from the Dalvik bytecode + * spec for opcode values >= 0xff; see dexOpcodeFromCodeUnit() below. + * + * A note about the "breakpoint" opcode. This instruction is special, + * in that it should never be seen by anything but the debug + * interpreter. During debugging it takes the place of an arbitrary + * opcode, which means operations like "tell me the opcode width so I + * can find the next instruction" aren't possible. (This is + * correctable, but probably not useful.) + */ +enum Opcode { + // BEGIN(libdex-opcode-enum); GENERATED AUTOMATICALLY BY opcode-gen + OP_NOP = 0x00, + OP_MOVE = 0x01, + OP_MOVE_FROM16 = 0x02, + OP_MOVE_16 = 0x03, + OP_MOVE_WIDE = 0x04, + OP_MOVE_WIDE_FROM16 = 0x05, + OP_MOVE_WIDE_16 = 0x06, + OP_MOVE_OBJECT = 0x07, + OP_MOVE_OBJECT_FROM16 = 0x08, + OP_MOVE_OBJECT_16 = 0x09, + OP_MOVE_RESULT = 0x0a, + OP_MOVE_RESULT_WIDE = 0x0b, + OP_MOVE_RESULT_OBJECT = 0x0c, + OP_MOVE_EXCEPTION = 0x0d, + OP_RETURN_VOID = 0x0e, + OP_RETURN = 0x0f, + OP_RETURN_WIDE = 0x10, + OP_RETURN_OBJECT = 0x11, + OP_CONST_4 = 0x12, + OP_CONST_16 = 0x13, + OP_CONST = 0x14, + OP_CONST_HIGH16 = 0x15, + OP_CONST_WIDE_16 = 0x16, + OP_CONST_WIDE_32 = 0x17, + OP_CONST_WIDE = 0x18, + OP_CONST_WIDE_HIGH16 = 0x19, + OP_CONST_STRING = 0x1a, + OP_CONST_STRING_JUMBO = 0x1b, + OP_CONST_CLASS = 0x1c, + OP_MONITOR_ENTER = 0x1d, + OP_MONITOR_EXIT = 0x1e, + OP_CHECK_CAST = 0x1f, + OP_INSTANCE_OF = 0x20, + OP_ARRAY_LENGTH = 0x21, + OP_NEW_INSTANCE = 0x22, + OP_NEW_ARRAY = 0x23, + OP_FILLED_NEW_ARRAY = 0x24, + OP_FILLED_NEW_ARRAY_RANGE = 0x25, + OP_FILL_ARRAY_DATA = 0x26, + OP_THROW = 0x27, + OP_GOTO = 0x28, + OP_GOTO_16 = 0x29, + OP_GOTO_32 = 0x2a, + OP_PACKED_SWITCH = 0x2b, + OP_SPARSE_SWITCH = 0x2c, + OP_CMPL_FLOAT = 0x2d, + OP_CMPG_FLOAT = 0x2e, + OP_CMPL_DOUBLE = 0x2f, + OP_CMPG_DOUBLE = 0x30, + OP_CMP_LONG = 0x31, + OP_IF_EQ = 0x32, + OP_IF_NE = 0x33, + OP_IF_LT = 0x34, + OP_IF_GE = 0x35, + OP_IF_GT = 0x36, + OP_IF_LE = 0x37, + OP_IF_EQZ = 0x38, + OP_IF_NEZ = 0x39, + OP_IF_LTZ = 0x3a, + OP_IF_GEZ = 0x3b, + OP_IF_GTZ = 0x3c, + OP_IF_LEZ = 0x3d, + OP_UNUSED_3E = 0x3e, + OP_UNUSED_3F = 0x3f, + OP_UNUSED_40 = 0x40, + OP_UNUSED_41 = 0x41, + OP_UNUSED_42 = 0x42, + OP_UNUSED_43 = 0x43, + OP_AGET = 0x44, + OP_AGET_WIDE = 0x45, + OP_AGET_OBJECT = 0x46, + OP_AGET_BOOLEAN = 0x47, + OP_AGET_BYTE = 0x48, + OP_AGET_CHAR = 0x49, + OP_AGET_SHORT = 0x4a, + OP_APUT = 0x4b, + OP_APUT_WIDE = 0x4c, + OP_APUT_OBJECT = 0x4d, + OP_APUT_BOOLEAN = 0x4e, + OP_APUT_BYTE = 0x4f, + OP_APUT_CHAR = 0x50, + OP_APUT_SHORT = 0x51, + OP_IGET = 0x52, + OP_IGET_WIDE = 0x53, + OP_IGET_OBJECT = 0x54, + OP_IGET_BOOLEAN = 0x55, + OP_IGET_BYTE = 0x56, + OP_IGET_CHAR = 0x57, + OP_IGET_SHORT = 0x58, + OP_IPUT = 0x59, + OP_IPUT_WIDE = 0x5a, + OP_IPUT_OBJECT = 0x5b, + OP_IPUT_BOOLEAN = 0x5c, + OP_IPUT_BYTE = 0x5d, + OP_IPUT_CHAR = 0x5e, + OP_IPUT_SHORT = 0x5f, + OP_SGET = 0x60, + OP_SGET_WIDE = 0x61, + OP_SGET_OBJECT = 0x62, + OP_SGET_BOOLEAN = 0x63, + OP_SGET_BYTE = 0x64, + OP_SGET_CHAR = 0x65, + OP_SGET_SHORT = 0x66, + OP_SPUT = 0x67, + OP_SPUT_WIDE = 0x68, + OP_SPUT_OBJECT = 0x69, + OP_SPUT_BOOLEAN = 0x6a, + OP_SPUT_BYTE = 0x6b, + OP_SPUT_CHAR = 0x6c, + OP_SPUT_SHORT = 0x6d, + OP_INVOKE_VIRTUAL = 0x6e, + OP_INVOKE_SUPER = 0x6f, + OP_INVOKE_DIRECT = 0x70, + OP_INVOKE_STATIC = 0x71, + OP_INVOKE_INTERFACE = 0x72, + OP_UNUSED_73 = 0x73, + OP_INVOKE_VIRTUAL_RANGE = 0x74, + OP_INVOKE_SUPER_RANGE = 0x75, + OP_INVOKE_DIRECT_RANGE = 0x76, + OP_INVOKE_STATIC_RANGE = 0x77, + OP_INVOKE_INTERFACE_RANGE = 0x78, + OP_UNUSED_79 = 0x79, + OP_UNUSED_7A = 0x7a, + OP_NEG_INT = 0x7b, + OP_NOT_INT = 0x7c, + OP_NEG_LONG = 0x7d, + OP_NOT_LONG = 0x7e, + OP_NEG_FLOAT = 0x7f, + OP_NEG_DOUBLE = 0x80, + OP_INT_TO_LONG = 0x81, + OP_INT_TO_FLOAT = 0x82, + OP_INT_TO_DOUBLE = 0x83, + OP_LONG_TO_INT = 0x84, + OP_LONG_TO_FLOAT = 0x85, + OP_LONG_TO_DOUBLE = 0x86, + OP_FLOAT_TO_INT = 0x87, + OP_FLOAT_TO_LONG = 0x88, + OP_FLOAT_TO_DOUBLE = 0x89, + OP_DOUBLE_TO_INT = 0x8a, + OP_DOUBLE_TO_LONG = 0x8b, + OP_DOUBLE_TO_FLOAT = 0x8c, + OP_INT_TO_BYTE = 0x8d, + OP_INT_TO_CHAR = 0x8e, + OP_INT_TO_SHORT = 0x8f, + OP_ADD_INT = 0x90, + OP_SUB_INT = 0x91, + OP_MUL_INT = 0x92, + OP_DIV_INT = 0x93, + OP_REM_INT = 0x94, + OP_AND_INT = 0x95, + OP_OR_INT = 0x96, + OP_XOR_INT = 0x97, + OP_SHL_INT = 0x98, + OP_SHR_INT = 0x99, + OP_USHR_INT = 0x9a, + OP_ADD_LONG = 0x9b, + OP_SUB_LONG = 0x9c, + OP_MUL_LONG = 0x9d, + OP_DIV_LONG = 0x9e, + OP_REM_LONG = 0x9f, + OP_AND_LONG = 0xa0, + OP_OR_LONG = 0xa1, + OP_XOR_LONG = 0xa2, + OP_SHL_LONG = 0xa3, + OP_SHR_LONG = 0xa4, + OP_USHR_LONG = 0xa5, + OP_ADD_FLOAT = 0xa6, + OP_SUB_FLOAT = 0xa7, + OP_MUL_FLOAT = 0xa8, + OP_DIV_FLOAT = 0xa9, + OP_REM_FLOAT = 0xaa, + OP_ADD_DOUBLE = 0xab, + OP_SUB_DOUBLE = 0xac, + OP_MUL_DOUBLE = 0xad, + OP_DIV_DOUBLE = 0xae, + OP_REM_DOUBLE = 0xaf, + OP_ADD_INT_2ADDR = 0xb0, + OP_SUB_INT_2ADDR = 0xb1, + OP_MUL_INT_2ADDR = 0xb2, + OP_DIV_INT_2ADDR = 0xb3, + OP_REM_INT_2ADDR = 0xb4, + OP_AND_INT_2ADDR = 0xb5, + OP_OR_INT_2ADDR = 0xb6, + OP_XOR_INT_2ADDR = 0xb7, + OP_SHL_INT_2ADDR = 0xb8, + OP_SHR_INT_2ADDR = 0xb9, + OP_USHR_INT_2ADDR = 0xba, + OP_ADD_LONG_2ADDR = 0xbb, + OP_SUB_LONG_2ADDR = 0xbc, + OP_MUL_LONG_2ADDR = 0xbd, + OP_DIV_LONG_2ADDR = 0xbe, + OP_REM_LONG_2ADDR = 0xbf, + OP_AND_LONG_2ADDR = 0xc0, + OP_OR_LONG_2ADDR = 0xc1, + OP_XOR_LONG_2ADDR = 0xc2, + OP_SHL_LONG_2ADDR = 0xc3, + OP_SHR_LONG_2ADDR = 0xc4, + OP_USHR_LONG_2ADDR = 0xc5, + OP_ADD_FLOAT_2ADDR = 0xc6, + OP_SUB_FLOAT_2ADDR = 0xc7, + OP_MUL_FLOAT_2ADDR = 0xc8, + OP_DIV_FLOAT_2ADDR = 0xc9, + OP_REM_FLOAT_2ADDR = 0xca, + OP_ADD_DOUBLE_2ADDR = 0xcb, + OP_SUB_DOUBLE_2ADDR = 0xcc, + OP_MUL_DOUBLE_2ADDR = 0xcd, + OP_DIV_DOUBLE_2ADDR = 0xce, + OP_REM_DOUBLE_2ADDR = 0xcf, + OP_ADD_INT_LIT16 = 0xd0, + OP_RSUB_INT = 0xd1, + OP_MUL_INT_LIT16 = 0xd2, + OP_DIV_INT_LIT16 = 0xd3, + OP_REM_INT_LIT16 = 0xd4, + OP_AND_INT_LIT16 = 0xd5, + OP_OR_INT_LIT16 = 0xd6, + OP_XOR_INT_LIT16 = 0xd7, + OP_ADD_INT_LIT8 = 0xd8, + OP_RSUB_INT_LIT8 = 0xd9, + OP_MUL_INT_LIT8 = 0xda, + OP_DIV_INT_LIT8 = 0xdb, + OP_REM_INT_LIT8 = 0xdc, + OP_AND_INT_LIT8 = 0xdd, + OP_OR_INT_LIT8 = 0xde, + OP_XOR_INT_LIT8 = 0xdf, + OP_SHL_INT_LIT8 = 0xe0, + OP_SHR_INT_LIT8 = 0xe1, + OP_USHR_INT_LIT8 = 0xe2, + OP_IGET_VOLATILE = 0xe3, + OP_IPUT_VOLATILE = 0xe4, + OP_SGET_VOLATILE = 0xe5, + OP_SPUT_VOLATILE = 0xe6, + OP_IGET_OBJECT_VOLATILE = 0xe7, + OP_IGET_WIDE_VOLATILE = 0xe8, + OP_IPUT_WIDE_VOLATILE = 0xe9, + OP_SGET_WIDE_VOLATILE = 0xea, + OP_SPUT_WIDE_VOLATILE = 0xeb, + OP_BREAKPOINT = 0xec, + OP_THROW_VERIFICATION_ERROR = 0xed, + OP_EXECUTE_INLINE = 0xee, + OP_EXECUTE_INLINE_RANGE = 0xef, + OP_INVOKE_OBJECT_INIT_RANGE = 0xf0, + OP_RETURN_VOID_BARRIER = 0xf1, + OP_IGET_QUICK = 0xf2, + OP_UNUSED_F3 = 0xf3, + OP_UNUSED_F4 = 0xf4, + OP_UNUSED_F5 = 0xf5, + OP_UNUSED_F6 = 0xf6, + OP_UNUSED_F7 = 0xf7, + OP_UNUSED_F8 = 0xf8, + OP_UNUSED_F9 = 0xf9, + OP_INVOKE_POLYMORPHIC = 0xfa, + OP_INVOKE_POLYMORPHIC_RANGE = 0xfb, + OP_INVOKE_CUSTOM = 0xfc, + OP_INVOKE_CUSTOM_RANGE = 0xfd, + OP_CONST_METHOD_HANDLE = 0xfe, + OP_CONST_METHOD_TYPE = 0xff, + // END(libdex-opcode-enum) +}; + +/* + * Macro used to generate a computed goto table for use in implementing + * an interpreter in C. + */ +#define DEFINE_GOTO_TABLE(_name) \ + static const void* (_name)[kNumPackedOpcodes] = { \ + /* BEGIN(libdex-goto-table); GENERATED AUTOMATICALLY BY opcode-gen */ \ + H(OP_NOP), \ + H(OP_MOVE), \ + H(OP_MOVE_FROM16), \ + H(OP_MOVE_16), \ + H(OP_MOVE_WIDE), \ + H(OP_MOVE_WIDE_FROM16), \ + H(OP_MOVE_WIDE_16), \ + H(OP_MOVE_OBJECT), \ + H(OP_MOVE_OBJECT_FROM16), \ + H(OP_MOVE_OBJECT_16), \ + H(OP_MOVE_RESULT), \ + H(OP_MOVE_RESULT_WIDE), \ + H(OP_MOVE_RESULT_OBJECT), \ + H(OP_MOVE_EXCEPTION), \ + H(OP_RETURN_VOID), \ + H(OP_RETURN), \ + H(OP_RETURN_WIDE), \ + H(OP_RETURN_OBJECT), \ + H(OP_CONST_4), \ + H(OP_CONST_16), \ + H(OP_CONST), \ + H(OP_CONST_HIGH16), \ + H(OP_CONST_WIDE_16), \ + H(OP_CONST_WIDE_32), \ + H(OP_CONST_WIDE), \ + H(OP_CONST_WIDE_HIGH16), \ + H(OP_CONST_STRING), \ + H(OP_CONST_STRING_JUMBO), \ + H(OP_CONST_CLASS), \ + H(OP_MONITOR_ENTER), \ + H(OP_MONITOR_EXIT), \ + H(OP_CHECK_CAST), \ + H(OP_INSTANCE_OF), \ + H(OP_ARRAY_LENGTH), \ + H(OP_NEW_INSTANCE), \ + H(OP_NEW_ARRAY), \ + H(OP_FILLED_NEW_ARRAY), \ + H(OP_FILLED_NEW_ARRAY_RANGE), \ + H(OP_FILL_ARRAY_DATA), \ + H(OP_THROW), \ + H(OP_GOTO), \ + H(OP_GOTO_16), \ + H(OP_GOTO_32), \ + H(OP_PACKED_SWITCH), \ + H(OP_SPARSE_SWITCH), \ + H(OP_CMPL_FLOAT), \ + H(OP_CMPG_FLOAT), \ + H(OP_CMPL_DOUBLE), \ + H(OP_CMPG_DOUBLE), \ + H(OP_CMP_LONG), \ + H(OP_IF_EQ), \ + H(OP_IF_NE), \ + H(OP_IF_LT), \ + H(OP_IF_GE), \ + H(OP_IF_GT), \ + H(OP_IF_LE), \ + H(OP_IF_EQZ), \ + H(OP_IF_NEZ), \ + H(OP_IF_LTZ), \ + H(OP_IF_GEZ), \ + H(OP_IF_GTZ), \ + H(OP_IF_LEZ), \ + H(OP_UNUSED_3E), \ + H(OP_UNUSED_3F), \ + H(OP_UNUSED_40), \ + H(OP_UNUSED_41), \ + H(OP_UNUSED_42), \ + H(OP_UNUSED_43), \ + H(OP_AGET), \ + H(OP_AGET_WIDE), \ + H(OP_AGET_OBJECT), \ + H(OP_AGET_BOOLEAN), \ + H(OP_AGET_BYTE), \ + H(OP_AGET_CHAR), \ + H(OP_AGET_SHORT), \ + H(OP_APUT), \ + H(OP_APUT_WIDE), \ + H(OP_APUT_OBJECT), \ + H(OP_APUT_BOOLEAN), \ + H(OP_APUT_BYTE), \ + H(OP_APUT_CHAR), \ + H(OP_APUT_SHORT), \ + H(OP_IGET), \ + H(OP_IGET_WIDE), \ + H(OP_IGET_OBJECT), \ + H(OP_IGET_BOOLEAN), \ + H(OP_IGET_BYTE), \ + H(OP_IGET_CHAR), \ + H(OP_IGET_SHORT), \ + H(OP_IPUT), \ + H(OP_IPUT_WIDE), \ + H(OP_IPUT_OBJECT), \ + H(OP_IPUT_BOOLEAN), \ + H(OP_IPUT_BYTE), \ + H(OP_IPUT_CHAR), \ + H(OP_IPUT_SHORT), \ + H(OP_SGET), \ + H(OP_SGET_WIDE), \ + H(OP_SGET_OBJECT), \ + H(OP_SGET_BOOLEAN), \ + H(OP_SGET_BYTE), \ + H(OP_SGET_CHAR), \ + H(OP_SGET_SHORT), \ + H(OP_SPUT), \ + H(OP_SPUT_WIDE), \ + H(OP_SPUT_OBJECT), \ + H(OP_SPUT_BOOLEAN), \ + H(OP_SPUT_BYTE), \ + H(OP_SPUT_CHAR), \ + H(OP_SPUT_SHORT), \ + H(OP_INVOKE_VIRTUAL), \ + H(OP_INVOKE_SUPER), \ + H(OP_INVOKE_DIRECT), \ + H(OP_INVOKE_STATIC), \ + H(OP_INVOKE_INTERFACE), \ + H(OP_UNUSED_73), \ + H(OP_INVOKE_VIRTUAL_RANGE), \ + H(OP_INVOKE_SUPER_RANGE), \ + H(OP_INVOKE_DIRECT_RANGE), \ + H(OP_INVOKE_STATIC_RANGE), \ + H(OP_INVOKE_INTERFACE_RANGE), \ + H(OP_UNUSED_79), \ + H(OP_UNUSED_7A), \ + H(OP_NEG_INT), \ + H(OP_NOT_INT), \ + H(OP_NEG_LONG), \ + H(OP_NOT_LONG), \ + H(OP_NEG_FLOAT), \ + H(OP_NEG_DOUBLE), \ + H(OP_INT_TO_LONG), \ + H(OP_INT_TO_FLOAT), \ + H(OP_INT_TO_DOUBLE), \ + H(OP_LONG_TO_INT), \ + H(OP_LONG_TO_FLOAT), \ + H(OP_LONG_TO_DOUBLE), \ + H(OP_FLOAT_TO_INT), \ + H(OP_FLOAT_TO_LONG), \ + H(OP_FLOAT_TO_DOUBLE), \ + H(OP_DOUBLE_TO_INT), \ + H(OP_DOUBLE_TO_LONG), \ + H(OP_DOUBLE_TO_FLOAT), \ + H(OP_INT_TO_BYTE), \ + H(OP_INT_TO_CHAR), \ + H(OP_INT_TO_SHORT), \ + H(OP_ADD_INT), \ + H(OP_SUB_INT), \ + H(OP_MUL_INT), \ + H(OP_DIV_INT), \ + H(OP_REM_INT), \ + H(OP_AND_INT), \ + H(OP_OR_INT), \ + H(OP_XOR_INT), \ + H(OP_SHL_INT), \ + H(OP_SHR_INT), \ + H(OP_USHR_INT), \ + H(OP_ADD_LONG), \ + H(OP_SUB_LONG), \ + H(OP_MUL_LONG), \ + H(OP_DIV_LONG), \ + H(OP_REM_LONG), \ + H(OP_AND_LONG), \ + H(OP_OR_LONG), \ + H(OP_XOR_LONG), \ + H(OP_SHL_LONG), \ + H(OP_SHR_LONG), \ + H(OP_USHR_LONG), \ + H(OP_ADD_FLOAT), \ + H(OP_SUB_FLOAT), \ + H(OP_MUL_FLOAT), \ + H(OP_DIV_FLOAT), \ + H(OP_REM_FLOAT), \ + H(OP_ADD_DOUBLE), \ + H(OP_SUB_DOUBLE), \ + H(OP_MUL_DOUBLE), \ + H(OP_DIV_DOUBLE), \ + H(OP_REM_DOUBLE), \ + H(OP_ADD_INT_2ADDR), \ + H(OP_SUB_INT_2ADDR), \ + H(OP_MUL_INT_2ADDR), \ + H(OP_DIV_INT_2ADDR), \ + H(OP_REM_INT_2ADDR), \ + H(OP_AND_INT_2ADDR), \ + H(OP_OR_INT_2ADDR), \ + H(OP_XOR_INT_2ADDR), \ + H(OP_SHL_INT_2ADDR), \ + H(OP_SHR_INT_2ADDR), \ + H(OP_USHR_INT_2ADDR), \ + H(OP_ADD_LONG_2ADDR), \ + H(OP_SUB_LONG_2ADDR), \ + H(OP_MUL_LONG_2ADDR), \ + H(OP_DIV_LONG_2ADDR), \ + H(OP_REM_LONG_2ADDR), \ + H(OP_AND_LONG_2ADDR), \ + H(OP_OR_LONG_2ADDR), \ + H(OP_XOR_LONG_2ADDR), \ + H(OP_SHL_LONG_2ADDR), \ + H(OP_SHR_LONG_2ADDR), \ + H(OP_USHR_LONG_2ADDR), \ + H(OP_ADD_FLOAT_2ADDR), \ + H(OP_SUB_FLOAT_2ADDR), \ + H(OP_MUL_FLOAT_2ADDR), \ + H(OP_DIV_FLOAT_2ADDR), \ + H(OP_REM_FLOAT_2ADDR), \ + H(OP_ADD_DOUBLE_2ADDR), \ + H(OP_SUB_DOUBLE_2ADDR), \ + H(OP_MUL_DOUBLE_2ADDR), \ + H(OP_DIV_DOUBLE_2ADDR), \ + H(OP_REM_DOUBLE_2ADDR), \ + H(OP_ADD_INT_LIT16), \ + H(OP_RSUB_INT), \ + H(OP_MUL_INT_LIT16), \ + H(OP_DIV_INT_LIT16), \ + H(OP_REM_INT_LIT16), \ + H(OP_AND_INT_LIT16), \ + H(OP_OR_INT_LIT16), \ + H(OP_XOR_INT_LIT16), \ + H(OP_ADD_INT_LIT8), \ + H(OP_RSUB_INT_LIT8), \ + H(OP_MUL_INT_LIT8), \ + H(OP_DIV_INT_LIT8), \ + H(OP_REM_INT_LIT8), \ + H(OP_AND_INT_LIT8), \ + H(OP_OR_INT_LIT8), \ + H(OP_XOR_INT_LIT8), \ + H(OP_SHL_INT_LIT8), \ + H(OP_SHR_INT_LIT8), \ + H(OP_USHR_INT_LIT8), \ + H(OP_IGET_VOLATILE), \ + H(OP_IPUT_VOLATILE), \ + H(OP_SGET_VOLATILE), \ + H(OP_SPUT_VOLATILE), \ + H(OP_IGET_OBJECT_VOLATILE), \ + H(OP_IGET_WIDE_VOLATILE), \ + H(OP_IPUT_WIDE_VOLATILE), \ + H(OP_SGET_WIDE_VOLATILE), \ + H(OP_SPUT_WIDE_VOLATILE), \ + H(OP_BREAKPOINT), \ + H(OP_THROW_VERIFICATION_ERROR), \ + H(OP_EXECUTE_INLINE), \ + H(OP_EXECUTE_INLINE_RANGE), \ + H(OP_INVOKE_OBJECT_INIT_RANGE), \ + H(OP_RETURN_VOID_BARRIER), \ + H(OP_IGET_QUICK), \ + H(OP_UNUSED_F3), \ + H(OP_UNUSED_F4), \ + H(OP_UNUSED_F5), \ + H(OP_UNUSED_F6), \ + H(OP_UNUSED_F7), \ + H(OP_UNUSED_F8), \ + H(OP_UNUSED_F9), \ + H(OP_INVOKE_POLYMORPHIC), \ + H(OP_INVOKE_POLYMORPHIC_RANGE), \ + H(OP_INVOKE_CUSTOM), \ + H(OP_INVOKE_CUSTOM_RANGE), \ + H(OP_CONST_METHOD_HANDLE), \ + H(OP_CONST_METHOD_TYPE), \ + /* END(libdex-goto-table) */ \ + }; + +/* + * Return the Opcode for a given raw opcode code unit (which may + * include data payload). The packed index is a zero-based index which + * can be used to point into various opcode-related tables. The Dalvik + * opcode space is inherently sparse, in that the opcode unit is 16 + * bits wide, but for most opcodes, eight of those bits are for data. + */ +DEX_INLINE Opcode dexOpcodeFromCodeUnit(u2 codeUnit) { + /* + * This will want to become table-driven should the opcode layout + * get more complicated. + * + * Note: This has to match the corresponding code in opcode-gen, so + * that data tables get generated in a consistent way. + */ + int lowByte = codeUnit & 0xff; + return (Opcode) lowByte; +} + +/* + * Return the name of an opcode. + */ +const char* dexGetOpcodeName(Opcode op); + +#endif // LIBDEX_DEXOPCODES_H_ diff --git a/libdex/DexOptData.cpp b/libdex/DexOptData.cpp new file mode 100644 index 000000000..109e851f9 --- /dev/null +++ b/libdex/DexOptData.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010 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. + */ + +/* + * Functions to parse and manipulate the additional data tables added + * to optimized .dex files. + */ + +#include <zlib.h> + +#include "DexOptData.h" + +/* + * Check to see if a given data pointer is a valid double-word-aligned + * pointer into the given memory range (from start inclusive to end + * exclusive). Returns true if valid. + */ +static bool isValidPointer(const void* ptr, const void* start, const void* end) +{ + return (ptr >= start) && (ptr < end) && (((uintptr_t) ptr & 7) == 0); +} + +/* (documented in header file) */ +u4 dexComputeOptChecksum(const DexOptHeader* pOptHeader) +{ + const u1* start = (const u1*) pOptHeader + pOptHeader->depsOffset; + const u1* end = (const u1*) pOptHeader + + pOptHeader->optOffset + pOptHeader->optLength; + + uLong adler = adler32(0L, Z_NULL, 0); + + return (u4) adler32(adler, start, end - start); +} + +/* (documented in header file) */ +bool dexParseOptData(const u1* data, size_t length, DexFile* pDexFile) +{ + const void* pOptStart = data + pDexFile->pOptHeader->optOffset; + const void* pOptEnd = data + length; + const u4* pOpt = (const u4*) pOptStart; + u4 optLength = (const u1*) pOptEnd - (const u1*) pOptStart; + + /* + * Make sure the opt data start is in range and aligned. This may + * seem like a superfluous check, but (a) if the file got + * truncated, it might turn out that pOpt >= pOptEnd; and (b) + * if the opt data header got corrupted, pOpt might not be + * properly aligned. This test will catch both of these cases. + */ + if (!isValidPointer(pOpt, pOptStart, pOptEnd)) { + ALOGE("Bogus opt data start pointer"); + return false; + } + + /* Make sure that the opt data length is a whole number of words. */ + if ((optLength & 3) != 0) { + ALOGE("Unaligned opt data area end"); + return false; + } + + /* + * Make sure that the opt data area is large enough to have at least + * one chunk header. + */ + if (optLength < 8) { + ALOGE("Undersized opt data area (%u)", optLength); + return false; + } + + /* Process chunks until we see the end marker. */ + while (*pOpt != kDexChunkEnd) { + if (!isValidPointer(pOpt + 2, pOptStart, pOptEnd)) { + const u4 offset = ((const u1*) pOpt) - data; + ALOGE("Bogus opt data content pointer at offset %u", offset); + return false; + } + + u4 size = *(pOpt + 1); + const u1* pOptData = (const u1*) (pOpt + 2); + + /* + * The rounded size is 64-bit aligned and includes +8 for the + * type/size header (which was extracted immediately above). + */ + u4 roundedSize = (size + 8 + 7) & ~7; + const u4* pNextOpt = pOpt + (roundedSize / sizeof(u4)); + + if (!isValidPointer(pNextOpt, pOptStart, pOptEnd)) { + const u4 offset = ((const u1*) pOpt) - data; + ALOGE("Opt data area problem for chunk of size %u at offset %u", size, offset); + return false; + } + + switch (*pOpt) { + case kDexChunkClassLookup: + pDexFile->pClassLookup = (const DexClassLookup*) pOptData; + break; + case kDexChunkRegisterMaps: + ALOGV("+++ found register maps, size=%u", size); + pDexFile->pRegisterMapPool = pOptData; + break; + default: + ALOGI("Unknown chunk 0x%08x (%c%c%c%c), size=%d in opt data area", + *pOpt, + (char) ((*pOpt) >> 24), (char) ((*pOpt) >> 16), + (char) ((*pOpt) >> 8), (char) (*pOpt), + size); + break; + } + + pOpt = pNextOpt; + } + + return true; +} diff --git a/libdex/DexOptData.h b/libdex/DexOptData.h new file mode 100644 index 000000000..69eda0142 --- /dev/null +++ b/libdex/DexOptData.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 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. + */ + +/* + * Functions to parse and manipulate the additional data tables added + * to optimized .dex files. + */ + +#ifndef _LIBDEX_DEXOPTDATA +#define _LIBDEX_DEXOPTDATA + +#include "libdex/DexFile.h" + +/* + * Parse the optimized data tables in the given dex file. + * + * @param data pointer to the start of the entire dex file + * @param length length of the entire dex file, in bytes + * @param pDexFile pointer to the associated dex file structure + */ +bool dexParseOptData(const u1* data, size_t length, DexFile* pDexFile); + +/* + * Compute the checksum of the optimized data tables pointed at by the given + * header. + */ +u4 dexComputeOptChecksum(const DexOptHeader* pOptHeader); + +#endif /* def _LIBDEX_DEXOPTDATA */ diff --git a/libdex/DexProto.cpp b/libdex/DexProto.cpp new file mode 100644 index 000000000..06c59b37d --- /dev/null +++ b/libdex/DexProto.cpp @@ -0,0 +1,523 @@ +/* + * 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. + */ + +/* + * Functions for dealing with method prototypes + */ + +#include "DexProto.h" + +#include <stdlib.h> +#include <string.h> + +/* + * =========================================================================== + * String Cache + * =========================================================================== + */ + +/* + * Make sure that the given cache can hold a string of the given length, + * including the final '\0' byte. + */ +void dexStringCacheAlloc(DexStringCache* pCache, size_t length) { + if (pCache->allocatedSize != 0) { + if (pCache->allocatedSize >= length) { + return; + } + free((void*) pCache->value); + } + + if (length <= sizeof(pCache->buffer)) { + pCache->value = pCache->buffer; + pCache->allocatedSize = 0; + } else { + pCache->value = (char*) malloc(length); + pCache->allocatedSize = length; + } +} + +/* + * Initialize the given DexStringCache. Use this function before passing + * one into any other function. + */ +void dexStringCacheInit(DexStringCache* pCache) { + pCache->value = pCache->buffer; + pCache->allocatedSize = 0; + pCache->buffer[0] = '\0'; +} + +/* + * Release the allocated contents of the given DexStringCache, if any. + * Use this function after your last use of a DexStringCache. + */ +void dexStringCacheRelease(DexStringCache* pCache) { + if (pCache->allocatedSize != 0) { + free((void*) pCache->value); + pCache->value = pCache->buffer; + pCache->allocatedSize = 0; + } +} + +/* + * If the given DexStringCache doesn't already point at the given value, + * make a copy of it into the cache. This always returns a writable + * pointer to the contents (whether or not a copy had to be made). This + * function is intended to be used after making a call that at least + * sometimes doesn't populate a DexStringCache. + */ +char* dexStringCacheEnsureCopy(DexStringCache* pCache, const char* value) { + if (value != pCache->value) { + size_t length = strlen(value) + 1; + dexStringCacheAlloc(pCache, length); + memcpy(pCache->value, value, length); + } + + return pCache->value; +} + +/* + * Abandon the given DexStringCache, and return a writable copy of the + * given value (reusing the string cache's allocation if possible). + * The return value must be free()d by the caller. Use this instead of + * dexStringCacheRelease() if you want the buffer to survive past the + * scope of the DexStringCache. + */ +char* dexStringCacheAbandon(DexStringCache* pCache, const char* value) { + if ((value == pCache->value) && (pCache->allocatedSize != 0)) { + char* result = pCache->value; + pCache->allocatedSize = 0; + pCache->value = pCache->buffer; + return result; + } else { + return strdup(value); + } +} + + +/* + * =========================================================================== + * Method Prototypes + * =========================================================================== + */ + +/* + * Return the DexProtoId from the given DexProto. The DexProto must + * actually refer to a DexProtoId. + */ +static inline const DexProtoId* getProtoId(const DexProto* pProto) { + return dexGetProtoId(pProto->dexFile, pProto->protoIdx); +} + +/* (documented in header file) */ +const char* dexProtoGetShorty(const DexProto* pProto) { + const DexProtoId* protoId = getProtoId(pProto); + + return dexStringById(pProto->dexFile, protoId->shortyIdx); +} + +/* (documented in header file) */ +const char* dexProtoGetMethodDescriptor(const DexProto* pProto, + DexStringCache* pCache) { + const DexFile* dexFile = pProto->dexFile; + const DexProtoId* protoId = getProtoId(pProto); + const DexTypeList* typeList = dexGetProtoParameters(dexFile, protoId); + size_t length = 3; // parens and terminating '\0' + u4 paramCount = (typeList == NULL) ? 0 : typeList->size; + u4 i; + + for (i = 0; i < paramCount; i++) { + u4 idx = dexTypeListGetIdx(typeList, i); + length += strlen(dexStringByTypeIdx(dexFile, idx)); + } + + length += strlen(dexStringByTypeIdx(dexFile, protoId->returnTypeIdx)); + + dexStringCacheAlloc(pCache, length); + + char *at = (char*) pCache->value; + *(at++) = '('; + + for (i = 0; i < paramCount; i++) { + u4 idx = dexTypeListGetIdx(typeList, i); + const char* desc = dexStringByTypeIdx(dexFile, idx); + strcpy(at, desc); + at += strlen(desc); + } + + *(at++) = ')'; + + strcpy(at, dexStringByTypeIdx(dexFile, protoId->returnTypeIdx)); + return pCache->value; +} + +/* (documented in header file) */ +char* dexProtoCopyMethodDescriptor(const DexProto* pProto) { + DexStringCache cache; + + dexStringCacheInit(&cache); + return dexStringCacheAbandon(&cache, + dexProtoGetMethodDescriptor(pProto, &cache)); +} + +/* (documented in header file) */ +const char* dexProtoGetParameterDescriptors(const DexProto* pProto, + DexStringCache* pCache) { + DexParameterIterator iterator; + size_t length = 1; /* +1 for the terminating '\0' */ + + dexParameterIteratorInit(&iterator, pProto); + + for (;;) { + const char* descriptor = dexParameterIteratorNextDescriptor(&iterator); + if (descriptor == NULL) { + break; + } + + length += strlen(descriptor); + } + + dexParameterIteratorInit(&iterator, pProto); + + dexStringCacheAlloc(pCache, length); + char *at = (char*) pCache->value; + + for (;;) { + const char* descriptor = dexParameterIteratorNextDescriptor(&iterator); + if (descriptor == NULL) { + break; + } + + strcpy(at, descriptor); + at += strlen(descriptor); + } + + return pCache->value; +} + +/* (documented in header file) */ +const char* dexProtoGetReturnType(const DexProto* pProto) { + const DexProtoId* protoId = getProtoId(pProto); + return dexStringByTypeIdx(pProto->dexFile, protoId->returnTypeIdx); +} + +/* (documented in header file) */ +size_t dexProtoGetParameterCount(const DexProto* pProto) { + const DexProtoId* protoId = getProtoId(pProto); + const DexTypeList* typeList = + dexGetProtoParameters(pProto->dexFile, protoId); + return (typeList == NULL) ? 0 : typeList->size; +} + +/* (documented in header file) */ +int dexProtoComputeArgsSize(const DexProto* pProto) { + const char* shorty = dexProtoGetShorty(pProto); + int count = 0; + + /* Skip the return type. */ + shorty++; + + for (;;) { + switch (*(shorty++)) { + case '\0': { + return count; + } + case 'D': + case 'J': { + count += 2; + break; + } + default: { + count++; + break; + } + } + } +} + +/* + * Common implementation for dexProtoCompare() and dexProtoCompareParameters(). + */ +static int protoCompare(const DexProto* pProto1, const DexProto* pProto2, + bool compareReturnType) { + + if (pProto1 == pProto2) { + // Easy out. + return 0; + } else { + const DexFile* dexFile1 = pProto1->dexFile; + const DexProtoId* protoId1 = getProtoId(pProto1); + const DexTypeList* typeList1 = + dexGetProtoParameters(dexFile1, protoId1); + int paramCount1 = (typeList1 == NULL) ? 0 : typeList1->size; + + const DexFile* dexFile2 = pProto2->dexFile; + const DexProtoId* protoId2 = getProtoId(pProto2); + const DexTypeList* typeList2 = + dexGetProtoParameters(dexFile2, protoId2); + int paramCount2 = (typeList2 == NULL) ? 0 : typeList2->size; + + if (protoId1 == protoId2) { + // Another easy out. + return 0; + } + + // Compare return types. + + if (compareReturnType) { + int result = + strcmp(dexStringByTypeIdx(dexFile1, protoId1->returnTypeIdx), + dexStringByTypeIdx(dexFile2, protoId2->returnTypeIdx)); + + if (result != 0) { + return result; + } + } + + // Compare parameters. + + int minParam = (paramCount1 > paramCount2) ? paramCount2 : paramCount1; + int i; + + for (i = 0; i < minParam; i++) { + u4 idx1 = dexTypeListGetIdx(typeList1, i); + u4 idx2 = dexTypeListGetIdx(typeList2, i); + int result = + strcmp(dexStringByTypeIdx(dexFile1, idx1), + dexStringByTypeIdx(dexFile2, idx2)); + + if (result != 0) { + return result; + } + } + + if (paramCount1 < paramCount2) { + return -1; + } else if (paramCount1 > paramCount2) { + return 1; + } else { + return 0; + } + } +} + +/* (documented in header file) */ +int dexProtoCompare(const DexProto* pProto1, const DexProto* pProto2) { + return protoCompare(pProto1, pProto2, true); +} + +/* (documented in header file) */ +int dexProtoCompareParameters(const DexProto* pProto1, const DexProto* pProto2){ + return protoCompare(pProto1, pProto2, false); +} + + +/* + * Helper for dexProtoCompareToDescriptor(), which gets the return type + * descriptor from a method descriptor string. + */ +static const char* methodDescriptorReturnType(const char* descriptor) { + const char* result = strchr(descriptor, ')'); + + if (result == NULL) { + return NULL; + } + + // The return type is the character just past the ')'. + return result + 1; +} + +/* + * Helper for dexProtoCompareToDescriptor(), which indicates the end + * of an embedded argument type descriptor, which is also the + * beginning of the next argument type descriptor. Since this is for + * argument types, it doesn't accept 'V' as a valid type descriptor. + */ +static const char* methodDescriptorNextType(const char* descriptor) { + // Skip any array references. + + while (*descriptor == '[') { + descriptor++; + } + + switch (*descriptor) { + case 'B': case 'C': case 'D': case 'F': + case 'I': case 'J': case 'S': case 'Z': { + return descriptor + 1; + } + case 'L': { + const char* result = strchr(descriptor + 1, ';'); + if (result != NULL) { + // The type ends just past the ';'. + return result + 1; + } + } + } + + return NULL; +} + +/* + * Common implementation for dexProtoCompareToDescriptor() and + * dexProtoCompareToParameterDescriptors(). The descriptor argument + * can be either a full method descriptor (with parens and a return + * type) or an unadorned concatenation of types (e.g. a list of + * argument types). + */ +static int protoCompareToParameterDescriptors(const DexProto* proto, + const char* descriptor, bool expectParens) { + char expectedEndChar = expectParens ? ')' : '\0'; + DexParameterIterator iterator; + dexParameterIteratorInit(&iterator, proto); + + if (expectParens) { + // Skip the '('. + assert (*descriptor == '('); + descriptor++; + } + + for (;;) { + const char* protoDesc = dexParameterIteratorNextDescriptor(&iterator); + + if (*descriptor == expectedEndChar) { + // It's the end of the descriptor string. + if (protoDesc == NULL) { + // It's also the end of the prototype's arguments. + return 0; + } else { + // The prototype still has more arguments. + return 1; + } + } + + if (protoDesc == NULL) { + /* + * The prototype doesn't have arguments left, but the + * descriptor string does. + */ + return -1; + } + + // Both prototype and descriptor have arguments. Compare them. + + const char* nextDesc = methodDescriptorNextType(descriptor); + assert(nextDesc != NULL); + + for (;;) { + char c1 = *(protoDesc++); + char c2 = (descriptor < nextDesc) ? *(descriptor++) : '\0'; + + if (c1 < c2) { + // This includes the case where the proto is shorter. + return -1; + } else if (c1 > c2) { + // This includes the case where the desc is shorter. + return 1; + } else if (c1 == '\0') { + // The two types are equal in length. (c2 necessarily == '\0'.) + break; + } + } + + /* + * If we made it here, the two arguments matched, and + * descriptor == nextDesc. + */ + } +} + +/* (documented in header file) */ +int dexProtoCompareToDescriptor(const DexProto* proto, + const char* descriptor) { + // First compare the return types. + + const char *returnType = methodDescriptorReturnType(descriptor); + assert(returnType != NULL); + + int result = strcmp(dexProtoGetReturnType(proto), returnType); + + if (result != 0) { + return result; + } + + // The return types match, so we have to check arguments. + return protoCompareToParameterDescriptors(proto, descriptor, true); +} + +/* (documented in header file) */ +int dexProtoCompareToParameterDescriptors(const DexProto* proto, + const char* descriptors) { + return protoCompareToParameterDescriptors(proto, descriptors, false); +} + + + + + + +/* + * =========================================================================== + * Parameter Iterators + * =========================================================================== + */ + +/* + * Initialize the given DexParameterIterator to be at the start of the + * parameters of the given prototype. + */ +void dexParameterIteratorInit(DexParameterIterator* pIterator, + const DexProto* pProto) { + pIterator->proto = pProto; + pIterator->cursor = 0; + + pIterator->parameters = + dexGetProtoParameters(pProto->dexFile, getProtoId(pProto)); + pIterator->parameterCount = (pIterator->parameters == NULL) ? 0 + : pIterator->parameters->size; +} + +/* + * Get the type_id index for the next parameter, if any. This returns + * kDexNoIndex if the last parameter has already been consumed. + */ +u4 dexParameterIteratorNextIndex(DexParameterIterator* pIterator) { + int cursor = pIterator->cursor; + int parameterCount = pIterator->parameterCount; + + if (cursor >= parameterCount) { + // The iteration is complete. + return kDexNoIndex; + } else { + u4 idx = dexTypeListGetIdx(pIterator->parameters, cursor); + pIterator->cursor++; + return idx; + } +} + +/* + * Get the type descriptor for the next parameter, if any. This returns + * NULL if the last parameter has already been consumed. + */ +const char* dexParameterIteratorNextDescriptor( + DexParameterIterator* pIterator) { + u4 idx = dexParameterIteratorNextIndex(pIterator); + + if (idx == kDexNoIndex) { + return NULL; + } + + return dexStringByTypeIdx(pIterator->proto->dexFile, idx); +} diff --git a/libdex/DexProto.h b/libdex/DexProto.h new file mode 100644 index 000000000..dccae6c04 --- /dev/null +++ b/libdex/DexProto.h @@ -0,0 +1,229 @@ +/* + * 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. + */ + +/* + * Functions for dealing with method prototypes + */ + +#ifndef LIBDEX_DEXPROTO_H_ +#define LIBDEX_DEXPROTO_H_ + +#include "DexFile.h" + +/* + * Single-thread single-string cache. This structure holds a pointer to + * a string which is semi-automatically manipulated by some of the + * method prototype functions. Functions which use in this struct + * generally return a string that is valid until the next + * time the same DexStringCache is used. + */ +struct DexStringCache { + char* value; /* the latest value */ + size_t allocatedSize; /* size of the allocated buffer, if allocated */ + char buffer[120]; /* buffer used to hold small-enough results */ +}; + +/* + * Make sure that the given cache can hold a string of the given length, + * including the final '\0' byte. + */ +void dexStringCacheAlloc(DexStringCache* pCache, size_t length); + +/* + * Initialize the given DexStringCache. Use this function before passing + * one into any other function. + */ +void dexStringCacheInit(DexStringCache* pCache); + +/* + * Release the allocated contents of the given DexStringCache, if any. + * Use this function after your last use of a DexStringCache. + */ +void dexStringCacheRelease(DexStringCache* pCache); + +/* + * If the given DexStringCache doesn't already point at the given value, + * make a copy of it into the cache. This always returns a writable + * pointer to the contents (whether or not a copy had to be made). This + * function is intended to be used after making a call that at least + * sometimes doesn't populate a DexStringCache. + */ +char* dexStringCacheEnsureCopy(DexStringCache* pCache, const char* value); + +/* + * Abandon the given DexStringCache, and return a writable copy of the + * given value (reusing the string cache's allocation if possible). + * The return value must be free()d by the caller. Use this instead of + * dexStringCacheRelease() if you want the buffer to survive past the + * scope of the DexStringCache. + */ +char* dexStringCacheAbandon(DexStringCache* pCache, const char* value); + +/* + * Method prototype structure, which refers to a protoIdx in a + * particular DexFile. + */ +struct DexProto { + const DexFile* dexFile; /* file the idx refers to */ + u4 protoIdx; /* index into proto_ids table of dexFile */ +}; + +/* + * Set the given DexProto to refer to the prototype of the given MethodId. + */ +DEX_INLINE void dexProtoSetFromMethodId(DexProto* pProto, + const DexFile* pDexFile, const DexMethodId* pMethodId) +{ + pProto->dexFile = pDexFile; + pProto->protoIdx = pMethodId->protoIdx; +} + +/* + * Get the short-form method descriptor for the given prototype. The + * prototype must be protoIdx-based. + */ +const char* dexProtoGetShorty(const DexProto* pProto); + +/* + * Get the full method descriptor for the given prototype. + */ +const char* dexProtoGetMethodDescriptor(const DexProto* pProto, + DexStringCache* pCache); + +/* + * Get a copy of the descriptor string associated with the given prototype. + * The returned pointer must be free()ed by the caller. + */ +char* dexProtoCopyMethodDescriptor(const DexProto* pProto); + +/* + * Get the parameter descriptors for the given prototype. This is the + * concatenation of all the descriptors for all the parameters, in + * order, with no other adornment. + */ +const char* dexProtoGetParameterDescriptors(const DexProto* pProto, + DexStringCache* pCache); + +/* + * Return the utf-8 encoded descriptor string from the proto of a MethodId. + */ +DEX_INLINE const char* dexGetDescriptorFromMethodId(const DexFile* pDexFile, + const DexMethodId* pMethodId, DexStringCache* pCache) +{ + DexProto proto; + + dexProtoSetFromMethodId(&proto, pDexFile, pMethodId); + return dexProtoGetMethodDescriptor(&proto, pCache); +} + +/* + * Get a copy of the utf-8 encoded method descriptor string from the + * proto of a MethodId. The returned pointer must be free()ed by the + * caller. + */ +DEX_INLINE char* dexCopyDescriptorFromMethodId(const DexFile* pDexFile, + const DexMethodId* pMethodId) +{ + DexProto proto; + + dexProtoSetFromMethodId(&proto, pDexFile, pMethodId); + return dexProtoCopyMethodDescriptor(&proto); +} + +/* + * Get the type descriptor for the return type of the given prototype. + */ +const char* dexProtoGetReturnType(const DexProto* pProto); + +/* + * Get the parameter count of the given prototype. + */ +size_t dexProtoGetParameterCount(const DexProto* pProto); + +/* + * Compute the number of parameter words (u4 units) required by the + * given prototype. For example, if the method takes (int, long) and + * returns double, this would return 3 (one for the int, two for the + * long, and the return type isn't relevant). + */ +int dexProtoComputeArgsSize(const DexProto* pProto); + +/* + * Compare the two prototypes. The two prototypes are compared + * with the return type as the major order, then the first arguments, + * then second, etc. If two prototypes are identical except that one + * has extra arguments, then the shorter argument is considered the + * earlier one in sort order (similar to strcmp()). + */ +int dexProtoCompare(const DexProto* pProto1, const DexProto* pProto2); + +/* + * Compare the two prototypes, ignoring return type. The two + * prototypes are compared with the first argument as the major order, + * then second, etc. If two prototypes are identical except that one + * has extra arguments, then the shorter argument is considered the + * earlier one in sort order (similar to strcmp()). + */ +int dexProtoCompareParameters(const DexProto* pProto1, + const DexProto* pProto2); + +/* + * Compare a prototype and a string method descriptor. The comparison + * is done as if the descriptor were converted to a prototype and compared + * with dexProtoCompare(). + */ +int dexProtoCompareToDescriptor(const DexProto* proto, const char* descriptor); + +/* + * Compare a prototype and a concatenation of type descriptors. The + * comparison is done as if the descriptors were converted to a + * prototype and compared with dexProtoCompareParameters(). + */ +int dexProtoCompareToParameterDescriptors(const DexProto* proto, + const char* descriptors); + +/* + * Single-thread prototype parameter iterator. This structure holds a + * pointer to a prototype and its parts, along with a cursor. + */ +struct DexParameterIterator { + const DexProto* proto; + const DexTypeList* parameters; + int parameterCount; + int cursor; +}; + +/* + * Initialize the given DexParameterIterator to be at the start of the + * parameters of the given prototype. + */ +void dexParameterIteratorInit(DexParameterIterator* pIterator, + const DexProto* pProto); + +/* + * Get the type_id index for the next parameter, if any. This returns + * kDexNoIndex if the last parameter has already been consumed. + */ +u4 dexParameterIteratorNextIndex(DexParameterIterator* pIterator); + +/* + * Get the type descriptor for the next parameter, if any. This returns + * NULL if the last parameter has already been consumed. + */ +const char* dexParameterIteratorNextDescriptor( + DexParameterIterator* pIterator); + +#endif // LIBDEX_DEXPROTO_H_ diff --git a/libdex/DexSwapVerify.cpp b/libdex/DexSwapVerify.cpp new file mode 100644 index 000000000..8ffc74371 --- /dev/null +++ b/libdex/DexSwapVerify.cpp @@ -0,0 +1,3070 @@ +/* + * 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. + */ + +/* + * Byte-swapping and verification of dex files. + */ + +#include "DexFile.h" +#include "DexClass.h" +#include "DexDataMap.h" +#include "DexProto.h" +#include "DexUtf.h" +#include "Leb128.h" + +#include <zlib.h> + +#include <stdlib.h> +#include <string.h> + +#define SWAP2(_value) (_value) +#define SWAP4(_value) (_value) +#define SWAP8(_value) (_value) + +#define SWAP_FIELD2(_field) (_field) = SWAP2(_field) +#define SWAP_FIELD4(_field) (_field) = SWAP4(_field) +#define SWAP_FIELD8(_field) (_field) = SWAP8(_field) + +/* + * Some information we pass around to help verify values. + */ +struct CheckState { + const DexHeader* pHeader; + const u1* fileStart; + const u1* fileEnd; // points to fileStart + fileLen + u4 fileLen; + DexDataMap* pDataMap; // set after map verification + const DexFile* pDexFile; // set after intraitem verification + const DexMapItem* pCallSiteIds; // set after intraitem verification + const DexMapItem* pMethodHandleItems; // set after intraitem verification + + /* + * bitmap of type_id indices that have been used to define classes; + * initialized immediately before class_def cross-verification, and + * freed immediately after it + */ + u4* pDefinedClassBits; + + const void* previousItem; // set during section iteration +}; + +/* + * Return the file offset of the given pointer. + */ +static inline u4 fileOffset(const CheckState* state, const void* ptr) { + return ((const u1*) ptr) - state->fileStart; +} + +/* + * Return a pointer for the given file offset. + */ +static inline void* filePointer(const CheckState* state, u4 offset) { + return (void*) (state->fileStart + offset); +} + +/* + * Verify that a pointer range, start inclusive to end exclusive, only + * covers bytes in the file and doesn't point beyond the end of the + * file. That is, the start must indicate a valid byte or may point at + * the byte just past the end of the file (but no further), and the + * end must be no less than the start and must also not point beyond + * the byte just past the end of the file. + */ +static inline bool checkPtrRange(const CheckState* state, + const void* start, const void* end, const char* label) { + const void* fileStart = state->fileStart; + const void* fileEnd = state->fileEnd; + if ((start < fileStart) || (start > fileEnd) + || (end < start) || (end > fileEnd)) { + ALOGW("Bad offset range for %s: %#x..%#x", label, + fileOffset(state, start), fileOffset(state, end)); + return false; + } + return true; +} + +/* + * Verify that a range of offsets, start inclusive to end exclusive, + * are all valid. That is, the start must indicate a valid byte or may + * point at the byte just past the end of the file (but no further), + * and the end must be no less than the start and must also not point + * beyond the byte just past the end of the file. + * + * Assumes "const CheckState* state". + */ +#define CHECK_OFFSET_RANGE(_start, _end) { \ + const u1* _startPtr = (const u1*) filePointer(state, (_start)); \ + const u1* _endPtr = (const u1*) filePointer(state, (_end)); \ + if (!checkPtrRange(state, _startPtr, _endPtr, \ + #_start ".." #_end)) { \ + return 0; \ + } \ + } + +/* + * Verify that a pointer range, start inclusive to end exclusive, only + * covers bytes in the file and doesn't point beyond the end of the + * file. That is, the start must indicate a valid byte or may point at + * the byte just past the end of the file (but no further), and the + * end must be no less than the start and must also not point beyond + * the byte just past the end of the file. + * + * Assumes "const CheckState* state". + */ +#define CHECK_PTR_RANGE(_start, _end) { \ + if (!checkPtrRange(state, (_start), (_end), #_start ".." #_end)) { \ + return 0; \ + } \ + } + +/* + * Make sure a list of items fits entirely within the file. + * + * Assumes "const CheckState* state" and "typeof(_count) == typeof(_elemSize)" + * If the type sizes or signs are mismatched, this will return 0. + */ +#define CHECK_LIST_SIZE(_ptr, _count, _elemSize) { \ + const u1* _start = (const u1*) (_ptr); \ + const u1* _end = _start + ((_count) * (_elemSize)); \ + u4 _dummy; \ + if (__builtin_mul_overflow((_count), (_elemSize), &_dummy) || \ + !checkPtrRange(state, _start, _end, #_ptr)) { \ + return 0; \ + } \ + } + +/* + * Swap a field that is known to hold an absolute DEX file offset. Note: + * This does not check to see that the swapped offset points within the + * mapped file, since that should be handled (with even more rigor) by + * the cross-verification phase. + * + * Assumes "const CheckState* state". + */ +#define SWAP_OFFSET4(_field) { \ + SWAP_FIELD4((_field)); \ + } + +/* + * Verify that an index falls in a valid range. + */ +#define CHECK_INDEX(_field, _limit) { \ + if ((_field) >= (_limit)) { \ + ALOGW("Bad index: %s(%u) > %s(%u)", \ + #_field, (u4)(_field), #_limit, (u4)(_limit)); \ + return 0; \ + } \ + } + +/* + * Swap an index, and verify that it falls in a valid range. + */ +#define SWAP_INDEX2(_field, _limit) { \ + SWAP_FIELD2((_field)); \ + CHECK_INDEX((_field), (_limit)); \ + } + +/* + * Verify that an index falls in a valid range or is kDexNoIndex. + */ +#define CHECK_INDEX_OR_NOINDEX(_field, _limit) { \ + if ((_field) != kDexNoIndex && (_field) >= (_limit)) { \ + ALOGW("Bad index: %s(%u) > %s(%u)", \ + #_field, (u4)(_field), #_limit, (u4)(_limit)); \ + return 0; \ + } \ + } + +/* + * Swap an index, and verify that it falls in a valid range. + */ +#define SWAP_INDEX4(_field, _limit) { \ + SWAP_FIELD4((_field)); \ + CHECK_INDEX((_field), (_limit)); \ + } + +/* + * Swap an index, and verify that it falls in a valid range or is + * kDexNoIndex. + */ +#define SWAP_INDEX4_OR_NOINDEX(_field, _limit) { \ + SWAP_FIELD4((_field)); \ + CHECK_INDEX_OR_NOINDEX((_field), (_limit)); \ + } + +/* Verify the definer of a given field_idx. */ +static bool verifyFieldDefiner(const CheckState* state, u4 definingClass, + u4 fieldIdx) { + const DexFieldId* field = dexGetFieldId(state->pDexFile, fieldIdx); + return field->classIdx == definingClass; +} + +/* Verify the definer of a given method_idx. */ +static bool verifyMethodDefiner(const CheckState* state, u4 definingClass, + u4 methodIdx) { + const DexMethodId* meth = dexGetMethodId(state->pDexFile, methodIdx); + return meth->classIdx == definingClass; +} + +/* + * Calculate the required size (in elements) of the array pointed at by + * pDefinedClassBits. + */ +static size_t calcDefinedClassBitsSize(const CheckState* state) +{ + // Divide typeIdsSize by 32 (0x20), rounding up. + return (state->pHeader->typeIdsSize + 0x1f) >> 5; +} + +/* + * Set the given bit in pDefinedClassBits, returning its former value. + */ +static bool setDefinedClassBit(const CheckState* state, u4 typeIdx) { + u4 arrayIdx = typeIdx >> 5; + u4 bit = 1 << (typeIdx & 0x1f); + u4* element = &state->pDefinedClassBits[arrayIdx]; + bool result = (*element & bit) != 0; + + *element |= bit; + + return result; +} + +/* + * Swap the header_item. + */ +static bool swapDexHeader(const CheckState* state, DexHeader* pHeader) +{ + CHECK_PTR_RANGE(pHeader, pHeader + 1); + + // magic is ok + SWAP_FIELD4(pHeader->checksum); + // signature is ok + SWAP_FIELD4(pHeader->fileSize); + SWAP_FIELD4(pHeader->headerSize); + SWAP_FIELD4(pHeader->endianTag); + SWAP_FIELD4(pHeader->linkSize); + SWAP_OFFSET4(pHeader->linkOff); + SWAP_OFFSET4(pHeader->mapOff); + SWAP_FIELD4(pHeader->stringIdsSize); + SWAP_OFFSET4(pHeader->stringIdsOff); + SWAP_FIELD4(pHeader->typeIdsSize); + SWAP_OFFSET4(pHeader->typeIdsOff); + SWAP_FIELD4(pHeader->fieldIdsSize); + SWAP_OFFSET4(pHeader->fieldIdsOff); + SWAP_FIELD4(pHeader->methodIdsSize); + SWAP_OFFSET4(pHeader->methodIdsOff); + SWAP_FIELD4(pHeader->protoIdsSize); + SWAP_OFFSET4(pHeader->protoIdsOff); + SWAP_FIELD4(pHeader->classDefsSize); + SWAP_OFFSET4(pHeader->classDefsOff); + SWAP_FIELD4(pHeader->dataSize); + SWAP_OFFSET4(pHeader->dataOff); + + if (pHeader->endianTag != kDexEndianConstant) { + ALOGE("Unexpected endian_tag: %#x", pHeader->endianTag); + return false; + } + + // Assign variables so the diagnostic is prettier. (Hooray for macros.) + u4 linkOff = pHeader->linkOff; + u4 linkEnd = linkOff + pHeader->linkSize; + u4 dataOff = pHeader->dataOff; + u4 dataEnd = dataOff + pHeader->dataSize; + CHECK_OFFSET_RANGE(linkOff, linkEnd); + CHECK_OFFSET_RANGE(dataOff, dataEnd); + + /* + * Note: The offsets and ranges of the other header items end up getting + * checked during the first iteration over the map. + */ + + return true; +} + +/* Check the header section for sanity. */ +static bool checkHeaderSection(const CheckState* state, u4 sectionOffset, + u4 sectionCount, u4* endOffset) { + if (sectionCount != 1) { + ALOGE("Multiple header items"); + return false; + } + + if (sectionOffset != 0) { + ALOGE("Header at %#x; not at start of file", sectionOffset); + return false; + } + + const DexHeader* pHeader = (const DexHeader*) filePointer(state, 0); + *endOffset = pHeader->headerSize; + return true; +} + +/* + * Helper for swapMap(), which turns a map type constant into a small + * one-bit-on integer, suitable for use in an int-sized bit set. + */ +static u4 mapTypeToBitMask(int mapType) { + switch (mapType) { + case kDexTypeHeaderItem: return 1 << 0; + case kDexTypeStringIdItem: return 1 << 1; + case kDexTypeTypeIdItem: return 1 << 2; + case kDexTypeProtoIdItem: return 1 << 3; + case kDexTypeFieldIdItem: return 1 << 4; + case kDexTypeMethodIdItem: return 1 << 5; + case kDexTypeClassDefItem: return 1 << 6; + case kDexTypeMapList: return 1 << 7; + case kDexTypeTypeList: return 1 << 8; + case kDexTypeAnnotationSetRefList: return 1 << 9; + case kDexTypeAnnotationSetItem: return 1 << 10; + case kDexTypeClassDataItem: return 1 << 11; + case kDexTypeCodeItem: return 1 << 12; + case kDexTypeStringDataItem: return 1 << 13; + case kDexTypeDebugInfoItem: return 1 << 14; + case kDexTypeAnnotationItem: return 1 << 15; + case kDexTypeEncodedArrayItem: return 1 << 16; + case kDexTypeAnnotationsDirectoryItem: return 1 << 17; + case kDexTypeCallSiteIdItem: return 1 << 18; + case kDexTypeMethodHandleItem: return 1 << 19; + default: { + ALOGE("Unknown map item type %04x", mapType); + return 0; + } + } +} + +/* + * Helper for swapMap(), which indicates if an item type should appear + * in the data section. + */ +static bool isDataSectionType(int mapType) { + switch (mapType) { + case kDexTypeHeaderItem: + case kDexTypeStringIdItem: + case kDexTypeTypeIdItem: + case kDexTypeProtoIdItem: + case kDexTypeFieldIdItem: + case kDexTypeMethodIdItem: + case kDexTypeClassDefItem: { + return false; + } + } + + return true; +} + +/* + * Swap the map_list and verify what we can about it. Also, if verification + * passes, allocate the state's DexDataMap. + */ +static bool swapMap(CheckState* state, DexMapList* pMap) +{ + DexMapItem* item = pMap->list; + u4 count; + u4 dataItemCount = 0; // Total count of items in the data section. + u4 dataItemsLeft = state->pHeader->dataSize; // See use below. + u4 usedBits = 0; // Bit set: one bit per section + bool first = true; + u4 lastOffset = 0; + + SWAP_FIELD4(pMap->size); + count = pMap->size; + const u4 sizeOfItem = (u4) sizeof(DexMapItem); + CHECK_LIST_SIZE(item, count, sizeOfItem); + + while (count--) { + SWAP_FIELD2(item->type); + SWAP_FIELD2(item->unused); + SWAP_FIELD4(item->size); + SWAP_OFFSET4(item->offset); + + if (first) { + first = false; + } else if (lastOffset >= item->offset) { + ALOGE("Out-of-order map item: %#x then %#x", + lastOffset, item->offset); + return false; + } + + if (item->offset >= state->pHeader->fileSize) { + ALOGE("Map item after end of file: %x, size %#x", + item->offset, state->pHeader->fileSize); + return false; + } + + if (isDataSectionType(item->type)) { + u4 icount = item->size; + + /* + * This sanity check on the data section items ensures that + * there are no more items than the number of bytes in + * the data section. + */ + if (icount > dataItemsLeft) { + ALOGE("Unrealistically many items in the data section: " + "at least %d", dataItemCount + icount); + return false; + } + + dataItemsLeft -= icount; + dataItemCount += icount; + } + + u4 bit = mapTypeToBitMask(item->type); + + if (bit == 0) { + return false; + } + + if ((usedBits & bit) != 0) { + ALOGE("Duplicate map section of type %#x", item->type); + return false; + } + + if (item->type == kDexTypeCallSiteIdItem) { + state->pCallSiteIds = item; + } else if (item->type == kDexTypeMethodHandleItem) { + state->pMethodHandleItems = item; + } + + usedBits |= bit; + lastOffset = item->offset; + item++; + } + + if ((usedBits & mapTypeToBitMask(kDexTypeHeaderItem)) == 0) { + ALOGE("Map is missing header entry"); + return false; + } + + if ((usedBits & mapTypeToBitMask(kDexTypeMapList)) == 0) { + ALOGE("Map is missing map_list entry"); + return false; + } + + if (((usedBits & mapTypeToBitMask(kDexTypeStringIdItem)) == 0) + && ((state->pHeader->stringIdsOff != 0) + || (state->pHeader->stringIdsSize != 0))) { + ALOGE("Map is missing string_ids entry"); + return false; + } + + if (((usedBits & mapTypeToBitMask(kDexTypeTypeIdItem)) == 0) + && ((state->pHeader->typeIdsOff != 0) + || (state->pHeader->typeIdsSize != 0))) { + ALOGE("Map is missing type_ids entry"); + return false; + } + + if (((usedBits & mapTypeToBitMask(kDexTypeProtoIdItem)) == 0) + && ((state->pHeader->protoIdsOff != 0) + || (state->pHeader->protoIdsSize != 0))) { + ALOGE("Map is missing proto_ids entry"); + return false; + } + + if (((usedBits & mapTypeToBitMask(kDexTypeFieldIdItem)) == 0) + && ((state->pHeader->fieldIdsOff != 0) + || (state->pHeader->fieldIdsSize != 0))) { + ALOGE("Map is missing field_ids entry"); + return false; + } + + if (((usedBits & mapTypeToBitMask(kDexTypeMethodIdItem)) == 0) + && ((state->pHeader->methodIdsOff != 0) + || (state->pHeader->methodIdsSize != 0))) { + ALOGE("Map is missing method_ids entry"); + return false; + } + + if (((usedBits & mapTypeToBitMask(kDexTypeClassDefItem)) == 0) + && ((state->pHeader->classDefsOff != 0) + || (state->pHeader->classDefsSize != 0))) { + ALOGE("Map is missing class_defs entry"); + return false; + } + + state->pDataMap = dexDataMapAlloc(dataItemCount); + if (state->pDataMap == NULL) { + ALOGE("Unable to allocate data map (size %#x)", dataItemCount); + return false; + } + + return true; +} + +/* Check the map section for sanity. */ +static bool checkMapSection(const CheckState* state, u4 sectionOffset, + u4 sectionCount, u4* endOffset) { + if (sectionCount != 1) { + ALOGE("Multiple map list items"); + return false; + } + + if (sectionOffset != state->pHeader->mapOff) { + ALOGE("Map not at header-defined offset: %#x, expected %#x", + sectionOffset, state->pHeader->mapOff); + return false; + } + + const DexMapList* pMap = (const DexMapList*) filePointer(state, sectionOffset); + + *endOffset = + sectionOffset + sizeof(u4) + (pMap->size * sizeof(DexMapItem)); + return true; +} + +/* Perform byte-swapping and intra-item verification on string_id_item. */ +static void* swapStringIdItem(const CheckState* state, void* ptr) { + DexStringId* item = (DexStringId*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_OFFSET4(item->stringDataOff); + + return item + 1; +} + +/* Perform cross-item verification of string_id_item. */ +static void* crossVerifyStringIdItem(const CheckState* state, void* ptr) { + const DexStringId* item = (const DexStringId*) ptr; + + if (!dexDataMapVerify(state->pDataMap, + item->stringDataOff, kDexTypeStringDataItem)) { + return NULL; + } + + const DexStringId* item0 = (const DexStringId*) state->previousItem; + if (item0 != NULL) { + // Check ordering. + const char* s0 = dexGetStringData(state->pDexFile, item0); + const char* s1 = dexGetStringData(state->pDexFile, item); + if (dexUtf8Cmp(s0, s1) >= 0) { + ALOGE("Out-of-order string_ids: '%s' then '%s'", s0, s1); + return NULL; + } + } + + return (void*) (item + 1); +} + +/* Perform byte-swapping and intra-item verification on type_id_item. */ +static void* swapTypeIdItem(const CheckState* state, void* ptr) { + DexTypeId* item = (DexTypeId*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_INDEX4(item->descriptorIdx, state->pHeader->stringIdsSize); + + return item + 1; +} + +/* Perform cross-item verification of type_id_item. */ +static void* crossVerifyTypeIdItem(const CheckState* state, void* ptr) { + const DexTypeId* item = (const DexTypeId*) ptr; + const char* descriptor = + dexStringById(state->pDexFile, item->descriptorIdx); + + if (!dexIsValidTypeDescriptor(descriptor)) { + ALOGE("Invalid type descriptor: '%s'", descriptor); + return NULL; + } + + const DexTypeId* item0 = (const DexTypeId*) state->previousItem; + if (item0 != NULL) { + // Check ordering. This relies on string_ids being in order. + if (item0->descriptorIdx >= item->descriptorIdx) { + ALOGE("Out-of-order type_ids: %#x then %#x", + item0->descriptorIdx, item->descriptorIdx); + return NULL; + } + } + + return (void*) (item + 1); +} + +/* Perform byte-swapping and intra-item verification on proto_id_item. */ +static void* swapProtoIdItem(const CheckState* state, void* ptr) { + DexProtoId* item = (DexProtoId*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_INDEX4(item->shortyIdx, state->pHeader->stringIdsSize); + SWAP_INDEX4(item->returnTypeIdx, state->pHeader->typeIdsSize); + SWAP_OFFSET4(item->parametersOff); + + return item + 1; +} + +/* Helper for crossVerifyProtoIdItem(), which checks a shorty character + * to see if it is compatible with a type descriptor. Returns true if + * so, false if not. */ +static bool shortyDescMatch(char shorty, const char* descriptor, bool + isReturnType) { + switch (shorty) { + case 'V': { + if (!isReturnType) { + ALOGE("Invalid use of void"); + return false; + } + FALLTHROUGH_INTENDED; + } + case 'B': + case 'C': + case 'D': + case 'F': + case 'I': + case 'J': + case 'S': + case 'Z': { + if ((descriptor[0] != shorty) || (descriptor[1] != '\0')) { + ALOGE("Shorty vs. primitive type mismatch: '%c', '%s'", + shorty, descriptor); + return false; + } + break; + } + case 'L': { + if ((descriptor[0] != 'L') && (descriptor[0] != '[')) { + ALOGE("Shorty vs. type mismatch: '%c', '%s'", + shorty, descriptor); + return false; + } + break; + } + default: { + ALOGE("Bogus shorty: '%c'", shorty); + return false; + } + } + + return true; +} + +/* Perform cross-item verification of proto_id_item. */ +static void* crossVerifyProtoIdItem(const CheckState* state, void* ptr) { + const DexProtoId* item = (const DexProtoId*) ptr; + const char* shorty = + dexStringById(state->pDexFile, item->shortyIdx); + + if (!dexDataMapVerify0Ok(state->pDataMap, + item->parametersOff, kDexTypeTypeList)) { + return NULL; + } + + if (!shortyDescMatch(*shorty, + dexStringByTypeIdx(state->pDexFile, item->returnTypeIdx), + true)) { + return NULL; + } + + u4 protoIdx = item - state->pDexFile->pProtoIds; + DexProto proto = { state->pDexFile, protoIdx }; + DexParameterIterator iterator; + + dexParameterIteratorInit(&iterator, &proto); + shorty++; // Skip the return type. + + for (;;) { + const char *desc = dexParameterIteratorNextDescriptor(&iterator); + + if (desc == NULL) { + break; + } + + if (*shorty == '\0') { + ALOGE("Shorty is too short"); + return NULL; + } + + if (!shortyDescMatch(*shorty, desc, false)) { + return NULL; + } + + shorty++; + } + + if (*shorty != '\0') { + ALOGE("Shorty is too long"); + return NULL; + } + + const DexProtoId* item0 = (const DexProtoId*) state->previousItem; + if (item0 != NULL) { + // Check ordering. This relies on type_ids being in order. + if (item0->returnTypeIdx > item->returnTypeIdx) { + ALOGE("Out-of-order proto_id return types"); + return NULL; + } else if (item0->returnTypeIdx == item->returnTypeIdx) { + bool badOrder = false; + DexProto proto0 = { state->pDexFile, protoIdx - 1 }; + DexParameterIterator iterator0; + + dexParameterIteratorInit(&iterator, &proto); + dexParameterIteratorInit(&iterator0, &proto0); + + for (;;) { + u4 idx0 = dexParameterIteratorNextIndex(&iterator0); + u4 idx1 = dexParameterIteratorNextIndex(&iterator); + + if (idx1 == kDexNoIndex) { + badOrder = true; + break; + } + + if (idx0 == kDexNoIndex) { + break; + } + + if (idx0 < idx1) { + break; + } else if (idx0 > idx1) { + badOrder = true; + break; + } + } + + if (badOrder) { + ALOGE("Out-of-order proto_id arguments"); + return NULL; + } + } + } + + return (void*) (item + 1); +} + +/* Perform byte-swapping and intra-item verification on field_id_item. */ +static void* swapFieldIdItem(const CheckState* state, void* ptr) { + DexFieldId* item = (DexFieldId*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_INDEX2(item->classIdx, state->pHeader->typeIdsSize); + SWAP_INDEX2(item->typeIdx, state->pHeader->typeIdsSize); + SWAP_INDEX4(item->nameIdx, state->pHeader->stringIdsSize); + + return item + 1; +} + +/* Perform cross-item verification of field_id_item. */ +static void* crossVerifyFieldIdItem(const CheckState* state, void* ptr) { + const DexFieldId* item = (const DexFieldId*) ptr; + const char* s; + + s = dexStringByTypeIdx(state->pDexFile, item->classIdx); + if (!dexIsClassDescriptor(s)) { + ALOGE("Invalid descriptor for class_idx: '%s'", s); + return NULL; + } + + s = dexStringByTypeIdx(state->pDexFile, item->typeIdx); + if (!dexIsFieldDescriptor(s)) { + ALOGE("Invalid descriptor for type_idx: '%s'", s); + return NULL; + } + + s = dexStringById(state->pDexFile, item->nameIdx); + if (!dexIsValidMemberName(s)) { + ALOGE("Invalid name: '%s'", s); + return NULL; + } + + const DexFieldId* item0 = (const DexFieldId*) state->previousItem; + if (item0 != NULL) { + // Check ordering. This relies on the other sections being in order. + bool done = false; + bool bogus = false; + + if (item0->classIdx > item->classIdx) { + bogus = true; + done = true; + } else if (item0->classIdx < item->classIdx) { + done = true; + } + + if (!done) { + if (item0->nameIdx > item->nameIdx) { + bogus = true; + done = true; + } else if (item0->nameIdx < item->nameIdx) { + done = true; + } + } + + if (!done) { + if (item0->typeIdx >= item->typeIdx) { + bogus = true; + } + } + + if (bogus) { + ALOGE("Out-of-order field_ids"); + return NULL; + } + } + + return (void*) (item + 1); +} + +/* Perform byte-swapping and intra-item verification on method_id_item. */ +static void* swapMethodIdItem(const CheckState* state, void* ptr) { + DexMethodId* item = (DexMethodId*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_INDEX2(item->classIdx, state->pHeader->typeIdsSize); + SWAP_INDEX2(item->protoIdx, state->pHeader->protoIdsSize); + SWAP_INDEX4(item->nameIdx, state->pHeader->stringIdsSize); + + return item + 1; +} + +/* Perform cross-item verification of method_id_item. */ +static void* crossVerifyMethodIdItem(const CheckState* state, void* ptr) { + const DexMethodId* item = (const DexMethodId*) ptr; + const char* s; + + s = dexStringByTypeIdx(state->pDexFile, item->classIdx); + if (!dexIsReferenceDescriptor(s)) { + ALOGE("Invalid descriptor for class_idx: '%s'", s); + return NULL; + } + + s = dexStringById(state->pDexFile, item->nameIdx); + if (!dexIsValidMemberName(s)) { + ALOGE("Invalid name: '%s'", s); + return NULL; + } + + const DexMethodId* item0 = (const DexMethodId*) state->previousItem; + if (item0 != NULL) { + // Check ordering. This relies on the other sections being in order. + bool done = false; + bool bogus = false; + + if (item0->classIdx > item->classIdx) { + bogus = true; + done = true; + } else if (item0->classIdx < item->classIdx) { + done = true; + } + + if (!done) { + if (item0->nameIdx > item->nameIdx) { + bogus = true; + done = true; + } else if (item0->nameIdx < item->nameIdx) { + done = true; + } + } + + if (!done) { + if (item0->protoIdx >= item->protoIdx) { + bogus = true; + } + } + + if (bogus) { + ALOGE("Out-of-order method_ids"); + return NULL; + } + } + + return (void*) (item + 1); +} + +/* Perform byte-swapping and intra-item verification on class_def_item. */ +static void* swapClassDefItem(const CheckState* state, void* ptr) { + DexClassDef* item = (DexClassDef*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_INDEX4(item->classIdx, state->pHeader->typeIdsSize); + SWAP_FIELD4(item->accessFlags); + SWAP_INDEX4_OR_NOINDEX(item->superclassIdx, state->pHeader->typeIdsSize); + SWAP_OFFSET4(item->interfacesOff); + SWAP_INDEX4_OR_NOINDEX(item->sourceFileIdx, state->pHeader->stringIdsSize); + SWAP_OFFSET4(item->annotationsOff); + SWAP_OFFSET4(item->classDataOff); + + if ((item->accessFlags & ~ACC_CLASS_MASK) != 0) { + // The VM specification says that unknown flags should be ignored. + ALOGV("Bogus class access flags %x", item->accessFlags); + item->accessFlags &= ACC_CLASS_MASK; + } + + return item + 1; +} + +/* defined below */ +static u4 findFirstClassDataDefiner(const CheckState* state, + DexClassData* classData); +static u4 findFirstAnnotationsDirectoryDefiner(const CheckState* state, + const DexAnnotationsDirectoryItem* dir); + +/* Helper for crossVerifyClassDefItem(), which checks a class_data_item to + * make sure all its references are to a given class. */ +static bool verifyClassDataIsForDef(const CheckState* state, u4 offset, + u4 definerIdx) { + if (offset == 0) { + return true; + } + + const u1* data = (const u1*) filePointer(state, offset); + DexClassData* classData = dexReadAndVerifyClassData(&data, NULL); + + if (classData == NULL) { + // Shouldn't happen, but bail here just in case. + return false; + } + + /* + * The class_data_item verification ensures that + * it consistently refers to the same definer, so all we need to + * do is check the first one. + */ + u4 dataDefiner = findFirstClassDataDefiner(state, classData); + bool result = (dataDefiner == definerIdx) || (dataDefiner == kDexNoIndex); + + free(classData); + return result; +} + +/* Helper for crossVerifyClassDefItem(), which checks an + * annotations_directory_item to make sure all its references are to a + * given class. */ +static bool verifyAnnotationsDirectoryIsForDef(const CheckState* state, + u4 offset, u4 definerIdx) { + if (offset == 0) { + return true; + } + + const DexAnnotationsDirectoryItem* dir = + (const DexAnnotationsDirectoryItem*) filePointer(state, offset); + u4 annoDefiner = findFirstAnnotationsDirectoryDefiner(state, dir); + + return (annoDefiner == definerIdx) || (annoDefiner == kDexNoIndex); +} + +/* Perform cross-item verification of class_def_item. */ +static void* crossVerifyClassDefItem(const CheckState* state, void* ptr) { + const DexClassDef* item = (const DexClassDef*) ptr; + u4 classIdx = item->classIdx; + const char* descriptor = dexStringByTypeIdx(state->pDexFile, classIdx); + + if (!dexIsClassDescriptor(descriptor)) { + ALOGE("Invalid class: '%s'", descriptor); + return NULL; + } + + if (setDefinedClassBit(state, classIdx)) { + ALOGE("Duplicate class definition: '%s'", descriptor); + return NULL; + } + + bool okay = + dexDataMapVerify0Ok(state->pDataMap, + item->interfacesOff, kDexTypeTypeList) + && dexDataMapVerify0Ok(state->pDataMap, + item->annotationsOff, kDexTypeAnnotationsDirectoryItem) + && dexDataMapVerify0Ok(state->pDataMap, + item->classDataOff, kDexTypeClassDataItem) + && dexDataMapVerify0Ok(state->pDataMap, + item->staticValuesOff, kDexTypeEncodedArrayItem); + + if (!okay) { + return NULL; + } + + if (item->superclassIdx != kDexNoIndex) { + descriptor = dexStringByTypeIdx(state->pDexFile, item->superclassIdx); + if (!dexIsClassDescriptor(descriptor)) { + ALOGE("Invalid superclass: '%s'", descriptor); + return NULL; + } + } + + const DexTypeList* interfaces = + dexGetInterfacesList(state->pDexFile, item); + if (interfaces != NULL) { + u4 size = interfaces->size; + u4 i; + + /* + * Ensure that all interfaces refer to classes (not arrays or + * primitives). + */ + for (i = 0; i < size; i++) { + descriptor = dexStringByTypeIdx(state->pDexFile, + dexTypeListGetIdx(interfaces, i)); + if (!dexIsClassDescriptor(descriptor)) { + ALOGE("Invalid interface: '%s'", descriptor); + return NULL; + } + } + + /* + * Ensure that there are no duplicates. This is an O(N^2) test, + * but in practice the number of interfaces implemented by any + * given class is low. I will buy a milkshake for the + * first person to show me a realistic case for which this test + * would be unacceptably slow. + */ + for (i = 1; i < size; i++) { + u4 idx1 = dexTypeListGetIdx(interfaces, i); + u4 j; + for (j = 0; j < i; j++) { + u4 idx2 = dexTypeListGetIdx(interfaces, j); + if (idx1 == idx2) { + ALOGE("Duplicate interface: '%s'", + dexStringByTypeIdx(state->pDexFile, idx1)); + return NULL; + } + } + } + } + + if (!verifyClassDataIsForDef(state, item->classDataOff, item->classIdx)) { + ALOGE("Invalid class_data_item"); + return NULL; + } + + if (!verifyAnnotationsDirectoryIsForDef(state, item->annotationsOff, + item->classIdx)) { + ALOGE("Invalid annotations_directory_item"); + return NULL; + } + + return (void*) (item + 1); +} + +/* Perform cross-item verification of call_site_id. */ +static void* crossVerifyCallSiteId(const CheckState* state, void* ptr) { + const DexCallSiteId* item = (const DexCallSiteId*) ptr; + if (state->pCallSiteIds == nullptr) { + ALOGE("Verifying call site but expecting none"); + return NULL; + } + if (item->callSiteOff < state->pHeader->dataOff || + item->callSiteOff >= state->pHeader->dataOff + state->pHeader->dataSize) { + ALOGE("Bad call site offset: %u", item->callSiteOff); + return NULL; + } + return (void*) (item + 1); +} + +/* Perform cross-item verification of method_handle_item. */ +static void* crossVerifyMethodHandleItem(const CheckState* state, void* ptr) { + const DexMethodHandleItem* item = (const DexMethodHandleItem*) ptr; + if (state->pMethodHandleItems == nullptr) { + ALOGE("Verifying method handle but expecting none"); + return NULL; + } + if (item->methodHandleType > (u2) MethodHandleType::INVOKE_INTERFACE) { + ALOGE("Unknown method handle type: %u", item->methodHandleType); + return NULL; + } + switch ((MethodHandleType) item->methodHandleType) { + case MethodHandleType::STATIC_PUT: + case MethodHandleType::STATIC_GET: + case MethodHandleType::INSTANCE_PUT: + case MethodHandleType::INSTANCE_GET: + if (item->fieldOrMethodIdx >= state->pHeader->fieldIdsSize) { + ALOGE("Method handle has invalid field id: %u\n", item->fieldOrMethodIdx); + return NULL; + } + break; + case MethodHandleType::INVOKE_STATIC: + case MethodHandleType::INVOKE_INSTANCE: + case MethodHandleType::INVOKE_CONSTRUCTOR: + case MethodHandleType::INVOKE_DIRECT: + case MethodHandleType::INVOKE_INTERFACE: + if (item->fieldOrMethodIdx >= state->pHeader->methodIdsSize) { + ALOGE("Method handle has invalid method id: %u\n", item->fieldOrMethodIdx); + return NULL; + } + break; + } + return (void*) (item + 1); +} + +/* Helper for swapAnnotationsDirectoryItem(), which performs + * byte-swapping and intra-item verification on an + * annotation_directory_item's field elements. */ +static u1* swapFieldAnnotations(const CheckState* state, u4 count, u1* addr) { + DexFieldAnnotationsItem* item = (DexFieldAnnotationsItem*) addr; + bool first = true; + u4 lastIdx = 0; + + const u4 sizeOfItem = (u4) sizeof(DexFieldAnnotationsItem); + CHECK_LIST_SIZE(item, count, sizeOfItem); + + while (count--) { + SWAP_INDEX4(item->fieldIdx, state->pHeader->fieldIdsSize); + SWAP_OFFSET4(item->annotationsOff); + + if (first) { + first = false; + } else if (lastIdx >= item->fieldIdx) { + ALOGE("Out-of-order field_idx: %#x then %#x", lastIdx, + item->fieldIdx); + return NULL; + } + + lastIdx = item->fieldIdx; + item++; + } + + return (u1*) item; +} + +/* Helper for swapAnnotationsDirectoryItem(), which performs + * byte-swapping and intra-item verification on an + * annotation_directory_item's method elements. */ +static u1* swapMethodAnnotations(const CheckState* state, u4 count, u1* addr) { + DexMethodAnnotationsItem* item = (DexMethodAnnotationsItem*) addr; + bool first = true; + u4 lastIdx = 0; + + const u4 sizeOfItem = (u4) sizeof(DexMethodAnnotationsItem); + CHECK_LIST_SIZE(item, count, sizeOfItem); + + while (count--) { + SWAP_INDEX4(item->methodIdx, state->pHeader->methodIdsSize); + SWAP_OFFSET4(item->annotationsOff); + + if (first) { + first = false; + } else if (lastIdx >= item->methodIdx) { + ALOGE("Out-of-order method_idx: %#x then %#x", lastIdx, + item->methodIdx); + return NULL; + } + + lastIdx = item->methodIdx; + item++; + } + + return (u1*) item; +} + +/* Helper for swapAnnotationsDirectoryItem(), which performs + * byte-swapping and intra-item verification on an + * annotation_directory_item's parameter elements. */ +static u1* swapParameterAnnotations(const CheckState* state, u4 count, + u1* addr) { + DexParameterAnnotationsItem* item = (DexParameterAnnotationsItem*) addr; + bool first = true; + u4 lastIdx = 0; + + const u4 sizeOfItem = (u4) sizeof(DexParameterAnnotationsItem); + CHECK_LIST_SIZE(item, count, sizeOfItem); + + while (count--) { + SWAP_INDEX4(item->methodIdx, state->pHeader->methodIdsSize); + SWAP_OFFSET4(item->annotationsOff); + + if (first) { + first = false; + } else if (lastIdx >= item->methodIdx) { + ALOGE("Out-of-order method_idx: %#x then %#x", lastIdx, + item->methodIdx); + return NULL; + } + + lastIdx = item->methodIdx; + item++; + } + + return (u1*) item; +} + +/* Perform byte-swapping and intra-item verification on + * annotations_directory_item. */ +static void* swapAnnotationsDirectoryItem(const CheckState* state, void* ptr) { + DexAnnotationsDirectoryItem* item = (DexAnnotationsDirectoryItem*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_OFFSET4(item->classAnnotationsOff); + SWAP_FIELD4(item->fieldsSize); + SWAP_FIELD4(item->methodsSize); + SWAP_FIELD4(item->parametersSize); + + u1* addr = (u1*) (item + 1); + + if (item->fieldsSize != 0) { + addr = swapFieldAnnotations(state, item->fieldsSize, addr); + if (addr == NULL) { + return NULL; + } + } + + if (item->methodsSize != 0) { + addr = swapMethodAnnotations(state, item->methodsSize, addr); + if (addr == NULL) { + return NULL; + } + } + + if (item->parametersSize != 0) { + addr = swapParameterAnnotations(state, item->parametersSize, addr); + if (addr == NULL) { + return NULL; + } + } + + return addr; +} + +static void* swapCallSiteId(const CheckState* state, void* ptr) { + DexCallSiteId* item = (DexCallSiteId*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_OFFSET4(item->callSiteOff); + + return (item + 1); +} + +static void* swapMethodHandleItem(const CheckState* state, void* ptr) { + DexMethodHandleItem* item = (DexMethodHandleItem*) ptr; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_FIELD2(item->methodHandleType); + SWAP_FIELD2(item->fieldOrMethodIdx); + + return (item + 1); +} + + +/* Helper for crossVerifyAnnotationsDirectoryItem(), which checks the + * field elements. */ +static const u1* crossVerifyFieldAnnotations(const CheckState* state, u4 count, + const u1* addr, u4 definingClass) { + const DexFieldAnnotationsItem* item = (DexFieldAnnotationsItem*) addr; + + while (count--) { + if (!verifyFieldDefiner(state, definingClass, item->fieldIdx)) { + return NULL; + } + if (!dexDataMapVerify(state->pDataMap, item->annotationsOff, + kDexTypeAnnotationSetItem)) { + return NULL; + } + item++; + } + + return (const u1*) item; +} + +/* Helper for crossVerifyAnnotationsDirectoryItem(), which checks the + * method elements. */ +static const u1* crossVerifyMethodAnnotations(const CheckState* state, + u4 count, const u1* addr, u4 definingClass) { + const DexMethodAnnotationsItem* item = (DexMethodAnnotationsItem*) addr; + + while (count--) { + if (!verifyMethodDefiner(state, definingClass, item->methodIdx)) { + return NULL; + } + if (!dexDataMapVerify(state->pDataMap, item->annotationsOff, + kDexTypeAnnotationSetItem)) { + return NULL; + } + item++; + } + + return (const u1*) item; +} + +/* Helper for crossVerifyAnnotationsDirectoryItem(), which checks the + * parameter elements. */ +static const u1* crossVerifyParameterAnnotations(const CheckState* state, + u4 count, const u1* addr, u4 definingClass) { + const DexParameterAnnotationsItem* item = + (DexParameterAnnotationsItem*) addr; + + while (count--) { + if (!verifyMethodDefiner(state, definingClass, item->methodIdx)) { + return NULL; + } + if (!dexDataMapVerify(state->pDataMap, item->annotationsOff, + kDexTypeAnnotationSetRefList)) { + return NULL; + } + item++; + } + + return (const u1*) item; +} + +/* Helper for crossVerifyClassDefItem() and + * crossVerifyAnnotationsDirectoryItem(), which finds the type_idx of + * the definer of the first item in the data. */ +static u4 findFirstAnnotationsDirectoryDefiner(const CheckState* state, + const DexAnnotationsDirectoryItem* dir) { + if (dir->fieldsSize != 0) { + const DexFieldAnnotationsItem* fields = + dexGetFieldAnnotations(state->pDexFile, dir); + const DexFieldId* field = + dexGetFieldId(state->pDexFile, fields[0].fieldIdx); + return field->classIdx; + } + + if (dir->methodsSize != 0) { + const DexMethodAnnotationsItem* methods = + dexGetMethodAnnotations(state->pDexFile, dir); + const DexMethodId* method = + dexGetMethodId(state->pDexFile, methods[0].methodIdx); + return method->classIdx; + } + + if (dir->parametersSize != 0) { + const DexParameterAnnotationsItem* parameters = + dexGetParameterAnnotations(state->pDexFile, dir); + const DexMethodId* method = + dexGetMethodId(state->pDexFile, parameters[0].methodIdx); + return method->classIdx; + } + + return kDexNoIndex; +} + +/* Perform cross-item verification of annotations_directory_item. */ +static void* crossVerifyAnnotationsDirectoryItem(const CheckState* state, + void* ptr) { + const DexAnnotationsDirectoryItem* item = (const DexAnnotationsDirectoryItem*) ptr; + u4 definingClass = findFirstAnnotationsDirectoryDefiner(state, item); + + if (!dexDataMapVerify0Ok(state->pDataMap, + item->classAnnotationsOff, kDexTypeAnnotationSetItem)) { + return NULL; + } + + const u1* addr = (const u1*) (item + 1); + + if (item->fieldsSize != 0) { + addr = crossVerifyFieldAnnotations(state, item->fieldsSize, addr, + definingClass); + if (addr == NULL) { + return NULL; + } + } + + if (item->methodsSize != 0) { + addr = crossVerifyMethodAnnotations(state, item->methodsSize, addr, + definingClass); + if (addr == NULL) { + return NULL; + } + } + + if (item->parametersSize != 0) { + addr = crossVerifyParameterAnnotations(state, item->parametersSize, + addr, definingClass); + if (addr == NULL) { + return NULL; + } + } + + return (void*) addr; +} + +/* Perform byte-swapping and intra-item verification on type_list. */ +static void* swapTypeList(const CheckState* state, void* ptr) +{ + DexTypeList* pTypeList = (DexTypeList*) ptr; + DexTypeItem* pType; + u4 count; + + CHECK_PTR_RANGE(pTypeList, pTypeList + 1); + SWAP_FIELD4(pTypeList->size); + count = pTypeList->size; + pType = pTypeList->list; + + const u4 sizeOfItem = (u4) sizeof(DexTypeItem); + CHECK_LIST_SIZE(pType, count, sizeOfItem); + + while (count--) { + SWAP_INDEX2(pType->typeIdx, state->pHeader->typeIdsSize); + pType++; + } + + return pType; +} + +/* Perform byte-swapping and intra-item verification on + * annotation_set_ref_list. */ +static void* swapAnnotationSetRefList(const CheckState* state, void* ptr) { + DexAnnotationSetRefList* list = (DexAnnotationSetRefList*) ptr; + DexAnnotationSetRefItem* item; + u4 count; + + CHECK_PTR_RANGE(list, list + 1); + SWAP_FIELD4(list->size); + count = list->size; + item = list->list; + + const u4 sizeOfItem = (u4) sizeof(DexAnnotationSetRefItem); + CHECK_LIST_SIZE(item, count, sizeOfItem); + + while (count--) { + SWAP_OFFSET4(item->annotationsOff); + item++; + } + + return item; +} + +/* Perform cross-item verification of annotation_set_ref_list. */ +static void* crossVerifyAnnotationSetRefList(const CheckState* state, + void* ptr) { + const DexAnnotationSetRefList* list = (const DexAnnotationSetRefList*) ptr; + const DexAnnotationSetRefItem* item = list->list; + int count = list->size; + + while (count--) { + if (!dexDataMapVerify0Ok(state->pDataMap, + item->annotationsOff, kDexTypeAnnotationSetItem)) { + return NULL; + } + item++; + } + + return (void*) item; +} + +/* Perform byte-swapping and intra-item verification on + * annotation_set_item. */ +static void* swapAnnotationSetItem(const CheckState* state, void* ptr) { + DexAnnotationSetItem* set = (DexAnnotationSetItem*) ptr; + u4* item; + u4 count; + + CHECK_PTR_RANGE(set, set + 1); + SWAP_FIELD4(set->size); + count = set->size; + item = set->entries; + + const u4 sizeOfItem = (u4) sizeof(u4); + CHECK_LIST_SIZE(item, count, sizeOfItem); + + while (count--) { + SWAP_OFFSET4(*item); + item++; + } + + return item; +} + +/* Helper for crossVerifyAnnotationSetItem(), which extracts the type_idx + * out of an annotation_item. */ +static u4 annotationItemTypeIdx(const DexAnnotationItem* item) { + const u1* data = item->annotation; + return readUnsignedLeb128(&data); +} + +/* Perform cross-item verification of annotation_set_item. */ +static void* crossVerifyAnnotationSetItem(const CheckState* state, void* ptr) { + const DexAnnotationSetItem* set = (const DexAnnotationSetItem*) ptr; + int count = set->size; + u4 lastIdx = 0; + bool first = true; + int i; + + for (i = 0; i < count; i++) { + if (!dexDataMapVerify0Ok(state->pDataMap, + dexGetAnnotationOff(set, i), kDexTypeAnnotationItem)) { + return NULL; + } + + const DexAnnotationItem* annotation = + dexGetAnnotationItem(state->pDexFile, set, i); + u4 idx = annotationItemTypeIdx(annotation); + + if (first) { + first = false; + } else if (lastIdx >= idx) { + ALOGE("Out-of-order entry types: %#x then %#x", + lastIdx, idx); + return NULL; + } + + lastIdx = idx; + } + + return (void*) (set->entries + count); +} + +/* Helper for verifyClassDataItem(), which checks a list of fields. */ +static bool verifyFields(const CheckState* state, u4 size, + DexField* fields, bool expectStatic) { + u4 i; + + for (i = 0; i < size; i++) { + DexField* field = &fields[i]; + u4 accessFlags = field->accessFlags; + bool isStatic = (accessFlags & ACC_STATIC) != 0; + + CHECK_INDEX(field->fieldIdx, state->pHeader->fieldIdsSize); + + if (isStatic != expectStatic) { + ALOGE("Field in wrong list @ %d", i); + return false; + } + + if ((accessFlags & ~ACC_FIELD_MASK) != 0) { + // The VM specification says that unknown flags should be ignored. + ALOGV("Bogus field access flags %x @ %d", accessFlags, i); + field->accessFlags &= ACC_FIELD_MASK; + } + } + + return true; +} + +/* Helper for verifyClassDataItem(), which checks a list of methods. */ +static bool verifyMethods(const CheckState* state, u4 size, + DexMethod* methods, bool expectDirect) { + u4 i; + + for (i = 0; i < size; i++) { + DexMethod* method = &methods[i]; + + CHECK_INDEX(method->methodIdx, state->pHeader->methodIdsSize); + + u4 accessFlags = method->accessFlags; + bool isDirect = + (accessFlags & (ACC_STATIC | ACC_PRIVATE | ACC_CONSTRUCTOR)) != 0; + bool expectCode = (accessFlags & (ACC_NATIVE | ACC_ABSTRACT)) == 0; + bool isSynchronized = (accessFlags & ACC_SYNCHRONIZED) != 0; + bool allowSynchronized = (accessFlags & ACC_NATIVE) != 0; + + if (isDirect != expectDirect) { + ALOGE("Method in wrong list @ %d", i); + return false; + } + + if (isSynchronized && !allowSynchronized) { + ALOGE("Bogus method access flags (synchronization) %x @ %d", accessFlags, i); + return false; + } + + if ((accessFlags & ~ACC_METHOD_MASK) != 0) { + // The VM specification says that unknown flags should be ignored. + ALOGV("Bogus method access flags %x @ %d", accessFlags, i); + method->accessFlags &= ACC_METHOD_MASK; + } + + if (expectCode) { + if (method->codeOff == 0) { + ALOGE("Unexpected zero code_off for access_flags %x", + accessFlags); + return false; + } + } else if (method->codeOff != 0) { + ALOGE("Unexpected non-zero code_off %#x for access_flags %x", + method->codeOff, accessFlags); + return false; + } + } + + return true; +} + +/* Helper for verifyClassDataItem(), which does most of the work. */ +static bool verifyClassDataItem0(const CheckState* state, + DexClassData* classData) { + bool okay; + + okay = verifyFields(state, classData->header.staticFieldsSize, + classData->staticFields, true); + + if (!okay) { + ALOGE("Trouble with static fields"); + return false; + } + + verifyFields(state, classData->header.instanceFieldsSize, + classData->instanceFields, false); + + if (!okay) { + ALOGE("Trouble with instance fields"); + return false; + } + + okay = verifyMethods(state, classData->header.directMethodsSize, + classData->directMethods, true); + + if (!okay) { + ALOGE("Trouble with direct methods"); + return false; + } + + okay = verifyMethods(state, classData->header.virtualMethodsSize, + classData->virtualMethods, false); + + if (!okay) { + ALOGE("Trouble with virtual methods"); + return false; + } + + return true; +} + +/* Perform intra-item verification on class_data_item. */ +static void* intraVerifyClassDataItem(const CheckState* state, void* ptr) { + const u1* data = (const u1*) ptr; + DexClassData* classData = dexReadAndVerifyClassData(&data, state->fileEnd); + + if (classData == NULL) { + ALOGE("Unable to parse class_data_item"); + return NULL; + } + + bool okay = verifyClassDataItem0(state, classData); + + free(classData); + + if (!okay) { + return NULL; + } + + return (void*) data; +} + +/* Helper for crossVerifyClassDefItem() and + * crossVerifyClassDataItem(), which finds the type_idx of the definer + * of the first item in the data. */ +static u4 findFirstClassDataDefiner(const CheckState* state, + DexClassData* classData) { + if (classData->header.staticFieldsSize != 0) { + u4 fieldIdx = classData->staticFields[0].fieldIdx; + const DexFieldId* field = dexGetFieldId(state->pDexFile, fieldIdx); + return field->classIdx; + } + + if (classData->header.instanceFieldsSize != 0) { + u4 fieldIdx = classData->instanceFields[0].fieldIdx; + const DexFieldId* field = dexGetFieldId(state->pDexFile, fieldIdx); + return field->classIdx; + } + + if (classData->header.directMethodsSize != 0) { + u4 methodIdx = classData->directMethods[0].methodIdx; + const DexMethodId* meth = dexGetMethodId(state->pDexFile, methodIdx); + return meth->classIdx; + } + + if (classData->header.virtualMethodsSize != 0) { + u4 methodIdx = classData->virtualMethods[0].methodIdx; + const DexMethodId* meth = dexGetMethodId(state->pDexFile, methodIdx); + return meth->classIdx; + } + + return kDexNoIndex; +} + +/* Perform cross-item verification of class_data_item. */ +static void* crossVerifyClassDataItem(const CheckState* state, void* ptr) { + const u1* data = (const u1*) ptr; + DexClassData* classData = dexReadAndVerifyClassData(&data, state->fileEnd); + u4 definingClass = findFirstClassDataDefiner(state, classData); + bool okay = true; + u4 i; + + for (i = classData->header.staticFieldsSize; okay && (i > 0); /*i*/) { + i--; + const DexField* field = &classData->staticFields[i]; + okay = verifyFieldDefiner(state, definingClass, field->fieldIdx); + } + + for (i = classData->header.instanceFieldsSize; okay && (i > 0); /*i*/) { + i--; + const DexField* field = &classData->instanceFields[i]; + okay = verifyFieldDefiner(state, definingClass, field->fieldIdx); + } + + for (i = classData->header.directMethodsSize; okay && (i > 0); /*i*/) { + i--; + const DexMethod* meth = &classData->directMethods[i]; + okay = dexDataMapVerify0Ok(state->pDataMap, meth->codeOff, + kDexTypeCodeItem) + && verifyMethodDefiner(state, definingClass, meth->methodIdx); + } + + for (i = classData->header.virtualMethodsSize; okay && (i > 0); /*i*/) { + i--; + const DexMethod* meth = &classData->virtualMethods[i]; + okay = dexDataMapVerify0Ok(state->pDataMap, meth->codeOff, + kDexTypeCodeItem) + && verifyMethodDefiner(state, definingClass, meth->methodIdx); + } + + free(classData); + + if (!okay) { + return NULL; + } + + return (void*) data; +} + +/* Helper for swapCodeItem(), which fills an array with all the valid + * handlerOff values for catch handlers and also verifies the handler + * contents. */ +static u4 setHandlerOffsAndVerify(const CheckState* state, + DexCode* code, u4 firstOffset, u4 handlersSize, u4* handlerOffs) { + const u1* fileEnd = state->fileEnd; + const u1* handlersBase = dexGetCatchHandlerData(code); + u4 offset = firstOffset; + bool okay = true; + u4 i; + + for (i = 0; i < handlersSize; i++) { + const u1* ptr = handlersBase + offset; + int size = readAndVerifySignedLeb128(&ptr, fileEnd, &okay); + bool catchAll; + + if (!okay) { + ALOGE("Bogus size"); + return 0; + } + + if ((size < -65536) || (size > 65536)) { + ALOGE("Invalid size: %d", size); + return 0; + } + + if (size <= 0) { + catchAll = true; + size = -size; + } else { + catchAll = false; + } + + handlerOffs[i] = offset; + + while (size-- > 0) { + u4 typeIdx = + readAndVerifyUnsignedLeb128(&ptr, fileEnd, &okay); + + if (!okay) { + ALOGE("Bogus type_idx"); + return 0; + } + + CHECK_INDEX(typeIdx, state->pHeader->typeIdsSize); + + u4 addr = readAndVerifyUnsignedLeb128(&ptr, fileEnd, &okay); + + if (!okay) { + ALOGE("Bogus addr"); + return 0; + } + + if (addr >= code->insnsSize) { + ALOGE("Invalid addr: %#x", addr); + return 0; + } + } + + if (catchAll) { + u4 addr = readAndVerifyUnsignedLeb128(&ptr, fileEnd, &okay); + + if (!okay) { + ALOGE("Bogus catch_all_addr"); + return 0; + } + + if (addr >= code->insnsSize) { + ALOGE("Invalid catch_all_addr: %#x", addr); + return 0; + } + } + + offset = ptr - handlersBase; + } + + return offset; +} + +/* Helper for swapCodeItem(), which does all the try-catch related + * swapping and verification. */ +static void* swapTriesAndCatches(const CheckState* state, DexCode* code) { + const u1* encodedHandlers = dexGetCatchHandlerData(code); + const u1* encodedPtr = encodedHandlers; + bool okay = true; + u4 handlersSize = + readAndVerifyUnsignedLeb128(&encodedPtr, state->fileEnd, &okay); + + if (!okay) { + ALOGE("Bogus handlers_size"); + return NULL; + } + + if ((handlersSize == 0) || (handlersSize >= 65536)) { + ALOGE("Invalid handlers_size: %d", handlersSize); + return NULL; + } + + u4 handlerOffs[handlersSize]; // list of valid handlerOff values + u4 endOffset = setHandlerOffsAndVerify(state, code, + encodedPtr - encodedHandlers, + handlersSize, handlerOffs); + + if (endOffset == 0) { + return NULL; + } + + DexTry* tries = (DexTry*) dexGetTries(code); + u4 count = code->triesSize; + u4 lastEnd = 0; + + const u4 sizeOfItem = (u4) sizeof(DexTry); + CHECK_LIST_SIZE(tries, count, sizeOfItem); + + while (count--) { + u4 i; + + SWAP_FIELD4(tries->startAddr); + SWAP_FIELD2(tries->insnCount); + SWAP_FIELD2(tries->handlerOff); + + if (tries->startAddr < lastEnd) { + ALOGE("Out-of-order try"); + return NULL; + } + + if (tries->startAddr >= code->insnsSize) { + ALOGE("Invalid start_addr: %#x", tries->startAddr); + return NULL; + } + + for (i = 0; i < handlersSize; i++) { + if (tries->handlerOff == handlerOffs[i]) { + break; + } + } + + if (i == handlersSize) { + ALOGE("Bogus handler offset: %#x", tries->handlerOff); + return NULL; + } + + lastEnd = tries->startAddr + tries->insnCount; + + if (lastEnd > code->insnsSize) { + ALOGE("Invalid insn_count: %#x (end addr %#x)", + tries->insnCount, lastEnd); + return NULL; + } + + tries++; + } + + return (u1*) encodedHandlers + endOffset; +} + +/* Perform byte-swapping and intra-item verification on code_item. */ +static void* swapCodeItem(const CheckState* state, void* ptr) { + DexCode* item = (DexCode*) ptr; + u2* insns; + u4 count; + + CHECK_PTR_RANGE(item, item + 1); + SWAP_FIELD2(item->registersSize); + SWAP_FIELD2(item->insSize); + SWAP_FIELD2(item->outsSize); + SWAP_FIELD2(item->triesSize); + SWAP_OFFSET4(item->debugInfoOff); + SWAP_FIELD4(item->insnsSize); + + if (item->insSize > item->registersSize) { + ALOGE("insSize (%u) > registersSize (%u)", item->insSize, + item->registersSize); + return NULL; + } + + if ((item->outsSize > 5) && (item->outsSize > item->registersSize)) { + /* + * It's okay for outsSize to be up to five, even if registersSize + * is smaller, since the short forms of method invocation allow + * repetition of a register multiple times within a single parameter + * list. Longer parameter lists, though, need to be represented + * in-order in the register file. + */ + ALOGE("outsSize (%u) > registersSize (%u)", item->outsSize, + item->registersSize); + return NULL; + } + + count = item->insnsSize; + insns = item->insns; + + const u4 sizeOfItem = (u4) sizeof(u2); + CHECK_LIST_SIZE(insns, count, sizeOfItem); + + while (count--) { + *insns = SWAP2(*insns); + insns++; + } + + if (item->triesSize == 0) { + ptr = insns; + } else { + if ((((uintptr_t) insns) & 3) != 0) { + // Four-byte alignment for the tries. Verify the spacer is a 0. + if (*insns != 0) { + ALOGE("Non-zero padding: %#x", (u4) *insns); + return NULL; + } + } + + ptr = swapTriesAndCatches(state, item); + } + + return ptr; +} + +/* Perform intra-item verification on string_data_item. */ +static void* intraVerifyStringDataItem(const CheckState* state, void* ptr) { + const u1* fileEnd = state->fileEnd; + const u1* data = (const u1*) ptr; + bool okay = true; + u4 utf16Size = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + u4 i; + + if (!okay) { + ALOGE("Bogus utf16_size"); + return NULL; + } + + for (i = 0; i < utf16Size; i++) { + if (data >= fileEnd) { + ALOGE("String data would go beyond end-of-file"); + return NULL; + } + + u1 byte1 = *(data++); + + // Switch on the high four bits. + switch (byte1 >> 4) { + case 0x00: { + // Special case of bit pattern 0xxx. + if (byte1 == 0) { + ALOGE("String shorter than indicated utf16_size %#x", + utf16Size); + return NULL; + } + break; + } + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: { + // Bit pattern 0xxx. No need for any extra bytes or checks. + break; + } + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0f: { + /* + * Bit pattern 10xx or 1111, which are illegal start bytes. + * Note: 1111 is valid for normal UTF-8, but not the + * modified UTF-8 used here. + */ + ALOGE("Illegal start byte %#x", byte1); + return NULL; + } + case 0x0e: { + // Bit pattern 1110, so there are two additional bytes. + u1 byte2 = *(data++); + if ((byte2 & 0xc0) != 0x80) { + ALOGE("Illegal continuation byte %#x", byte2); + return NULL; + } + u1 byte3 = *(data++); + if ((byte3 & 0xc0) != 0x80) { + ALOGE("Illegal continuation byte %#x", byte3); + return NULL; + } + u2 value = ((byte1 & 0x0f) << 12) | ((byte2 & 0x3f) << 6) + | (byte3 & 0x3f); + if (value < 0x800) { + ALOGE("Illegal representation for value %x", value); + return NULL; + } + break; + } + case 0x0c: + case 0x0d: { + // Bit pattern 110x, so there is one additional byte. + u1 byte2 = *(data++); + if ((byte2 & 0xc0) != 0x80) { + ALOGE("Illegal continuation byte %#x", byte2); + return NULL; + } + u2 value = ((byte1 & 0x1f) << 6) | (byte2 & 0x3f); + if ((value != 0) && (value < 0x80)) { + ALOGE("Illegal representation for value %x", value); + return NULL; + } + break; + } + } + } + + if (*(data++) != '\0') { + ALOGE("String longer than indicated utf16_size %#x", utf16Size); + return NULL; + } + + return (void*) data; +} + +/* Perform intra-item verification on debug_info_item. */ +static void* intraVerifyDebugInfoItem(const CheckState* state, void* ptr) { + const u1* fileEnd = state->fileEnd; + const u1* data = (const u1*) ptr; + bool okay = true; + u4 i; + + readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + + if (!okay) { + ALOGE("Bogus line_start"); + return NULL; + } + + u4 parametersSize = + readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + + if (!okay) { + ALOGE("Bogus parameters_size"); + return NULL; + } + + if (parametersSize > 65536) { + ALOGE("Invalid parameters_size: %#x", parametersSize); + return NULL; + } + + for (i = 0; i < parametersSize; i++) { + u4 parameterName = + readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + + if (!okay) { + ALOGE("Bogus parameter_name"); + return NULL; + } + + if (parameterName != 0) { + parameterName--; + CHECK_INDEX(parameterName, state->pHeader->stringIdsSize); + } + } + + bool done = false; + while (!done) { + u1 opcode = *(data++); + + switch (opcode) { + case DBG_END_SEQUENCE: { + done = true; + break; + } + case DBG_ADVANCE_PC: { + readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + break; + } + case DBG_ADVANCE_LINE: { + readAndVerifySignedLeb128(&data, fileEnd, &okay); + break; + } + case DBG_START_LOCAL: { + u4 idx; + u4 regNum = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (regNum >= 65536) { + okay = false; + break; + } + idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (idx != 0) { + idx--; + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + } + idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (idx != 0) { + idx--; + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + } + break; + } + case DBG_END_LOCAL: + case DBG_RESTART_LOCAL: { + u4 regNum = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (regNum >= 65536) { + okay = false; + break; + } + break; + } + case DBG_START_LOCAL_EXTENDED: { + u4 idx; + u4 regNum = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (regNum >= 65536) { + okay = false; + break; + } + idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (idx != 0) { + idx--; + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + } + idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (idx != 0) { + idx--; + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + } + idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (idx != 0) { + idx--; + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + } + break; + } + case DBG_SET_FILE: { + u4 idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + if (!okay) break; + if (idx != 0) { + idx--; + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + } + break; + } + default: { + // No arguments to parse for anything else. + } + } + + if (!okay) { + ALOGE("Bogus syntax for opcode %02x", opcode); + return NULL; + } + } + + return (void*) data; +} + +/* defined below */ +static const u1* verifyEncodedValue(const CheckState* state, const u1* data, + bool crossVerify); +static const u1* verifyEncodedAnnotation(const CheckState* state, + const u1* data, bool crossVerify); + +/* Helper for verifyEncodedValue(), which reads a 1- to 4- byte unsigned + * little endian value. */ +static u4 readUnsignedLittleEndian(const CheckState* state, const u1** pData, + u4 size) { + const u1* data = *pData; + u4 result = 0; + u4 i; + + CHECK_PTR_RANGE(data, data + size); + + for (i = 0; i < size; i++) { + result |= ((u4) *(data++)) << (i * 8); + } + + *pData = data; + return result; +} + +/* Helper for *VerifyAnnotationItem() and *VerifyEncodedArrayItem(), which + * verifies an encoded_array. */ +static const u1* verifyEncodedArray(const CheckState* state, + const u1* data, bool crossVerify) { + bool okay = true; + u4 size = readAndVerifyUnsignedLeb128(&data, state->fileEnd, &okay); + + if (!okay) { + ALOGE("Bogus encoded_array size"); + return NULL; + } + + while (size--) { + data = verifyEncodedValue(state, data, crossVerify); + if (data == NULL) { + ALOGE("Bogus encoded_array value"); + return NULL; + } + } + + return data; +} + +static u4 numberOfMethodHandles(const CheckState* state) { + if (state->pMethodHandleItems != nullptr) { + return state->pMethodHandleItems->size; + } + return 0; +} + +/* Helper for *VerifyAnnotationItem() and *VerifyEncodedArrayItem(), which + * verifies an encoded_value. */ +static const u1* verifyEncodedValue(const CheckState* state, + const u1* data, bool crossVerify) { + CHECK_PTR_RANGE(data, data + 1); + + u1 headerByte = *(data++); + u4 valueType = headerByte & kDexAnnotationValueTypeMask; + u4 valueArg = headerByte >> kDexAnnotationValueArgShift; + + switch (valueType) { + case kDexAnnotationByte: { + if (valueArg != 0) { + ALOGE("Bogus byte size %#x", valueArg); + return NULL; + } + data++; + break; + } + case kDexAnnotationShort: + case kDexAnnotationChar: { + if (valueArg > 1) { + ALOGE("Bogus char/short size %#x", valueArg); + return NULL; + } + data += valueArg + 1; + break; + } + case kDexAnnotationInt: + case kDexAnnotationFloat: { + if (valueArg > 3) { + ALOGE("Bogus int/float size %#x", valueArg); + return NULL; + } + data += valueArg + 1; + break; + } + case kDexAnnotationLong: + case kDexAnnotationDouble: { + data += valueArg + 1; + break; + } + case kDexAnnotationMethodType: { + if (valueArg > 3) { + ALOGE("Bogus method type size %#x", valueArg); + return NULL; + } + u4 idx = readUnsignedLittleEndian(state, &data, valueArg + 1); + CHECK_INDEX(idx, state->pHeader->protoIdsSize); + break; + } + case kDexAnnotationMethodHandle: { + if (valueArg > 3) { + ALOGE("Bogus method type size %#x", valueArg); + return NULL; + } + u4 idx = readUnsignedLittleEndian(state, &data, valueArg + 1); + CHECK_INDEX(idx, numberOfMethodHandles(state)); + break; + } + case kDexAnnotationString: { + if (valueArg > 3) { + ALOGE("Bogus string size %#x", valueArg); + return NULL; + } + u4 idx = readUnsignedLittleEndian(state, &data, valueArg + 1); + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + break; + } + case kDexAnnotationType: { + if (valueArg > 3) { + ALOGE("Bogus type size %#x", valueArg); + return NULL; + } + u4 idx = readUnsignedLittleEndian(state, &data, valueArg + 1); + CHECK_INDEX(idx, state->pHeader->typeIdsSize); + break; + } + case kDexAnnotationField: + case kDexAnnotationEnum: { + if (valueArg > 3) { + ALOGE("Bogus field/enum size %#x", valueArg); + return NULL; + } + u4 idx = readUnsignedLittleEndian(state, &data, valueArg + 1); + CHECK_INDEX(idx, state->pHeader->fieldIdsSize); + break; + } + case kDexAnnotationMethod: { + if (valueArg > 3) { + ALOGE("Bogus method size %#x", valueArg); + return NULL; + } + u4 idx = readUnsignedLittleEndian(state, &data, valueArg + 1); + CHECK_INDEX(idx, state->pHeader->methodIdsSize); + break; + } + case kDexAnnotationArray: { + if (valueArg != 0) { + ALOGE("Bogus array value_arg %#x", valueArg); + return NULL; + } + data = verifyEncodedArray(state, data, crossVerify); + break; + } + case kDexAnnotationAnnotation: { + if (valueArg != 0) { + ALOGE("Bogus annotation value_arg %#x", valueArg); + return NULL; + } + data = verifyEncodedAnnotation(state, data, crossVerify); + break; + } + case kDexAnnotationNull: { + if (valueArg != 0) { + ALOGE("Bogus null value_arg %#x", valueArg); + return NULL; + } + // Nothing else to do for this type. + break; + } + case kDexAnnotationBoolean: { + if (valueArg > 1) { + ALOGE("Bogus boolean value_arg %#x", valueArg); + return NULL; + } + // Nothing else to do for this type. + break; + } + default: { + ALOGE("Bogus value_type %#x", valueType); + return NULL; + } + } + + return data; +} + +/* Helper for *VerifyAnnotationItem() and *VerifyEncodedArrayItem(), which + * verifies an encoded_annotation. */ +static const u1* verifyEncodedAnnotation(const CheckState* state, + const u1* data, bool crossVerify) { + const u1* fileEnd = state->fileEnd; + bool okay = true; + u4 idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + + if (!okay) { + ALOGE("Bogus encoded_annotation type_idx"); + return NULL; + } + + CHECK_INDEX(idx, state->pHeader->typeIdsSize); + + if (crossVerify) { + const char* descriptor = dexStringByTypeIdx(state->pDexFile, idx); + if (!dexIsClassDescriptor(descriptor)) { + ALOGE("Bogus annotation type: '%s'", descriptor); + return NULL; + } + } + + u4 size = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + u4 lastIdx = 0; + bool first = true; + + if (!okay) { + ALOGE("Bogus encoded_annotation size"); + return NULL; + } + + while (size--) { + idx = readAndVerifyUnsignedLeb128(&data, fileEnd, &okay); + + if (!okay) { + ALOGE("Bogus encoded_annotation name_idx"); + return NULL; + } + + CHECK_INDEX(idx, state->pHeader->stringIdsSize); + + if (crossVerify) { + const char* name = dexStringById(state->pDexFile, idx); + if (!dexIsValidMemberName(name)) { + ALOGE("Bogus annotation member name: '%s'", name); + return NULL; + } + } + + if (first) { + first = false; + } else if (lastIdx >= idx) { + ALOGE("Out-of-order encoded_annotation name_idx: %#x then %#x", + lastIdx, idx); + return NULL; + } + + data = verifyEncodedValue(state, data, crossVerify); + lastIdx = idx; + + if (data == NULL) { + return NULL; + } + } + + return data; +} + +/* Perform intra-item verification on encoded_array_item. */ +static void* intraVerifyEncodedArrayItem(const CheckState* state, void* ptr) { + return (void*) verifyEncodedArray(state, (const u1*) ptr, false); +} + +/* Perform intra-item verification on annotation_item. */ +static void* intraVerifyAnnotationItem(const CheckState* state, void* ptr) { + const u1* data = (const u1*) ptr; + + CHECK_PTR_RANGE(data, data + 1); + + switch (*(data++)) { + case kDexVisibilityBuild: + case kDexVisibilityRuntime: + case kDexVisibilitySystem: { + break; + } + default: { + ALOGE("Bogus annotation visibility: %#x", *data); + return NULL; + } + } + + return (void*) verifyEncodedAnnotation(state, data, false); +} + +/* + * Function to visit an individual top-level item type. + */ +typedef void* ItemVisitorFunction(const CheckState* state, void* ptr); + +/* + * Iterate over all the items in a section, optionally updating the + * data map (done if mapType is passed as non-negative). The section + * must consist of concatenated items of the same type. + */ +static bool iterateSectionWithOptionalUpdate(CheckState* state, + u4 offset, u4 count, ItemVisitorFunction* func, u4 alignment, + u4* nextOffset, int mapType) { + u4 alignmentMask = alignment - 1; + u4 i; + + state->previousItem = NULL; + + for (i = 0; i < count; i++) { + u4 newOffset = (offset + alignmentMask) & ~alignmentMask; + u1* ptr = (u1*) filePointer(state, newOffset); + + if (offset < newOffset) { + ptr = (u1*) filePointer(state, offset); + if (offset < newOffset) { + CHECK_OFFSET_RANGE(offset, newOffset); + while (offset < newOffset) { + if (*ptr != '\0') { + ALOGE("Non-zero padding 0x%02x @ %x", *ptr, offset); + return false; + } + ptr++; + offset++; + } + } + } + + u1* newPtr = (u1*) func(state, ptr); + newOffset = fileOffset(state, newPtr); + + if (newPtr == NULL) { + ALOGE("Trouble with item %d @ offset %#x", i, offset); + return false; + } + + if (newOffset > state->fileLen) { + ALOGE("Item %d @ offset %#x ends out of bounds", i, offset); + return false; + } + + if (mapType >= 0) { + dexDataMapAdd(state->pDataMap, offset, mapType); + } + + state->previousItem = ptr; + offset = newOffset; + } + + if (nextOffset != NULL) { + *nextOffset = offset; + } + + return true; +} + +/* + * Iterate over all the items in a section. The section must consist of + * concatenated items of the same type. This variant will not update the data + * map. + */ +static bool iterateSection(CheckState* state, u4 offset, u4 count, + ItemVisitorFunction* func, u4 alignment, u4* nextOffset) { + return iterateSectionWithOptionalUpdate(state, offset, count, func, + alignment, nextOffset, -1); +} + +/* + * Like iterateSection(), but also check that the offset and count match + * a given pair of expected values. + */ +static bool checkBoundsAndIterateSection(CheckState* state, + u4 offset, u4 count, u4 expectedOffset, u4 expectedCount, + ItemVisitorFunction* func, u4 alignment, u4* nextOffset) { + if (offset != expectedOffset) { + ALOGE("Bogus offset for section: got %#x; expected %#x", + offset, expectedOffset); + return false; + } + + if (count != expectedCount) { + ALOGE("Bogus size for section: got %#x; expected %#x", + count, expectedCount); + return false; + } + + return iterateSection(state, offset, count, func, alignment, nextOffset); +} + +/* + * Like iterateSection(), but also update the data section map and + * check that all the items fall within the data section. + */ +static bool iterateDataSection(CheckState* state, u4 offset, u4 count, + ItemVisitorFunction* func, u4 alignment, u4* nextOffset, int mapType) { + u4 dataStart = state->pHeader->dataOff; + u4 dataEnd = dataStart + state->pHeader->dataSize; + + assert(nextOffset != NULL); + + if ((offset < dataStart) || (offset >= dataEnd)) { + ALOGE("Bogus offset for data subsection: %#x", offset); + return false; + } + + if (!iterateSectionWithOptionalUpdate(state, offset, count, func, + alignment, nextOffset, mapType)) { + return false; + } + + if (*nextOffset > dataEnd) { + ALOGE("Out-of-bounds end of data subsection: %#x", *nextOffset); + return false; + } + + return true; +} + +/* + * Byte-swap all items in the given map except the header and the map + * itself, both of which should have already gotten swapped. This also + * does all possible intra-item verification, that is, verification + * that doesn't need to assume the sanctity of the contents of *other* + * items. The intra-item limitation is because at the time an item is + * asked to verify itself, it can't assume that the items it refers to + * have been byte-swapped and verified. + */ +static bool swapEverythingButHeaderAndMap(CheckState* state, + DexMapList* pMap) { + const DexMapItem* item = pMap->list; + u4 lastOffset = 0; + u4 count = pMap->size; + bool okay = true; + + while (okay && count--) { + u4 sectionOffset = item->offset; + u4 sectionCount = item->size; + u2 type = item->type; + + if (lastOffset < sectionOffset) { + CHECK_OFFSET_RANGE(lastOffset, sectionOffset); + const u1* ptr = (const u1*) filePointer(state, lastOffset); + while (lastOffset < sectionOffset) { + if (*ptr != '\0') { + ALOGE("Non-zero padding 0x%02x before section start @ %x", + *ptr, lastOffset); + okay = false; + break; + } + ptr++; + lastOffset++; + } + } else if (lastOffset > sectionOffset) { + ALOGE("Section overlap or out-of-order map: %x, %x", + lastOffset, sectionOffset); + okay = false; + } + + if (!okay) { + break; + } + + switch (type) { + case kDexTypeHeaderItem: { + /* + * The header got swapped very early on, but do some + * additional sanity checking here. + */ + okay = checkHeaderSection(state, sectionOffset, sectionCount, + &lastOffset); + break; + } + case kDexTypeStringIdItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, state->pHeader->stringIdsOff, + state->pHeader->stringIdsSize, swapStringIdItem, + sizeof(u4), &lastOffset); + break; + } + case kDexTypeTypeIdItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, state->pHeader->typeIdsOff, + state->pHeader->typeIdsSize, swapTypeIdItem, + sizeof(u4), &lastOffset); + break; + } + case kDexTypeProtoIdItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, state->pHeader->protoIdsOff, + state->pHeader->protoIdsSize, swapProtoIdItem, + sizeof(u4), &lastOffset); + break; + } + case kDexTypeFieldIdItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, state->pHeader->fieldIdsOff, + state->pHeader->fieldIdsSize, swapFieldIdItem, + sizeof(u4), &lastOffset); + break; + } + case kDexTypeMethodIdItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, state->pHeader->methodIdsOff, + state->pHeader->methodIdsSize, swapMethodIdItem, + sizeof(u4), &lastOffset); + break; + } + case kDexTypeClassDefItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, state->pHeader->classDefsOff, + state->pHeader->classDefsSize, swapClassDefItem, + sizeof(u4), &lastOffset); + break; + } + case kDexTypeCallSiteIdItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, sectionOffset, sectionCount, + swapCallSiteId, sizeof(u4), &lastOffset); + break; + } + case kDexTypeMethodHandleItem: { + okay = checkBoundsAndIterateSection(state, sectionOffset, + sectionCount, sectionOffset, sectionCount, + swapMethodHandleItem, sizeof(u4), &lastOffset); + break; + } + case kDexTypeMapList: { + /* + * The map section was swapped early on, but do some + * additional sanity checking here. + */ + okay = checkMapSection(state, sectionOffset, sectionCount, + &lastOffset); + break; + } + case kDexTypeTypeList: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + swapTypeList, sizeof(u4), &lastOffset, type); + break; + } + case kDexTypeAnnotationSetRefList: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + swapAnnotationSetRefList, sizeof(u4), &lastOffset, + type); + break; + } + case kDexTypeAnnotationSetItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + swapAnnotationSetItem, sizeof(u4), &lastOffset, type); + break; + } + case kDexTypeClassDataItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + intraVerifyClassDataItem, sizeof(u1), &lastOffset, + type); + break; + } + case kDexTypeCodeItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + swapCodeItem, sizeof(u4), &lastOffset, type); + break; + } + case kDexTypeStringDataItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + intraVerifyStringDataItem, sizeof(u1), &lastOffset, + type); + break; + } + case kDexTypeDebugInfoItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + intraVerifyDebugInfoItem, sizeof(u1), &lastOffset, + type); + break; + } + case kDexTypeAnnotationItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + intraVerifyAnnotationItem, sizeof(u1), &lastOffset, + type); + break; + } + case kDexTypeEncodedArrayItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + intraVerifyEncodedArrayItem, sizeof(u1), &lastOffset, + type); + break; + } + case kDexTypeAnnotationsDirectoryItem: { + okay = iterateDataSection(state, sectionOffset, sectionCount, + swapAnnotationsDirectoryItem, sizeof(u4), &lastOffset, + type); + break; + } + default: { + ALOGE("Unknown map item type %04x", type); + return false; + } + } + + if (!okay) { + ALOGE("Swap of section type %04x failed", type); + } + + item++; + } + + return okay; +} + +/* + * Perform cross-item verification on everything that needs it. This + * pass is only called after all items are byte-swapped and + * intra-verified (checked for internal consistency). + */ +static bool crossVerifyEverything(CheckState* state, DexMapList* pMap) +{ + const DexMapItem* item = pMap->list; + u4 count = pMap->size; + bool okay = true; + + while (okay && count--) { + u4 sectionOffset = item->offset; + u4 sectionCount = item->size; + + switch (item->type) { + case kDexTypeHeaderItem: + case kDexTypeMapList: + case kDexTypeTypeList: + case kDexTypeCodeItem: + case kDexTypeStringDataItem: + case kDexTypeDebugInfoItem: + case kDexTypeAnnotationItem: + case kDexTypeEncodedArrayItem: { + // There is no need for cross-item verification for these. + break; + } + case kDexTypeStringIdItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyStringIdItem, sizeof(u4), NULL); + break; + } + case kDexTypeTypeIdItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyTypeIdItem, sizeof(u4), NULL); + break; + } + case kDexTypeProtoIdItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyProtoIdItem, sizeof(u4), NULL); + break; + } + case kDexTypeFieldIdItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyFieldIdItem, sizeof(u4), NULL); + break; + } + case kDexTypeMethodIdItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyMethodIdItem, sizeof(u4), NULL); + break; + } + case kDexTypeClassDefItem: { + // Allocate (on the stack) the "observed class_def" bits. + size_t arraySize = calcDefinedClassBitsSize(state); + u4 definedClassBits[arraySize]; + memset(definedClassBits, 0, arraySize * sizeof(u4)); + state->pDefinedClassBits = definedClassBits; + + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyClassDefItem, sizeof(u4), NULL); + + state->pDefinedClassBits = NULL; + break; + } + case kDexTypeCallSiteIdItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyCallSiteId, sizeof(u4), NULL); + break; + } + case kDexTypeMethodHandleItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyMethodHandleItem, sizeof(u4), NULL); + break; + } + case kDexTypeAnnotationSetRefList: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyAnnotationSetRefList, sizeof(u4), NULL); + break; + } + case kDexTypeAnnotationSetItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyAnnotationSetItem, sizeof(u4), NULL); + break; + } + case kDexTypeClassDataItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyClassDataItem, sizeof(u1), NULL); + break; + } + case kDexTypeAnnotationsDirectoryItem: { + okay = iterateSection(state, sectionOffset, sectionCount, + crossVerifyAnnotationsDirectoryItem, sizeof(u4), NULL); + break; + } + default: { + ALOGE("Unknown map item type %04x", item->type); + return false; + } + } + + if (!okay) { + ALOGE("Cross-item verify of section type %04x failed", + item->type); + } + + item++; + } + + return okay; +} + +/* (documented in header file) */ +bool dexHasValidMagic(const DexHeader* pHeader) +{ + const u1* magic = pHeader->magic; + const u1* version = &magic[4]; + + if (memcmp(magic, DEX_MAGIC, 4) != 0) { + ALOGE("ERROR: unrecognized magic number (%02x %02x %02x %02x)", + magic[0], magic[1], magic[2], magic[3]); + return false; + } + + if ((memcmp(version, DEX_MAGIC_VERS, 4) != 0) && + (memcmp(version, DEX_MAGIC_VERS_API_13, 4) != 0) && + (memcmp(version, DEX_MAGIC_VERS_37, 4) != 0) && + (memcmp(version, DEX_MAGIC_VERS_38, 4) != 0) && + (memcmp(version, DEX_MAGIC_VERS_39, 4) != 0)) { + /* + * Magic was correct, but this is an unsupported older or + * newer format variant. + */ + ALOGE("ERROR: unsupported dex version (%02x %02x %02x %02x)", + version[0], version[1], version[2], version[3]); + return false; + } + + return true; +} + +/* + * Fix the byte ordering of all fields in the DEX file, and do + * structural verification. This is only required for code that opens + * "raw" DEX files, such as the DEX optimizer. + * + * Returns 0 on success, nonzero on failure. + */ +int dexSwapAndVerify(u1* addr, size_t len) +{ + DexHeader* pHeader; + CheckState state; + bool okay = true; + + memset(&state, 0, sizeof(state)); + ALOGV("+++ swapping and verifying"); + + /* + * Note: The caller must have verified that "len" is at least as + * large as a dex file header. + */ + pHeader = (DexHeader*) addr; + + if (!dexHasValidMagic(pHeader)) { + okay = false; + } + + if (okay) { + u4 expectedLen = SWAP4(pHeader->fileSize); + if (len != expectedLen) { + ALOGE("ERROR: Bad length: expected %u, got %zu", expectedLen, len); + okay = false; + } + } + + if (okay) { + /* + * Compute the adler32 checksum and compare it to what's stored in + * the file. This isn't free, but chances are good that we just + * unpacked this from a jar file and have all of the pages sitting + * in memory, so it's pretty quick. + * + * This might be a big-endian system, so we need to do this before + * we byte-swap the header. + */ + uLong adler = adler32(0L, Z_NULL, 0); + const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum); + u4 storedFileSize = SWAP4(pHeader->fileSize); + u4 expectedChecksum = SWAP4(pHeader->checksum); + + adler = adler32(adler, ((const u1*) pHeader) + nonSum, + storedFileSize - nonSum); + + if (adler != expectedChecksum) { + ALOGE("ERROR: bad checksum (%08lx, expected %08x)", + adler, expectedChecksum); + okay = false; + } + } + + if (okay) { + state.fileStart = addr; + state.fileEnd = addr + len; + state.fileLen = len; + state.pDexFile = NULL; + state.pDataMap = NULL; + state.pDefinedClassBits = NULL; + state.previousItem = NULL; + + /* + * Swap the header and check the contents. + */ + okay = swapDexHeader(&state, pHeader); + } + + if (okay) { + state.pHeader = pHeader; + + if (pHeader->headerSize < sizeof(DexHeader)) { + ALOGE("ERROR: Small header size %d, struct %d", + pHeader->headerSize, (int) sizeof(DexHeader)); + okay = false; + } else if (pHeader->headerSize > sizeof(DexHeader)) { + ALOGW("WARNING: Large header size %d, struct %d", + pHeader->headerSize, (int) sizeof(DexHeader)); + // keep going? + } + } + + if (okay) { + /* + * Look for the map. Swap it and then use it to find and swap + * everything else. + */ + if (pHeader->mapOff != 0) { + DexFile dexFile; + DexMapList* pDexMap = (DexMapList*) (addr + pHeader->mapOff); + + okay = okay && swapMap(&state, pDexMap); + okay = okay && swapEverythingButHeaderAndMap(&state, pDexMap); + + dexFileSetupBasicPointers(&dexFile, addr); + state.pDexFile = &dexFile; + + okay = okay && crossVerifyEverything(&state, pDexMap); + } else { + ALOGE("ERROR: No map found; impossible to byte-swap and verify"); + okay = false; + } + } + + if (!okay) { + ALOGE("ERROR: Byte swap + verify failed"); + } + + if (state.pDataMap != NULL) { + dexDataMapFree(state.pDataMap); + } + + return !okay; // 0 == success +} + +/* + * Detect the file type of the given memory buffer via magic number. + * Call dexSwapAndVerify() on an unoptimized DEX file, do nothing + * but return successfully on an optimized DEX file, and report an + * error for all other cases. + * + * Returns 0 on success, nonzero on failure. + */ +int dexSwapAndVerifyIfNecessary(u1* addr, size_t len) +{ + if (memcmp(addr, DEX_OPT_MAGIC, 4) == 0) { + // It is an optimized dex file. + return 0; + } + + if (memcmp(addr, DEX_MAGIC, 4) == 0) { + // It is an unoptimized dex file. + return dexSwapAndVerify(addr, len); + } + + ALOGE("ERROR: Bad magic number (0x%02x %02x %02x %02x)", + addr[0], addr[1], addr[2], addr[3]); + + return 1; +} diff --git a/libdex/DexUtf.cpp b/libdex/DexUtf.cpp new file mode 100644 index 000000000..df49d1831 --- /dev/null +++ b/libdex/DexUtf.cpp @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Validate and manipulate MUTF-8 encoded string data. + */ + +#include "DexUtf.h" + +/* Compare two '\0'-terminated modified UTF-8 strings, using Unicode + * code point values for comparison. This treats different encodings + * for the same code point as equivalent, except that only a real '\0' + * byte is considered the string terminator. The return value is as + * for strcmp(). */ +int dexUtf8Cmp(const char* s1, const char* s2) { + for (;;) { + if (*s1 == '\0') { + if (*s2 == '\0') { + return 0; + } + return -1; + } else if (*s2 == '\0') { + return 1; + } + + int utf1 = dexGetUtf16FromUtf8(&s1); + int utf2 = dexGetUtf16FromUtf8(&s2); + int diff = utf1 - utf2; + + if (diff != 0) { + return diff; + } + } +} + +/* for dexIsValidMemberNameUtf8(), a bit vector indicating valid low ascii */ +u4 DEX_MEMBER_VALID_LOW_ASCII[4] = { + 0x00000000, // 00..1f low control characters; nothing valid + 0x03ff2010, // 20..3f digits and symbols; valid: '0'..'9', '$', '-' + 0x87fffffe, // 40..5f uppercase etc.; valid: 'A'..'Z', '_' + 0x07fffffe // 60..7f lowercase etc.; valid: 'a'..'z' +}; + +/* Helper for dexIsValidMemberNameUtf8(); do not call directly. */ +bool dexIsValidMemberNameUtf8_0(const char** pUtf8Ptr) { + /* + * It's a multibyte encoded character. Decode it and analyze. We + * accept anything that isn't (a) an improperly encoded low value, + * (b) an improper surrogate pair, (c) an encoded '\0', (d) a high + * control character, or (e) a high space, layout, or special + * character (U+00a0, U+2000..U+200f, U+2028..U+202f, + * U+fff0..U+ffff). This is all specified in the dex format + * document. + */ + + u2 utf16 = dexGetUtf16FromUtf8(pUtf8Ptr); + + // Perform follow-up tests based on the high 8 bits. + switch (utf16 >> 8) { + case 0x00: { + // It's only valid if it's above the ISO-8859-1 high space (0xa0). + return (utf16 > 0x00a0); + } + case 0xd8: + case 0xd9: + case 0xda: + case 0xdb: { + /* + * It's a leading surrogate. Check to see that a trailing + * surrogate follows. + */ + utf16 = dexGetUtf16FromUtf8(pUtf8Ptr); + return (utf16 >= 0xdc00) && (utf16 <= 0xdfff); + } + case 0xdc: + case 0xdd: + case 0xde: + case 0xdf: { + // It's a trailing surrogate, which is not valid at this point. + return false; + } + case 0x20: + case 0xff: { + // It's in the range that has spaces, controls, and specials. + switch (utf16 & 0xfff8) { + case 0x2000: + case 0x2008: + case 0x2028: + case 0xfff0: + case 0xfff8: { + return false; + } + } + break; + } + } + + return true; +} + +/* Return whether the given string is a valid field or method name. */ +bool dexIsValidMemberName(const char* s) { + bool angleName = false; + + switch (*s) { + case '\0': { + // The empty string is not a valid name. + return false; + } + case '<': { + /* + * '<' is allowed only at the start of a name, and if present, + * means that the name must end with '>'. + */ + angleName = true; + s++; + break; + } + } + + for (;;) { + switch (*s) { + case '\0': { + return !angleName; + } + case '>': { + return angleName && s[1] == '\0'; + } + } + if (!dexIsValidMemberNameUtf8(&s)) { + return false; + } + } +} + +/* Helper for validating type descriptors and class names, which is parametric + * with respect to type vs. class and dot vs. slash. */ +static bool isValidTypeDescriptorOrClassName(const char* s, bool isClassName, + bool dotSeparator) { + int arrayCount = 0; + + while (*s == '[') { + arrayCount++; + s++; + } + + if (arrayCount > 255) { + // Arrays may have no more than 255 dimensions. + return false; + } + + if (arrayCount != 0) { + /* + * If we're looking at an array of some sort, then it doesn't + * matter if what is being asked for is a class name; the + * format looks the same as a type descriptor in that case, so + * treat it as such. + */ + isClassName = false; + } + + if (!isClassName) { + /* + * We are looking for a descriptor. Either validate it as a + * single-character primitive type, or continue on to check the + * embedded class name (bracketed by "L" and ";"). + */ + switch (*(s++)) { + case 'B': + case 'C': + case 'D': + case 'F': + case 'I': + case 'J': + case 'S': + case 'Z': { + // These are all single-character descriptors for primitive types. + return (*s == '\0'); + } + case 'V': { + // Non-array void is valid, but you can't have an array of void. + return (arrayCount == 0) && (*s == '\0'); + } + case 'L': { + // Class name: Break out and continue below. + break; + } + default: { + // Oddball descriptor character. + return false; + } + } + } + + /* + * We just consumed the 'L' that introduces a class name as part + * of a type descriptor, or we are looking for an unadorned class + * name. + */ + + bool sepOrFirst = true; // first character or just encountered a separator. + for (;;) { + u1 c = (u1) *s; + switch (c) { + case '\0': { + /* + * Premature end for a type descriptor, but valid for + * a class name as long as we haven't encountered an + * empty component (including the degenerate case of + * the empty string ""). + */ + return isClassName && !sepOrFirst; + } + case ';': { + /* + * Invalid character for a class name, but the + * legitimate end of a type descriptor. In the latter + * case, make sure that this is the end of the string + * and that it doesn't end with an empty component + * (including the degenerate case of "L;"). + */ + return !isClassName && !sepOrFirst && (s[1] == '\0'); + } + case '/': + case '.': { + if (dotSeparator != (c == '.')) { + // The wrong separator character. + return false; + } + if (sepOrFirst) { + // Separator at start or two separators in a row. + return false; + } + sepOrFirst = true; + s++; + break; + } + default: { + if (!dexIsValidMemberNameUtf8(&s)) { + return false; + } + sepOrFirst = false; + break; + } + } + } +} + +/* Return whether the given string is a valid type descriptor. */ +bool dexIsValidTypeDescriptor(const char* s) { + return isValidTypeDescriptorOrClassName(s, false, false); +} + +/* (documented in header) */ +bool dexIsValidClassName(const char* s, bool dotSeparator) { + return isValidTypeDescriptorOrClassName(s, true, dotSeparator); +} + +/* Return whether the given string is a valid reference descriptor. This + * is true if dexIsValidTypeDescriptor() returns true and the descriptor + * is for a class or array and not a primitive type. */ +bool dexIsReferenceDescriptor(const char* s) { + if (!dexIsValidTypeDescriptor(s)) { + return false; + } + + return (s[0] == 'L') || (s[0] == '['); +} + +/* Return whether the given string is a valid class descriptor. This + * is true if dexIsValidTypeDescriptor() returns true and the descriptor + * is for a class and not an array or primitive type. */ +bool dexIsClassDescriptor(const char* s) { + if (!dexIsValidTypeDescriptor(s)) { + return false; + } + + return s[0] == 'L'; +} + +/* Return whether the given string is a valid field type descriptor. This + * is true if dexIsValidTypeDescriptor() returns true and the descriptor + * is for anything but "void". */ +bool dexIsFieldDescriptor(const char* s) { + if (!dexIsValidTypeDescriptor(s)) { + return false; + } + + return s[0] != 'V'; +} + diff --git a/libdex/DexUtf.h b/libdex/DexUtf.h new file mode 100644 index 000000000..cb3d919ae --- /dev/null +++ b/libdex/DexUtf.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Validate and manipulate MUTF-8 (modified UTF-8) encoded string data. + */ + +#ifndef LIBDEX_DEXUTF_H_ +#define LIBDEX_DEXUTF_H_ + +#include "DexFile.h" + +/* + * Retrieve the next UTF-16 character from a UTF-8 string. + * + * Advances "*pUtf8Ptr" to the start of the next character. + * + * WARNING: If a string is corrupted by dropping a '\0' in the middle + * of a 3-byte sequence, you can end up overrunning the buffer with + * reads (and possibly with the writes if the length was computed and + * cached before the damage). For performance reasons, this function + * assumes that the string being parsed is known to be valid (e.g., by + * already being verified). Most strings we process here are coming + * out of dex files or other internal translations, so the only real + * risk comes from the JNI NewStringUTF call. + */ +DEX_INLINE u2 dexGetUtf16FromUtf8(const char** pUtf8Ptr) +{ + unsigned int one, two, three; + + one = *(*pUtf8Ptr)++; + if ((one & 0x80) != 0) { + /* two- or three-byte encoding */ + two = *(*pUtf8Ptr)++; + if ((one & 0x20) != 0) { + /* three-byte encoding */ + three = *(*pUtf8Ptr)++; + return ((one & 0x0f) << 12) | + ((two & 0x3f) << 6) | + (three & 0x3f); + } else { + /* two-byte encoding */ + return ((one & 0x1f) << 6) | + (two & 0x3f); + } + } else { + /* one-byte encoding */ + return one; + } +} + +/* Compare two '\0'-terminated modified UTF-8 strings, using Unicode + * code point values for comparison. This treats different encodings + * for the same code point as equivalent, except that only a real '\0' + * byte is considered the string terminator. The return value is as + * for strcmp(). */ +int dexUtf8Cmp(const char* s1, const char* s2); + +/* for dexIsValidMemberNameUtf8(), a bit vector indicating valid low ascii */ +extern u4 DEX_MEMBER_VALID_LOW_ASCII[4]; + +/* Helper for dexIsValidMemberUtf8(); do not call directly. */ +bool dexIsValidMemberNameUtf8_0(const char** pUtf8Ptr); + +/* Return whether the pointed-at modified-UTF-8 encoded character is + * valid as part of a member name, updating the pointer to point past + * the consumed character. This will consume two encoded UTF-16 code + * points if the character is encoded as a surrogate pair. Also, if + * this function returns false, then the given pointer may only have + * been partially advanced. */ +DEX_INLINE bool dexIsValidMemberNameUtf8(const char** pUtf8Ptr) { + u1 c = (u1) **pUtf8Ptr; + if (c <= 0x7f) { + // It's low-ascii, so check the table. + u4 wordIdx = c >> 5; + u4 bitIdx = c & 0x1f; + (*pUtf8Ptr)++; + return (DEX_MEMBER_VALID_LOW_ASCII[wordIdx] & (1 << bitIdx)) != 0; + } + + /* + * It's a multibyte encoded character. Call a non-inline function + * for the heavy lifting. + */ + return dexIsValidMemberNameUtf8_0(pUtf8Ptr); +} + +/* Return whether the given string is a valid field or method name. */ +bool dexIsValidMemberName(const char* s); + +/* Return whether the given string is a valid type descriptor. */ +bool dexIsValidTypeDescriptor(const char* s); + +/* Return whether the given string is a valid internal-form class + * name, with components separated either by dots or slashes as + * specified. A class name is like a type descriptor, except that it + * can't name a primitive type (including void). In terms of syntax, + * the form is either (a) the name of the class without adornment + * (that is, not bracketed by "L" and ";"); or (b) identical to the + * type descriptor syntax for array types. */ +bool dexIsValidClassName(const char* s, bool dotSeparator); + +/* Return whether the given string is a valid reference descriptor. This + * is true if dexIsValidTypeDescriptor() returns true and the descriptor + * is for a class or array and not a primitive type. */ +bool dexIsReferenceDescriptor(const char* s); + +/* Return whether the given string is a valid class descriptor. This + * is true if dexIsValidTypeDescriptor() returns true and the descriptor + * is for a class and not an array or primitive type. */ +bool dexIsClassDescriptor(const char* s); + +/* Return whether the given string is a valid field type descriptor. This + * is true if dexIsValidTypeDescriptor() returns true and the descriptor + * is for anything but "void". */ +bool dexIsFieldDescriptor(const char* s); + +#endif // LIBDEX_DEXUTF_H_ diff --git a/libdex/InstrUtils.cpp b/libdex/InstrUtils.cpp new file mode 100644 index 000000000..56f5dd80b --- /dev/null +++ b/libdex/InstrUtils.cpp @@ -0,0 +1,702 @@ +/* + * 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. + */ + +/* + * Dalvik instruction utility functions. + * + * IMPORTANT NOTE: Much of the contents of this file are generated + * automatically by the opcode-gen tool. Any edits to the generated + * sections will get wiped out the next time the tool is run. + */ + +#include "InstrUtils.h" +#include <stdlib.h> + +/* + * Table that maps each opcode to the full width of instructions that + * use that opcode, in (16-bit) code units. Unimplemented opcodes as + * well as the "breakpoint" opcode have a width of zero. + */ +static InstructionWidth gInstructionWidthTable[kNumPackedOpcodes] = { + // BEGIN(libdex-widths); GENERATED AUTOMATICALLY BY opcode-gen + 1, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 2, 3, 2, 2, 3, 5, 2, 2, 3, 2, 1, 1, 2, + 2, 1, 2, 2, 3, 3, 3, 1, 1, 2, 3, 3, 3, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, + 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 0, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 3, 3, + 3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 4, 4, 3, 3, 2, 2, + // END(libdex-widths) +}; + +/* + * Table that maps each opcode to the flags associated with that + * opcode. + */ +static u1 gOpcodeFlagsTable[kNumPackedOpcodes] = { + // BEGIN(libdex-flags); GENERATED AUTOMATICALLY BY opcode-gen + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanReturn, + kInstrCanReturn, + kInstrCanReturn, + kInstrCanReturn, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue, + kInstrCanThrow, + kInstrCanBranch, + kInstrCanBranch, + kInstrCanBranch, + kInstrCanContinue|kInstrCanSwitch, + kInstrCanContinue|kInstrCanSwitch, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue|kInstrCanBranch, + kInstrCanContinue|kInstrCanBranch, + kInstrCanContinue|kInstrCanBranch, + kInstrCanContinue|kInstrCanBranch, + kInstrCanContinue|kInstrCanBranch, + kInstrCanContinue|kInstrCanBranch, + kInstrCanContinue|kInstrCanBranch, + kInstrCanContinue|kInstrCanBranch, + kInstrCanContinue|kInstrCanBranch, + kInstrCanContinue|kInstrCanBranch, + kInstrCanContinue|kInstrCanBranch, + kInstrCanContinue|kInstrCanBranch, + 0, + 0, + 0, + 0, + 0, + 0, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + 0, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + 0, + 0, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + 0, + kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanReturn, + kInstrCanContinue|kInstrCanThrow, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanContinue|kInstrCanThrow|kInstrInvoke, + kInstrCanContinue|kInstrCanThrow, + kInstrCanContinue|kInstrCanThrow, + // END(libdex-flags) +}; + +/* + * Table that maps each opcode to the instruction format associated + * that opcode. + */ +static u1 gInstructionFormatTable[kNumPackedOpcodes] = { + // BEGIN(libdex-formats); GENERATED AUTOMATICALLY BY opcode-gen + kFmt10x, kFmt12x, kFmt22x, kFmt32x, kFmt12x, kFmt22x, kFmt32x, + kFmt12x, kFmt22x, kFmt32x, kFmt11x, kFmt11x, kFmt11x, kFmt11x, + kFmt10x, kFmt11x, kFmt11x, kFmt11x, kFmt11n, kFmt21s, kFmt31i, + kFmt21h, kFmt21s, kFmt31i, kFmt51l, kFmt21h, kFmt21c, kFmt31c, + kFmt21c, kFmt11x, kFmt11x, kFmt21c, kFmt22c, kFmt12x, kFmt21c, + kFmt22c, kFmt35c, kFmt3rc, kFmt31t, kFmt11x, kFmt10t, kFmt20t, + kFmt30t, kFmt31t, kFmt31t, kFmt23x, kFmt23x, kFmt23x, kFmt23x, + kFmt23x, kFmt22t, kFmt22t, kFmt22t, kFmt22t, kFmt22t, kFmt22t, + kFmt21t, kFmt21t, kFmt21t, kFmt21t, kFmt21t, kFmt21t, kFmt00x, + kFmt00x, kFmt00x, kFmt00x, kFmt00x, kFmt00x, kFmt23x, kFmt23x, + kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, + kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt22c, kFmt22c, + kFmt22c, kFmt22c, kFmt22c, kFmt22c, kFmt22c, kFmt22c, kFmt22c, + kFmt22c, kFmt22c, kFmt22c, kFmt22c, kFmt22c, kFmt21c, kFmt21c, + kFmt21c, kFmt21c, kFmt21c, kFmt21c, kFmt21c, kFmt21c, kFmt21c, + kFmt21c, kFmt21c, kFmt21c, kFmt21c, kFmt21c, kFmt35c, kFmt35c, + kFmt35c, kFmt35c, kFmt35c, kFmt00x, kFmt3rc, kFmt3rc, kFmt3rc, + kFmt3rc, kFmt3rc, kFmt00x, kFmt00x, kFmt12x, kFmt12x, kFmt12x, + kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, + kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, + kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt23x, kFmt23x, kFmt23x, + kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, + kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, + kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, + kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, kFmt23x, + kFmt23x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, + kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, + kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, + kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, + kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt12x, kFmt22s, kFmt22s, + kFmt22s, kFmt22s, kFmt22s, kFmt22s, kFmt22s, kFmt22s, kFmt22b, + kFmt22b, kFmt22b, kFmt22b, kFmt22b, kFmt22b, kFmt22b, kFmt22b, + kFmt22b, kFmt22b, kFmt22b, kFmt22c, kFmt22c, kFmt21c, kFmt21c, + kFmt22c, kFmt22c, kFmt22c, kFmt21c, kFmt21c, kFmt00x, kFmt20bc, + kFmt35mi, kFmt3rmi, kFmt35c, kFmt10x, kFmt22cs, kFmt00x, kFmt00x, + kFmt00x, kFmt00x, kFmt00x, kFmt00x, kFmt00x, kFmt45cc, kFmt4rcc, + kFmt35c, kFmt3rc, kFmt21c, kFmt21c, + // END(libdex-formats) +}; + +/* + * Table that maps each opcode to the index type implied by that + * opcode. + */ +static u1 gInstructionIndexTypeTable[kNumPackedOpcodes] = { + // BEGIN(libdex-index-types); GENERATED AUTOMATICALLY BY opcode-gen + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexStringRef, + kIndexStringRef, kIndexTypeRef, kIndexNone, + kIndexNone, kIndexTypeRef, kIndexTypeRef, + kIndexNone, kIndexTypeRef, kIndexTypeRef, + kIndexTypeRef, kIndexTypeRef, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexUnknown, + kIndexUnknown, kIndexUnknown, kIndexUnknown, + kIndexUnknown, kIndexUnknown, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexFieldRef, kIndexFieldRef, + kIndexFieldRef, kIndexFieldRef, kIndexFieldRef, + kIndexFieldRef, kIndexFieldRef, kIndexFieldRef, + kIndexFieldRef, kIndexFieldRef, kIndexFieldRef, + kIndexFieldRef, kIndexFieldRef, kIndexFieldRef, + kIndexFieldRef, kIndexFieldRef, kIndexFieldRef, + kIndexFieldRef, kIndexFieldRef, kIndexFieldRef, + kIndexFieldRef, kIndexFieldRef, kIndexFieldRef, + kIndexFieldRef, kIndexFieldRef, kIndexFieldRef, + kIndexFieldRef, kIndexFieldRef, kIndexMethodRef, + kIndexMethodRef, kIndexMethodRef, kIndexMethodRef, + kIndexMethodRef, kIndexUnknown, kIndexMethodRef, + kIndexMethodRef, kIndexMethodRef, kIndexMethodRef, + kIndexMethodRef, kIndexUnknown, kIndexUnknown, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexNone, + kIndexNone, kIndexNone, kIndexFieldRef, + kIndexFieldRef, kIndexFieldRef, kIndexFieldRef, + kIndexFieldRef, kIndexFieldRef, kIndexFieldRef, + kIndexFieldRef, kIndexFieldRef, kIndexUnknown, + kIndexVaries, kIndexInlineMethod, kIndexInlineMethod, + kIndexMethodRef, kIndexNone, kIndexFieldOffset, + kIndexUnknown, kIndexUnknown, kIndexUnknown, + kIndexUnknown, kIndexUnknown, kIndexUnknown, + kIndexUnknown, kIndexMethodAndProtoRef, kIndexMethodAndProtoRef, + kIndexCallSiteRef, kIndexCallSiteRef, kIndexMethodHandleRef, + kIndexProtoRef, + // END(libdex-index-types) +}; + +/* + * Global InstructionInfoTables struct. + */ +InstructionInfoTables gDexOpcodeInfo = { + gInstructionFormatTable, + gInstructionIndexTypeTable, + gOpcodeFlagsTable, + gInstructionWidthTable +}; + +/* + * Handy macros for helping decode instructions. + */ +#define FETCH(_offset) (insns[(_offset)]) +#define FETCH_u4(_offset) (fetch_u4_impl((_offset), insns)) +#define INST_A(_inst) (((u2)(_inst) >> 8) & 0x0f) +#define INST_B(_inst) ((u2)(_inst) >> 12) +#define INST_AA(_inst) ((_inst) >> 8) + +/* Helper for FETCH_u4, above. */ +static inline u4 fetch_u4_impl(u4 offset, const u2* insns) { + return insns[offset] | ((u4) insns[offset+1] << 16); +} + +/* + * Decode the instruction pointed to by "insns". + * + * Fills out the pieces of "pDec" that are affected by the current + * instruction. Does not touch anything else. + */ +void dexDecodeInstruction(const u2* insns, DecodedInstruction* pDec) +{ + u2 inst = *insns; + Opcode opcode = dexOpcodeFromCodeUnit(inst); + InstructionFormat format = dexGetFormatFromOpcode(opcode); + + pDec->opcode = opcode; + pDec->indexType = dexGetIndexTypeFromOpcode(opcode); + + switch (format) { + case kFmt10x: // op + /* nothing to do; copy the AA bits out for the verifier */ + pDec->vA = INST_AA(inst); + break; + case kFmt12x: // op vA, vB + pDec->vA = INST_A(inst); + pDec->vB = INST_B(inst); + break; + case kFmt11n: // op vA, #+B + pDec->vA = INST_A(inst); + pDec->vB = (s4) (INST_B(inst) << 28) >> 28; // sign extend 4-bit value + break; + case kFmt11x: // op vAA + pDec->vA = INST_AA(inst); + break; + case kFmt10t: // op +AA + pDec->vA = (s1) INST_AA(inst); // sign-extend 8-bit value + break; + case kFmt20t: // op +AAAA + pDec->vA = (s2) FETCH(1); // sign-extend 16-bit value + break; + case kFmt20bc: // [opt] op AA, thing@BBBB + case kFmt21c: // op vAA, thing@BBBB + case kFmt22x: // op vAA, vBBBB + pDec->vA = INST_AA(inst); + pDec->vB = FETCH(1); + break; + case kFmt21s: // op vAA, #+BBBB + case kFmt21t: // op vAA, +BBBB + pDec->vA = INST_AA(inst); + pDec->vB = (s2) FETCH(1); // sign-extend 16-bit value + break; + case kFmt21h: // op vAA, #+BBBB0000[00000000] + pDec->vA = INST_AA(inst); + /* + * The value should be treated as right-zero-extended, but we don't + * actually do that here. Among other things, we don't know if it's + * the top bits of a 32- or 64-bit value. + */ + pDec->vB = FETCH(1); + break; + case kFmt23x: // op vAA, vBB, vCC + pDec->vA = INST_AA(inst); + pDec->vB = FETCH(1) & 0xff; + pDec->vC = FETCH(1) >> 8; + break; + case kFmt22b: // op vAA, vBB, #+CC + pDec->vA = INST_AA(inst); + pDec->vB = FETCH(1) & 0xff; + pDec->vC = (s1) (FETCH(1) >> 8); // sign-extend 8-bit value + break; + case kFmt22s: // op vA, vB, #+CCCC + case kFmt22t: // op vA, vB, +CCCC + pDec->vA = INST_A(inst); + pDec->vB = INST_B(inst); + pDec->vC = (s2) FETCH(1); // sign-extend 16-bit value + break; + case kFmt22c: // op vA, vB, thing@CCCC + case kFmt22cs: // [opt] op vA, vB, field offset CCCC + pDec->vA = INST_A(inst); + pDec->vB = INST_B(inst); + pDec->vC = FETCH(1); + break; + case kFmt30t: // op +AAAAAAAA + pDec->vA = FETCH_u4(1); // signed 32-bit value + break; + case kFmt31t: // op vAA, +BBBBBBBB + case kFmt31c: // op vAA, string@BBBBBBBB + pDec->vA = INST_AA(inst); + pDec->vB = FETCH_u4(1); // 32-bit value + break; + case kFmt32x: // op vAAAA, vBBBB + pDec->vA = FETCH(1); + pDec->vB = FETCH(2); + break; + case kFmt31i: // op vAA, #+BBBBBBBB + pDec->vA = INST_AA(inst); + pDec->vB = FETCH_u4(1); // signed 32-bit value + break; + case kFmt35c: // op {vC, vD, vE, vF, vG}, thing@BBBB + case kFmt35ms: // [opt] invoke-virtual+super + case kFmt35mi: // [opt] inline invoke + { + /* + * Note that the fields mentioned in the spec don't appear in + * their "usual" positions here compared to most formats. This + * was done so that the field names for the argument count and + * reference index match between this format and the corresponding + * range formats (3rc and friends). + * + * Bottom line: The argument count is always in vA, and the + * method constant (or equivalent) is always in vB. + */ + u2 regList; + int count; + + pDec->vA = INST_B(inst); // This is labeled A in the spec. + pDec->vB = FETCH(1); + regList = FETCH(2); + + count = pDec->vA; + + /* + * Copy the argument registers into the arg[] array, and + * also copy the first argument (if any) into vC. (The + * DecodedInstruction structure doesn't have separate + * fields for {vD, vE, vF, vG}, so there's no need to make + * copies of those.) Note that cases 5..2 fall through. + */ + switch (count) { + case 5: { + if (format == kFmt35mi) { + /* A fifth arg is verboten for inline invokes. */ + ALOGW("Invalid arg count in 35mi (5)"); + goto bail; + } + /* + * Per note at the top of this format decoder, the + * fifth argument comes from the A field in the + * instruction, but it's labeled G in the spec. + */ + pDec->arg[4] = INST_A(inst); + FALLTHROUGH_INTENDED; + } + case 4: pDec->arg[3] = (regList >> 12) & 0x0f; FALLTHROUGH_INTENDED; + case 3: pDec->arg[2] = (regList >> 8) & 0x0f; FALLTHROUGH_INTENDED; + case 2: pDec->arg[1] = (regList >> 4) & 0x0f; FALLTHROUGH_INTENDED; + case 1: pDec->vC = pDec->arg[0] = regList & 0x0f; break; + case 0: break; // Valid, but no need to do anything. + default: + ALOGW("Invalid arg count in 35c/35ms/35mi (%d)", count); + goto bail; + } + } + break; + case kFmt3rc: // op {vCCCC .. v(CCCC+AA-1)}, meth@BBBB + case kFmt3rms: // [opt] invoke-virtual+super/range + case kFmt3rmi: // [opt] execute-inline/range + pDec->vA = INST_AA(inst); + pDec->vB = FETCH(1); + pDec->vC = FETCH(2); + break; + case kFmt51l: // op vAA, #+BBBBBBBBBBBBBBBB + pDec->vA = INST_AA(inst); + pDec->vB_wide = FETCH_u4(1) | ((u8) FETCH_u4(3) << 32); + break; + case kFmt45cc: + { + // AG op BBBB FEDC HHHH + pDec->vA = INST_B(inst); // This is labelled A in the spec. + pDec->vB = FETCH(1); // vB meth@BBBB + u2 fedc = FETCH(2); + pDec->vC = fedc & 0xf; + pDec->arg[0] = (fedc >> 4) & 0xf; // vD + pDec->arg[1] = (fedc >> 8) & 0xf; // vE + pDec->arg[2] = (fedc >> 12); // vF + pDec->arg[3] = INST_A(inst); // vG + pDec->arg[4] = FETCH(3); // vH proto@HHHH + } + break; + case kFmt4rcc: + { + // AA op BBBB CCCC HHHH + pDec->vA = INST_AA(inst); + pDec->vB = FETCH(1); + pDec->vC = FETCH(2); + pDec->arg[4] = FETCH(3); // vH proto@HHHH + } + break; + default: + ALOGW("Can't decode unexpected format %d (op=%d)", format, opcode); + assert(false); + break; + } + +bail: + ; +} + +/* + * Return the width of the specified instruction, or 0 if not defined. Also + * works for special OP_NOP entries, including switch statement data tables + * and array data. + */ +size_t dexGetWidthFromInstruction(const u2* insns) +{ + size_t width; + + if (*insns == kPackedSwitchSignature) { + width = 4 + insns[1] * 2; + } else if (*insns == kSparseSwitchSignature) { + width = 2 + insns[1] * 4; + } else if (*insns == kArrayDataSignature) { + u2 elemWidth = insns[1]; + u4 len = insns[2] | (((u4)insns[3]) << 16); + // The plus 1 is to round up for odd size and width. + width = 4 + (elemWidth * len + 1) / 2; + } else { + width = dexGetWidthFromOpcode(dexOpcodeFromCodeUnit(insns[0])); + } + + return width; +} diff --git a/libdex/InstrUtils.h b/libdex/InstrUtils.h new file mode 100644 index 000000000..c5bf77cb0 --- /dev/null +++ b/libdex/InstrUtils.h @@ -0,0 +1,201 @@ +/* + * 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. + */ + +/* + * Dalvik instruction utility functions. + */ +#ifndef LIBDEX_INSTRUTILS_H_ +#define LIBDEX_INSTRUTILS_H_ + +#include "DexFile.h" +#include "DexOpcodes.h" + +/* + * Possible instruction formats associated with Dalvik opcodes. + * + * See the file opcode-gen/README.txt for information about updating + * opcodes and instruction formats. + */ +enum InstructionFormat { + kFmt00x = 0, // unknown format (also used for "breakpoint" opcode) + kFmt10x, // op + kFmt12x, // op vA, vB + kFmt11n, // op vA, #+B + kFmt11x, // op vAA + kFmt10t, // op +AA + kFmt20bc, // [opt] op AA, thing@BBBB + kFmt20t, // op +AAAA + kFmt22x, // op vAA, vBBBB + kFmt21t, // op vAA, +BBBB + kFmt21s, // op vAA, #+BBBB + kFmt21h, // op vAA, #+BBBB00000[00000000] + kFmt21c, // op vAA, thing@BBBB + kFmt23x, // op vAA, vBB, vCC + kFmt22b, // op vAA, vBB, #+CC + kFmt22t, // op vA, vB, +CCCC + kFmt22s, // op vA, vB, #+CCCC + kFmt22c, // op vA, vB, thing@CCCC + kFmt22cs, // [opt] op vA, vB, field offset CCCC + kFmt30t, // op +AAAAAAAA + kFmt32x, // op vAAAA, vBBBB + kFmt31i, // op vAA, #+BBBBBBBB + kFmt31t, // op vAA, +BBBBBBBB + kFmt31c, // op vAA, string@BBBBBBBB + kFmt35c, // op {vC,vD,vE,vF,vG}, thing@BBBB + kFmt35ms, // [opt] invoke-virtual+super + kFmt3rc, // op {vCCCC .. v(CCCC+AA-1)}, thing@BBBB + kFmt3rms, // [opt] invoke-virtual+super/range + kFmt51l, // op vAA, #+BBBBBBBBBBBBBBBB + kFmt35mi, // [opt] inline invoke + kFmt3rmi, // [opt] inline invoke/range + kFmt45cc, // op {vC, vD, vE, vF, vG}, meth@BBBB, proto@HHHH + kFmt4rcc, // op {VCCCC .. v(CCCC+AA-1)}, meth@BBBB, proto@HHHH +}; + +/* + * Types of indexed reference that are associated with opcodes whose + * formats include such an indexed reference (e.g., 21c and 35c). + */ +enum InstructionIndexType { + kIndexUnknown = 0, + kIndexNone, // has no index + kIndexVaries, // "It depends." Used for throw-verification-error + kIndexTypeRef, // type reference index + kIndexStringRef, // string reference index + kIndexMethodRef, // method reference index + kIndexFieldRef, // field reference index + kIndexInlineMethod, // inline method index (for inline linked methods) + kIndexVtableOffset, // vtable offset (for static linked methods) + kIndexFieldOffset, // field offset (for static linked fields) + kIndexMethodAndProtoRef, // method index and proto index + kIndexCallSiteRef, // call site index + kIndexMethodHandleRef, // constant method handle reference index + kIndexProtoRef, // constant prototype reference index +}; + +/* + * Instruction width implied by an opcode's format; a value in the + * range 0 to 5. Note that there are special "pseudo-instructions" + * which are used to encode switch and data tables, and these don't + * have a fixed width. See dexGetWidthFromInstruction(), below. + */ +typedef u1 InstructionWidth; + +/* + * Opcode control flow flags, used by the verifier and JIT. + */ +typedef u1 OpcodeFlags; +enum OpcodeFlagsBits { + kInstrCanBranch = 1, // conditional or unconditional branch + kInstrCanContinue = 1 << 1, // flow can continue to next statement + kInstrCanSwitch = 1 << 2, // switch statement + kInstrCanThrow = 1 << 3, // could cause an exception to be thrown + kInstrCanReturn = 1 << 4, // returns, no additional statements + kInstrInvoke = 1 << 5, // a flavor of invoke +}; + +/* + * Struct that includes a pointer to each of the opcode information + * tables. + * + * Note: We use "u1*" here instead of the names of the enumerated + * types to guarantee that elements don't use much space. We hold out + * hope for a standard way to indicate the size of an enumerated type + * that works for both C and C++, but in the mean time, this will + * suffice. + */ +struct InstructionInfoTables { + u1* formats; /* InstructionFormat elements */ + u1* indexTypes; /* InstructionIndexType elements */ + OpcodeFlags* flags; + InstructionWidth* widths; +}; + +/* + * Global InstructionInfoTables struct. + */ +extern InstructionInfoTables gDexOpcodeInfo; + +/* + * Holds the contents of a decoded instruction. + */ +struct DecodedInstruction { + u4 vA; + u4 vB; + u8 vB_wide; /* for kFmt51l */ + u4 vC; + u4 arg[5]; /* vC/D/E/F/G in invoke or filled-new-array */ + Opcode opcode; + InstructionIndexType indexType; +}; + +/* + * Return the instruction width of the specified opcode, or 0 if not defined. + */ +DEX_INLINE size_t dexGetWidthFromOpcode(Opcode opcode) +{ + assert((u4) opcode < kNumPackedOpcodes); + return gDexOpcodeInfo.widths[opcode]; +} + +/* + * Return the width of the specified instruction, or 0 if not defined. Also + * works for special OP_NOP entries, including switch statement data tables + * and array data. + */ +size_t dexGetWidthFromInstruction(const u2* insns); + +/* + * Returns the flags for the specified opcode. + */ +DEX_INLINE OpcodeFlags dexGetFlagsFromOpcode(Opcode opcode) +{ + assert((u4) opcode < kNumPackedOpcodes); + return gDexOpcodeInfo.flags[opcode]; +} + +/* + * Returns true if the given flags represent a goto (unconditional branch). + */ +DEX_INLINE bool dexIsGoto(OpcodeFlags flags) +{ + return (flags & (kInstrCanBranch | kInstrCanContinue)) == kInstrCanBranch; +} + +/* + * Return the instruction format for the specified opcode. + */ +DEX_INLINE InstructionFormat dexGetFormatFromOpcode(Opcode opcode) +{ + assert((u4) opcode < kNumPackedOpcodes); + return (InstructionFormat) gDexOpcodeInfo.formats[opcode]; +} + +/* + * Return the instruction index type for the specified opcode. + */ +DEX_INLINE InstructionIndexType dexGetIndexTypeFromOpcode(Opcode opcode) +{ + assert((u4) opcode < kNumPackedOpcodes); + return (InstructionIndexType) gDexOpcodeInfo.indexTypes[opcode]; +} + +/* + * Decode the instruction pointed to by "insns". + */ +void dexDecodeInstruction(const u2* insns, DecodedInstruction* pDec); + +#endif // LIBDEX_INSTRUTILS_H_ diff --git a/libdex/Leb128.cpp b/libdex/Leb128.cpp new file mode 100644 index 000000000..ed09e19aa --- /dev/null +++ b/libdex/Leb128.cpp @@ -0,0 +1,65 @@ +/* + * 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. + */ + +/* + * Functions for interpreting LEB128 (little endian base 128) values + */ + +#include "Leb128.h" + +/* + * Reads an unsigned LEB128 value, updating the given pointer to point + * just past the end of the read value and also indicating whether the + * value was syntactically valid. The only syntactically *invalid* + * values are ones that are five bytes long where the final byte has + * any but the low-order four bits set. Additionally, if the limit is + * passed as non-NULL and bytes would need to be read past the limit, + * then the read is considered invalid. + */ +int readAndVerifyUnsignedLeb128(const u1** pStream, const u1* limit, + bool* okay) { + const u1* ptr = *pStream; + int result = readUnsignedLeb128(pStream); + + if (((limit != NULL) && (*pStream > limit)) + || (((*pStream - ptr) == 5) && (ptr[4] > 0x0f))) { + *okay = false; + } + + return result; +} + +/* + * Reads a signed LEB128 value, updating the given pointer to point + * just past the end of the read value and also indicating whether the + * value was syntactically valid. The only syntactically *invalid* + * values are ones that are five bytes long where the final byte has + * any but the low-order four bits set. Additionally, if the limit is + * passed as non-NULL and bytes would need to be read past the limit, + * then the read is considered invalid. + */ +int readAndVerifySignedLeb128(const u1** pStream, const u1* limit, + bool* okay) { + const u1* ptr = *pStream; + int result = readSignedLeb128(pStream); + + if (((limit != NULL) && (*pStream > limit)) + || (((*pStream - ptr) == 5) && (ptr[4] > 0x0f))) { + *okay = false; + } + + return result; +} diff --git a/libdex/Leb128.h b/libdex/Leb128.h new file mode 100644 index 000000000..21f4edaa1 --- /dev/null +++ b/libdex/Leb128.h @@ -0,0 +1,164 @@ +/* + * 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. + */ + +/* + * Functions for interpreting LEB128 (little endian base 128) values + */ + +#ifndef LIBDEX_LEB128_H_ +#define LIBDEX_LEB128_H_ + +#include "DexFile.h" + +/* + * Reads an unsigned LEB128 value, updating the given pointer to point + * just past the end of the read value. This function tolerates + * non-zero high-order bits in the fifth encoded byte. + */ +DEX_INLINE int readUnsignedLeb128(const u1** pStream) { + const u1* ptr = *pStream; + int result = *(ptr++); + + if (result > 0x7f) { + int cur = *(ptr++); + result = (result & 0x7f) | ((cur & 0x7f) << 7); + if (cur > 0x7f) { + cur = *(ptr++); + result |= (cur & 0x7f) << 14; + if (cur > 0x7f) { + cur = *(ptr++); + result |= (cur & 0x7f) << 21; + if (cur > 0x7f) { + /* + * Note: We don't check to see if cur is out of + * range here, meaning we tolerate garbage in the + * high four-order bits. + */ + cur = *(ptr++); + result |= cur << 28; + } + } + } + } + + *pStream = ptr; + return result; +} + +/* + * Reads a signed LEB128 value, updating the given pointer to point + * just past the end of the read value. This function tolerates + * non-zero high-order bits in the fifth encoded byte. + */ +DEX_INLINE int readSignedLeb128(const u1** pStream) { + const u1* ptr = *pStream; + int result = *(ptr++); + + if (result <= 0x7f) { + result = (result << 25) >> 25; + } else { + int cur = *(ptr++); + result = (result & 0x7f) | ((cur & 0x7f) << 7); + if (cur <= 0x7f) { + result = (result << 18) >> 18; + } else { + cur = *(ptr++); + result |= (cur & 0x7f) << 14; + if (cur <= 0x7f) { + result = (result << 11) >> 11; + } else { + cur = *(ptr++); + result |= (cur & 0x7f) << 21; + if (cur <= 0x7f) { + result = (result << 4) >> 4; + } else { + /* + * Note: We don't check to see if cur is out of + * range here, meaning we tolerate garbage in the + * high four-order bits. + */ + cur = *(ptr++); + result |= cur << 28; + } + } + } + } + + *pStream = ptr; + return result; +} + +/* + * Reads an unsigned LEB128 value, updating the given pointer to point + * just past the end of the read value and also indicating whether the + * value was syntactically valid. The only syntactically *invalid* + * values are ones that are five bytes long where the final byte has + * any but the low-order four bits set. Additionally, if the limit is + * passed as non-NULL and bytes would need to be read past the limit, + * then the read is considered invalid. + */ +int readAndVerifyUnsignedLeb128(const u1** pStream, const u1* limit, + bool* okay); + +/* + * Reads a signed LEB128 value, updating the given pointer to point + * just past the end of the read value and also indicating whether the + * value was syntactically valid. The only syntactically *invalid* + * values are ones that are five bytes long where the final byte has + * any but the low-order four bits set. Additionally, if the limit is + * passed as non-NULL and bytes would need to be read past the limit, + * then the read is considered invalid. + */ +int readAndVerifySignedLeb128(const u1** pStream, const u1* limit, bool* okay); + + +/* + * Writes a 32-bit value in unsigned ULEB128 format. + * + * Returns the updated pointer. + */ +DEX_INLINE u1* writeUnsignedLeb128(u1* ptr, u4 data) +{ + while (true) { + u1 out = data & 0x7f; + if (out != data) { + *ptr++ = out | 0x80; + data >>= 7; + } else { + *ptr++ = out; + break; + } + } + + return ptr; +} + +/* + * Returns the number of bytes needed to encode "val" in ULEB128 form. + */ +DEX_INLINE int unsignedLeb128Size(u4 data) +{ + int count = 0; + + do { + data >>= 7; + count++; + } while (data != 0); + + return count; +} + +#endif diff --git a/libdex/OptInvocation.cpp b/libdex/OptInvocation.cpp new file mode 100644 index 000000000..35d6262e5 --- /dev/null +++ b/libdex/OptInvocation.cpp @@ -0,0 +1,177 @@ +/* + * 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. + */ + +/* + * Utility functions for dealing with optimized dex files. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <errno.h> + +#include "OptInvocation.h" +#include "DexFile.h" + +static const char* kCacheDirectoryName = "dalvik-cache"; + +#if defined(__aarch64__) +static const char* kInstructionSet = "arm64"; +#elif defined(__arm__) +static const char* kInstructionSet = "arm"; +#elif defined(__i386__) +static const char* kInstructionSet = "x86"; +#elif defined(__mips__) +static const char* kInstructionSet = "mips"; +#elif defined(__x86_64__) +static const char* kInstructionSet = "x86_64"; +#else +#error Unsupported instruction set. +#endif + +static int dexOptMkdir(const char* path, int mode) +{ +#ifdef _WIN32 + return mkdir(path); +#else + return mkdir(path, mode); +#endif +} + +/* + * Given the filename of a .jar or .dex file, construct the DEX file cache + * name. + * + * For a Jar, "subFileName" is the name of the entry (usually "classes.dex"). + * For a DEX, it may be NULL. + * + * Returns a newly-allocated string, or NULL on failure. + */ +char* dexOptGenerateCacheFileName(const char* fileName, const char* subFileName) +{ + char nameBuf[512]; + char absoluteFile[sizeof(nameBuf)]; + const size_t kBufLen = sizeof(nameBuf) - 1; + const char* dataRoot; + char* cp; + + /* + * Get the absolute path of the Jar or DEX file. + */ + absoluteFile[0] = '\0'; + if (fileName[0] != '/') { + /* + * Generate the absolute path. This doesn't do everything it + * should, e.g. if filename is "./out/whatever" it doesn't crunch + * the leading "./" out, but it'll do. + */ + if (getcwd(absoluteFile, kBufLen) == NULL) { + ALOGE("Can't get CWD while opening jar file"); + return NULL; + } + strncat(absoluteFile, "/", kBufLen - strlen(absoluteFile)); + } + strncat(absoluteFile, fileName, kBufLen - strlen(absoluteFile)); + + /* + * Append the name of the Jar file entry, if any. This is not currently + * required, but will be if we start putting more than one DEX file + * in a Jar. + */ + if (subFileName != NULL) { + strncat(absoluteFile, "/", kBufLen - strlen(absoluteFile)); + strncat(absoluteFile, subFileName, kBufLen - strlen(absoluteFile)); + } + + /* Turn the path into a flat filename by replacing + * any slashes after the first one with '@' characters. + */ + cp = absoluteFile + 1; + while (*cp != '\0') { + if (*cp == '/') { + *cp = '@'; + } + cp++; + } + + /* Build the name of the cache directory. + */ + dataRoot = getenv("ANDROID_DATA"); + if (dataRoot == NULL) + dataRoot = "/data"; + snprintf(nameBuf, kBufLen, "%s/%s", dataRoot, kCacheDirectoryName); + if (strcmp(dataRoot, "/data") != 0) { + int result = dexOptMkdir(nameBuf, 0700); + if (result != 0 && errno != EEXIST) { + ALOGE("Failed to create dalvik-cache directory %s: %s", nameBuf, strerror(errno)); + return NULL; + } + } + snprintf(nameBuf, kBufLen, "%s/%s/%s", dataRoot, kCacheDirectoryName, kInstructionSet); + if (strcmp(dataRoot, "/data") != 0) { + int result = dexOptMkdir(nameBuf, 0700); + if (result != 0 && errno != EEXIST) { + ALOGE("Failed to create dalvik-cache directory %s: %s", nameBuf, strerror(errno)); + return NULL; + } + } + + /* Tack on the file name for the actual cache file path. + */ + strncat(nameBuf, absoluteFile, kBufLen - strlen(nameBuf)); + + ALOGV("Cache file for '%s' '%s' is '%s'", fileName, subFileName, nameBuf); + return strdup(nameBuf); +} + +/* + * Create a skeletal "opt" header in a new file. Most of the fields are + * initialized to garbage, but we fill in "dexOffset" so others can + * see how large the header is. + * + * "fd" must be positioned at the start of the file. On return, it will + * be positioned just past the header, and the place where the DEX data + * should go. + * + * Returns 0 on success, errno on failure. + */ +int dexOptCreateEmptyHeader(int fd) +{ + DexOptHeader optHdr; + ssize_t actual; + + assert(lseek(fd, 0, SEEK_CUR) == 0); + + /* + * The data is only expected to be readable on the current system, so + * we just write the structure. We do need the file offset to be 64-bit + * aligned to fulfill a DEX requirement. + */ + assert((sizeof(optHdr) & 0x07) == 0); + memset(&optHdr, 0xff, sizeof(optHdr)); + optHdr.dexOffset = sizeof(optHdr); + actual = write(fd, &optHdr, sizeof(optHdr)); + if (actual != sizeof(optHdr)) { + ALOGE("opt header write failed: %s", strerror(errno)); + return errno; + } + + return 0; +} diff --git a/libdex/OptInvocation.h b/libdex/OptInvocation.h new file mode 100644 index 000000000..3f32b94bf --- /dev/null +++ b/libdex/OptInvocation.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +/* + * Utility functions related to "dexopt". + */ +#ifndef LIBDEX_OPTINVOCATION_H_ +#define LIBDEX_OPTINVOCATION_H_ + +/* + * Utility routines, used by the VM. + */ +char* dexOptGenerateCacheFileName(const char* fileName, + const char* subFileName); +int dexOptCreateEmptyHeader(int fd); + +#endif // LIBDEX_OPTINVOCATION_H_ diff --git a/libdex/SysUtil.cpp b/libdex/SysUtil.cpp new file mode 100644 index 000000000..3a1cba3b5 --- /dev/null +++ b/libdex/SysUtil.cpp @@ -0,0 +1,370 @@ +/* + * 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. + */ + +/* + * System utilities. + */ +#include "DexFile.h" +#include "SysUtil.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#if !defined(__MINGW32__) +# include <sys/mman.h> +#endif +#include <limits.h> +#include <errno.h> + +/* + * TEMP_FAILURE_RETRY is defined by some, but not all, versions of + * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's + * not already defined, then define it here. + */ +#ifndef TEMP_FAILURE_RETRY +/* Used to retry syscalls that can return EINTR. */ +#define TEMP_FAILURE_RETRY(exp) ({ \ + typeof (exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; }) +#endif + +/* + * Create an anonymous shared memory segment large enough to hold "length" + * bytes. The actual segment may be larger because mmap() operates on + * page boundaries (usually 4K). + */ +static void* sysCreateAnonShmem(size_t length) +{ +#if !defined(__MINGW32__) + void* ptr; + + ptr = mmap(NULL, length, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANON, -1, 0); + if (ptr == MAP_FAILED) { + ALOGW("mmap(%d, RW, SHARED|ANON) failed: %s", (int) length, + strerror(errno)); + return NULL; + } + + return ptr; +#else + ALOGE("sysCreateAnonShmem not implemented."); + return NULL; +#endif +} + +/* + * Create a private anonymous storage area. + */ +int sysCreatePrivateMap(size_t length, MemMapping* pMap) +{ + void* memPtr; + + memPtr = sysCreateAnonShmem(length); + if (memPtr == NULL) + return -1; + + pMap->addr = pMap->baseAddr = memPtr; + pMap->length = pMap->baseLength = length; + return 0; +} + +/* + * Determine the current offset and remaining length of the open file. + */ +static int getFileStartAndLength(int fd, off_t *start_, size_t *length_) +{ + off_t start, end; + size_t length; + + assert(start_ != NULL); + assert(length_ != NULL); + + start = lseek(fd, 0L, SEEK_CUR); + end = lseek(fd, 0L, SEEK_END); + (void) lseek(fd, start, SEEK_SET); + + if (start == (off_t) -1 || end == (off_t) -1) { + ALOGE("could not determine length of file"); + return -1; + } + + length = end - start; + if (length == 0) { + ALOGE("file is empty"); + return -1; + } + + *start_ = start; + *length_ = length; + + return 0; +} + +#if defined(__MINGW32__) +int sysFakeMapFile(int fd, MemMapping* pMap) +{ + /* No MMAP, just fake it by copying the bits. + For Win32 we could use MapViewOfFile if really necessary + (see libs/utils/FileMap.cpp). + */ + off_t start; + size_t length; + void* memPtr; + + assert(pMap != NULL); + + if (getFileStartAndLength(fd, &start, &length) < 0) + return -1; + + memPtr = malloc(length); + if (read(fd, memPtr, length) < 0) { + ALOGW("read(fd=%d, start=%d, length=%d) failed: %s", (int) length, + fd, (int) start, strerror(errno)); + free(memPtr); + return -1; + } + + pMap->baseAddr = pMap->addr = memPtr; + pMap->baseLength = pMap->length = length; + + return 0; +} +#endif + +/* + * Map a file (from fd's current offset) into a private, read-write memory + * segment that will be marked read-only (a/k/a "writable read-only"). The + * file offset must be a multiple of the system page size. + * + * In some cases the mapping will be fully writable (e.g. for files on + * FAT filesystems). + * + * On success, returns 0 and fills out "pMap". On failure, returns a nonzero + * value and does not disturb "pMap". + */ +int sysMapFileInShmemWritableReadOnly(int fd, MemMapping* pMap) +{ +#if !defined(__MINGW32__) + off_t start; + size_t length; + void* memPtr; + + assert(pMap != NULL); + + if (getFileStartAndLength(fd, &start, &length) < 0) + return -1; + + memPtr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_FILE | MAP_PRIVATE, + fd, start); + if (memPtr == MAP_FAILED) { + ALOGW("mmap(%d, R/W, FILE|PRIVATE, %d, %d) failed: %s", (int) length, + fd, (int) start, strerror(errno)); + return -1; + } + if (mprotect(memPtr, length, PROT_READ) < 0) { + /* this fails with EACCESS on FAT filesystems, e.g. /sdcard */ + int err = errno; + ALOGV("mprotect(%p, %zd, PROT_READ) failed: %s", + memPtr, length, strerror(err)); + ALOGD("mprotect(RO) failed (%d), file will remain read-write", err); + } + + pMap->baseAddr = pMap->addr = memPtr; + pMap->baseLength = pMap->length = length; + + return 0; +#else + return sysFakeMapFile(fd, pMap); +#endif +} + +/* + * Map part of a file into a shared, read-only memory segment. The "start" + * offset is absolute, not relative. + * + * On success, returns 0 and fills out "pMap". On failure, returns a nonzero + * value and does not disturb "pMap". + */ +int sysMapFileSegmentInShmem(int fd, off_t start, size_t length, + MemMapping* pMap) +{ +#if !defined(__MINGW32__) + size_t actualLength; + off_t actualStart; + int adjust; + void* memPtr; + + assert(pMap != NULL); + + /* adjust to be page-aligned */ + adjust = start % SYSTEM_PAGE_SIZE; + actualStart = start - adjust; + actualLength = length + adjust; + + memPtr = mmap(NULL, actualLength, PROT_READ, MAP_FILE | MAP_SHARED, + fd, actualStart); + if (memPtr == MAP_FAILED) { + ALOGW("mmap(%d, R, FILE|SHARED, %d, %d) failed: %s", + (int) actualLength, fd, (int) actualStart, strerror(errno)); + return -1; + } + + pMap->baseAddr = memPtr; + pMap->baseLength = actualLength; + pMap->addr = (char*)memPtr + adjust; + pMap->length = length; + + LOGVV("mmap seg (st=%d ln=%d): bp=%p bl=%d ad=%p ln=%d", + (int) start, (int) length, + pMap->baseAddr, (int) pMap->baseLength, + pMap->addr, (int) pMap->length); + + return 0; +#else + ALOGE("sysMapFileSegmentInShmem not implemented."); + return -1; +#endif +} + +/* + * Change the access rights on one or more pages to read-only or read-write. + * + * Returns 0 on success. + */ +int sysChangeMapAccess(void* addr, size_t length, int wantReadWrite, + MemMapping* pMap) +{ +#if !defined(__MINGW32__) + /* + * Verify that "addr" is part of this mapping file. + */ + if (addr < pMap->baseAddr || + (u1*)addr >= (u1*)pMap->baseAddr + pMap->baseLength) + { + ALOGE("Attempted to change %p; map is %p - %p", + addr, pMap->baseAddr, (u1*)pMap->baseAddr + pMap->baseLength); + return -1; + } + + /* + * Align "addr" to a page boundary and adjust "length" appropriately. + * (The address must be page-aligned, the length doesn't need to be, + * but we do need to ensure we cover the same range.) + */ + u1* alignAddr = (u1*) ((uintptr_t) addr & ~(SYSTEM_PAGE_SIZE-1)); + size_t alignLength = length + ((u1*) addr - alignAddr); + + //ALOGI("%p/%zd --> %p/%zd", addr, length, alignAddr, alignLength); + int prot = wantReadWrite ? (PROT_READ|PROT_WRITE) : (PROT_READ); + if (mprotect(alignAddr, alignLength, prot) != 0) { + ALOGV("mprotect (%p,%zd,%d) failed: %s", + alignAddr, alignLength, prot, strerror(errno)); + return (errno != 0) ? errno : -1; + } +#endif + + /* for "fake" mapping, no need to do anything */ + return 0; +} + +/* + * Release a memory mapping. + */ +void sysReleaseShmem(MemMapping* pMap) +{ +#if !defined(__MINGW32__) + if (pMap->baseAddr == NULL && pMap->baseLength == 0) + return; + + if (munmap(pMap->baseAddr, pMap->baseLength) < 0) { + ALOGW("munmap(%p, %zd) failed: %s", + pMap->baseAddr, pMap->baseLength, strerror(errno)); + } else { + ALOGV("munmap(%p, %zd) succeeded", pMap->baseAddr, pMap->baseLength); + pMap->baseAddr = NULL; + pMap->baseLength = 0; + } +#else + /* Free the bits allocated by sysMapFileInShmem. */ + if (pMap->baseAddr != NULL) { + free(pMap->baseAddr); + pMap->baseAddr = NULL; + } + pMap->baseLength = 0; +#endif +} + +/* + * Make a copy of a MemMapping. + */ +void sysCopyMap(MemMapping* dst, const MemMapping* src) +{ + memcpy(dst, src, sizeof(MemMapping)); +} + +/* + * Write until all bytes have been written. + * + * Returns 0 on success, or an errno value on failure. + */ +int sysWriteFully(int fd, const void* buf, size_t count, const char* logMsg) +{ + while (count != 0) { + ssize_t actual = TEMP_FAILURE_RETRY(write(fd, buf, count)); + if (actual < 0) { + int err = errno; + ALOGE("%s: write failed: %s", logMsg, strerror(err)); + return err; + } else if (actual != (ssize_t) count) { + ALOGD("%s: partial write (will retry): (%d of %zd)", + logMsg, (int) actual, count); + buf = (const void*) (((const u1*) buf) + actual); + } + count -= actual; + } + + return 0; +} + +/* See documentation comment in header file. */ +int sysCopyFileToFile(int outFd, int inFd, size_t count) +{ + const size_t kBufSize = 32768; + unsigned char buf[kBufSize]; + + while (count != 0) { + size_t getSize = (count > kBufSize) ? kBufSize : count; + + ssize_t actual = TEMP_FAILURE_RETRY(read(inFd, buf, getSize)); + if (actual != (ssize_t) getSize) { + ALOGW("sysCopyFileToFile: copy read failed (%d vs %zd)", + (int) actual, getSize); + return -1; + } + + if (sysWriteFully(outFd, buf, getSize, "sysCopyFileToFile") != 0) + return -1; + + count -= getSize; + } + + return 0; +} diff --git a/libdex/SysUtil.h b/libdex/SysUtil.h new file mode 100644 index 000000000..c02ec6eda --- /dev/null +++ b/libdex/SysUtil.h @@ -0,0 +1,109 @@ +/* + * 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. + */ + +/* + * System utilities. + */ +#ifndef LIBDEX_SYSUTIL_H_ +#define LIBDEX_SYSUTIL_H_ + +#include <sys/types.h> + +/* + * System page size. Normally you're expected to get this from + * sysconf(_SC_PAGESIZE) or some system-specific define (usually PAGESIZE + * or PAGE_SIZE). If we use a simple #define the compiler can generate + * appropriate masks directly, so we define it here and verify it as the + * VM is starting up. + * + * Must be a power of 2. + */ +#ifdef PAGE_SHIFT +#define SYSTEM_PAGE_SIZE (1<<PAGE_SHIFT) +#else +#define SYSTEM_PAGE_SIZE 4096 +#endif + +/* + * Use this to keep track of mapped segments. + */ +struct MemMapping { + void* addr; /* start of data */ + size_t length; /* length of data */ + + void* baseAddr; /* page-aligned base address */ + size_t baseLength; /* length of mapping */ +}; + +/* + * Copy a map. + */ +void sysCopyMap(MemMapping* dst, const MemMapping* src); + +/* + * Map a file (from fd's current offset) into a shared, read-only memory + * segment that can be made writable. (In some cases, such as when + * mapping a file on a FAT filesystem, the result may be fully writable.) + * + * On success, "pMap" is filled in, and zero is returned. + */ +int sysMapFileInShmemWritableReadOnly(int fd, MemMapping* pMap); + +/* + * Map part of a file into a shared, read-only memory segment. + * + * On success, "pMap" is filled in, and zero is returned. + */ +int sysMapFileSegmentInShmem(int fd, off_t start, size_t length, + MemMapping* pMap); + +/* + * Create a private anonymous mapping, useful for large allocations. + * + * On success, "pMap" is filled in, and zero is returned. + */ +int sysCreatePrivateMap(size_t length, MemMapping* pMap); + +/* + * Change the access rights on one or more pages. If "wantReadWrite" is + * zero, the pages will be made read-only; otherwise they will be read-write. + * + * Returns 0 on success. + */ +int sysChangeMapAccess(void* addr, size_t length, int wantReadWrite, + MemMapping* pmap); + +/* + * Release the pages associated with a shared memory segment. + * + * This does not free "pMap"; it just releases the memory. + */ +void sysReleaseShmem(MemMapping* pMap); + +/* + * Write until all bytes have been written. + * + * Returns 0 on success, or an errno value on failure. + */ +int sysWriteFully(int fd, const void* buf, size_t count, const char* logMsg); + +/* + * Copy the given number of bytes from one fd to another. Returns + * 0 on success, -1 on failure. + */ +int sysCopyFileToFile(int outFd, int inFd, size_t count); + +#endif // LIBDEX_SYSUTIL_H_ diff --git a/libdex/ZipArchive.h b/libdex/ZipArchive.h new file mode 100644 index 000000000..206afd55c --- /dev/null +++ b/libdex/ZipArchive.h @@ -0,0 +1,86 @@ +/* + * 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. + */ + +/* + * Read-only access to Zip archives, with minimal heap allocation. + */ +#ifndef LIBDEX_ZIPARCHIVE_H_ +#define LIBDEX_ZIPARCHIVE_H_ + +#include <ziparchive/zip_archive.h> + +#include "SysUtil.h" +#include "DexFile.h" // need DEX_INLINE + +/* + * Open a Zip archive. + * + * On success, returns 0 and populates "pArchive". Returns nonzero errno + * value on failure. + */ +DEX_INLINE int dexZipOpenArchive(const char* fileName, ZipArchiveHandle* pArchive) { + return OpenArchive(fileName, pArchive); +} + +/* + * Like dexZipOpenArchive, but takes a file descriptor open for reading + * at the start of the file. The descriptor must be mappable (this does + * not allow access to a stream). + * + * "debugFileName" will appear in error messages, but is not otherwise used. + */ +DEX_INLINE int dexZipOpenArchiveFd(int fd, const char* debugFileName, + ZipArchiveHandle* pArchive) { + return OpenArchiveFd(fd, debugFileName, pArchive); +} + +/* + * Close archive, releasing resources associated with it. + * + * Depending on the implementation this could unmap pages used by classes + * stored in a Jar. This should only be done after unloading classes. + */ +DEX_INLINE void dexZipCloseArchive(ZipArchiveHandle archive) { + CloseArchive(archive); +} + +/* + * Return the archive's file descriptor. + */ +DEX_INLINE int dexZipGetArchiveFd(const ZipArchiveHandle pArchive) { + return GetFileDescriptor(pArchive); +} + +/* + * Find an entry in the Zip archive, by name. Returns NULL if the entry + * was not found. + */ +DEX_INLINE int dexZipFindEntry(const ZipArchiveHandle pArchive, + const char* entryName, ZipEntry* data) { + return FindEntry(pArchive, ZipString(entryName), data); +} + +/* + * Uncompress and write an entry to a file descriptor. + * + * Returns 0 on success. + */ +DEX_INLINE int dexZipExtractEntryToFile(ZipArchiveHandle handle, + ZipEntry* entry, int fd) { + return ExtractEntryToFile(handle, entry, fd); +} + +#endif // LIBDEX_ZIPARCHIVE_H_ diff --git a/libdex/sha1.cpp b/libdex/sha1.cpp new file mode 100644 index 000000000..d59493451 --- /dev/null +++ b/libdex/sha1.cpp @@ -0,0 +1,512 @@ +/* + * Tweaked in various ways for Google/Android: + * - Changed from .cpp to .c. + * - Made argument to SHA1Update a const pointer, and enabled + * SHA1HANDSOFF. This incurs a speed penalty but prevents us from + * trashing the input. + * - Include <endian.h> to get endian info. + * - Split a small piece into a header file. + */ + +/* +sha1sum: inspired by md5sum. + +SHA-1 in C +By Steve Reid <steve@edmweb.com> +100% Public Domain + +----------------- +Modified 7/98 +By James H. Brown <jbrown@burgoyne.com> +Still 100% Public Domain + +bit machines +Routine SHA1Update changed from + void SHA1Update(SHA1_CTX* context, unsigned char* data, + unsigned int len) +to + void SHA1Update(SHA1_CTX* context, unsigned char* data, + unsigned long len) + +The 'len' parameter was declared an int which works fine on 32 +bit machines. However, on 16 bit machines an int is too small +for the shifts being done against it. This caused the hash +function to generate incorrect values if len was greater than +8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update(). + +Since the file IO in main() reads 16K at a time, any file 8K or +larger would be guaranteed to generate the wrong hash (e.g. +Test Vector #3, a million "a"s). + +I also changed the declaration of variables i & j in SHA1Update +to unsigned long from unsigned int for the same reason. + +These changes should make no difference to any 32 bit +implementations since an int and a long are the same size in +those environments. + +-- +I also corrected a few compiler warnings generated by Borland +C. +1. Added #include <process.h> for exit() prototype +2. Removed unused variable 'j' in SHA1Final +3. Changed exit(0) to return(0) at end of main. + +ALL changes I made can be located by searching for comments +containing 'JHB' + +----------------- +Modified 13 August 2000 +By Michael Paul Johnson <mpj@cryptography.org> +Still 100% Public Domain + +Changed command line syntax, added feature to automatically +check files against their previous SHA-1 check values, kind of +like md5sum does. Added functions hexval, verifyfile, +and sha1file. Rewrote main(). +----------------- + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +#define SHA1HANDSOFF /*Copies data before messing with it.*/ + +/*#define CMDLINE * include main() and file processing */ + +#include "sha1.h" + +#include <stdio.h> +#include <string.h> +#ifdef __BORLANDC__ +#include <dir.h> +#include <dos.h> +#include <process.h> /* prototype for exit() - JHB + needed for Win32, but chokes Linux - MPJ */ +#define X_LITTLE_ENDIAN /* This should be #define'd if true.*/ +#else +# include <unistd.h> +# include <stdlib.h> +//# include <endian.h> +//# if __BYTE_ORDER == __LITTLE_ENDIAN +# define X_LITTLE_ENDIAN +//# endif +#endif +#include <ctype.h> + +#define LINESIZE 2048 + +static void SHA1Transform(unsigned long state[5], + const unsigned char buffer[64]); + +#define rol(value,bits) \ + (((value)<<(bits))|((value)>>(32-(bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from + SSLeay */ +#ifdef X_LITTLE_ENDIAN +#define blk0(i) (block->l[i]=(rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif +#define blk(i) (block->l[(i)&15] = rol(block->l[((i)+13)&15]^block->l[((i)+8)&15] \ + ^block->l[((i)+2)&15]^block->l[(i)&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=(((w)&((x)^(y)))^(y))+blk0(i)+0x5A827999+rol(v,5);(w)=rol(w,30); +#define R1(v,w,x,y,z,i) z+=(((w)&((x)^(y)))^(y))+blk(i)+0x5A827999+rol(v,5);(w)=rol(w,30); +#define R2(v,w,x,y,z,i) z+=((w)^(x)^(y))+blk(i)+0x6ED9EBA1+rol(v,5);(w)=rol(w,30); +#define R3(v,w,x,y,z,i) z+=((((w)|(x))&(y))|((w)&(x)))+blk(i)+0x8F1BBCDC+rol(v,5);(w)=rol(w,30); +#define R4(v,w,x,y,z,i) z+=((w)^(x)^(y))+blk(i)+0xCA62C1D6+rol(v,5);(w)=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +static void SHA1Transform(unsigned long state[5], + const unsigned char buffer[64]) +{ +unsigned long a, b, c, d, e; +union CHAR64LONG16 { + unsigned char c[64]; + unsigned long l[16]; +}; +CHAR64LONG16* block; +#ifdef SHA1HANDSOFF +static unsigned char workspace[64]; + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); +#else + block = (CHAR64LONG16*)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); + R0(c,d,e,a,b, 3); R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); + R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); R0(c,d,e,a,b, 8); + R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); + R0(a,b,c,d,e,15); R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); + R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); R2(a,b,c,d,e,20); + R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); + R2(d,e,a,b,c,27); R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); + R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); R2(d,e,a,b,c,32); + R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); + R2(b,c,d,e,a,39); R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); + R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); R3(b,c,d,e,a,44); + R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); + R3(e,a,b,c,d,51); R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); + R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); R3(e,a,b,c,d,56); + R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); + R4(c,d,e,a,b,63); R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); + R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); R4(c,d,e,a,b,68); + R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); + R4(a,b,c,d,e,75); R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); + R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ +/* a = b = c = d = e = 0; Nice try, but the compiler +optimizes this out, anyway, and it produces an annoying +warning. */ +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update(SHA1_CTX* context, const unsigned char* data, + unsigned long len) /* JHB */ +{ + unsigned long i, j; /* JHB */ + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) + context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) + { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else + i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final(unsigned char digest[HASHSIZE], SHA1_CTX* +context) +{ +unsigned long i; /* JHB */ +unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) + { + finalcount[i] = (unsigned char)((context->count[(i>=4? + 0:1)]>>((3-(i&3))*8))&255); + /* Endian independent */ + } + SHA1Update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + SHA1Update(context, (unsigned char *)"\0", 1); + } + SHA1Update(context, finalcount, 8); + /* Should cause a SHA1Transform() */ + for (i = 0; i < HASHSIZE; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + memset(context->buffer, 0, 64); + memset(context->state, 0, HASHSIZE); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +#ifdef SHA1HANDSOFF + /* make SHA1Transform overwrite it's own static vars */ + SHA1Transform(context->state, context->buffer); +#endif +} + + + +#ifdef CMDLINE + +/* sha1file computes the SHA-1 hash of the named file and puts + it in the 20-byte array digest. If fname is NULL, stdin is + assumed. +*/ +void sha1file(char *fname, unsigned char* digest) +{ + int bytesread; + SHA1_CTX context; + unsigned char buffer[16384]; + FILE* f; + + if (fname) + { + f = fopen(fname, "rb"); + if (!f) + { + fprintf(stderr, "Can't open %s\n", fname); + memset(digest, 0, HASHSIZE); + return; + } + } + else + { + f = stdin; + } + SHA1Init(&context); + while (!feof(f)) + { + bytesread = fread(buffer, 1, 16384, f); + SHA1Update(&context, buffer, bytesread); + } + SHA1Final(digest, &context); + if (fname) + fclose(f); +} + +/* Convert ASCII hexidecimal digit to 4-bit value. */ +unsigned char hexval(char c) +{ + unsigned char h; + + c = toupper(c); + if (c >= 'A') + h = c - 'A' + 10; + else + h = c - '0'; + return h; +} + +/* Verify a file created with sha1sum by redirecting output + to a file. */ +int verifyfile(char *fname) +{ + int j, k; + int found = 0; + unsigned char digest[HASHSIZE]; + unsigned char expected_digest[HASHSIZE]; + FILE *checkfile; + char checkline[LINESIZE]; + char *s; + unsigned char err; + + checkfile = fopen(fname, "rt"); + if (!checkfile) + { + fprintf(stderr, "Can't open %s\n", fname); + return(0); + } + do + { + s = fgets(checkline, LINESIZE, checkfile); + if (s) + { + if ((strlen(checkline)>26)&& + 1 /*(!strncmp(checkline,"SHA1=", 5))*/) + { + /* Overwrite newline. */ + checkline[strlen(checkline)-1]=0; + found = 1; + + /* Read expected check value. */ + for (k=0, j=5; k < HASHSIZE; k++) + { + expected_digest[k]=hexval(checkline[j++]); + expected_digest[k]=(expected_digest[k]<<4) + +hexval(checkline[j++]); + } + + /* Compute fingerprints */ + s = checkline+46; + sha1file(s, digest); + + /* Compare fingerprints */ + err = 0; + for (k=0; k<HASHSIZE; k++) + err |= digest[k]- + expected_digest[k]; + if (err) + { + fprintf(stderr, "FAILED: %s\n" + " EXPECTED: ", s); + for (k=0; k<HASHSIZE; k++) + fprintf(stderr, "%02X", + expected_digest[k]); + fprintf(stderr,"\n FOUND: "); + for (k=0; k<HASHSIZE; k++) + fprintf(stderr, "%02X", digest[k]); + fprintf(stderr, "\n"); + } + else + { + printf("OK: %s\n", s); + } + } + } + } while (s); + fclose(checkfile); + return found; +} + + + +void syntax(char *progname) +{ + printf("\nsyntax:\n" + "%s [-c|-h][-q] file name[s]\n" + " -c = check files against previous check values\n" + " -g = generate SHA-1 check values (default action)\n" + " -h = display this help\n" + "For example,\n" + "sha1sum test.txt > check.txt\n" + "generates check value for test.txt in check.txt, and\n" + "sha1sum -c check.txt\n" + "checks test.txt against the check value in check.txt\n", + progname); + exit(1); +} + + +/**********************************************************/ + +int main(int argc, char** argv) +{ + int i, j, k; + int check = 0; + int found = 0; + unsigned char digest[HASHSIZE]; + unsigned char expected_digest[HASHSIZE]; + FILE *checkfile; + char checkline[LINESIZE]; + char *s; +#ifdef __BORLANDC__ + struct ffblk f; + int done; + char path[MAXPATH]; + char drive[MAXDRIVE]; + char dir[MAXDIR]; + char name[MAXFILE]; + char ext[MAXEXT]; +#endif + unsigned char err; + + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + switch (argv[i][1]) + { + case 'c': + case 'C': + check = 1; + break; + case 'g': + case 'G': + check = 0; + break; + default: + syntax(argv[0]); + } + } + } + + for (i=1; i<argc; i++) + { + if (argv[i][0] != '-') + { +#ifdef __BORLANDC__ + fnsplit(argv[i], drive, dir, name, ext); + done = findfirst(argv[i], &f, FA_RDONLY | + FA_HIDDEN|FA_SYSTEM|FA_ARCH); + while (!done) + { + sprintf(path, "%s%s%s", drive, dir, f.ff_name); + s = path; +#else + s = argv[i]; +#endif + + if (check) + { /* Check fingerprint file. */ + found |= verifyfile(s); + } + else + { /* Generate fingerprints & write to + stdout. */ + sha1file(s, digest); + //printf("SHA1="); + for (j=0; j<HASHSIZE; j++) + printf("%02x", digest[j]); + printf(" %s\n", s); + found = 1; + } + +#ifdef __BORLANDC__ + done = findnext(&f); + } +#endif + + } + } + if (!found) + { + if (check) + { + fprintf(stderr, + "No SHA1 lines found in %s\n", + argv[i]); + } + else + { + fprintf(stderr, "No files checked.\n"); + syntax(argv[0]); + } + } + return(0); /* JHB */ +} + +#endif /*CMDLINE*/ diff --git a/libdex/sha1.h b/libdex/sha1.h new file mode 100644 index 000000000..28907dedc --- /dev/null +++ b/libdex/sha1.h @@ -0,0 +1,20 @@ +/* + * See "sha1.cpp" for author info. + */ +#ifndef LIBDEX_SHA1_H_ +#define LIBDEX_SHA1_H_ + +struct SHA1_CTX { + unsigned long state[5]; + unsigned long count[2]; + unsigned char buffer[64]; +}; + +#define HASHSIZE 20 + +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, const unsigned char* data, + unsigned long len); +void SHA1Final(unsigned char digest[HASHSIZE], SHA1_CTX* context); + +#endif // LIBDEX_SHA1_H_ diff --git a/tools/dexdeps/src/com/android/dexdeps/DexData.java b/tools/dexdeps/src/com/android/dexdeps/DexData.java index 1b6cc98ed..cc1978c86 100644 --- a/tools/dexdeps/src/com/android/dexdeps/DexData.java +++ b/tools/dexdeps/src/com/android/dexdeps/DexData.java @@ -18,8 +18,6 @@ package com.android.dexdeps; import java.io.IOException; import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -37,7 +35,7 @@ public class DexData { private ClassDefItem[] mClassDefs; private byte tmpBuf[] = new byte[4]; - private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; + private boolean isBigEndian = false; /** * Constructs a new DexData for this file. @@ -101,7 +99,7 @@ public class DexData { /* do nothing */ } else if (mHeaderItem.endianTag == HeaderItem.REVERSE_ENDIAN_CONSTANT){ /* file is big-endian (!), reverse future reads */ - mByteOrder = ByteOrder.BIG_ENDIAN; + isBigEndian = true; } else { System.err.println("Endian constant has unexpected value " + Integer.toHexString(mHeaderItem.endianTag)); @@ -109,27 +107,26 @@ public class DexData { } seek(8+4+20); // magic, checksum, signature - ByteBuffer buffer = readByteBuffer(Integer.BYTES * 20); - mHeaderItem.fileSize = buffer.getInt(); - mHeaderItem.headerSize = buffer.getInt(); - /*mHeaderItem.endianTag =*/ buffer.getInt(); - /*mHeaderItem.linkSize =*/ buffer.getInt(); - /*mHeaderItem.linkOff =*/ buffer.getInt(); - /*mHeaderItem.mapOff =*/ buffer.getInt(); - mHeaderItem.stringIdsSize = buffer.getInt(); - mHeaderItem.stringIdsOff = buffer.getInt(); - mHeaderItem.typeIdsSize = buffer.getInt(); - mHeaderItem.typeIdsOff = buffer.getInt(); - mHeaderItem.protoIdsSize = buffer.getInt(); - mHeaderItem.protoIdsOff = buffer.getInt(); - mHeaderItem.fieldIdsSize = buffer.getInt(); - mHeaderItem.fieldIdsOff = buffer.getInt(); - mHeaderItem.methodIdsSize = buffer.getInt(); - mHeaderItem.methodIdsOff = buffer.getInt(); - mHeaderItem.classDefsSize = buffer.getInt(); - mHeaderItem.classDefsOff = buffer.getInt(); - /*mHeaderItem.dataSize =*/ buffer.getInt(); - /*mHeaderItem.dataOff =*/ buffer.getInt(); + mHeaderItem.fileSize = readInt(); + mHeaderItem.headerSize = readInt(); + /*mHeaderItem.endianTag =*/ readInt(); + /*mHeaderItem.linkSize =*/ readInt(); + /*mHeaderItem.linkOff =*/ readInt(); + /*mHeaderItem.mapOff =*/ readInt(); + mHeaderItem.stringIdsSize = readInt(); + mHeaderItem.stringIdsOff = readInt(); + mHeaderItem.typeIdsSize = readInt(); + mHeaderItem.typeIdsOff = readInt(); + mHeaderItem.protoIdsSize = readInt(); + mHeaderItem.protoIdsOff = readInt(); + mHeaderItem.fieldIdsSize = readInt(); + mHeaderItem.fieldIdsOff = readInt(); + mHeaderItem.methodIdsSize = readInt(); + mHeaderItem.methodIdsOff = readInt(); + mHeaderItem.classDefsSize = readInt(); + mHeaderItem.classDefsOff = readInt(); + /*mHeaderItem.dataSize =*/ readInt(); + /*mHeaderItem.dataOff =*/ readInt(); } /** @@ -146,7 +143,9 @@ public class DexData { //System.out.println("reading " + count + " strings"); seek(mHeaderItem.stringIdsOff); - readByteBuffer(Integer.BYTES * count).asIntBuffer().get(stringOffsets); + for (int i = 0; i < count; i++) { + stringOffsets[i] = readInt(); + } mStrings = new String[count]; @@ -167,10 +166,9 @@ public class DexData { //System.out.println("reading " + count + " typeIds"); seek(mHeaderItem.typeIdsOff); - ByteBuffer buffer = readByteBuffer(Integer.BYTES * count); for (int i = 0; i < count; i++) { mTypeIds[i] = new TypeIdItem(); - mTypeIds[i].descriptorIdx = buffer.getInt(); + mTypeIds[i].descriptorIdx = readInt(); //System.out.println(i + ": " + mTypeIds[i].descriptorIdx + // " " + mStrings[mTypeIds[i].descriptorIdx]); @@ -186,16 +184,15 @@ public class DexData { //System.out.println("reading " + count + " protoIds"); seek(mHeaderItem.protoIdsOff); - ByteBuffer buffer = readByteBuffer(Integer.BYTES * 3 * count); /* * Read the proto ID items. */ for (int i = 0; i < count; i++) { mProtoIds[i] = new ProtoIdItem(); - mProtoIds[i].shortyIdx = buffer.getInt(); - mProtoIds[i].returnTypeIdx = buffer.getInt(); - mProtoIds[i].parametersOff = buffer.getInt(); + mProtoIds[i].shortyIdx = readInt(); + mProtoIds[i].returnTypeIdx = readInt(); + mProtoIds[i].parametersOff = readInt(); //System.out.println(i + ": " + mProtoIds[i].shortyIdx + // " " + mStrings[mProtoIds[i].shortyIdx]); @@ -215,11 +212,10 @@ public class DexData { } else { seek(offset); int size = readInt(); // #of entries in list - buffer = readByteBuffer(Short.BYTES * size); protoId.types = new int[size]; for (int j = 0; j < size; j++) { - protoId.types[j] = buffer.getShort() & 0xffff; + protoId.types[j] = readShort() & 0xffff; } } } @@ -234,12 +230,11 @@ public class DexData { //System.out.println("reading " + count + " fieldIds"); seek(mHeaderItem.fieldIdsOff); - ByteBuffer buffer = readByteBuffer((Integer.BYTES + Short.BYTES * 2) * count); for (int i = 0; i < count; i++) { mFieldIds[i] = new FieldIdItem(); - mFieldIds[i].classIdx = buffer.getShort() & 0xffff; - mFieldIds[i].typeIdx = buffer.getShort() & 0xffff; - mFieldIds[i].nameIdx = buffer.getInt(); + mFieldIds[i].classIdx = readShort() & 0xffff; + mFieldIds[i].typeIdx = readShort() & 0xffff; + mFieldIds[i].nameIdx = readInt(); //System.out.println(i + ": " + mFieldIds[i].nameIdx + // " " + mStrings[mFieldIds[i].nameIdx]); @@ -255,12 +250,11 @@ public class DexData { //System.out.println("reading " + count + " methodIds"); seek(mHeaderItem.methodIdsOff); - ByteBuffer buffer = readByteBuffer((Integer.BYTES + Short.BYTES * 2) * count); for (int i = 0; i < count; i++) { mMethodIds[i] = new MethodIdItem(); - mMethodIds[i].classIdx = buffer.getShort() & 0xffff; - mMethodIds[i].protoIdx = buffer.getShort() & 0xffff; - mMethodIds[i].nameIdx = buffer.getInt(); + mMethodIds[i].classIdx = readShort() & 0xffff; + mMethodIds[i].protoIdx = readShort() & 0xffff; + mMethodIds[i].nameIdx = readInt(); //System.out.println(i + ": " + mMethodIds[i].nameIdx + // " " + mStrings[mMethodIds[i].nameIdx]); @@ -276,18 +270,17 @@ public class DexData { //System.out.println("reading " + count + " classDefs"); seek(mHeaderItem.classDefsOff); - ByteBuffer buffer = readByteBuffer(Integer.BYTES * 8 * count); for (int i = 0; i < count; i++) { mClassDefs[i] = new ClassDefItem(); - mClassDefs[i].classIdx = buffer.getInt(); + mClassDefs[i].classIdx = readInt(); - /* access_flags = */ buffer.getInt(); - /* superclass_idx = */ buffer.getInt(); - /* interfaces_off = */ buffer.getInt(); - /* source_file_idx = */ buffer.getInt(); - /* annotations_off = */ buffer.getInt(); - /* class_data_off = */ buffer.getInt(); - /* static_values_off = */ buffer.getInt(); + /* access_flags = */ readInt(); + /* superclass_idx = */ readInt(); + /* interfaces_off = */ readInt(); + /* source_file_idx = */ readInt(); + /* annotations_off = */ readInt(); + /* class_data_off = */ readInt(); + /* static_values_off = */ readInt(); //System.out.println(i + ": " + mClassDefs[i].classIdx + " " + // mStrings[mTypeIds[mClassDefs[i].classIdx].descriptorIdx]); @@ -459,12 +452,24 @@ public class DexData { } /** + * Reads a signed 16-bit integer, byte-swapping if necessary. + */ + short readShort() throws IOException { + mDexFile.readFully(tmpBuf, 0, 2); + if (isBigEndian) { + return (short) ((tmpBuf[1] & 0xff) | ((tmpBuf[0] & 0xff) << 8)); + } else { + return (short) ((tmpBuf[0] & 0xff) | ((tmpBuf[1] & 0xff) << 8)); + } + } + + /** * Reads a signed 32-bit integer, byte-swapping if necessary. */ int readInt() throws IOException { mDexFile.readFully(tmpBuf, 0, 4); - if (mByteOrder == ByteOrder.BIG_ENDIAN) { + if (isBigEndian) { return (tmpBuf[3] & 0xff) | ((tmpBuf[2] & 0xff) << 8) | ((tmpBuf[1] & 0xff) << 16) | ((tmpBuf[0] & 0xff) << 24); } else { @@ -492,38 +497,29 @@ public class DexData { } /** - * Reads bytes and transforms them into a ByteBuffer with the desired byte order set, from which - * primitive values can be read. - */ - ByteBuffer readByteBuffer(int size) throws IOException { - byte bytes[] = new byte[size]; - mDexFile.read(bytes); - return ByteBuffer.wrap(bytes).order(mByteOrder); - } - - /** * Reads a UTF-8 string. * - * We don't know how long the UTF-8 string is, so we try to read the worst case amount of bytes. - * - * Note that the dex file pointer will likely be at a wrong location after this operation, which - * means it can't be used in the middle of sequential reads. + * We don't know how long the UTF-8 string is, so we have to read one + * byte at a time. We could make an educated guess based on the + * utf16_size and seek back if we get it wrong, but seeking backward + * may cause the underlying implementation to reload I/O buffers. */ String readString() throws IOException { int utf16len = readUnsignedLeb128(); byte inBuf[] = new byte[utf16len * 3]; // worst case + int idx; - int bytesRead = mDexFile.read(inBuf); - for (int i = 0; i < bytesRead; i++) { - if (inBuf[i] == 0) { - bytesRead = i; + for (idx = 0; idx < inBuf.length; idx++) { + byte val = readByte(); + if (val == 0) break; - } + inBuf[idx] = val; } - return new String(inBuf, 0, bytesRead, "UTF-8"); + return new String(inBuf, 0, idx, "UTF-8"); } + /* * ======================================================================= * Internal "structure" declarations |