Java源文件通过Java编译器生成CLASS文件,再通过dx工具转换为classes.dex文件。

DEX文件从整体上来看是一个索引的结构,类名、方法名、字段名等信息都存储在常量池中,这样能够充分减少存储空间,相较于Java字节码文件更适合手机设备。

DEX文件的相关结构声明定义在/dalvik/libdex/DexFile.h文件中,下面我们先来看一下DEX文件中使用到的数据结构。

表1 dex文件使用到的数据结构

类型

含义

u1

等同于uint8_t,表示1字节的无符号数

u2

等同于uint16_t,表示2字节的无符号数

u4

等同于uint32_t,表示4字节的无符号数

u8

等同于uint64_t,表示8字节的无符号数

sleb128

有符号LEB128,可变长度1~5字节

uleb128

无符号LEB128,可变长度1~5字节

uleb128p1

无符号LEB128加1,可变长度1~5字节

DEX文件的基本结构如下图所示:

header

string_ids

type_ids

proto_ids

field_ids

method_ids

class_def

data

link_data

图1 DEX文件结构

header是DEX文件头,包含magic字段、alder32校验值、SHA-1哈希值、string_ids的个数以及偏移地址等。DEX文件的头结构很固定,占用0x70个字节,具体定义代码如下所示(摘自DexFile.h):

/*

* Direct-mapped "header_item" struct.

*/

struct DexHeader {

u1 magic[8]; /* includes version number */

u4 checksum; /* adler32 checksum */

u1 signature[kSHA1DigestLen]; /* SHA-1 hash */

u4 fileSize; /* length of entire file */

u4 headerSize; /* offset to start of next section */

u4 endianTag;

u4 linkSize;

u4 linkOff;

u4 mapOff;

u4 stringIdsSize;

u4 stringIdsOff;

u4 typeIdsSize;

u4 typeIdsOff;

u4 protoIdsSize;

u4 protoIdsOff;

u4 fieldIdsSize;

u4 fieldIdsOff;

u4 methodIdsSize;

u4 methodIdsOff;

u4 classDefsSize;

u4 classDefsOff;

u4 dataSize;

u4 dataOff;

};

magic[8]:共8个字节。目前为固定值dexn035。

checksum:文件校验码,使用alder32算法校验文件除去magic、checksum外余下的所有文件区域,用于检查文件错误。

signature:使用 SHA-1算法hash除去magic,checksum和signature外余下的所有文件区域 ,用于唯一识别本文件 。

fileSize:DEX文件的长度。

headerSize:header大小,一般固定为0x70字节。

endianTag:指定了DEX运行环境的cpu字节序,预设值ENDIAN_CONSTANT等于0x12345678,表示默认采用Little-Endian字节序。

linkSize和linkOff:指定链接段的大小与文件偏移,大多数情况下它们的值都为0。link_size:LinkSection大小,如果为0则表示该DEX文件不是静态链接。link_off用来表示LinkSection距离DEX头的偏移地址,如果LinkSize为0,此值也会为0。

mapOff:DexMapList结构的文件偏移。

stringIdsSize和stringIdsOff:DexStringId结构的数据段大小与文件偏移。

typeIdsSize和typeIdsOff:DexTypeId结构的数据段大小与文件偏移。

protoIdsSize和protoIdsSize:DexProtoId结构的数据段大小与文件偏移。

fieldIdsSize和fieldIdsSize:DexFieldId结构的数据段大小与文件偏移。

methodIdsSize和methodIdsSize:DexMethodId结构的数据段大小与文件偏移。

classDefsSize和classDefsOff:DexClassDef结构的数据段大小与文件偏移。

dataSize和dataOff:数据段的大小与文件偏移。

下面我们来看某apk中classes.dex的解析结果,确实与上面的结构一致:

2.DexMapList区段(大纲)

Dalvik虚拟机解析DEX文件的内容,最终将其映射成DexMapList数据结构,它实际上包含所有其他区段的结构大纲。DexHeader中的mapOff字段指明了DexMapList结构在DEX文件中的偏移。具体定义代码如下所示:

