dex字节码介绍和二进制译码分析

本文通过一个实例详细解析了如何从 Dex 字节码的二进制格式逐步解读字节码指令,涉及Dalvik Executable instruction formats和Dalvik bytecode的相关知识。通过译码步骤,包括确定指令码、确立指令格式和分析参数,阐述了字节码指令的解析过程,有助于深入理解Android系统的字节码执行机制。

对于dex字节码,官方提供了三篇文档对其进行比较详尽的介绍,分别是:

上面三篇文章,通读下来,如果不认真仔细看,很难把它们之间的关系串联起来,在实际阅读分析系统源码或者一些字节码指令时,还是较为困难的。下面就以一个实际例子,分析dex字节码指令的运用和阅读。
 

源码例子
public class Main {

    public static void main(String[] args) {
        Hello hello = new Hello();
        String sentence = hello.sayHello();
        System.out.printf(sentence);
    }

    public static class Hello {
        public String sayHello() {
            return "hello!!!!";
        }
    }

}

我们主要分析 String sentence = hello.sayHello(); 这行代码的字节码指令情况。

使用工具,将上面源码编译成dex文件,用 010编译器 打开 :
dex文件格式
从上图看,dex 文件总体的格式,就如 Dalvik Executable format 中介绍的一样。上图中对应字段,和下图dex 文件格式是一致吻合的。

 
要找到 String sentence = hello.sayHello(); 该语句的对应二进制指令,就要在dex 文件中,首先找到 main 方法。
根据官方文档,它是在 :


dex文件 —>class_defsclass_def_item —> class_data_item —> direct_methods


在本例子中,main 方法隐藏在字节码中,如下图:

main 方法的二进制字节码指令就在 insns 这个18位数组中。
 
我们看看 insns 这些指令的二进制:

如果仅仅看二进制,很难明白这么多条指令的内容,我们可以借助dexdump,先看看二进制译码后的字节码指令究竟是怎样的,dexdump 如下(只包含 dexdumpmain 方法的部分):

从上图看到,对于语句 :
String sentence = hello.sayHello();
其对应的dex字节码指令是:
invoke-virtual {v0}, LMain$Hello;.sayHello:()Ljava/lang/String; // method@0001
该指令在二进制中的表示是:
6e10 0100 0000
 
这么复杂的一个dex字节码指令,对应的二进制表示,是如此的简单。这个编码和译码的关系就是 Dalvik bytecodeDalvik Executable instruction formats 里面说的内容.
 
接下来,就是详细介绍该如何译码 6e10 0100 0000 这条二进制指令 。
要译码指令,首先要清楚,指令的格式 如何。
Dalvik bytecode 知道:


指令 = 指令码op + 指令格式format + 参数


先不要管这几个组成部分在指令中的排列顺序,一条指令就是上面的三部分组成的,三部分确定一条字节码指令。
 

步骤一 :译码op

要译码,首先,我们要确定op(指令码)是什么?
6e10 0100 0000 二进制指令中,op = 6e
这里有一个疑问,从 Dalvik bytecodeDalvik Executable instruction formats 中,我们知道一条指令,大概 (仅仅是大概)是下面的类似形式:

B|A|op CCCC

从直观阅读来看, op 在参数 B|A 之后,那么在这里,10 才是 op, 为什么是 6e 呢?
主要原因是,字节码的字节序为 Little Endian 。这样,在16进制表示中,我们用编辑器看到op 在前面,而实际上,指令的格式是op 在后面,这有利于程序按字节序解释指令的。
那么,在6e10 0100 0000 中,6e10 对应到字节码指令,实际是 1|0|6e 的形式。
 

步骤二 :确立Format

知道 op=6e 后,下一步,就是确定指令format。查阅 Dalvik bytecode 的指令集表格:

op & formatMnemonic / SyntaxArguement
6e 35c6e: invoke-virtual {vC, vD, vE, vF, vG}, meth@BBBBA: argument word count (4 bits)
B: method reference index (16 bits)
C…G: argument registers (4 bits each)

从表格中,看到,op=6e 对应的 format=35copformat 是多对一关系) 。
 
format=35c ,实际上,这个 35cformatID,这个我们可以在 Dalvik Executable instruction formats 中提供的表格中查到:

FormatIDSyntax
A|G|op
BBBB
F|E|D|C
35c[A=5] op {vC, vD, vE, vF, vG}, meth@BBBB
[A=5] op {vC, vD, vE, vF, vG}, site@BBBB
[A=5] op {vC, vD, vE, vF, vG}, type@BBBB
[A=4] op {vC, vD, vE, vF}, kind@BBBB
[A=3] op {vC, vD, vE}, kind@BBBB
[A=2] op {vC, vD}, kind@BBBB
[A=1] op {vC}, kind@BBBB
[A=0] op {}, kind@BBBB

这里,我们就可以确立 6e10 0100 0000 二进制指令对应的字节码指令格式为 :


A|G|op
BBBB
F|E|D|C


步骤三 :分析参数

前面我们已经知道指令的Format,这时候我们需要 代入参数
6e10 0100 0000 中,6e 后面的10 ,对应Format,可以代入 A=1G=0,确立,字节码指令语法为:


op {vC}, kind@BBBB


 
op 实际为 invoke-virtual , C=0 , kindmethodBBBB=0001 (注意 Little Endian )。
最终, 6e10 0100 0000 的字节码指令(其实只是助记符,方便人理解指令) :

invoke-virtual {V0}, method@0001

 
invoke-virtual {V0}, method@0001 这条指令实际上就是调用一个virtual方法(非private,非static,非final,非构造函数),这个方法的索引由 参数 0001 指定。
那么,0001 方法索引在哪里?这个就要到dex文件中的method_ids中去找。

method_ids 数组中,准确找到了index=0x0001 的对应方法为 Main$Hello.sayHello()

剩下的参数是 {V0} , 其意思是需要使用 V0 寄存器的值作为方法调用的参数,但 sayHello 是不带参数的方法。在 Dalvik bytecode 中提及,对于实例方法(instantce methods)的调用,第一个参数为 调用者本身,所以 V0 存有的肯定是 Hello 类的实例对象,这符合前面指令new-instance 时,V0 寄存器是作为目的寄存器的情况。

 

总结

上文全部,只是其中一个方法调用的例子,但是实际上,任何二进制指令都是可以按以上步骤进行译码:

  • 指令 = 指令码op + 指令格式format + 参数
  • 译码步骤:确立op --> 查 format --> 读参数 --> 最终译码
  • 字节码指令实际上不是必然要存在的,它只是反编译dex文件的产物,帮组人更易理解指令,也可以理解为一种IR
  • 理解字节码指令和dex 文件相关的知识,利于对android虚拟机,AOT等模块的进行深入了解。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值