Java方法签名

学习bytecode、dalvik bytecode甚至JNI时,都会用到方法的签名,因此这里先给大家介绍下。

使用javap(将在2.2章中介绍)工具可生成方法的签名,如:

方法定义:

1
public native int add(long a, String b);

对应的签名:

1
(JLjava/lang/String;)I

我们可以看到,在签名中,返回值在最后,参数使用括号括住。

下面我来详细解释一下它们的关系:

基本数据类型都有一一对应的签名:

数据类型 签名
void V
boolean Z
byte B
char C
short S
int I
float F
long J
double D

对象与数组比较特殊。

对象均以L开头,以;结束,中间为包名+类名,以/分隔,如类java.lang.String的签名为Ljava/lang/String;

数组使用[来表示,一个[代表一维。如String[]的签名为[Ljava/lang/String;,String[][]的签名为[[Ljava/lang/String;,如果是int[][][]三维数组,则其签名为[[[I

bytecode介绍

关于bytecode的介绍,可参考http://en.wikipedia.org/wiki/Java_bytecode

学习bytecode与学习汇编有些相似,没学过汇编的童鞋或许会觉得汇编很难,其实汇编语法不难,只是使用汇编写复杂程序难。而bytecode相对来说比汇编还要简单些,这里大概介绍bytecode的语法。

bytecode是JVM规范中所定义的指令集,由JVM识别并运行。

bytecode是基于栈的,指令所用到的数据均取自于栈,而指令的操作结果,则会压入栈顶。对于栈不理解的,请先看下数据结构中栈的定义。另外,参数与临时变量都是存放在本地变量中,可以把本地变量和栈都理解为一个固定的int型数组。

这里先举个简单的例子:

下面是一个简单的方法:

1
2
3
4
int add(int a, int b) {
int c = a + b;
return c;
}

这个方法中,需要把两个参数中的数据进行加法运算,加法运算的指令为iadd,上面说了指令所用到的数据均来自于栈,因此就需要先把参数a和参数b加载到栈中。

我们看下相应的字节码:

1
2
3
4
5
6
7
8
int add(int a, int b) {
iload 1 // 加载参数a到栈顶
iload 2 // 加载参数b到栈顶
iadd // 从栈顶取出两个整数进行相加,并把相加后的结果放入栈中
istore 3 // 从栈顶取出一个整数存放到c中
iload 3 // 加载临时变量c到栈顶
ireturn // 从栈顶取出一个整数,并作为返回值返回
}

为了书写方便,以后均用locals代表本地变量,locals[i]代表第i个本地变量。

iload是用来从locals中加载内容的,但为什么是1呢?或许有人会说locals的计数是从1开始的。但其实不是,这里的计数与java语法中数组的计数一样,都是从0开始的。

大家有没注意到,这个方法是个成员方法,我们知道成员方法是有this指针的,所以locals[0]就是this指针。当然如果是静态方法,就没this指针了,那么locals[0]就是第1个参数了。

也就是说,参数是按顺序放在locals的最前面的,而临时变量就是放在locals的参数后面。这个特性非常重要,大家要记住,后面会提到。

bytecode指令可以分为几类,这里是我综合对ASM(后缀文章会介绍到)与维基百科的理解进行分类,相比维基百科所述的分类复杂一点,但更详细更好理解:

1)常量赋值

对象常量(aconst_null)、基本数据类型常量(iconst_、lconst_、fconst_、dconst_、bipush、sipush)、另外ldc指令适用于所有常量。

需要注意的是,dconst_*泛指了dconst_0与dconst_1,其它类似,后面也有类似的写法。

2)数组操作

创建(newarray/anewarray)、读取(xaload)、写入(xastore)、取长度(arraylength)。

3)栈操作

压入栈(pop/pop2)、从栈中拷贝(dup*)、交换栈中的值(swap)

4)算术运算

加(add)、减(sub)、乘(mul)、除(div)、取模(rem)、取反(neg)、左移(shl)、算术右移(shr)、逻辑右移(ushr)、与(and)、或(or)、异或(xor)

5)复杂类型相关

强制类型转换(2、checkcast)、创建(new)、是否某个类的实例(instanceof)

6)比较或控制跳转

比较指令(cmp),控制跳转指令(if*、goto、jsr)

7)返回(*return/ret)

8)异常(athrow)

