【理论知识】Dex文件结构分析

构造Dex文件

Dex文件就是Dalvik可执行文件,实际上它就是一个优化后的java字节码文件,因此构造这类文件需要先写个java文件

Pino.java

public class Pino {
    public static void main(String args[]) {
        System.out.println("Hello World");
    }
}

然后编译

javac Pino.java

之后得到了Pino.class文件,之后我们用dx工具,该工具需要安装Android SDK才能有的工具

dex --dex --output=Pino.dex Pino.class

这样就得到了一个dex文件了,之后我们利用010editor工具来进行分析。


dex文件

Dex文件整体结构

Dex文件整体结构

那我们从头开始分析

Dex文件分析

首先,我们来看一下Dex文件头的结构体

struct DexHeader {
    u1  magic[8];           /* dex的魔数 */
    u4  checksum;           /* 校验和 */
    u1  signature[kSHA1DigestLen]; /* SHA-1哈希值*/
    u4  fileSize;           /* dex文件的大小 */
    u4  headerSize;         /* dex文件头的大小 */
    u4  endianTag;          /* 字节序标记 */
    u4  linkSize;            /* 链接段大小 */
    u4  linkOff;            /* 链接段偏移 */
    u4  mapOff;          /* DexMapList的文件偏移 */
    u4  stringIdsSize;          /* DexStringId的个数 */
    u4  stringIdsOff;           /* DexStringId的偏移 */
    u4  typeIdsSize;           /* DexTypeId的个数 */
    u4  typeIdsOff;           /* DexTypeId的偏移 */
    u4  protoIdsSize;            /* DexProtoId的个数 */
    u4  protoIdsOff;           /* DexStringId的偏移 */
    u4  fieldIdsSize;           /* DexFieldId的个数 */
    u4  fieldIdsOff;           /* DexFieldId的偏移 */
    u4  methodIdsSize;           /* DexMethodId的个数 */
    u4  methodIdsOff;           /* DexMethodId的偏移 */
    u4  classDefsSize;           /* DexClassDef的个数 */
    u4  classDefsOff;           /* DexClassDef的偏移 */
    u4  dataSize;           /* 数据段的大小 */
    u4  dataOff;           /* 数据段的偏移 */
};
  1. magic[8] 由8个u1类型的数据,内容是“64 65 78 0A 30 33 35 00”,u1就是1个字节的无符号数,这个是dex文件的标志,用来识别dex文件的。

  2. checksum 没什么好说的,就是dex文件的校验和

  3. signature[kSHA1DigestLen],就是整个dex文件进行SHA-1哈希计算得到的字符串,一般来说20个字节

  4. fileSize, 整个dex文件的大小

  5. headerSize, dex文件的头的大小

  6. endianTag, dex文件的字节序标记,用于指定dex文件运行环境的cpu,预设值为0x12345678,在010editor的话就是“78 56 34 12”(小端序)

  7. linkSizelinkOff, 链接段的大小和文件偏移,通常为0,linkSize为0表示静态链接

  8. mapOff, DexMapList的文件偏移

  9. stringIdsSizestringIdsOff,这两个是DexStringId的个数和文件偏移

    stringid

这里stringIdSize的值为0E,10进制就是14,也就是说这个dex文件的字符串的个数为14个,文件偏移是70,我们到70的位置看一下



蓝色部分就是DexStringId的内容了,每个字符串4字节,总共14个,我们先看一下第一组“76 01 00 00”,这个值并不是字符串的具体内容,而是字符串所在位置的文件偏移,我们去看一下176h这个位置


蓝色部分我一共选中了8个字节,其中第一个字节06代表的是之后多少个字节属于字符串,也就是“3C 69 6E 69 74 3E”,而最后一个字节的00其实是字符串结尾的空字节,但是计数的时候并没有算上而已,总结一下这个dex文件中所有的字符串如下:

