【Android基础随笔】 Dalvik可执行格式(dex)上
虽然ART发布也有段时间了,但Dalvik在相当长一段时间仍会保持很大的市场份额。谷歌官方对于Dalvik可执行格式中对于各个类的定义以及附属信息有着很详细的说明介绍。
主要内容翻译自 Android设备官方文档-核心技术-Dex格式
数据类型
名称 | 描述 |
---|---|
byte | 8bit有符号整型 |
ubyte | 8bit无符号整型 |
short | 16bit有符号整型,小端 |
ushort | 16bit无符号整型,小端 |
int | 32bit有符号整型,小端 |
uint | 32bit无符号整型,小端 |
long | 64bit有符号整型,小端 |
ulong | 64bit无符号整型,小端 |
sleb128 | 有符号LEB128,可变长度 |
uleb128 | 无符号LEB128,可变长度 |
uleb128p1 | 无符号LEB128加1,可变长度 |
LEB128
- LEB128全称Little-Endian Base 128,顾名思义,小端
- LEB128是面向有符号或无符号整型的一种可变长编码方式
- LEB128借鉴了DWARF3
- 在Android Dex文件中,LEB128只会用于32bit值的编码
- 每个LEB128编码可以包含有1至5个字节,用于表达一个32bit的值
- 每个字节的最后一个bit用于表示其所在字节是否为最后一个,是为0,不是为1
- 每个字节其余七个bit用于表达实际值
- 在计算实际值时,缺少的比特位需要填值
- 对于有符号LEB128(sleb128),最后一个字节的最高位bit是符号位,1则补1,0则补0
- 对于无符号LEB128(uleb128),所有没指定过的比特位均补0
双字节LEB128编码的二进制情况
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|0|bit6|bit5|bit4|bit3|bit2|bit1|bit0|1|bit13|bit12|bit11|bit10|bit9|bit8|bit7|
uleb128p1
- uleb128p1是uleb128的变体,uleb128的编码加1就是uleb128p1的编码
- uleb128p1让-1(0xffffffff)可以只用一个字节来表示,在需要表达一个值必须为非负或者为-1(其余负数情况一律不允许)的情况下这种表示方式非常有用
- 也就是说,uleb128p1可以用于表示所有的非负数以及-1(uleb128是无符号的……)
例子
编码:00 (0000 0000)-> 实际有效值000,0000
sleb128对应的实际值:0,000,0000 = 0
uleb128对应的实际值:0,000,0000 = 0
uleb128p1对应的实际值:0,000,0000 - 1 = 1111,1111 = -1
编码:01 (0000 0001)-> 实际有效值000,0001
sleb128对应的实际值:0,000,0001 = 1
uleb128对应的实际值:0,000,0001 = 1
uleb128p1对应的实际值:0,000,0001 - 1 = 0000,0000 = 0
编码:7f (0111 1111)-> 实际有效值111,1111
sleb128对应的实际值:1,1111,111 = -1
uleb128对应的实际值:0,111,1111 = 127
uleb128p1对应的实际值:0,111,1111 - 1 = 0111,1110 = 126
编码:80 7f(1000 0000 0111 1111)-> 实际有效值111,1111,000,0000
sleb128对应的实际值:1111,1111,1000,0000 = -128
uleb128对应的实际值:0011,1111,1000,0000 = 16256
uleb128p1对应的实际值:0011,1111,1000,0000 - 1 = 0011,1111,0111,1111 = 16255
文件布局
从左至右依次为【名称】-【格式】-【描述】
- 【header】【header_item】头部
- 【string_ids】【string_id_item[]】 字符串标识列表,涉及了文件中所用到的所有字符串,包括内部名称(比如类型描述符)或者代码引用的常量对象。这个列表根据字符串内容按照UTF-16(避免了各地区语言差异导致的不同)来进行排序,不得包含重复条目。
- 【type_ids】【type_id_item[]】 类型标识列表,涉及了文件中所有用到的类型(如类、数组或者原始类型),不论是否是在文件中定义。这个列表必须按照类型字符串在string_ids中的索引来排序,不得含有重复条目。
- 【proto_ids】【proto_id_item[]】 方法原型标识列表涉及了文件中所有用到的方法原型。列表按返回值类型在type_ids中的索引进行排序,索引相同的话再按参数类型在type_ids中的索引排序,不得含有重复条目。
- 【field_ids】【field_id_item[]】 类属性(类成员)标识列表,涉及了文件中所有用到的类属性,不论其是否在文件中定义。列表依次按照所在类的类型(按type_ids索引)、属性名(按string_ids索引)、自身类型(按type_ids索引)进行排序,不得含有重复条目。
- 【method_ids】【method_id_item[]】 方法标识列表,涉及了文件中所有用到的方法,不论是否在文件中定义。列表依次按照方法所在类的类型(按type_ids索引)、方法名(按string_ids索引)、方法原型(按proto_ids索引),不得含有重复条目。
- 【class_defs】【class_def_item[]】 类定义列表,列表的顺序必须符合一个类的基类以及其所实现的接口在这个类的前面这一规则。此外,列表中出现多个同名类的定义是无效的。
- 【data】【ubyte[]】 数据区,包含上述各个结构所需的所有支持数据。不同的条目有不同的数据对齐要求,如果有需要,会在条目之前插入若干字节以满足合适的对齐。
- 【link_data】【ubyte[]】 用于静态链接文件的数据,数据的类型其实是不确定的,对于不存在链接的文件,这个区域是空的,此外不同的运行时实现也会对这一区域的数据格式做相应修改。
实战
看了也翻译了不少,不过仍然缺乏一个直观的认识。首先,需要用到的工具有:7zip,010 Editor
7zip用于解压apk获取dex文件,不多说
010 Editor可谓神器是一个异常强大是十六进制编辑器,非常适合分析二进制文件。而且010 Editor最厉害之处在提供了大量已经编写好的脚本来帮助开发人员分析各种类型的文件。
参见:http://www.sweetscape.com/010editor/templates/
这里要使用的就是DEXTemplate.bt
示例的Android源码很简单:
package com.example.dexdemo;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
使用010 Editor打开dex文件并运行分析脚本后,可以看到,dex文件的结构与上述一致:
在头部信息中,包含有校验信息、其余各部分信息的统计值(比如多少个方法、多少个字符串等)
在余下的各个列表中,条目以数组形式存储,索引从0开始,比如string_ids:
而对于proto_ids,这与我另一篇文章描述的是一致的
在method_ids与class_defs的介绍中,提到“不接受重复条目”。那么,对于不同类中的同名方法或者不同包下的同名类该如何处理呢?
dex使用的是完整路径,即包含有包名、类名的qualified name,所以可以保证不存在重复
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。