You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
539 lines
16 KiB
539 lines
16 KiB
/* |
|
* 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; |
|
}
|
|
|