指令语法由指令的位描述与指令格式标识来决定。
约定如下:
例:A|G|op BBBB F|E|D|C
此条指令由三个16位的字组成。
A|G|op
,高8位为A和G,低字节为操作码op。 BBBB
,表示一个16位的偏移值。约定如下:
助记符 | 位大小 | 说明 |
---|---|---|
b | 8 | 8位有符号立即数 |
c | 16,32 | 常量池索引 |
f | 16 | 接口常量(仅对静态链接格式有效) |
h | 16 | 有符号立即数(32位或64位数的高值位,低值位为0) |
i | 32 | 立即数,有符号整数或32位浮点数 |
l | 64 | 立即数,有符号整数或64位双精度浮点数 |
m | 16 | 方法常量(仅对静态链接格式有效) |
n | 4 | 4位的立即数 |
s | 16 | 短整型立即数 |
t | 8,16,32 | 跳转、分支 |
x | 0 | 无额外数据 |
特殊情况:末尾可能会多出另一个字母,如果是s表示指令采用静态链接,如果是i表示指令应该被内联处理。
例: 22x
Dalvik语法说明:
例: op vAA, string@BBBB
指令用到了1个寄存器参数vAA
,并且还附加了一个字符串常量池索引string@BBBB
,其实这条指令格式代表着const-string指令。
ps:在Android4.0源码Dalvik/docs目录下提供了一份文档instruction-formats.html,里面详细列举了Dalvik指令的所有格式。
主流的有BakSmali和Dedexer。
采用上一节得到的Hello.dex。
Hello.java内容如下
public class Hello {
public int foo(int a, int b) {
return (a + b) * (a - b);
}
public static void main(String[] argc) {
Hello hello = new Hello();
System.out.println(hello.foo(5, 3));
}
}
执行命令行:java -jar baksmali.jar -o baksmaliout Hello.dex
生成baksmaliout目录及目录下的Hello.smali文件,使用sublime打开它,foo函数部分如下:
# virtual methods
.method public foo(II)I
.registers 5
.parameter
.parameter
.prologue
.line 3
add-int v0, p1, p2
sub-int v1, p1, p2
mul-int/2addr v0, v1
return v0
.end method
注:BakSmali生成的方法代码以.method
指令开始,以.end method指令结束,根据方法类型的不同,在方法指令开始前可能会用井号“#”加以注释。如# virtual methods
表示这是一个虚方法,# direct methods
表示这是一个直接方法。
ps:BakSmali生成的字段代码以.field
指令开头,根据字段类型的不同,在字段指令的开始可能会用到井号“#”加以注释,如#instance fields
表示这是一个实例字段,#static fields
表示这是一个静态字段。
执行命令行:java -jar ddx.jar -d ddxout Hello.dex
生成ddxout目录及目录下的Hello.ddx文件,使用sublime打开它,foo函数部分如下:
.method public foo(II)I
.limit registers 5
; this: v2 (LHello;)
; parameter[0] : v3 (I)
; parameter[1] : v4 (I)
.line 3
add-int v0,v3,v4
sub-int v1,v3,v4
mul-int/2addr v0,v1
return v0
.end method
两者在语法细节上有如下不同点:
语法 | BakSmali | Dedexer |
---|---|---|
寄存器数目表示 | .registers | .limit registers |
this引用表示 | 寄存器p0 | 寄存器v2 |
函数参数指定 | 一条.parameter指令指定一个参数 | parameter数组指明参数寄存器 |
指明代码起始处 | .prologue | 无 |
寄存器表示法 | p命名法 | v命名法 |
BakSmali功能:反汇编功能、支持使用Smali工具打包反汇编代码重新生成dex文件(可用于apk文件的修改、补丁、破解等场合)。
Dalvik中用到的寄存器都是32位的,支持任何类型,64位类型用2个相邻寄存器表示。
ØØ|op AAAA BBBB
的指令的语法为op vAAAA vBBBB
,每个大写字母代表4位,AAAA
最大值为2的16次方,寄存器采用v0作起始值,因此其取值范围为v0-v65535。
Dalvik虚拟机为每个进程维护一个调用栈,这个调用栈的一个作用就是用来“虚拟”寄存器。虚拟机通过处理字节码,对寄存器进入读与写的操作,其实都是在写栈空间。
Dalvik虚拟机参数传递方式中的规定:
假设一个函数使用到M个寄存器,并且该函数有N个参数,参数使用最后的N个寄存器,局部变量使用从v0开始的前M-N个寄存器。
v命名规则:
所有寄存器从v0开始命名,依次递增。
对于foo()
函数,v0,v1表示函数的局部变量寄存器,v2表示被传入的Hello对象的引用,v3和v4分别表示两个传入的整形参数。
p命名规则:
引入的参数命名从p0开始,依次递增。对于foo()
函数,v0,v1表示函数的局部变量寄存器,p0表示被传入的Hello对象的引用,p1和p2分别表示两个传入的整形参数。
p命名法更容易让人判断寄存器是局部变量寄存器还是参数寄存器。
Dalvik字节码类型分为:基本类型与引用类型。除了对象与数组属于引用类型外,其它的Java类型都是基本类型。
语法 含义
V void,只用于返回类型
Z boolean
B byte
S short
C char
I int
J long
F float
D double
L java类类型
[ 数组类型
L
类型可以表示java类型中的任何类。这些类在java代码中以package.name.ObjectName
方式引用,到了Dalvik汇编代码中,它们以Lpackage/name/ObjectName;
形式表示,注意最后有个分号,L表示后面跟着一个java类,package/name表示对象所在的包,Objectname表示对象的名称,最后的分号表示对象名结束,例如:Ljava/lang/String;
相当于java.lang.String
。
[
类型可以表示所有基本类型数组。[
后面紧跟基本类型描述符,如[I
表示一个整型一维数组,相当与java中的int[]
,多个[
在一起时可以表示多维数组。记住多维数组最大为255个。
描述方法:方法名、类型参数和返回值
格式如下:
Lpackage/name/ObjectName;->MethodName(III)Z
Lpackage/name/ObjectName;
:表示一个类型MethodName
:具体的方法名(III)Z
:方法的签名部分,其中括号内的III
为方法的参数,在此为三个整型参数,Z
表示方法的返回类型为boolean类型。例:
method(I[[IILjava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
method
:方法名
括号里边的为方法参数,即
I
:int
[[I
:int[][]
Ljava/lang/String;
:String
[Ljava/lang/Object;
:Object[]
返回值:
Ljava/lang/String;
:String
还原来就是:
String method(int ,int[][],int ,String,Object[])
字段的格式:
Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;
Lpackage/name/ObjectName;
:类型
FieldName
:字段名
Ljava/lang/String;
:字段类型
字段名与字段类型中间用冒号“:”隔开
《Android软件安全与逆向分析》