序号 字符串
0 <init>
1 Hello World
2 LPino;
3 Ljava/io/PrintStream;
4 Ljava/lang/Object;
5 Ljava/lang/String;
6 Ljava/lang/System;
7 Pino.java
8 V
9 VL
10 [Ljava/lang/String;
11 main
12 out
13 println
  1. typeIdSizetypeIdOff,就是类的类型个数和文件偏移,可以根据之前字符串的进行类比

    typeIdSize的值为07,也就是说由7个类型,typeIdOff的值是A8h,我们到A8的位置看一下

    蓝色选中的部分都是类型,但是这个一种数据结构
struct DexTypeId {
        u4 descriptorIdx;        //指向DexStringId列表的索引
}

先看一下第一个4字节的值“02 00 00 00 ”,对照之前我们整理的字符串的表格,就是LPino;即Pino类型的,整理一下所有的类型,如下

序号 类的类型
0 LPino;
1 Ljava/io/PrintStream;
2 Ljava/lang/Object;
3 Ljava/lang/String;
4 Ljava/lang/System;
5 V
6 [Ljava/lang/String;
  1. protoIdSizeprotoIdOff,这两个是方法原型的个数和位置偏移
    proto

这里数量就是3,位置偏移为C4,跟过去看下


蓝色选中的部分就是所有的方法原型的结构了,这里又涉及到了一个新的数据结构

struct DexProtoId {
        u4 shortyIdx;          //指向DexStringId列表的索引
        u4 returntypeIdx;          //指向DexTypeId列表的索引
        u4 parameterOff;          //指向DexTypeList列表的位置偏移
}

这三个属性分别是第一个是方法声明的字符串,第二个是方法的返回类型,第三个是方法的参数列表,其中DexTypeList是新的数据结构

struct DexTypeList {
        u4 size;          //DexTypeItem的个数
        DexTypeItem list[1];
}
struct DexTypeItem {
        u2 typeIdx;        //指向DexTypeId列表的索引
}

回过头来看一下蓝色部分,12个字节,第一个4字节为8,说明DexStringId列表的索引是8,也就是V,第二个4字节是5,也就是V,最后一个是0,也就是没有参数,第一个方法就是void (),整理一下其他的如下:

序号 方法原型
0 void()
1 void (java.lang.String)
2 void (java.lang.String[])
  1. fieldIdSizefieldIdOff这两个是字段的数量和位置偏移

    这里字段数是1,位置偏移为E8,字段也有新的数据结构
struct DexFieldId{
    u2 classIdx;        /*类的类型,指向DexTypeId列表的索引*/
    u2 typeIdx;     /*字段类型,指向DexTypeId列表的索引*/
    u4 nameIdx;     /*字段名,指向DexStringId列表的索引*/
}

也就是一个DexFieldId是8个字节



classIdx的值是4,也就是Ljava/lang/System;,typeIdx的值是1,也就是Ljava/io/PrintStream;,nameIdx的值是C,也就是out,总结一下字段如下:

序号 字段
0 java.io.PrintStream java.lang.System.out
  1. methodIdSizemethodIdOff这两个分别是方法的数量和位置偏移

    fieldIdSize的值是4,也就是有4个方法,fieldIdOff是F0,跟过去看下

    新的数据结构如下:
struct DexMethodId{
    u2 classIdx;        /*类的类型,指向DexTypeId列表的索引*/
    u2 protoIdx;        /*声明类型,指向DexProtoId列表的索引*/
    u4 nameIdx;     /*方法名,指向DexStringId列表的索引*/
}

也就是说每个DexMethodId占8个字节,第一个8字节中的classIdx的值是0,也就是LPino;,protoIdx的值也是0,也就是void(),第三nameIdx也是0,也就是<init>,综合起来就是void Pino.<init>(),整理一下所有的方法如下:

序号 方法
0 void Pino.<init>()
1 void Pino.main(java.lang.String[])
2 void java.io.PrintStream.println(java.lang.String)
3 void java.lang.Object.<init>()
  1. classDefsSizeclassDefsOff是类的定义的数量和位置偏移

    classDefsSize的值为1,说明就定义了一个类,然后在到110h的位置看看,但是这里还是有新的结构体
struct DexClassDef{
    u4 classIdx;        /*类的类型,指向DexTypeId列表的索引*/
    u4 accessFlags;     /*访问标志,就是表示是public还是private等等*/
    u4 superclassIdx;   /*父类类型,指向DexTypeId列表的索引*/
    u4 interfacesOff;   /*接口,指向DexTypeList的偏移*/
    u4 sourceFileIdx;   /*源文件名,指向DexStringId列表的索引*/
    u4 annotationsOff;  /*注解,指向DexAnnotationsDirectoryItem结构*/
    u4 classDataOff;    /*指向DexClassData结构的偏移*/
    u4 staticValuesOff; /*指向DexEncodedArray结构的偏移*/
}

上面的数据结构28个字节,内容的话看注释也能看懂,我们直接上实例,在这里,classIdx是1,也就是LPino;,第二个accessFlags是1,也就是public,第三个superclassIdx是2,也就是父类是java.lang.Object,第四个interfacesOff是0,就是没有,第五个是sourceFileIdx是7,也就是Pino.java,第六个是annotationOff,是0,没有,第七个classData是22D,也就是DexClassData的偏移是22D,我们先来看看DexClassData的结构体

struct DexClassData{
    DexClassDataHeader          header;         /*指定字段与方法的个数*/
    DexField*           staticFields;       /*静态字段,DexField结构*/
    DexField*           instanceFields; /*实例字段,DexField结构*/
    DexMethod*          directMethods;      /*直接方法,DexMethod结构*/
    DexMethod*          virtualMethods;     /*虚方法,DexMethod结构*/
}

这里面又涉及到了其他三种结构体

struct DexClassDataHeader{
    u4 staticFieldsSize;    /*静态字段个数*/
    u4 instanceFieldsSize;  /*实例字段个数*/
    u4 directMethodsSize;   /*直接方法个数*/
    u4 virtualMethodsSize;  /*虚方法个数*/
}
 
struct DexField{
    u4 fieldIdx;        /*指向DexFieldId的索引*/
    u4 accessFlags;     /*访问标志*/
}
 
struct DexMethod{
    u4 methodIdx;       /*指向DexMethodId的索引*/
    u4 accessFlags;     /*访问标志*/
    u4 codeOff;     /*指向DexCode结构的偏移*/
}

这里需要注意的一点的就是这里的u4并不是值4字节,而是值uleb128的类型,具体是什么可以自行百度。

现在我们再去22D的位置看看



从这里可以判断姿态字段0个,实例字段0个,直接方法2个,虚方法0个。因为staticFields和instanceFields都是0个,所以直接从directMethods来看了,methodIdx为0,也就是void Pino.<init>(),accessFlags的值为“81 80 04”,这个是uleb128编码的,转换为16进制的话就是10001h,对照一下DexFile.h文件,知道方法是ACC_PUBLIC和ACC_CONSTRUCTOR



表示这个方法是public的并且是构造方法,然后是codeOff的值是“B0 02”,转换为16进制就是130h。第二个函数的methodIdx的值是1,也就是void Pino.main(java.lang.String[]),accessFlags的值是09h,也就是ACC_PUBLIC和ACC_STATIC,codeOff的值是“CB 02”,转换为16进制就是14Bh,也就是位置偏移在14Bh处。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342