struct DexMapList {

u4 size; /* DexMapItem的个数 */

DexMapItem list[1]; /* DexMapItem的结构 */

};

struct DexMapItem {

u2 type; /* kDexType开头的类型 */

u2 unused; /* 未使用,用于字节对齐 */

u4 size; /* type指定类型的个数,它们在dex文件中连续存放 */

u4 offset; /* 指定类型数据的文件偏移 */

};

/* type字段为一个枚举常量,通过类型名称很容易判断它的具体类型。 */

/* map item type codes */

enum {

kDexTypeHeaderItem = 0x0000,

kDexTypeStringIdItem = 0x0001,

kDexTypeTypeIdItem = 0x0002,

kDexTypeProtoIdItem = 0x0003,

kDexTypeFieldIdItem = 0x0004,

kDexTypeMethodIdItem = 0x0005,

kDexTypeClassDefItem = 0x0006,

kDexTypeMapList = 0x1000,

kDexTypeTypeList = 0x1001,

kDexTypeAnnotationSetRefList = 0x1002,

kDexTypeAnnotationSetItem = 0x1003,

kDexTypeClassDataItem = 0x2000,

kDexTypeCodeItem = 0x2001,

kDexTypeStringDataItem = 0x2002,

kDexTypeDebugInfoItem = 0x2003,

kDexTypeAnnotationItem = 0x2004,

kDexTypeEncodedArrayItem = 0x2005,

kDexTypeAnnotationsDirectoryItem = 0x2006,

};

下面我们来看一下010Editor对某classes.dex文件的解析出的DexMapList结构。上面DexMapList结构中的size字段表示list数组的成员个数,即DexMapItem结构的数量:图中是11h,表示共有17个DexMapItem结构,与图中的list数组大小相符。

然后我们再来看下DexMapItem的结构。例如对于下图中的DexMapItem的第一项来说,type等于0说明其是kDexTypeHeaderItem类型的结构;unused一般都为0;size为1代表该结构仅有一个,即只有一个Dex文件头;offset为0代表Dex文件头从0h开始。喎�"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="image" src="/uploadfile/Collfiles/20160627/20160627092154831.png" title="" />

最后我们将所有DexMapItem结构整理成下表:

类型(type)

个数(size)

偏移(offset)

kDexTypeHeaderItem(0x0000)

0x1

0x0

kDexTypeStringIdItem(0x0001)

0xA115

0x70

kDexTypeTypeIdItem(0x0002)

0x1D38

0x284C4

kDexTypeProtoIdItem(0x0003)

0x2505

0x2F9A4

kDexTypeFieldIdItem(0x0004)

0x9FB9

0x4B5E0

kDexTypeMethodIdItem(0x0005)

0xC344

0x9B3A8

kDexTypeClassDefItem(0x0006)

0x189D

0xFCDC8

kDexTypeAnnotationSetItem(0x1003)

0x10E0

0x12E168

kDexTypeCodeItem(0x2001)

0x96DB

0x138A34

kDexTypeAnnotationsDirectoryItem(0x2006)

0xCE6

0x4A3EEC

kDexTypeTypeList(0x1001)

0x1620

0x4B9894

kDexTypeStringDataItem(0x2002)

0xA115

0x4C74CA

kDexTypeDebugInfoItem(0x2003)

0x8FCC

0x5C8544

kDexTypeAnnotationItem(0x2004)

0x101C

0x63FBC1

kDexTypeEncodedArrayItem(0x2005)

0x10E

0x653536

kDexTypeClassDataItem(0x2000)

0x184F

0x65B97A

kDexTypeMapList(0x1000)

0x1

0x6B8828

可以看出,其中区段的offset与header中的off是完全相等的。

3.DexStringId区段(字符串)

struct DexStringId {

u4 stringDataOff; /* 字符串数据偏移 */

}

