Dex是android上的可执行文件类型,其包含了几乎所有代码及变量的内容,正确使用并辨识DEX文件内的内容对分析问题及使用一些较为灵活的操作方式有很大的帮助。
半成品
DEX格式定义在dalvik/libdex/DexFile.h的struct DexFile中,其由若干个部分的结构体组成,除了DexHeader文件头指定了dex文件的一些属性,包括签名,文件长度,各数据区的数目及偏移值等等外,其它部分(从DexStringId到DexClassDef部分)都是各自描述自己的内容,如DexClassDef则是描述类的定义,DexStringId则是描述字符常量的定义等。
以字符串为例,如Header中定义的1
2u4 stringIdsSize;
u4 stringIdsOff;
中,stringIdsSize为898,stringIdsOff为112,则表示stringId索引区起始于文件开始偏移112个字节的地方,字符串个数数量有898个。
而在文件起始112个偏移后的位置区域中,存在着898个DexStringId类型的指针,每一个大小为u4(4个字节),则字符串索引区的空间大小为4*898=3592byte。
每一个DexStringId的结构体中都有一个stringDataOff的变量,它指示了这个字符串在数据区(DATA)内距离文件开始时的偏移量,如stringDataOff=35764,则真正的字符串数据是在文件起始位置0x8BB4偏移开始。
开始的位置指向的是一个使用MUTF-8编码的数据表示的,MUTF-8字符串编码过的字符串头部是一个使用uleb128编码的数据,其表示这个字符串有多少个字符(使用uleb128就是表示个数最大可以表示为0xFFFFF,也就是android中最大的字符串可以有这么多个位?),其后的就是真正的数据内容,不再多说。
再拿文件格式来分析一下android中加载类的过程。
在dex文件中,class的索引区同样也定义在header中,分别为classDefsOff与classDefsSize,前者指向了class索引区的起始偏移量;在索引区中,同样也存在着classDefsSize个DexClassDef类型的指针数据,每一个数据都指向DATA区中的一个实体数据,其定义如下:1
2
3
4
5
6
7
8
9
10struct DexClassDef {
u4 classIdx; /* 类的类型,指向DexTypeId列表的 索引*/
u4 accessFlags; /* 访问标志 */
u4 superclassIdx; /* 父类类型,指向DexTypeId列表中的索引 */
u4 interfacesOff; /* 接口,指向DexTypeList的偏移 */
u4 sourceFileIdx; /* 源文件名,指向DexStringId列表的索引 */
u4 annotationsOff; /* 注解,指向DexAnnotationsDirectoryItem的结构 */
u4 classDataOff; /* 指向DexClassData结构的偏移 */
u4 staticValuesOff; /* 指向DexEncodedArray结构的偏移 */
};
从Java层的Class.forName开始,最终调用了Class中的一个native方法1
2static native Class<?> classForName(String className, boolean initializeBoolean,
ClassLoader classLoader) throws ClassNotFoundException;
来到了native层,来到了native层中的classForName中,源码的位置在dalvik/vm/native/java_lang_Class.cpp中,此外android作了一个转换,将触发的方法变成了
Dalvik_java_lang_Class_classForName的方法定义如下:
1 | static void Dalvik_java_lang_Class_classForName(const u4* args, JValue* pResult) |
实际上就是调用了dvmFindClassByName方法,这个方法是定义在InternalNative.cpp(dalvik/vm/native)里。
传进去的三个参数依次为
1. 需要加载的类名
2. 类加载器
3. 是否需要初始化
以下为此类的源码(4.1.2)
1 | ClassObject* dvmFindClassByName(StringObject* nameObj, Object* loader, |
前面所作的都是一些名字转换校验之类的工作,真正的加载是放在dvmFindClass与dvmFindClassNoInit之中,dvmFindClass也是调用dvmFindClassNoInit的,所以以doInit==false为例,dvmFindClassNoInit定义在dalvik/vm/oo/Class.cpp中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24ClassObject* dvmFindClassNoInit(const char* descriptor,
Object* loader)
{
assert(descriptor != NULL);
//assert(loader != NULL);
LOGVV("FindClassNoInit '%s' %p", descriptor, loader);
if (*descriptor == '[') {
/*
* Array class. Find in table, generate if not found.
*/
return dvmFindArrayClass(descriptor, loader);
} else {
/*
* Regular class. Find in table, load if not found.
*/
if (loader != NULL) {
return findClassFromLoaderNoInit(descriptor, loader);
} else {
return dvmFindSystemClassNoInit(descriptor);
}
}
}
此处也是作了检查,看需要加载的类是否是以”[“开始的(此为数组的限定形式),
由于本次讲的是DEX相关的,故以dvmFindSystemClassNoInit为例查看从DEX中加载类的做法。dvmFindSystemClassNoInit最终会调用到findClassNoInit方法里,在查到将要加载的类在当前加载器中没有被加载过之后,就查找bootpath和dex中有没有对应的类,如果有的话就调用loadClassFromDex方法从dex中获取到class。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36static ClassObject* loadClassFromDex(DvmDex* pDvmDex,
const DexClassDef* pClassDef, Object* classLoader)
{
ClassObject* result;
DexClassDataHeader header;
const u1* pEncodedData;
const DexFile* pDexFile;
assert((pDvmDex != NULL) && (pClassDef != NULL));
pDexFile = pDvmDex->pDexFile;
if (gDvm.verboseClass) {
ALOGV("CLASS: loading '%s'...",
dexGetClassDescriptor(pDexFile, pClassDef));
}
//从dex文件中获取class的数据起始指针
pEncodedData = dexGetClassData(pDexFile, pClassDef);
if (pEncodedData != NULL) {
dexReadClassDataHeader(&pEncodedData, &header);
} else {
// Provide an all-zeroes header for the rest of the loading.
memset(&header, 0, sizeof(header));
}
result = loadClassFromDex0(pDvmDex, pClassDef, &header, pEncodedData,
classLoader);
if (gDvm.verboseClass && (result != NULL)) {
ALOGI("[Loaded %s from DEX %p (cl=%p)]",
result->descriptor, pDvmDex, classLoader);
}
return result;
}
从DEX中获取class的数据主要是计算偏移,计算方法如下:1
2
3
4
5
6
7
8
9DEX_INLINE const u1* dexGetClassData(const DexFile* pDexFile,const DexClassDef* pClassDef)
{
// 如果class索引区内指向class具体内容的偏移值为0则认为失败
if (pClassDef->classDataOff == 0)
return NULL;
// pDexFile->baseAddr是DexFile结构体中的一个定义,实际上指向了这个Dex文件DexHeader起始的位置
// pClassDef->classDataOff 是相对于DEX文件的一个偏移,指向了这个类的数据定义位置
return (const u1*) (pDexFile->baseAddr + pClassDef->classDataOff);
}
然后就来到了loadClassFromDex0方法中,这个方法是用来协助从dex中获取到的数据来构建一个类对象的,包括解析类的常量池、字段表、方法表等。以下分段解析构建的部分。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31static ClassObject* loadClassFromDex0(DvmDex* pDvmDex,
const DexClassDef* pClassDef, const DexClassDataHeader* pHeader,
const u1* pEncodedData, Object* classLoader)
{
//定义在/dalvik/vm/oo/Object.h
ClassObject* newClass = NULL;
const DexFile* pDexFile;
const char* descriptor;
int i;
pDexFile = pDvmDex->pDexFile;
descriptor = dexGetClassDescriptor(pDexFile, pClassDef);
if ((pClassDef->accessFlags & ~EXPECTED_FILE_FLAGS) != 0) {
ALOGW("Invalid file flags in class %s: %04x",
descriptor, pClassDef->accessFlags);
return NULL;
}
if (classLoader == NULL &&
strcmp(descriptor, "Ljava/lang/Class;") == 0) {
//因为一般加载中都存在 classLoader 不为空,故暂不分析此分支
……
} else {
//获取到静态区的大小,然后以此大小创建一片内存区作为ClassObject,由此可见,静态数据在类创建的时候就已经预先分配好内存了。
size_t size = classObjectSize(pHeader->staticFieldsSize);
newClass = (ClassObject*) dvmMalloc(size, ALLOC_NON_MOVING);
}
......
}
EXPECTED_FILE_FLAGS的定义在 /dalvik/vm/oo/Object.h,其中各字段的含义可以查看同文件中的enum ClassFlags定义及/dalvik/libdex/DexFile.h中的ACC枚举定义。1
pHeader是DexClassDataHeader的指针类型,头部指示了这个类的一些基本情况1
2
3
4
5
6struct DexClassDataHeader {
u4 staticFieldsSize; //静态区的大小
u4 instanceFieldsSize; //实例字段的大小
u4 directMethodsSize; //直接方法的数目,包括private ,static 与<init>
u4 virtualMethodsSize; //虚方法的个数,目前来看public的方法都属于这个
};
其余的都是从dex中获取到对应的数据来构建ClassObject的其他字段,以方法区为例
1 | ...... |
其中loadMethodFromDex方法作用是从dex中读取方法对应的信息,包含读取accessFlag,参数边界等等。
1 | static void loadMethodFromDex(ClassObject* clazz, const DexMethod* pDexMethod, |
如此,类的加载部分就完成了,这里分析省略了很多细节性的东西,仅供参考。其他索引区的数据也能使用这种方法获取。
附:
参考:
http://blog.csdn.net/new_abc/article/details/36412081
http://www.cnblogs.com/santry/archive/2011/10/24/2222900.html
1 | /dalvik/libdex/DexClass.h |
=================================================================
使用010Editor分析DEX
010Editor支持直接查看二进制文件并进行分析,下载地址
其支持使用脚本模板文件来分析二进制文件,模板下载
在打开二进制文件后,就可以点击Template -> open Template 打开一个模板文件,如附件中的DEXTEMPLATE,打开后就可以点击 Template -> run Template来执行模板脚本,模板就会根据当前打开的二进制文件进行分析,然后得出结果