ART世界探险(8) - 面向对象编程
对象和方法调用
接口定义:
public interface SampleInterface {
void interfaceMethod();
}
给接口做一个实现:
public class SampleClass implements SampleInterface{
@Override
public void interfaceMethod() {
}
}
我们先做一个新建对象,并调用这个对象从接口。
public void testMethod(){
SampleClass sample = new SampleClass();
sample.interfaceMethod();
}
testMethod方法
Java字节码:
0:首先new一个对象。
3:对象的引用值在栈里,dup一个,给下面调用类时用。
4:调用SampleClass的构造函数。虽然是生动生成的,但是new完了之后是一定要调的。
9:invokevirtual是调用虚方法。
public void testMethod();
Code:
0: new #4 // class com/yunos/xulun/testcppjni2/SampleClass
3: dup
4: invokespecial #5 // Method com/yunos/xulun/testcppjni2/SampleClass."<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #6 // Method com/yunos/xulun/testcppjni2/SampleClass.interfaceMethod:()V
12: return
Dalvik代码:
Dalvik代码还是要更好读一些:
new-instance结果存到v0中。
然后间接寻址,调用v0中的类的构造方法
接着v0继续用,调interfaceMethod方法。
最后返回。
20: void com.yunos.xulun.testcppjni2.TestART.testMethod() (dex_method_idx=16799)
DEX CODE:
0x0000: 2200 1008 | new-instance v0, com.yunos.xulun.testcppjni2.SampleClass // type@2064
0x0002: 7010 8941 0000 | invoke-direct {v0}, void com.yunos.xulun.testcppjni2.SampleClass.<init>() // method@16777
0x0005: 6e10 8a41 0000 | invoke-virtual {v0}, void com.yunos.xulun.testcppjni2.SampleClass.interfaceMethod() // method@16778
0x0008: 0e00 | return-void
下面我们分析一下与面向对象相关的OAT指令:
new-instance, invoke-direct和invoke-virtual都是两个参数的指令,用到两个虚拟寄存器:
- v0:地址为sp+16
- v1: 地址为sp+40
CODE: (code_offset=0x0050337c size_offset=0x00503378 size=160)...
0x0050337c: d1400bf0 sub x16, sp, #0x2000 (8192)
0x00503380: b940021f ldr wzr, [x16]
suspend point dex PC: 0x0000
GC map objects: v1 ([sp + #40])
0x00503384: f81e0fe0 str x0, [sp, #-32]!
0x00503388: f9000ffe str lr, [sp, #24]
0x0050338c: b9002be1 str w1, [sp, #40]
0x00503390: 79400250 ldrh w16, [tr] (state_and_flags)
0x00503394: 350003f0 cbnz w16, #+0x7c (addr 0x503410)
前面还是保存参数,检查suspend状态。
下面是调用pAllocObject过程去创建新对象。w0中是类的类型。
0x00503398: f94003e1 ldr x1, [sp]
0x0050339c: 52810200 mov w0, #0x810
0x005033a0: f940d65e ldr lr, [tr, #424] (pAllocObject)
0x005033a4: d63f03c0 blr lr
suspend point dex PC: 0x0000
GC map objects: v1 ([sp + #40])
返回值就是新对象的引用,将其存到sp+16(v0)。再重新读回来。
0x005033a8: b90013e0 str w0, [sp, #16]
0x005033ac: b94013e0 ldr w0, [sp, #16]
0x005033b0: b940001f ldr wzr, [x0]
suspend point dex PC: 0x0002
GC map objects: v0 ([sp + #16]), v1 ([sp + #40])
将对象引用存到sp+12,再读入到w1。
然后计算SampleClass类的构造方法的地址,然后跳转到该函数。
0x005033b4: b9000fe0 str w0, [sp, #12]
0x005033b8: b9400fe1 ldr w1, [sp, #12]
0x005033bc: f94003e0 ldr x0, [sp]
0x005033c0: b9400400 ldr w0, [x0, #4]
0x005033c4: d2818b10 mov x16, #0xc58
0x005033c8: f2a00050 movk x16, #0x2, lsl #16
0x005033cc: f8706800 ldr x0, [x0, x16]
0x005033d0: f940181e ldr lr, [x0, #48]
0x005033d4: d63f03c0 blr lr
suspend point dex PC: 0x0002
GC map objects: v0 ([sp + #16]), v1 ([sp + #40])
返回值存到sp+16(v0)。
0x005033d8: b94013e0 ldr w0, [sp, #16]
0x005033dc: b940001f ldr wzr, [x0]
suspend point dex PC: 0x0005
GC map objects: v0 ([sp + #16]), v1 ([sp + #40])
w0再存到sp+12,再读到w1。
然后计算interfaceMethod方法的地址,最后直接跳转到那个方法。
0x005033e0: b9000fe0 str w0, [sp, #12]
0x005033e4: b9400fe1 ldr w1, [sp, #12]
0x005033e8: f94003e0 ldr x0, [sp]
0x005033ec: b9400400 ldr w0, [x0, #4]
0x005033f0: d2818c10 mov x16, #0xc60
0x005033f4: f2a00050 movk x16, #0x2, lsl #16
0x005033f8: f8706800 ldr x0, [x0, x16]
0x005033fc: f940181e ldr lr, [x0, #48]
0x00503400: d63f03c0 blr lr
suspend point dex PC: 0x0005
GC map objects: v0 ([sp + #16]), v1 ([sp + #40])
将进入时备份的lr恢复,并返回。
0x00503404: f9400ffe ldr lr, [sp, #24]
0x00503408: 910083ff add sp, sp, #0x20 (32)
0x0050340c: d65f03c0 ret
最后还是检查suspend的调用。
0x00503410: f9421e5e ldr lr, [tr, #1080] (pTestSuspend)
0x00503414: d63f03c0 blr lr
suspend point dex PC: 0x0000
GC map objects: v1 ([sp + #40])
0x00503418: 17ffffe0 b #-0x80 (addr 0x503398)
接口类:
public interface com.yunos.xulun.testcppjni2.SampleInterface {
public abstract void interfaceMethod();
}
接口的实现类:
正如以前我们看到的一样,这次再强调一下。如果没有默认的构造函数,Java会自动给生成一个,然后会调用Object类的构造函数。
public class com.yunos.xulun.testcppjni2.SampleClass implements com.yunos.xulun.testcppjni2.SampleInterface {
public com.yunos.xulun.testcppjni2.SampleClass();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void interfaceMethod();
Code:
0: return
}
SampleInterface接口
SampleInterface就没生成什么代码。
1095: Lcom/yunos/xulun/testcppjni2/SampleInterface; (offset=0x00272cb0) (type_idx=2065) (StatusInitialized) (OatClassNoneCompiled)
0: void com.yunos.xulun.testcppjni2.SampleInterface.interfaceMethod() (dex_method_idx=16779)
DEX CODE:
OatMethodOffsets (offset=0x00000000)
code_offset: 0x00000000
gc_map: (offset=0x00000000)
OatQuickMethodHeader (offset=0x00000000)
mapping_table: (offset=0x00000000)
vmap_table: (offset=0x00000000)
QuickMethodFrameInfo
frame_size_in_bytes: 0
core_spill_mask: 0x00000000
fp_spill_mask: 0x00000000
CODE: (code_offset=0x00000000 size_offset=0x00000000 size=0)
NO CODE!
SampleClass类
Dalvik代码:
Class #1446 -
Class descriptor : 'Lcom/yunos/xulun/testcppjni2/SampleClass;'
Access flags : 0x0001 (PUBLIC)
Superclass : 'Ljava/lang/Object;'
Interfaces -
#0 : 'Lcom/yunos/xulun/testcppjni2/SampleInterface;'
Static fields -
Instance fields -
Direct methods -
#0 : (in Lcom/yunos/xulun/testcppjni2/SampleClass;)
name : '<init>'
type : '()V'
access : 0x10001 (PUBLIC CONSTRUCTOR)
code -
registers : 1
ins : 1
outs : 1
insns size : 4 16-bit code units
132730: |[132730] com.yunos.xulun.testcppjni2.SampleClass.<init>:()V
132740: 7010 2942 0000 |0000: invoke-direct {v0}, Ljava/lang/Object;.<init>:()V // method@4229
132746: 0e00 |0003: return-void
catches : (none)
positions :
0x0000 line=3
locals :
0x0000 - 0x0004 reg=0 this Lcom/yunos/xulun/testcppjni2/SampleClass;
Virtual methods -
#0 : (in Lcom/yunos/xulun/testcppjni2/SampleClass;)
name : 'interfaceMethod'
type : '()V'
access : 0x0001 (PUBLIC)
code -
registers : 1
ins : 1
outs : 0
insns size : 1 16-bit code units
132748: |[132748] com.yunos.xulun.testcppjni2.SampleClass.interfaceMethod:()V
132758: 0e00 |0000: return-void
catches : (none)
positions :
0x0000 line=8
locals :
0x0000 - 0x0001 reg=0 this Lcom/yunos/xulun/testcppjni2/SampleClass;
source_file_idx : 5914 (SampleClass.java)
OAT代码:
1446: Lcom/yunos/xulun/testcppjni2/SampleClass; (offset=0x00277378) (type_idx=2064) (StatusInitialized) (OatClassAllCompiled)
0: void com.yunos.xulun.testcppjni2.SampleClass.<init>() (dex_method_idx=16777)
DEX CODE:
0x0000: 7010 2942 0000 | invoke-direct {v0}, void java.lang.Object.<init>() // method@16937
0x0003: 0e00 | return-void
OatMethodOffsets (offset=0x0027737c)
code_offset: 0x006622dc
gc_map: (offset=0x00278fac)
OatQuickMethodHeader (offset=0x006622c0)
mapping_table: (offset=0x002d7b56)
vmap_table: (offset=0x0030d99e)
v65535/r30
QuickMethodFrameInfo
frame_size_in_bytes: 32
core_spill_mask: 0x40000000 (r30)
fp_spill_mask: 0x00000000
vr_stack_locations:
ins: v0[sp + #40]
method*: v1[sp + #0]
outs: v0[sp + #8]
CODE: (code_offset=0x006622dc size_offset=0x006622d8 size=96)...
0x006622dc: d1400bf0 sub x16, sp, #0x2000 (8192)
0x006622e0: b940021f ldr wzr, [x16]
suspend point dex PC: 0x0000
GC map objects: v0 ([sp + #40])
0x006622e4: f81e0fe0 str x0, [sp, #-32]!
0x006622e8: f9000ffe str lr, [sp, #24]
0x006622ec: b9002be1 str w1, [sp, #40]
0x006622f0: 79400250 ldrh w16, [tr] (state_and_flags)
0x006622f4: 350001f0 cbnz w16, #+0x3c (addr 0x662330)
前面还是例行备份和检查。
下面先load v0(sp+40)的值,然后清0初始化。
最后计算Object的构造方法地址,并跳转过去。
然后返回。
0x006622f8: b9402be0 ldr w0, [sp, #40]
0x006622fc: b940001f ldr wzr, [x0]
suspend point dex PC: 0x0000
GC map objects: v0 ([sp + #40])
0x00662300: b90013e0 str w0, [sp, #16]
0x00662304: b94013e1 ldr w1, [sp, #16]
0x00662308: f94003e0 ldr x0, [sp]
0x0066230c: b9400400 ldr w0, [x0, #4]
0x00662310: d2822b10 mov x16, #0x1158
0x00662314: f2a00050 movk x16, #0x2, lsl #16
0x00662318: f8706800 ldr x0, [x0, x16]
0x0066231c: f940181e ldr lr, [x0, #48]
0x00662320: d63f03c0 blr lr
suspend point dex PC: 0x0000
GC map objects: v0 ([sp + #40])
0x00662324: f9400ffe ldr lr, [sp, #24]
0x00662328: 910083ff add sp, sp, #0x20 (32)
0x0066232c: d65f03c0 ret
0x00662330: f9421e5e ldr lr, [tr, #1080] (pTestSuspend)
0x00662334: d63f03c0 blr lr
suspend point dex PC: 0x0000
GC map objects: v0 ([sp + #40])
0x00662338: 17fffff0 b #-0x40 (addr 0x6622f8)
1: void com.yunos.xulun.testcppjni2.SampleClass.interfaceMethod() (dex_method_idx=16778)
DEX CODE:
0x0000: 0e00 | return-void
OatMethodOffsets (offset=0x00277380)
code_offset: 0x0066235c
gc_map: (offset=0x0027cdf4)
OatQuickMethodHeader (offset=0x00662340)
mapping_table: (offset=0x002d9eae)
vmap_table: (offset=0x0030d99e)
v65535/r30
QuickMethodFrameInfo
frame_size_in_bytes: 32
core_spill_mask: 0x40000000 (r30)
fp_spill_mask: 0x00000000
vr_stack_locations:
ins: v0[sp + #40]
method*: v1[sp + #0]
CODE: (code_offset=0x0066235c size_offset=0x00662358 size=52)...
0x0066235c: d1400bf0 sub x16, sp, #0x2000 (8192)
0x00662360: b940021f ldr wzr, [x16]
suspend point dex PC: 0x0000
GC map objects: v0 ([sp + #40])
这个空的interfaceMethod函数可真是够空的,除了查一下suspend状态,可以打个断点啥的。其余真的啥也没干。
lr存到sp+24
w1存到sp+40
然后从sp+24中把lr读回来
返回
0x00662364: f81e0fe0 str x0, [sp, #-32]!
0x00662368: f9000ffe str lr, [sp, #24]
0x0066236c: b9002be1 str w1, [sp, #40]
0x00662370: 79400250 ldrh w16, [tr] (state_and_flags)
0x00662374: 35000090 cbnz w16, #+0x10 (addr 0x662384)
0x00662378: f9400ffe ldr lr, [sp, #24]
0x0066237c: 910083ff add sp, sp, #0x20 (32)
0x00662380: d65f03c0 ret
0x00662384: f9421e5e ldr lr, [tr, #1080] (7)
0x00662388: d63f03c0 blr lr
suspend point dex PC: 0x0000
GC map objects: v0 ([sp + #40])
0x0066238c: 17fffffb b #-0x14 (addr 0x662378)
ART对于寄存器的使用
在看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定义了自己含义:
TR = X18, // ART Thread Register - Managed Runtime (Caller Saved Reg)
ETR = X21, // ART Thread Register - External Calls (Callee Saved Reg)
IP0 = X16, // Used as scratch by VIXL.
IP1 = X17, // Used as scratch by ART JNI Assembler.
FP = X29,
LR = X30,
FP是Frame Pointer,LR是Link Register。
在ART代码中,这些寄存器还有别名:
// Register holding Thread::Current().
#define xSELF x18
// x18 is not preserved by aapcs64, save it on xETR(External Thread reg) for restore and later use.
#define xETR x21
// Frame Pointer
#define xFP x29
// Link Register
#define xLR x30
// Define the intraprocedural linkage temporary registers.
#define xIP0 x16
#define wIP0 w16
#define xIP1 x17
#define wIP1 w17
在ART代码中,用得最多的就是xSELF,举个例子,判断线程是不是被挂起来了,生成的代码是这样的:
/*
* Called by managed code when the thread has been asked to suspend.
*/
.extern artTestSuspendFromCode
ENTRY art_quick_test_suspend
ldrh w0, [xSELF, #THREAD_FLAGS_OFFSET] // get xSELF->state_and_flags.as_struct.flags
cbnz w0, .Lneed_suspend // check flags == 0
ret // return if flags == 0
.Lneed_suspend:
mov x0, xSELF
SETUP_REFS_ONLY_CALLEE_SAVE_FRAME // save callee saves for stack crawl
bl artTestSuspendFromCode // (Thread*)
RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME_AND_RETURN
END art_quick_test_suspend
上面的代码是不是看起来有点似曾相识啊?
下面是保存刚才我们介绍的特殊寄存器的代码:
/*
* Macro that sets up the callee save frame to conform with
* Runtime::CreateCalleeSaveMethod(kRefsOnly).
*/
.macro SETUP_REFS_ONLY_CALLEE_SAVE_FRAME
adrp xIP0, :got:_ZN3art7Runtime9instance_E
ldr xIP0, [xIP0, #:got_lo12:_ZN3art7Runtime9instance_E]
// Our registers aren't intermixed - just spill in order.
ldr xIP0, [xIP0] // xIP0 = & (art::Runtime * art::Runtime.instance_) .
// xIP0 = (ArtMethod*) Runtime.instance_.callee_save_methods[kRefAndArgs] .
THIS_LOAD_REQUIRES_READ_BARRIER
// Loads appropriate callee-save-method.
ldr xIP0, [xIP0, RUNTIME_REFS_ONLY_CALLEE_SAVE_FRAME_OFFSET ]
sub sp, sp, #112
.cfi_adjust_cfa_offset 112
// Ugly compile-time check, but we only have the preprocessor.
#if (FRAME_SIZE_REFS_ONLY_CALLEE_SAVE != 112)
#error "REFS_ONLY_CALLEE_SAVE_FRAME(ARM64) size not as expected."
#endif
// Callee-saves
stp x19, x20, [sp, #16]
.cfi_rel_offset x19, 16
.cfi_rel_offset x20, 24
stp x21, x22, [sp, #32]
.cfi_rel_offset x21, 32
.cfi_rel_offset x22, 40
stp x23, x24, [sp, #48]
.cfi_rel_offset x23, 48
.cfi_rel_offset x24, 56
stp x25, x26, [sp, #64]
.cfi_rel_offset x25, 64
.cfi_rel_offset x26, 72
stp x27, x28, [sp, #80]
.cfi_rel_offset x27, 80
.cfi_rel_offset x28, 88
// x29(callee-save) and LR
stp x29, xLR, [sp, #96]
.cfi_rel_offset x29, 96
.cfi_rel_offset x30, 104
// Save xSELF to xETR.
mov xETR, xSELF
// Loads appropriate callee-save-method
str xIP0, [sp] // Store ArtMethod* Runtime::callee_save_methods_[kRefsOnly]
// Place sp in Thread::Current()->top_quick_frame.
mov xIP0, sp
str xIP0, [xSELF, # THREAD_TOP_QUICK_FRAME_OFFSET]
.endm
下面是恢复用的:
.macro RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME
// Restore xSELF.
mov xSELF, xETR
// Callee-saves
ldp x19, x20, [sp, #16]
.cfi_restore x19
.cfi_restore x20
ldp x21, x22, [sp, #32]
.cfi_restore x21
.cfi_restore x22
ldp x23, x24, [sp, #48]
.cfi_restore x23
.cfi_restore x24
ldp x25, x26, [sp, #64]
.cfi_restore x25
.cfi_restore x26
ldp x27, x28, [sp, #80]
.cfi_restore x27
.cfi_restore x28
// x29(callee-save) and LR
ldp x29, xLR, [sp, #96]
.cfi_restore x29
.cfi_restore x30
add sp, sp, #112
.cfi_adjust_cfa_offset -112
.endm
JNI
这一章的最后,我们说说JNI.
针对JNI,没有什么Java代码值得生成的。
我们看下反汇编的Java代码,对应test1并没有Java字节码生成。
public class com.yunos.xulun.testcppjni2.TestJNI {
public com.yunos.xulun.testcppjni2.TestJNI();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static native int test1();
static {};
Code:
0: ldc #2 // String testcppjni2
2: invokestatic #3 // Method java/lang/System.loadLibrary:(Ljava/lang/String;)V
5: return
}
但是OAT还是会生成对应的查找JNI函数的指令:
2: int com.yunos.xulun.testcppjni2.TestJNI.test1() (dex_method_idx=16803)
DEX CODE:
OatMethodOffsets (offset=0x00272d1c)
code_offset: 0x005034bc
gc_map: (offset=0x00000000)
OatQuickMethodHeader (offset=0x005034a0)
mapping_table: (offset=0x00000000)
vmap_table: (offset=0x00000000)
QuickMethodFrameInfo
frame_size_in_bytes: 192
core_spill_mask: 0x7ff80000 (r19, r20, r21, r22, r23, r24, r25, r26, r27, r28, r29, r30)
fp_spill_mask: 0x0000ff00 (fr8, fr9, fr10, fr11, fr12, fr13, fr14, fr15)
CODE: (code_offset=0x005034bc size_offset=0x005034b8 size=232)...
sp用掉了192的空间,先减掉。
然后备份x19~x30.d8~d15和TR(x18)的值。
0x005034bc: d10303ff sub sp, sp, #0xc0 (192)
0x005034c0: a90653f3 stp x19, x20, [sp, #96]
0x005034c4: a9075bf5 stp x21, x22, [sp, #112]
0x005034c8: a90863f7 stp x23, x24, [sp, #128]
0x005034cc: a9096bf9 stp x25, x26, [sp, #144]
0x005034d0: a90a73fb stp x27, x28, [sp, #160]
0x005034d4: a90b7bfd stp x29, lr, [sp, #176]
0x005034d8: 6d0227e8 stp d8, d9, [sp, #32]
0x005034dc: 6d032fea stp d10, d11, [sp, #48]
0x005034e0: 6d0437ec stp d12, d13, [sp, #64]
0x005034e4: 6d053fee stp d14, d15, [sp, #80]
0x005034e8: aa1203f5 mov x21, tr
下面将产生三次过程调用,分别是对应artQuickGenericJniTrampoline,native函数本身和artQuickGenericJniEndTrampoline。
再调用了artQuickGenericJniEndTrampoline之后,再去检查是否有异常产生。
0x005034ec: f90003e0 str x0, [sp]
0x005034f0: d2800034 mov x20, #0x1
0x005034f4: b90013f4 str w20, [sp, #16]
0x005034f8: f94086b4 ldr x20, [x21, #264]
0x005034fc: f90007f4 str x20, [sp, #8]
0x00503500: 910023f4 add x20, sp, #0x8 (8)
0x00503504: f90086b4 str x20, [x21, #264]
0x00503508: b9400014 ldr w20, [x0]
0x0050350c: b90017f4 str w20, [sp, #20]
0x00503510: 910003f0 mov x16, sp
0x00503514: f9004eb0 str x16, [x21, #152]
0x00503518: aa1503e0 mov x0, x21
0x0050351c: f9418014 ldr x20, [x0, #768]
0x00503520: d63f0280 blr x20
0x00503524: b9001be0 str w0, [sp, #24]
0x00503528: 910053e1 add x1, sp, #0x14 (20)
0x0050352c: f9405ea0 ldr x0, [x21, #184]
0x00503530: f94003f4 ldr x20, [sp]
0x00503534: f9401694 ldr x20, [x20, #40]
0x00503538: d63f0280 blr x20
0x0050353c: b9001fe0 str w0, [sp, #28]
0x00503540: b9401be0 ldr w0, [sp, #24]
0x00503544: aa1503e1 mov x1, x21
0x00503548: f9418834 ldr x20, [x1, #784]
0x0050354c: d63f0280 blr x20
0x00503550: b9401fe0 ldr w0, [sp, #28]
下面两句是处理异常的,#136是异常状态的地址,如果出现异常,就跳转到ret后面的异常处理部分去执行。
为什么异常状态的值是136呢,因为它在ART代码中的定义是:THREAD_EXCEPTION_OFFSET。
// Offset of field Thread::tlsPtr_.exception.
#define THREAD_EXCEPTION_OFFSET (THREAD_CARD_TABLE_OFFSET + __SIZEOF_POINTER__) //136
#define THREAD_CARD_TABLE_OFFSET 128
#define THREAD_TOP_QUICK_FRAME_OFFSET (THREAD_CARD_TABLE_OFFSET + (3 * __SIZEOF_POINTER__)) //152
#define THREAD_SELF_OFFSET (THREAD_CARD_TABLE_OFFSET + (9 * __SIZEOF_POINTER__)) //200
#define THREAD_LOCAL_POS_OFFSET (THREAD_CARD_TABLE_OFFSET + 147 * __SIZEOF_POINTER__) //1304
在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.
0x00503554: f94046b4 ldr x20, [x21, #136]
0x00503558: b50001d4 cbnz x20, #+0x38 (addr 0x503590)
0x0050355c: aa1503f2 mov tr, x21
0x00503560: a94653f3 ldp x19, x20, [sp, #96]
0x00503564: a9475bf5 ldp x21, x22, [sp, #112]
0x00503568: a94863f7 ldp x23, x24, [sp, #128]
0x0050356c: a9496bf9 ldp x25, x26, [sp, #144]
0x00503570: a94a73fb ldp x27, x28, [sp, #160]
0x00503574: a94b7bfd ldp x29, lr, [sp, #176]
0x00503578: 6d4227e8 ldp d8, d9, [sp, #32]
0x0050357c: 6d432fea ldp d10, d11, [sp, #48]
0x00503580: 6d4437ec ldp d12, d13, [sp, #64]
0x00503584: 6d453fee ldp d14, d15, [sp, #80]
把一开始减掉的192加回来,然后返回。
0x00503588: 910303ff add sp, sp, #0xc0 (192)
0x0050358c: d65f03c0 ret
后面是异常处理的部分
0x00503590: aa1403e0 mov x0, x20
0x00503594: f94222b0 ldr x16, [x21, #1088]
0x00503598: aa1503f2 mov tr, x21
0x0050359c: d63f0200 blr x16
0x005035a0: d4200000 brk #0x0
这段代码用了大量的寄存器啊,那他们是干嘛用的呢?
不用担心,虽然没有文档,但是ART代码中对此有说明:
如果有参数传进来,如果是整型的,最多7个参数,位于X1~X7中。如果是浮点数,就在D0~D7中。
返回地址在X30/LR中。
1492/*
1493 * Generic JNI frame layout:
1494 *
1495 * #-------------------#
1496 * | |
1497 * | caller method... |
1498 * #-------------------# <--- SP on entry
1499 * | Return X30/LR |
1500 * | X29/FP | callee save
1501 * | X28 | callee save
1502 * | X27 | callee save
1503 * | X26 | callee save
1504 * | X25 | callee save
1505 * | X24 | callee save
1506 * | X23 | callee save
1507 * | X22 | callee save
1508 * | X21 | callee save
1509 * | X20 | callee save
1510 * | X19 | callee save
1511 * | X7 | arg7
1512 * | X6 | arg6
1513 * | X5 | arg5
1514 * | X4 | arg4
1515 * | X3 | arg3
1516 * | X2 | arg2
1517 * | X1 | arg1
1518 * | D7 | float arg 8
1519 * | D6 | float arg 7
1520 * | D5 | float arg 6
1521 * | D4 | float arg 5
1522 * | D3 | float arg 4
1523 * | D2 | float arg 3
1524 * | D1 | float arg 2
1525 * | D0 | float arg 1
1526 | Method | <- X0
1527 * #-------------------#
1528 * | local ref cookie | // 4B
1529 * | handle scope size | // 4B
1530 * #-------------------#
1531 * | JNI Call Stack |
1532 * #-------------------# <--- SP on native call
1533 * | |
1534 * | Stack for Regs | The trampoline assembly will pop these values
1535 * | | into registers for native call
1536 * #-------------------#
1537 * | Native code ptr |
1538 * #-------------------#
1539 * | Free scratch |
1540 * #-------------------#
1541 * | Ptr to (1) | <--- SP
1542 * #-------------------#
1543 */
对照着下面的代码去看上面生成的test1调用JNI的代码,是不是觉得有点能看懂了的意思了?
这些代码位于/art/runtime/arch/arm64/quick_entrypoints_arm64.S中。
通过看这里面的汇编过程的代码,再去对照编译出来的OAT代码,为我们打开一条继续深入的路径。
1544 /*
1545 * Called to do a generic JNI down-call
1546 */
1547ENTRY art_quick_generic_jni_trampoline
1548 SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_X0
1549
1550 // Save SP , so we can have static CFI info.
1551 mov x28, sp
1552 .cfi_def_cfa_register x28
1553
1554 // This looks the same, but is different: this will be updated to point to the bottom
1555 // of the frame when the handle scope is inserted.
1556 mov xFP, sp
1557
1558 mov xIP0, #5120
1559 sub sp, sp, xIP0
1560
1561 // prepare for artQuickGenericJniTrampoline call
1562 // (Thread*, SP)
1563 // x0 x1 <= C calling convention
1564 // xSELF xFP <= where they are
1565
1566 mov x0, xSELF // Thread*
1567 mov x1, xFP
1568 bl artQuickGenericJniTrampoline // (Thread*, sp)
1569
1570 // The C call will have registered the complete save-frame on success.
1571 // The result of the call is:
1572 // x0: pointer to native code, 0 on error.
1573 // x1: pointer to the bottom of the used area of the alloca, can restore stack till there.
1574
1575 // Check for error = 0.
1576 cbz x0, .Lexception_in_native
1577
1578 // Release part of the alloca.
1579 mov sp, x1
1580
1581 // Save the code pointer
1582 mov xIP0, x0
1583
1584 // Load parameters from frame into registers.
1585 // TODO Check with artQuickGenericJniTrampoline.
1586 // Also, check again APPCS64 - the stack arguments are interleaved.
1587 ldp x0, x1, [sp]
1588 ldp x2, x3, [sp, #16]
1589 ldp x4, x5, [sp, #32]
1590 ldp x6, x7, [sp, #48]
1591
1592 ldp d0, d1, [sp, #64]
1593 ldp d2, d3, [sp, #80]
1594 ldp d4, d5, [sp, #96]
1595 ldp d6, d7, [sp, #112]
1596
1597 add sp, sp, #128
1598
1599 blr xIP0 // native call.
1600
1601 // result sign extension is handled in C code
1602 // prepare for artQuickGenericJniEndTrampoline call
1603 // (Thread*, result, result_f)
1604 // x0 x1 x2 <= C calling convention
1605 mov x1, x0 // Result (from saved)
1606 mov x0, xETR // Thread register, original xSELF might be scratched by native code.
1607 fmov x2, d0 // d0 will contain floating point result, but needs to go into x2
1608
1609 bl artQuickGenericJniEndTrampoline
1610
1611 // Pending exceptions possible.
1612 // Use xETR as xSELF might be scratched by native code
1613 ldr x2, [xETR, THREAD_EXCEPTION_OFFSET]
1614 cbnz x2, .Lexception_in_native
1615
1616 // Tear down the alloca.
1617 mov sp, x28
1618 .cfi_def_cfa_register sp
1619
1620 // Tear down the callee-save frame.
1621 RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME
1622
1623 // store into fpr, for when it's a fpr return...
1624 fmov d0, x0
1625 ret
1626
1627.Lexception_in_native:
1628 // Restore xSELF. It might have been scratched by native code.
1629 mov xSELF, xETR
1630 // Move to x1 then sp to please assembler.
1631 ldr x1, [xSELF, # THREAD_TOP_QUICK_FRAME_OFFSET]
1632 mov sp, x1
1633 .cfi_def_cfa_register sp
1634 # This will create a new save-all frame, required by the runtime.
1635 DELIVER_PENDING_EXCEPTION
1636END art_quick_generic_jni_trampoline