DexStringId结构只有一个stringDataOff字段,直接指向字符串数据。这个区段中包含了DEX文件中用到的所有字符串。

4.DexTypeId区段(类名/类型名称字符串)

struct DexTypeId {

u4 descriptorIdx; /* 指向 DexStringId列表的索引 */

};

descriptorIdx为指向DexStringId列表的索引,它对应的字符串代表了具体类的类型(DEX文件中用到的所有基本数据类型和类的名称)。如下图中的第一项值为0xAEB,表示其是DexStringId中第0xAEB(2795)项;而第8项值为0x1969,表示其是DexStringId中第0x1969(6505)项。经过我们的验证,以上分析是正确的。

5.DexProtoId区段(方法声明=返回类型 + 参数列表)

struct DexProtoId {

u4 shortyIdx; /* 指向DexStringId列表的索引 */

u4 returnTypeIdx; /* 指向DexTypeId列表的索引 */

u4 parametersOff; /* 指向DexTypeList的偏移 */

}

struct DexTypeList {

u4 size; /* 接下来DexTypeItem的个数 */

DexTypeItem list[1]; /* DexTypeItem结构 */

};

struct DexTypeItem {

u2 typeIdx; /* 指向DexTypeId列表的索引 */

};

下面结合实例进行分析:

DexProtoId

shortyIdx:方法声明字符串,具体而言是由方法的返回类型与参数列表组成的一个字符串,并且返回类型位于参数列表的前面。如“III”“V”“VI”“VL”等。在下图的三个方法声明中分别为B、BL、DL。

returnTypeIdx:方法返回类型,指向DexTypeId列表。下图的分别为byte、byte、double。

parametersOff:指向一个DexTypeList结构体,存放了方法的参数类型。下图分别为0、0x4BA78C、0x4BA7BC。值为0表示参数为void。

DexTypeList

size:DexTypeItem的个数,即参数的数量。下图分别为?、1、1,表示后两个方法都只有一个参数。 list:指向size个DexTypeItem项,每一项代表方法的一个参数。

DexTypeItem

typeIdx:指向DexTypeId列表,最终指向参数类型的字符串。如第三图61h(97)项在DexTypeId列表中正好指向”Landroid/content/Context;”类型字符串。

6.DexFieldId区段(字段)

DexFieldId结构中的数据全部是索引值,指明了字段所在的类、字段的类型以及字段名。

struct DexFieldId {

u2 classIdx; /* 类的类型,指向DexTypeId列表的索引 */

u2 typeIdx; /* 字段类型,指向DexTypeId列表的索引 */

u4 nameIdx; /* 字段名,指向DexStringId列表的索引 */

};

如下图,可以看到字段所属类名为MTT.ThirdAppInfoNew,字段类型为int,字段名为iCoreType。

7.DexMethodId区段(方法)

DexMethodId结构中的数据全部是索引值,指明了方法所在的类、方法的声明以及方法名。

struct DexMethodId {

u2 classIdx; /* 类的类型,指向DexTypeId列表的索引 */

u2 protoIdx; /* 声明类型,指向DexProtoId列表的索引 */

u4 nameIdx; /* 方法名,指向DexStringId列表的索引 */

};

如下图,可以看到方法所属类为MTT.ThirdAppInfoNew,方法声明为V,方法名为。

8.DexTypeClassDefItem(类定义)

struct DexClassDef {

u4 classIdx; /* 类的类型,指向DexTypeId列表的索引 */

u4 accessFlags; /* 访问标志 */

u4 superclassIdx; /* 父类类型,指向DexTypeId列表的索引 */

u4 interfacesOff; /* 接口,指向DexTypeList的偏移 */

u4 sourceFileIdx; /* 源文件名,指向DexStringId列表的索引 */

u4 annotationsOff; /* 注解,指向DexAnnotationsDirectoryItem结构 */

u4 classDataOff; /* 指向DexClassData结构的偏移 */

u4 staticValuesOff; /* 指向DexEncodedArray结构的偏移 */

};

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结构的偏移 */

};

