From df2a55dee586bc7de69949cb0acd454ef4eb45ff Mon Sep 17 00:00:00 2001 From: "JesusFreke@JesusFreke.com" Date: Tue, 8 Sep 2009 06:26:34 +0000 Subject: [PATCH] Initial commit of deodexerant git-svn-id: https://smali.googlecode.com/svn/trunk@434 55b6fa8a-2a1e-11de-a435-ffa8d773f76a --- deodexerant/Android.mk | 36 ++ deodexerant/Main.c | 780 +++++++++++++++++++++++++++++++++++++++++ deodexerant/README | 26 ++ 3 files changed, 842 insertions(+) create mode 100644 deodexerant/Android.mk create mode 100644 deodexerant/Main.c create mode 100644 deodexerant/README diff --git a/deodexerant/Android.mk b/deodexerant/Android.mk new file mode 100644 index 00000000..d7a7b9bc --- /dev/null +++ b/deodexerant/Android.mk @@ -0,0 +1,36 @@ +# 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. + + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + Main.c + +LOCAL_C_INCLUDES := \ + dalvik/include \ + dalvik \ + dalvik/libdex \ + dalvik/vm \ + $(JNI_H_INCLUDE) + +LOCAL_SHARED_LIBRARIES := \ + libdvm \ + libcutils + +LOCAL_MODULE:= deodexerant + + +include $(BUILD_EXECUTABLE) diff --git a/deodexerant/Main.c b/deodexerant/Main.c new file mode 100644 index 00000000..15d005f6 --- /dev/null +++ b/deodexerant/Main.c @@ -0,0 +1,780 @@ +/* + * 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. + */ +/* + * Command-line invocation of the Dalvik VM. + */ +#include "jni.h" +#include "Dalvik.h" +#include "libdex/OptInvocation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "utils/Log.h" + +typedef struct InlineSub { + Method* method; + int inlineIdx; +} InlineSub; + + +static ClassObject* findCommonSuperclass(ClassObject* c1, ClassObject* c2); + +//The following methods yanked from vm/analysis/CodeVerify.c +/* + * Compute the "class depth" of a class. This is the distance from the + * class to the top of the tree, chasing superclass links. java.lang.Object + * has a class depth of 0. + */ +static int getClassDepth(ClassObject* clazz) +{ + int depth = 0; + + while (clazz->super != NULL) { + clazz = clazz->super; + depth++; + } + return depth; +} + +/* + * Given two classes, walk up the superclass tree to find a common + * ancestor. (Called from findCommonSuperclass().) + * + * TODO: consider caching the class depth in the class object so we don't + * have to search for it here. + */ +static ClassObject* digForSuperclass(ClassObject* c1, ClassObject* c2) +{ + int depth1, depth2; + + depth1 = getClassDepth(c1); + depth2 = getClassDepth(c2); + + /* pull the deepest one up */ + if (depth1 > depth2) { + while (depth1 > depth2) { + c1 = c1->super; + depth1--; + } + } else { + while (depth2 > depth1) { + c2 = c2->super; + depth2--; + } + } + + /* walk up in lock-step */ + while (c1 != c2) { + c1 = c1->super; + c2 = c2->super; + + assert(c1 != NULL && c2 != NULL); + } + + return c1; +} + +/* + * Merge two array classes. We can't use the general "walk up to the + * superclass" merge because the superclass of an array is always Object. + * We want String[] + Integer[] = Object[]. This works for higher dimensions + * as well, e.g. String[][] + Integer[][] = Object[][]. + * + * If Foo1 and Foo2 are subclasses of Foo, Foo1[] + Foo2[] = Foo[]. + * + * If Class implements Type, Class[] + Type[] = Type[]. + * + * If the dimensions don't match, we want to convert to an array of Object + * with the least dimension, e.g. String[][] + String[][][][] = Object[][]. + * + * This gets a little awkward because we may have to ask the VM to create + * a new array type with the appropriate element and dimensions. However, we + * shouldn't be doing this often. + */ +static ClassObject* findCommonArraySuperclass(ClassObject* c1, ClassObject* c2) +{ + ClassObject* arrayClass = NULL; + ClassObject* commonElem; + int i, numDims; + + assert(c1->arrayDim > 0); + assert(c2->arrayDim > 0); + + if (c1->arrayDim == c2->arrayDim) { + //commonElem = digForSuperclass(c1->elementClass, c2->elementClass); + commonElem = findCommonSuperclass(c1->elementClass, c2->elementClass); + numDims = c1->arrayDim; + } else { + if (c1->arrayDim < c2->arrayDim) + numDims = c1->arrayDim; + else + numDims = c2->arrayDim; + commonElem = c1->super; // == java.lang.Object + } + + /* walk from the element to the (multi-)dimensioned array type */ + for (i = 0; i < numDims; i++) { + arrayClass = dvmFindArrayClassForElement(commonElem); + commonElem = arrayClass; + } + + return arrayClass; +} + +/* + * Find the first common superclass of the two classes. We're not + * interested in common interfaces. + * + * The easiest way to do this for concrete classes is to compute the "class + * depth" of each, move up toward the root of the deepest one until they're + * at the same depth, then walk both up to the root until they match. + * + * If both classes are arrays of non-primitive types, we need to merge + * based on array depth and element type. + * + * If one class is an interface, we check to see if the other class/interface + * (or one of its predecessors) implements the interface. If so, we return + * the interface; otherwise, we return Object. + * + * NOTE: we continue the tradition of "lazy interface handling". To wit, + * suppose we have three classes: + * One implements Fancy, Free + * Two implements Fancy, Free + * Three implements Free + * where Fancy and Free are unrelated interfaces. The code requires us + * to merge One into Two. Ideally we'd use a common interface, which + * gives us a choice between Fancy and Free, and no guidance on which to + * use. If we use Free, we'll be okay when Three gets merged in, but if + * we choose Fancy, we're hosed. The "ideal" solution is to create a + * set of common interfaces and carry that around, merging further references + * into it. This is a pain. The easy solution is to simply boil them + * down to Objects and let the runtime invokeinterface call fail, which + * is what we do. + */ +static ClassObject* findCommonSuperclass(ClassObject* c1, ClassObject* c2) +{ + assert(!dvmIsPrimitiveClass(c1) && !dvmIsPrimitiveClass(c2)); + + if (c1 == c2) + return c1; + + if (dvmIsInterfaceClass(c1) && dvmImplements(c2, c1)) { + return c1; + } + if (dvmIsInterfaceClass(c2) && dvmImplements(c1, c2)) { + return c2; + } + + if (dvmIsArrayClass(c1) && dvmIsArrayClass(c2) && + !dvmIsPrimitiveClass(c1->elementClass) && + !dvmIsPrimitiveClass(c2->elementClass)) + { + return findCommonArraySuperclass(c1, c2); + } + + return digForSuperclass(c1, c2); +} + + + + + + + + + + + + + + +//method yanked from vm/analysis/DexOptimize.c +/* + * Try to load all classes in the specified DEX. If they have some sort + * of broken dependency, e.g. their superclass lives in a different DEX + * that wasn't previously loaded into the bootstrap class path, loading + * will fail. This is the desired behavior. + * + * We have no notion of class loader at this point, so we load all of + * the classes with the bootstrap class loader. It turns out this has + * exactly the behavior we want, and has no ill side effects because we're + * running in a separate process and anything we load here will be forgotten. + * + * We set the CLASS_MULTIPLE_DEFS flag here if we see multiple definitions. + * This works because we only call here as part of optimization / pre-verify, + * not during verification as part of loading a class into a running VM. + * + * This returns "false" if the world is too screwed up to do anything + * useful at all. + */ +loadAllClasses(DvmDex* pDvmDex) +{ + u4 count = pDvmDex->pDexFile->pHeader->classDefsSize; + u4 idx; + int loaded = 0; + + dvmSetBootPathExtraDex(pDvmDex); + + /* + * We have some circularity issues with Class and Object that are most + * easily avoided by ensuring that Object is never the first thing we + * try to find. Take care of that here. (We only need to do this when + * loading classes from the DEX file that contains Object, and only + * when Object comes first in the list, but it costs very little to + * do it in all cases.) + */ + if (dvmFindSystemClass("Ljava/lang/Class;") == NULL) { + return false; + } + + for (idx = 0; idx < count; idx++) { + const DexClassDef* pClassDef; + const char* classDescriptor; + ClassObject* newClass; + + pClassDef = dexGetClassDef(pDvmDex->pDexFile, idx); + classDescriptor = + dexStringByTypeIdx(pDvmDex->pDexFile, pClassDef->classIdx); + + //newClass = dvmDefineClass(pDexFile, classDescriptor, + // NULL); + newClass = dvmFindSystemClassNoInit(classDescriptor); + if (newClass == NULL) { + dvmClearOptException(dvmThreadSelf()); + } else if (newClass->pDvmDex != pDvmDex) { + /* + * We don't load the new one, and we tag the first one found + * with the "multiple def" flag so the resolver doesn't try + * to make it available. + */ + SET_CLASS_FLAG(newClass, CLASS_MULTIPLE_DEFS); + } else { + loaded++; + } + } + + dvmSetBootPathExtraDex(NULL); + return true; +} + +Field *lookupField(char *classType, int offset) +{ + ClassObject *clazz; + if (classType[0] == '[') + clazz = dvmFindArrayClass(classType, NULL); + else + clazz = dvmFindSystemClassNoInit(classType); + + if (clazz == NULL) + return NULL; + + int i; + do + { + InstField *pField = clazz->ifields; + for (i=0; iifieldCount; i++, pField++) + { + if (pField->byteOffset == offset) + return &pField->field; + } + + clazz = clazz->super; + } while (clazz != NULL); + + return NULL; +} + +Method *lookupInlineMethod(int index) +{ + InlineOperation *inlineTable = dvmGetInlineOpsTable(); + int count = dvmGetInlineOpsTableLength(); + + if (index >= count) + return NULL; + + InlineOperation *inlineOp = &inlineTable[index]; + + ClassObject *clazz = dvmFindSystemClassNoInit(inlineOp->classDescriptor); + if (clazz == NULL) + return NULL; + + Method *method = dvmFindDirectMethodByDescriptor(clazz, inlineOp->methodName, inlineOp->methodSignature); + if (method != NULL) + return method; + + method = dvmFindVirtualMethodByDescriptor(clazz, inlineOp->methodName, inlineOp->methodSignature); + return method; +} + +Method *lookupVirtualMethod(char *classType, int index, ClassObject **clazz) +{ + if (classType[0] == '[') + *clazz = dvmFindArrayClass(classType, NULL); + else + *clazz = dvmFindSystemClassNoInit(classType); + + if (*clazz == NULL) + return NULL; + + //interface classes don't have virtual methods, by definition. But it's possible + //to call virtual methods defined on the Object class via an interface type + if (dvmIsInterfaceClass(*clazz)) + *clazz = dvmFindSystemClassNoInit("Ljava/lang/Object;"); + + if (index >= (*clazz)->vtableCount) + return NULL; + + Method *method = (*clazz)->vtable[index]; + + return method; +} + +Method *lookupSuperMethod(char *classType, int index, ClassObject **clazz) +{ + if (classType[0] == '[') + *clazz = dvmFindArrayClass(classType, NULL); + else + *clazz = dvmFindSystemClassNoInit(classType); + + if (*clazz == NULL) + return NULL; + *clazz = (*clazz)->super; + if (*clazz == NULL) + return NULL; + + if (index >= (*clazz)->vtableCount) + return NULL; + + Method *method = (*clazz)->vtable[index]; + + return method; +} + +ClassObject *lookupSuperclass(char *classType) +{ + ClassObject *clazz = dvmFindSystemClassNoInit(classType); + + if (clazz == NULL) + return NULL; + + return clazz->super; +} + +/* + * Parse arguments. Most of it just gets passed through to the VM. The + * JNI spec defines a handful of standard arguments. + */ +int main(int argc, char* const argv[]) +{ + const char* inputFileName; + JavaVM* vm = NULL; + JNIEnv* env = NULL; + DvmDex* pDvmDex = NULL; + DexClassLookup* pClassLookup; + + if (argc != 3) { + fprintf(stderr, "usage: deodexerant \n"); + return 1; + } + + inputFileName = argv[1]; + + struct stat inputInfo; + + if (stat(inputFileName, &inputInfo) != 0) { + fprintf(stderr, "could not stat '%s' : %s\n", inputFileName, strerror(errno)); + return 1; + } + + int odexFd = open(inputFileName, O_RDWR, 0644); + if (odexFd < 0) { + fprintf(stderr, "Unable to open '%s': %s\n", inputFileName, strerror(errno)); + return 1; + } + + int port = atoi(argv[2]); + int socketFd = socket(AF_INET, SOCK_STREAM, 0); + if (socketFd < 0) + { + fprintf(stderr, "Unable to open socket\n"); + return 1; + } + + struct sockaddr_in serverAddress, clientAddress; + bzero((char *)&serverAddress, sizeof(serverAddress)); + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = INADDR_ANY; + serverAddress.sin_port = htons(port); + if (bind(socketFd, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0) + { + fprintf(stderr, "Unable to bind socket\n"); + return 1; + } + + const char* bcp = getenv("BOOTCLASSPATH"); + if (bcp == NULL) { + fprintf(stderr, "BOOTCLASSPATH not set\n"); + return 1; + } + + + DexClassVerifyMode verifyMode = VERIFY_MODE_ALL; + DexOptimizerMode dexOptMode = OPTIMIZE_MODE_VERIFIED; + + if (dvmPrepForDexOpt(bcp, dexOptMode, verifyMode, + 0) != 0) + { + fprintf(stderr, "VM init failed\n"); + return 1; + } + + /* + * Map the entire file (so we don't have to worry about page + * alignment). The expectation is that the output file contains + * our DEX data plus room for a small header. + */ + bool success; + void* mapAddr; + mapAddr = mmap(NULL, inputInfo.st_size, PROT_READ|PROT_WRITE, + MAP_SHARED, odexFd, 0); + if (mapAddr == MAP_FAILED) { + printf(stderr, "unable to mmap DEX cache: %s\n", strerror(errno)); + return 1; + } + + if (dvmDexFileOpenPartial(mapAddr + *((int *)(mapAddr+8)), *((int *)(mapAddr+12)), &pDvmDex) != 0) { + fprintf(stderr, "Unable to create DexFile\n"); + return 1; + } + + pClassLookup = dexCreateClassLookup(pDvmDex->pDexFile); + if (pClassLookup == NULL) + { + fprintf(stderr, "unable to create class lookup\n"); + return 1; + } + + pDvmDex->pDexFile->pClassLookup = pClassLookup; + + if (!loadAllClasses(pDvmDex)) + { + fprintf(stderr, "error while loading classes\n"); + return 1; + } + + + + + listen(socketFd, 1); + + int clientSocketLength = sizeof(clientAddress); + int clientFd = accept(socketFd, (struct sockaddr *) &clientAddress, &clientSocketLength); + if (clientFd < 0) + { + fprintf(stderr, "Unable to accept incomming connection\n"); + return 1; + } + + FILE *clientIn = fdopen(clientFd, "r"); + if (clientIn == 0) + { + fprintf(stderr, "Unable to fdopen socket to get input stream\n"); + return 1; + } + + FILE *clientOut = fdopen(dup(clientFd), "w"); + if (clientOut == 0) + { + fprintf(stderr, "Unable to fdopen socket to get output stream\n"); + return 1; + } + + char *command = NULL; + int len = 0; + DexStringCache cache; + dexStringCacheInit(&cache); + + while ((command = fgetln(clientIn, &len)) != NULL) { + while (len > 0 && (command[len-1] == '\r' || command[len-1] == '\n')) + len--; + char *buf = malloc(len+1); + memcpy(buf, command, len); + buf[len] = 0; + + /*struct timeval tv; + gettimeofday(&tv, NULL); + printf("start %07d %s\n", tv.tv_usec, buf);*/ + + char *cmd = strtok(buf, " "); + if (cmd == NULL) { + fprintf(clientOut, "err: error interpreting command\n"); + fflush(clientOut); + continue; + } + + switch (cmd[0]) + { + case 'F' : + { + char *classType = strtok(NULL, " "); + if (classType == NULL) + { + fprintf(clientOut, "err: no classType for field lookup\n"); + fflush(clientOut); + break; + } + + char *offsetStr = strtok(NULL, " "); + if (offsetStr == NULL) + { + fprintf(clientOut, "err: no offset for field lookup\n"); + fflush(clientOut); + break; + } + + char *end; + int offset = strtol(offsetStr, &end, 10); + if (*end != '\0') + { + fprintf(clientOut, "err: offset not a valid number for field lookup\n"); + fflush(clientOut); + break; + } + + Field *field = lookupField(classType, offset); + if (field == NULL) + { + fprintf(clientOut, "err: field not found\n"); + fflush(clientOut); + break; + } + + fprintf(clientOut, "field: %s->%s:%s\n", classType, field->name, field->signature); + fflush(clientOut); + break; + } + case 'I': + { + char *indexStr = strtok(NULL, " "); + if (indexStr == NULL) + { + fprintf(clientOut, "err: no index for inline method lookup\n"); + fflush(clientOut); + break; + } + + char *end; + int index = strtol(indexStr, &end, 10); + if (*end != '\0') + { + fprintf(clientOut, "err: index not a valid number for inline method lookup\n"); + fflush(clientOut); + break; + } + + Method *method = lookupInlineMethod(index); + if (method == NULL) + { + fprintf(clientOut, "err: inline method not found\n"); + fflush(clientOut); + break; + } + + char *methodType; + if (dvmIsStaticMethod(method)) + methodType = "static"; + else if (dvmIsDirectMethod(method)) + methodType = "direct"; + else + methodType = "virtual"; + + fprintf(clientOut, "%s method: %s->%s%s\n", methodType, method->clazz->descriptor, method->name, dexProtoGetMethodDescriptor(&method->prototype, &cache)); + fflush(clientOut); + break; + } + case 'V': + { + char *classType = strtok(NULL, " "); + if (classType == NULL) + { + fprintf(clientOut, "err: no classType for virtual method lookup\n"); + fflush(clientOut); + break; + } + + char *indexStr = strtok(NULL, " "); + if (indexStr == NULL) + { + fprintf(clientOut, "err: no vtable index for virtual method lookup\n"); + fflush(clientOut); + break; + } + + char *end; + int index = strtol(indexStr, &end, 10); + if (*end != '\0') + { + fprintf(clientOut, "err: vtable index not a valid number for virtual method lookup\n"); + fflush(clientOut); + break; + } + + ClassObject *clazz; + Method *method = lookupVirtualMethod(classType, index, &clazz); + if (method == NULL) + { + fprintf(clientOut, "err: method not found\n"); + fflush(clientOut); + break; + } + + fprintf(clientOut, "method: %s->%s%s\n", clazz->descriptor, method->name, dexProtoGetMethodDescriptor(&method->prototype, &cache)); + fflush(clientOut); + break; + } + case 'S': + { + char *classType = strtok(NULL, " "); + if (classType == NULL) + { + fprintf(clientOut, "err: no classType for super method lookup\n"); + fflush(clientOut); + break; + } + + char *indexStr = strtok(NULL, " "); + if (indexStr == NULL) + { + fprintf(clientOut, "err: no vtable index for super method lookup\n"); + fflush(clientOut); + break; + } + + char *end; + int index = strtol(indexStr, &end, 10); + if (*end != '\0') + { + fprintf(clientOut, "err: vtable index not a valid number for super method lookup\n"); + fflush(clientOut); + break; + } + + ClassObject *clazz; + Method *method = lookupSuperMethod(classType, index, &clazz); + if (method == NULL) + { + fprintf(clientOut, "err: method not found\n"); + fflush(clientOut); + break; + } + + fprintf(clientOut, "method: %s->%s%s\n", clazz->descriptor, method->name, dexProtoGetMethodDescriptor(&method->prototype, &cache)); + fflush(clientOut); + break; + } + case 'P': + { + char *classType = strtok(NULL, " "); + if (classType == NULL) + { + fprintf(clientOut, "err: no classType for superclass lookup\n"); + fflush(clientOut); + break; + } + + ClassObject *clazz = lookupSuperclass(classType); + fprintf(clientOut, "class: %s\n", clazz->descriptor); + fflush(clientOut); + break; + } + case 'C': + { + char *classType1 = strtok(NULL, " "); + if (classType1 == NULL) + { + fprintf(clientOut, "err: no classType for common superclass lookup\n"); + fflush(clientOut); + break; + } + + ClassObject *clazz1; + if (classType1[0] == '[') + clazz1 = dvmFindArrayClass(classType1, NULL); + else + clazz1 = dvmFindSystemClassNoInit(classType1); + + if (clazz1 == NULL) + { + fprintf(clientOut, "err: class %s could not be found for common superclass lookup\n"); + fflush(clientOut); + break; + } + + char *classType2 = strtok(NULL, " "); + if (classType2 == NULL) + { + fprintf(clientOut, "err: no classType for common superclass lookup\n"); + fflush(clientOut); + break; + } + + ClassObject *clazz2; + if (classType2[0] == '[') + clazz2 = dvmFindArrayClass(classType2, NULL); + else + clazz2 = dvmFindSystemClassNoInit(classType2); + + if (clazz2 == NULL) + { + fprintf(clientOut, "err: class %s could not be found for common superclass lookup\n"); + fflush(clientOut); + break; + } + + ClassObject *clazz = findCommonSuperclass(clazz1, clazz2); + fprintf(clientOut, "class: %s\n", clazz->descriptor); + fflush(clientOut); + break; + } + default: + fprintf(clientOut, "err: not a valid command\n"); + fflush(clientOut); + } + + /*gettimeofday(&tv, NULL); + + printf("end %07d\n", tv.tv_usec);*/ + + + } + + return 0; +} + diff --git a/deodexerant/README b/deodexerant/README new file mode 100644 index 00000000..967abc7d --- /dev/null +++ b/deodexerant/README @@ -0,0 +1,26 @@ +usage: +deodexerant + +deodexerant is a binary that is intended to run on an android phone, in order +to provide assistance to baksmali in deodexing .odex files. It communicates +over TCP and implements a simplistic protocol for looking up various +information needed during the deodex process, which can only be provided by +the dalvik vm. I.E. vtable lookups, field lookups by byte offset, superclass +lookups for classes not defined in the .odex file being processed, etc. + +deodexerant is intended to be build within the AOSP build system. Assuming +you have $MYDROID set to the root of the AOSP source tree, and $SMALI +set to the root of the smali source tree, + +1. cp -r $SMALI/deodexerant $MYDROID/dalvik/deodexerant +2. cd $MYDROID/dalvik/deodexerant +3. source ../../build/envsetup.sh +4. mm + +It should should spit out a deodexerant binary at + +$MYDROID/out/target/product/common/system/bin/deodexerant + +Or wherever your current build setup is configured to stick those types of +things +