内容简介:最近在看《深入理解Android: Java虚拟机ART》,说实话,这本书的内容还是很深的,对于我来说就像一个小学生在做初中的数学题一样。在看第三章深入理解Dex文件格式的时候,其中最后一部分讲“指令码描述规则”,没有看懂(怪自己水平太低),最后通过我的不懈努力,终于看懂了,下面记录一下这个过程,希望对后面的新手有所帮助。要想将这部分内容讲懂,那肯定要结合具体的例子,下面就通过最简单的一个例子来讲解这个过程:首先我们先贴出一段在《深入理解java虚拟机》这本书中经常用到的java代码:
最近在看《深入理解Android: Java虚拟机ART》,说实话,这本书的内容还是很深的,对于我来说就像一个小学生在做初中的数学题一样。在看第三章深入理解Dex文件格式的时候,其中最后一部分讲“指令码描述规则”,没有看懂(怪自己水平太低),最后通过我的不懈努力,终于看懂了,下面记录一下这个过程,希望对后面的新手有所帮助。
要想将这部分内容讲懂,那肯定要结合具体的例子,下面就通过最简单的一个例子来讲解这个过程:
从.java到.class
首先我们先贴出一段在《深入理解 java 虚拟机》这本书中经常用到的java代码:
public class TestClass { private int m; public int inc() { return m + 1; } } 复制代码
然后我们通过javac编译成.class文件
javac TestClass 复制代码
编译完成后,会在当前目录生成TestClass.class文件。
从.class到.dex
在android运行时环境中,需要将.class文件进行翻译、重构、解释、压缩等操作生成.dex文件,这个时候需要借助工具dx.bat,这个 工具 位于 sdk根目录/build-tools/任意版本 里面,例如我的是在 E:\Java\Sdk\build-tools\28.0.3 下面(比如你是用的是 cmd
命令,需要切换至该目录下)。通过如下命令将.class文件编译成.dex文件:
dx --dex --output=TestClass.dex TestClass.class 注意:这个命令中TestClass.class文件需要放置在当前目录下 复制代码
执行完成后,我们会在当前目录下看到 TestClass.dex
文件。接下来我们需要通过 dexdump
工具来反编译 TestClass.dex
文件(注: dexdump
工具和 javap
工具作用类似,只是 javap
是反编译的 .class
文件)
dexdump -d TestClass.dex 复制代码
执行结果如下
Processing 'TestClass.dex'... Opened 'TestClass.dex', DEX version '035' Class #0 - Class descriptor : 'LTestClass;' Access flags : 0x0001 (PUBLIC) Superclass : 'Ljava/lang/Object;' Interfaces - Static fields - Instance fields - #0 : (in LTestClass;) name : 'm' type : 'I' access : 0x0002 (PRIVATE) Direct methods - #0 : (in LTestClass;) name : '<init>' type : '()V' access : 0x10001 (PUBLIC CONSTRUCTOR) code - registers : 1 ins : 1 outs : 1 insns size : 4 16-bit code units 0000f8: |[0000f8] TestClass.<init>:()V 000108: 7010 0200 0000 |0000: invoke-direct {v0}, Ljava/lang/Object;.<init>:()V // method@0002 00010e: 0e00 |0003: return-void catches : (none) positions : 0x0000 line=1 locals : 0x0000 - 0x0004 reg=0 this LTestClass; Virtual methods - #0 : (in LTestClass;) name : 'inc' type : '()I' access : 0x0001 (PUBLIC) code - registers : 2 ins : 1 outs : 0 insns size : 5 16-bit code units 000110: |[000110] TestClass.inc:()I 000120: 5210 0000 |0000: iget v0, v1, LTestClass;.m:I // field@0000 000124: d800 0001 |0002: add-int/lit8 v0, v0, #int 1 // #01 000128: 0f00 |0004: return v0 catches : (none) positions : 0x0000 line=5 locals : 0x0000 - 0x0005 reg=1 this LTestClass; source_file_idx : 4 (TestClass.java) 复制代码
到这,我们终于拿到了反编译的 dex
文件了,下面我们就可以结合具体的内容来讲今天的重点 字节码格式 了,开心 ?:
dalvik字节码格式介绍
我们就只介绍 000124:
处指令码该如何解析。
d800 0001
。 看到这个指令的第一个问题肯定是这个是怎么来的(如何生成的)只有搞明白如何来的才能去分析它。
首先我们看如下箭头指示
这不就是我们在java代码中写的inc()
方法吗,但是这个方法在dex文件中的表示涉及到官方文档中
文件板式
,所以我们需要简单的介绍一下dalvik的文版样式(这里我只简单介绍这个inc()方法是怎么来的,感兴趣的同学可以去看官方文档):
首先我们看一下文版样式中 class_defs
, class_def
里面记录了类中的信息
class_def_item
中的
class_data_off
:
接下来我们看一下class_data_item
中的两个方法
direct_methods和virtual_methods
:
从这两个说明中我们能够知道,direct_methods
定义的是(static、private或构造方法),
virtual_methods
定义的是虚拟(非static、private或构造函数)方法。所以我们定义的inc()方法应该是在
encoded_method[virtual_methods_size]
中指定,然后我们看一下
encoded_method
:
这里要说一下 uleb128
,这个是无符号 leb128(“Little-Endian Base 128”)
,表示无符号整数的可变长度编码。而 little-endian
表示小端字节序(即低位在前,高位在后)
code_item
ushort
:表示16位无符号整数,采用小端字节序。
看说明我们可知,上面所说的指令码 d800 0001
就是这里的 ushort
类型的数据,表示两个字节。
d800 0001
的由来(详细的说明需要结合官方文档来看),下面我们就可以分析这个字节码的含义了,哈哈,开心 ( ?:)
解析指令码
前面我们已经介绍了指令码 d800 0001
的由来以及它的ushort类型,现在我们就可以进行解析了。
这里的解析我们需要结合Dalvik可执行指令格式中关于按位描述的内容进行,首先我们看官方文档按位描述中的一个例子
这里直接拿例子来说: “B|A|op CCCC”格式表示其包含两个 16 位代码单元。第一个字由低 8 位中的操作码和高 8 位中的两个四位值组成;第二个字由单个 16 位值组成 复制代码
然后我们将 d800 0001
来与之对应:
首先来看 d800
,它由 d8
和 00
两个字节构成,根据 little-endian
字节序,我们知道 d8
表示低8位, 00
表示高8位。而由上面按位描述的例子可知,低八位表示操作码。高八位表示参数
既然我们知道了操作码是 d8
,那么我们如何去查找操作码对应的内容了,这个我当时也是看了好长时间也没看明白,最后终于在字节码集合中找到了操作码的位置,在这个表格中,运算和格式标签中的运算就是我们要找的操作码,如图:
然后我们找到 d8
这个操作码,
运算和格式 | 助记符/语法 | 参数 | 说明 |
---|---|---|---|
d8 22b |
binop/lit8 vAA,vBB, #+CC add-int/lit8 |
A:目标寄存器(8位) B:源寄存器(8位) C:有符号整数常量(8位) |
对指定的寄存器(第一个参数)和字面值(第二个参数)执行指定的二元运算,并将结果存储到目标寄存器中 |
这里我们先不讨论具体的语法命令,我们先看运算和格式表格中的格式 22b
,这个在哪里能找到了,就在格式说明中的ID,然后我们找到22b这个ID,如下表:
格式 | ID | 语法 | 包含的重要操作码 |
---|---|---|---|
AA|op CC|BB | 22b | op vAA,vBB,#+CC |
现在我们看到了格式 AA|op CC|BB
,让我们将指令码 d800 0001
与这个格式相对应,即 AA
表示 d800
中的高八位 00
, op
表示 d800
中的低八位 d8
; CC|BB
即表示 01|00
。
至此,我们终于了解了 d800 0001
所代表的含义以及在官方文档中表格的对应关系。
延伸
有的读者可能想了解指令码 d800 0001
对应的具体操作,由于不是本章重点,所以这里只简单提一下, d8:add-int/lit8
指令根据描述其实就是执行 +
这个二目运算,然后根据说明中的内容:对指定寄存器(第一个参数这里为 0
)和字面量(第二个参数这里是 1
)执行指定的二元运算,并将结果存储到目标寄存器中。(具体的 vX
, #+X
所代表的含义参考语法)。
以上所述就是小编给大家介绍的《dalvik字节码初识》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
与孩子一起学编程
[美] 桑德Warren Sande、Carter Sande / 苏金国、姚曜 等 / 人民邮电出版社 / 2010-11 / 65.00元
一本老少咸宜的编程入门奇书!一册在手,你完全可以带着自己的孩子,跟随Sande父子组合在轻松的氛围中熟悉那些编程概念,如内存、循环、输入和输出、数据结构和图形用户界面等。这些知识一点儿也不高深,听起来备感亲切,书中言语幽默风趣而不失真义,让学习过程充满乐趣。细心的作者还配上了孩子们都喜欢的可爱漫画和经过运行测试的程序示例,教你用最易编写和最易理解的Python语言,写出你梦想中的游戏程序。 ......一起来看看 《与孩子一起学编程》 这本书的介绍吧!