struct DexCode {

u2 registersSize; /* 使用的寄存器个数 */

u2 insSize; /* 参数个数 */

u2 outsSize; /* 调用其他方法时使用的寄存器个数 */

u2 triesSize; /* Try/Catch个数 */

u4 debugInfoOff; /* 指向调试信息的偏移 */

u4 insnsSize; /*指令集个数,以2字节为单位 */

u2 insns[1]; /* 指令集 */

DexClassDef

classIdx:索引值,表明类的类型。下图中值为0x6,指向类型MTT.ThirdAppInfoNew。 accessFlags:类的访问标志,它是以ACC_开头的一个枚举值。具体定义可以参考这里。下图中值为0x11,表示同时具有ACC_PUBLIC和ACC_FINAL。 superclassIdx:父类类型索引值。下图中值为0x1B13,指向java.lang.Object类。 interfacesOff:如果类中含有接口声明或实现,interfaceOff会指向一个DexTypeList结构,否则这里的值为0。图中值为0x4B9894(4954260),指向的DexTypeList结构为java.lang.Cloneable。 sourceFileIdx:字符串索引值,表示类所在的源文件名称。图中值为NO_INDEX(0xffffffff),表示该值丢失。 annotationsOff:指向注解目录结构,根据类型不同会有注解类、注解方法、注解字段与注解参数,如果类中没有注解,这里的值则为0。图中值为0,表示类中没有注解。 classDataOff:指向DexClassData结构,它是类的数据部分。图中为0x65B97A。 staticValuesOff:指向DexEncodedArray结构,记录了类中的静态数据。图中为0,表示类中没有静态数据。

DexClassData

header:一个DexClassDataHeader结构,指定字段与方法的个数。如下图中的staticFieldsSize为0,表示没有静态字段;instanceFieldsSize为0xC,表示有12个实例字段;directMethodsSize为0x1,表示有一个直接方法;virtualMethodsSize为0x0,表示没有虚方法。

staticFields*:指向一个DexField结构,表示静态字段的类型与访问标志。由于本例没有静态字段,因此该结构无效。

directMethods*:指向一个DexField结构,表示实例字段的类型与访问标志。如下图本例中有一个实例字段。

directMethods*:指向一个DexMethod结构,表示直接方法的原型、名称、访问标志、代码数据块。。如下图本例中有12个实例字段。

virtualMethods*:指向一个DexMethod结构,表示虚方法的原型、名称、访问标志、代码数据块。由于本例没有静态字段,因此该结构无效。

DexField

fieldIdx:指向DexFieldId的索引,表示字段的所属类、字段类型和字段名。 accessFlags:访问标志。

DexMethod

methodIdx:指向DexMethodId的索引,表示方法的所在类、方法的声明和方法名。 accessFlags:访问标志。 codeOff:指向DexCode结构的偏移,图中为0x138A34。

DexCode

registersSize:该方法使用的寄存器个数。下图中为3。 insSize:该方法的参数个数,对应smali语法中的”.register”指令。下图中为1。 outsSize:该方法调用其他方法时,对应smali语法中的”.paramter”指令。例如现在有一个方法,使用了5个寄存器,其中有2个为参数,而该方法调用了另一个方法,后者使用了20个寄存器,那么Dalvik虚拟机在分配时,会在分配自身方法寄存器空间时加上那20个寄存器空间。下图中为1。 triesSize:方法中Try/Catch的个数。 debugInfoOff:如果dex文件保留了调试信息,debugInfoOff字段会指向它。 insnsSize:指令个数,以2字节为单位。 insns[1]:真正的指令部分。

android 定义集合长度,Android Dex文件结构解析相关推荐

  1. 一篇胎死腹中的Android文章——Dex文件结构解析

    前言 国庆的时候,为了理解DexDiff算法,花了几天时间研究了下Dex的文件结构,算是有个整体的把握,这篇文章是在姜维的 <Android逆向之旅-解析编译之后的Dex文件格式>基础上, ...

