Initial commit of deodexerant

git-svn-id: https://smali.googlecode.com/svn/trunk@434 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
JesusFreke@JesusFreke.com 2009-09-08 06:26:34 +00:00
parent 5f98a29260
commit df2a55dee5
3 changed files with 842 additions and 0 deletions

36
deodexerant/Android.mk Normal file
View File

@ -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)

780
deodexerant/Main.c Normal file
View File

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#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; i<clazz->ifieldCount; 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 <odex_file> <port>\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;
}

26
deodexerant/README Normal file
View File

@ -0,0 +1,26 @@
usage:
deodexerant <odex_file> <port>
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