美文网首页
【理论知识】Dex文件结构分析

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

作者: Pino_HD | 来源:发表于2018-08-01 21:16 被阅读0次

    构造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处。

    相关文章

      网友评论

          本文标题:【理论知识】Dex文件结构分析

          本文链接:https://www.haomeiwen.com/subject/wayrvftx.html