ART世界探险(8) - 面向对象编程-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

ART世界探险(8) - 面向对象编程

简介: Java字节码是支持面向对象编程的,比如new和instanceof都是一条指令。它们被编成OAT之后变成了什么样子呢?

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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章