9)本地变量操作

读取(load)、写入(store)

10)成员变量操作

读取(getfield/getstatic)、写入(putfield/putstatic)

11)方法调用(invoke*)

12)switch语句(*switch)

指令的详细说明请参考:

http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

由于指令非常多,不便于一一细说,大家可通过eclipse的bytecode插件来对照着看,此插件会在下一章中介绍。

另外有一点需要注意的是,long和double都是占8个字节的,而上面说过本地变量和栈都可当作int型数组,因为Java中int型占4个字节,所以long和double都占两个位置,而其它类型(boolean、byte、char、short、int、float、引用)都占4个字节。

举个例子,如果有如下方法定义void a(long a, char b),假设我们想加载参数b在栈中,需要怎么写呢?我们看到这是一个成员方法,成员方法含this指针,因此locals[0]为this指针。而参数a为long型,占两个字节,因为locals[1~2]为参数a,那么参数b就是locals[3]了。相应的指令为iload 3。

细心的童鞋会发现,bytecode的大部分操作都没有boolean、byte、char、short相应的指令,只有与int相关的指令,因为它们都直接作为int来处理了。

dalvik bytecode介绍

官方文档:https://source.android.com/devices/tech/dalvik/dalvik-bytecode.html

dalvik bytecode与bytecode相似,都是一套指令集,由相应的虚拟机来识别运行。但不同的是,davlik bytecode是dalvik VM的指令集,且它是基于寄存器的,也就是本地变量是保存在寄存器中,且指令的数据来源于寄存器。

与上面相同的例子,我们看下dalvik bytecode是怎样的

1
2
3
4
int add(int a, int b) {
add-int v0, v2, v3 // 把v2(参数a)和v3(参数b)相加存入到v0(临时变量c)中.
return v0 // 把v0作为返回值返回
}

根据注释可以知道,v2和v3分别是分别是参数a和参数b,而v0则是临时变量c。而这个方法又是成员方法,所以v1就是this指针了。

这里反映了dalvik bytecode的一个特点:临时变量在最前面的寄存器,参数在临时变量之后,寄存器计数从0开始。

有学过smali(后面会介绍)语法的童鞋或许会有疑问,参数不是应该从p0开始,而临时变量则是从v0开始么?实际上smali的写法只是为了方便大家而已,它生成dex时会针对性地做转换处理。

指令的分类和bytecode大同小异,这里不作区分,建议大家先把bytecode学好,这样学dalvik bytecode自然会事半功倍。

细心的童鞋会发现,很多指令对寄存器的范围以及常量有限制,例如const/4 vA, #+B中的A或B这类字母都代表4bit,而vA代表目的寄存器,#+B代表常量,因此目的寄存器需要在v0v15之间,而常量要在00xF之间。

bytecode与dalvik bytecode对比

bytecode是基于栈的,其参数与临时变量均存放在locals中,而指令所需要的数据,都存放在stack中。

dalvik bytecode是基于寄存器的,其参数、临时变量及指令所需要的数据,均存放在寄存器中。

bytecode有专门的指令(aconst_null)来代表空对象指针。

dalvik bytecode空对象没作区分,直接使用常量0来表示,也就意味着当一个寄存器被赋值为0,我们需要根据下文才能识别它是对象还是基本数据类型。

bytecode指令的执行结果会被JVM自动压入栈顶中,无须我们关心。

dalvik bytecode有部分指令的执行结果需要使用move-result*这一系列的指令来指定存放在某个寄存器中。

总结

本文并没深入介绍bytecode与dalvik bytecode,但介绍的知识点都是一些关键的点,对于入门来说已经足够了,建议有兴趣学习bytecode与dalvik bytecode的童鞋能够对照着代码来学,多看看。

关于如何对照着代码来
阅读bytecode,可参见2.2章ASM bytecode online插件部分,而dalvik bytecode的阅读,请参见2.3章asmdex,smali/baksmali。