/*
* 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 kVerifyChecksum = false;
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);
const DexClassDef* pClassDef =
(const DexClassDef*) (pDexFile->baseAddr + classDefOff);
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;
}