  2. android 获取视频长度,android中如何获取视频时长

    1.关键代码 MediaMetadataRetriever mmr = new MediaMetadataRetriever(); String duration = mmr.extractMetad ...

  3. android edittext 输入长度,Android EditText限制输入字数的方法

    本文实例讲述了Android EditText限制输入字数的方法.分享给大家供大家参考,具体如下: //新浪微博字数限制 private static final int WEIBO_CONTENT_ ...

  4. android 定义固定数组,Android 图片数组定义和读取

    位置:packages/apps/Launcher2 1.图片数组定义.资源读取 如果有多张图片,这些图片的使用与顺序无关,可以采取这种方式. drawable-nodpi中有3张图片,wallpap ...

  5. android string.format()长度,Android通过String.format格式化(动态改变)字符串资源的显示内容...

    一.实现效果: 最近在项目中需要做类似于上图显示的效果,里面的数字和称谓是动态获取的,对于这种显示效果,有如下两种解决方案来处理: (1)通过代码动态设置TextView的内容,比如: /** * 显 ...

  6. android定义颜色数组,android – 我如何保存在array.xml中的颜色,并让它回到Color []数组...

    定义颜色资源,然后将它们添加到阵列以进行访问. #FF007F #FF0000 #FF7F00 #FFFF00 #7FFF00 #00FF00 #00FF7F #00FFFF #007FFF #000 ...

  7. android 定义数组常量,android 设置屏幕常亮

    类似设置禁止截屏和保持屏幕常亮这种依赖生命周期方法的代码,如果需要应用到多个 Activity 的话,可以将其放在 BaseActivity 基类中,避免编写重复性代码.或者还可以借助这个神奇的 Ac ...

  8. 老罗Android开发视频教程( android解析json数据 )4集集合

    老罗Android开发视频教程( android解析json数据 )4集集合 老罗Android开发视频教程( android解析json数据 ) 第一集android解析json数据 http:// ...

  9. android 测量文字长度,【Android】TextView文字长度测量和各种Padding解析

    老规矩,先上张图 o,这篇好像是分析篇,没有效果图.不管了,位置占着,老规矩不能坏,下面开始正文. 这篇博客会讲得比较杂:TextView里各部分的大小该怎么测量? 如何计算每行文字的长度? 设置an ...

最新文章

  1. python创建新文件-如何在python中编辑文件并创建一个新的文件?
  2. Java Switch Statement
  3. .net Framework各个版本之间的发展
  4. 自学前端,你要的学习资料到了~~~~~~
  5. ThinkPHP6项目基操(10.不可预知的內部异常处理)
  6. A+B Problem(洛谷-P1001)
  7. AJAX 大数据量处理
  8. 计算机拆装与维修技能综述,综述虚拟机在计算机硬件组装与维护教学中的应用...
  9. Mybatis缓存机制及mybatis的各个组成部分
  10. abp vnext修改密码策略
  11. SPSS案例实践:RFM营销分析
  12. eclipse安装中文补丁包
  13. wordpress目录文件结构
  14. centos7快速搭建KMS服务器
  15. unity 接入移动MM (3.1.10)
  16. pragma HLS interface 端口综合
  17. 【PHP】\r \r\n \t是什么
  18. 海普完成数千万元战略融资
  19. 联想服务器系统备份,操作演示:恢复预装系统前的数据备份方法
  20. USA gov data from Bitly

热门文章

  1. equals()与hashCode()
  2. 使用Spring容器
  3. 激光雷达与汽车技术路线
  4. Conda安装Glossary词汇表
  5. 一些量化(quantization)技巧
  6. YOLO3升级优化版!Poly-YOLO:支持实例分割!
  7. 深度学习模型轻量化(下)
  8. 视频动作定位的分层自关注网络:ICCV2019论文解析
  9. 2021年大数据HBase(七):Hbase的架构!【建议收藏】
  10. 【注意事项】论文/申报书格式