接口定义:
给接口做一个实现:
我们先做一个新建对象,并调用这个对象从接口。
java字节码:
0:首先new一个对象。
3:对象的引用值在栈里,dup一个,给下面调用类时用。
4:调用sampleclass的构造函数。虽然是生动生成的,但是new完了之后是一定要调的。
9:invokevirtual是调用虚方法。
dalvik代码:
dalvik代码还是要更好读一些:
new-instance结果存到v0中。
然后间接寻址,调用v0中的类的构造方法
接着v0继续用,调interfacemethod方法。
最后返回。
下面我们分析一下与面向对象相关的oat指令:
new-instance, invoke-direct和invoke-virtual都是两个参数的指令,用到两个虚拟寄存器:
v0:地址为sp+16
v1: 地址为sp+40
前面还是保存参数,检查suspend状态。
下面是调用pallocobject过程去创建新对象。w0中是类的类型。
返回值就是新对象的引用,将其存到sp+16(v0)。再重新读回来。
将对象引用存到sp+12,再读入到w1。
然后计算sampleclass类的构造方法的地址,然后跳转到该函数。
返回值存到sp+16(v0)。
w0再存到sp+12,再读到w1。
然后计算interfacemethod方法的地址,最后直接跳转到那个方法。
将进入时备份的lr恢复,并返回。
最后还是检查suspend的调用。
接口类:
接口的实现类:
正如以前我们看到的一样,这次再强调一下。如果没有默认的构造函数,java会自动给生成一个,然后会调用object类的构造函数。
sampleinterface就没生成什么代码。
oat代码:
前面还是例行备份和检查。
下面先load v0(sp+40)的值,然后清0初始化。
最后计算object的构造方法地址,并跳转过去。
然后返回。
这个空的interfacemethod函数可真是够空的,除了查一下suspend状态,可以打个断点啥的。其余真的啥也没干。
lr存到sp+24
w1存到sp+40
然后从sp+24中把lr读回来
返回
在看oat生成的代码的时候,我们经常看到lr和tr这样的别名。下面我们先扫清一下寄存器的障碍,学习一下art使用寄存器的土话。
复习一下,arm 64下的64位通用寄存器有33个:
x0~x30共31个
sp栈寄存器
xzr零寄存器,从它读就读出个0,向它写等于空操作
这33个64位寄存器也可以用低32位,当作32位寄存器来使用:
w0~w30
wsp
wzr
另外,还有32个64位的neon浮点运算寄存器:
64位双精度是d0~d31
32位单精度是s0~s31
这其中,有下列寄存器被art定义了自己含义:
fp是frame pointer,lr是link register。
在art代码中,这些寄存器还有别名:
在art代码中,用得最多的就是xself,举个例子,判断线程是不是被挂起来了,生成的代码是这样的:
上面的代码是不是看起来有点似曾相识啊?
下面是保存刚才我们介绍的特殊寄存器的代码:
下面是恢复用的:
这一章的最后,我们说说jni.
针对jni,没有什么java代码值得生成的。
我们看下反汇编的java代码,对应test1并没有java字节码生成。
但是oat还是会生成对应的查找jni函数的指令:
sp用掉了192的空间,先减掉。
然后备份x19~x30.d8~d15和tr(x18)的值。
下面将产生三次过程调用,分别是对应artquickgenericjnitrampoline,native函数本身和artquickgenericjniendtrampoline。
再调用了artquickgenericjniendtrampoline之后,再去检查是否有异常产生。
下面两句是处理异常的,#136是异常状态的地址,如果出现异常,就跳转到ret后面的异常处理部分去执行。
为什么异常状态的值是136呢,因为它在art代码中的定义是:thread_exception_offset。
在64位系统里,指针的size是8,所以这个offset的值为128+8,为136.
thread_top_quick_frame_offset为128+3*8 = 152。
thread_self_offset为128+72=200.
thread_card_table_offset = 128 + 147 * 8 = 1304.
把一开始减掉的192加回来,然后返回。
后面是异常处理的部分
这段代码用了大量的寄存器啊,那他们是干嘛用的呢?
不用担心,虽然没有文档,但是art代码中对此有说明:
如果有参数传进来,如果是整型的,最多7个参数,位于x1~x7中。如果是浮点数,就在d0~d7中。
返回地址在x30/lr中。
对照着下面的代码去看上面生成的test1调用jni的代码,是不是觉得有点能看懂了的意思了?
这些代码位于/art/runtime/arch/arm64/quick_entrypoints_arm64.s中。
通过看这里面的汇编过程的代码,再去对照编译出来的oat代码,为我们打开一条继续深入的路径。