澳门新葡萄京官网首页 7

Java Main 如何是如何被执行的?

澳门新葡萄京官网首页 1

9.jpg

3.调用者/被调用者保存寄存器


https://msdn.microsoft.com/en-US/library/zthk2dkh(v=vs.80).aspx

那些传参进程是从HavalISC微处理器里面借鉴过来的,EnclaveISC微处理机常常采纳存放器传参,比方ARM就使用多少个寄放器讴歌ZDX0-Murano4传参,而开始时代的x86系统都以利用栈传参的。
澳门新葡萄京官网首页 ,关于缘何x64传参使用的存放器命名这么未有法规,主若是为了和事情发生前的x86微电脑包容,x86系统的ABI已经定义过一套贮存器使用标准。

1.依照函数调用栈的结构:

    pushq   %rbp
    movq    %rsp, %rbp
    subq    $32, %rsp
    movq    %rdi, -24(%rbp)
    movq    %rsi, -32(%rbp)
    movq    $3333, -24(%rbp)
    movq    $4444, -32(%rbp)
    movq    $5555, -16(%rbp)
    movq    $6666, -8(%rbp)
    movl    $0, %eax
    call    sub
    movl    $9999, %eax
    leave
    ret

(7卡塔尔(قطر‎.将艺术的调用次数保存在rcx/ecx中

先介绍一点x64的通用寄放器集:
x64的贮存器集有14个通用存放器,即rax, rbx, rcx, rdx, rbp, rsp, rsi, rdi,
r8, r9, r10, r11, r12, r13, r14, r15。
乍一看这么些贮存器排列毫无章法,命名也不有条有理;精通君越ISC微处理器的同桌应该都相比较赏识RubiconISC的寄放器命名,r0,
r1, …, r15照旧r0, r1, …,
r31,简单直白。x64重要是借鉴的XC90ISC微电脑的一对特征,扩充了通用存放器的个数,然后又为了同盟历史版本,引致以后大家看出的通用寄存器命名不规范。开始的一段时代x86微处理器就从没有过这么多存放器,也没有通用存放器概念,基本都以专项使用贮存器即各样存放器都有特地的用处,因为CISC不是使用load/store布局,大批量命令都是素来操作内部存款和储蓄器运算的。

2.再次来到值传递

表暗中表示义如下:

上边分别为回到结果类型为long、float、double的景色

  1. call callee指令之后
    call指令落成两件职业,1:
    把重临地址压栈,能够看看在栈顶0x4005f6难为call指令的下一个下令地址;2:
    pc指向函数callee的首先条指令。

依据以上代码深入分析,大致得出该程序调用栈构造:

void sub() {
}

long callee(long i, long j) {
    long k;
    long l;

    i = 3333L;
    j = 4444L;
    k = 5555L;
    l = 6666L;

    sub();
    return 99L;
}

JavaCallWrapper指针、再次来到结果指针、重回结果类型、被调用方法的methodOop、被调用方法的解释代码的输入地址、参数地址、参数个数。

……..instruction…….. description
movq $2222, %rsi 把第二个参数值2222放在寄存器rsi,前面说过第二个参数使用rsi传递
movq $1111, %rdi 把第一个参数值1111放在寄存器rdi,第一个参数使用rdi传递
call callee call指令调用函数callee;call指令完成两件事情:把当前指令的下一条指令(即将来callee函数的返回地址)压栈,然后把pc指向callee函数的入口,开始执行callee函数代码
movq %rax, -4(%rbp) 读取callee的返回值,函数返回值通过寄存器rax传递

generate_fixed_frame(卡塔尔(قطر‎的落到实处如下:

变化的callee代码如下:

随着的func的运算进程如下:

那几个callee的代码其实有几许主题材料,不知情您有未有在意的,那便是callee只是调动了%rbp,但并从未调度%rsp,使得%rsp并从未真的指向栈顶,而是原原本本%rsp和%rbp指向同一个地址,根据前边的逻辑callee进来的时候保存了caller的%rbp和%rsp,何况在回去时索要还原原先的值,而就是说%rbp和%rsp平常成对现身构成一个frame范围,那么这些callee为啥会如此呢?
由来是callee是三个卡牌函数,它不再调用别的函数,就是说从步入这一个函数到间隔这么些函数之间不会爆发栈的操作,设置%rsp的操作就能够归纳。
我们改进一下代码,增添二个子函数sub(卡塔尔国让callee来行使:

// compute beginning of parameters (r14)
  __ lea(r14, Address(rsp, rcx, Address::times_8, -wordSize))
  1. movq %rsp, %rbp指令之后
    移动rbp到近些日子rsp地址,
    当时rbp和rsp指向同一个地址;rbp正是callee的frame地址,后边callee函数内都将因此rbp加上偏移的议程来访问片段变量。举个例子:
    movq $3333, -24(%rbp)
    movq $4444, -32(%rbp)

7.被调用者保存贮存器的回复,以至栈指针的重置

这么些指令大约分为三大块,第一块入口指令,第二块函数功能代码,第三块再次回到指令;指令含义如下:

以如下程序为例,深入分析函数调用的栈帧结构:

这篇文章重要介绍x64平台下函数调用的进度。
最主要内容富含caller如何实现到callee的转换,两个之间参数字传送递方式,函数的栈分配模型,以致callee如何回到到caller。

澳门新葡萄京官网首页 2

8.jpg

/栈底指针的0x40偏移保存着参数的个数
  0x00000000024005ed: mov    0x40(%rbp),%r9d
  //若参数个数为0,则直接跳转0x000000000240060d准备调用Java方法
  0x00000000024005f1: test   %r9d,%r9d
  0x00000000024005f4: je     0x000000000240060d
  //若参数个数不为0,则遍历参数,将所有参数压入本地栈
  //其中栈底指针的0x38偏移保存着参数的地址,edx将用作循环的迭代器
  0x00000000024005fa: mov    0x38(%rbp),%r8
  0x00000000024005fe: mov    %r9d,%edx

  ;; loop:
  //从第一个参数开始,将Java方法的参数压人本地栈
  /*     
  *     i = parameter_size; //确保不等于0
  *     do{
  *       push(parameter[i]);
  *       i--;
  *     }while(i!=0);
  */
  0x0000000002400601: mov    (%r8),%rax
  0x0000000002400604: add    $0x8,%r8
  0x0000000002400608: dec    %edx
  0x000000000240060a: push   %rax
  0x000000000240060b: jne    0x0000000002400601

上面大家详细一步一步介绍函数调用进度中,寄放器和函数栈的变动进度:

(4卡塔尔(قطر‎.被调用函数当须求时要把寄放器中的参数移动到栈空间中(shadow spaceState of Qatar

6.jpg

address InterpreterGenerator::generate_normal_entry(bool synchronized) {
  // determine code generation flags
  bool inc_counter  = UseCompiler || CountCompiledCalls;

  // ebx: methodOop
  // r13: sender sp
  address entry_point = __ pc();

  const Address size_of_parameters(rbx,
                                   methodOopDesc::size_of_parameters_offset());
  const Address size_of_locals(rbx, methodOopDesc::size_of_locals_offset());
  const Address invocation_counter(rbx,
                                   methodOopDesc::invocation_counter_offset() +
                                   InvocationCounter::counter_offset());
  const Address access_flags(rbx, methodOopDesc::access_flags_offset());

  // get parameter size (always needed)
  __ load_unsigned_short(rcx, size_of_parameters);

可能以四个例证来证实(为了简化表明经过,全体参数和一些变量均运用long类型,首假若因为其大小恰巧是8字节和寄放器大小同等,其余浮点的传参标准行使的是浮点寄放器,不在此篇文章里面探究卡塔尔。

__ push(rax);        // save return address
  __ enter();          // save old & set new rbp
  __ push(r13);        // set sender sp
  __ push((int)NULL_WORD); // leave last_sp as null
  __ movptr(r13, Address(rbx, methodOopDesc::const_offset()));      // get constMethodOop
  __ lea(r13, Address(r13, constMethodOopDesc::codes_offset())); // get codebase
  __ push(rbx);
  • 对此整数和指针类型参数, x64运用6个存放器传递前6个参数。
    先是个参数使用rdi,第三个参数使用rsi,第三、四,五,三个参数依次使用rdx,
    rcx, r8,
    r9;从第多少个起来通过栈传递,由此一旦函数参数不超越6个,那么富有参数都以由此寄放器传递的。比方函数:
    void callee(int a, int b, int c, int d, int e, int f);
movl    -16(%rbp), %eax //local_i2 - local_i1
    subl    -12(%rbp), %eax

    pxor    %xmm0, %xmm0    //准备xmm0寄存器,按位异或,xmm0清零
    cvtsi2ss    %eax, %xmm0
    mulss   -20(%rbp), %xmm0    //local_f1 * (local_i2 - local_i1)
    cvtss2sd    %xmm0, %xmm0
    addsd   -32(%rbp), %xmm0    //local_d1 + local_f1 * (local_i2 - local_i1)
    subsd   48(%rbp), %xmm0     //local_d1 + local_f1 * (local_i2 - local_i1) - param_d2
    addsd   -8(%rbp), %xmm0     //local_d1 + local_f1 * (local_i2 - local_i1) - param_d2 + local_d2
    addq    $32, %rsp      //回收func栈,恢复栈顶地址
    popq    %rbp
    ret

遵照习贯下边步骤中的图示代码段地址从上往下以依次增加的办法排列,栈地址从上往下以依次减少的主意排列。

// jvmti support
  __ notify_method_entry();

6 movq $9999, %rax指令之后
那条指令就是把函数再次回到值放到存放器rax,在此个事例中9999=0x270f;后面大家说过函数重返值都以由此rax重返的。

归纳出call_stub栈布局如下:

………instruction……… description
pushq %rbp 保存caller的%rbp寄存器值,这个%rbp在函数返回给caller的时候需要恢复原来值,通过leave指令完成。
moveq %rsp, %rbp 把当前的%rsp作为callee的%rbp值
moveq …, offer(%rbp) 这些moveq指令都是callee函数体的功能,不细说
movq $9999, %rax 设置函数的返回值到%rax,函数返回值是通过寄存器%rax传递的
leave leave完成两件事:把%rbp的值move到%rsp,然后从栈中弹出%rbp;这条指令的功能就是恢复到caller的frame结构,即把%rsp和%rbp恢复到caller函数的值
ret 指令负责从栈中弹出返回地址,并且跳转的返回地址。

(10卡塔尔.同步方法的Monitor对象分配和形式的加锁(在汇编部分解析中从未该部分,假诺对协同感兴趣的请自行解析卡塔尔(قطر‎

再看callee的汇编代码:

在被调函数栈帧的栈底 %rbp +
8(栈地址向下增进,堆地址向上拉长,栈底的正偏移值指向调用函数栈帧内容卡塔尔保存着被调函数的传遍参数,这里即:

先看caller生成的汇编指令:

func函数的栈和操作数希图如下:

x64的函数字传送参标准:

(1卡塔尔国.依据在此以前的分析,伊始的栈构造如下:

7 leave命令之后
leave指令完毕两件事,1:把%rbp的值move到%rsp,在那时此刻这一个事例中,这一个效应还没效果与利益,因为
%rbp和%rsp的值始终相近。2:然后从栈中弹出%rbp。

8.对于差别的Java方法,虚构机在起头化时会生成不相同的点子入口例程

5.jpg

(当调用深迈过大会抛出StackOverFlow格外卡塔尔

caller函数调用callee的汇编代码:

// (pre-)fetch invocation count
  if (inc_counter) {
    __ movl(rcx, invocation_counter);
  }

相对比前边的callee代码,当时多了一条指令:
subq $32, %rsp
那条指令正是调动函数callee的新的%rsp值,使得%rbp和%rsp之间构成叁个标准的callee函数frame范围。栈布局如下:

澳门新葡萄京官网首页 3

2.jpg
} else {
    __ push(0); //methodData
  }

  __ movptr(rdx, Address(rbx, methodOopDesc::constants_offset()));
  __ movptr(rdx, Address(rdx, constantPoolOopDesc::cache_offset_in_bytes()));
  __ push(rdx); // set constant pool cache
  __ push(r14); // set locals pointer
  if (native_call) {
    __ push(0); // no bcp
  } else {
    __ push(r13); // set bcp
  }
  __ push(0); // reserve word for pointer to expression stack bottom
  __ movptr(Address(rsp, 0), rsp); // set expression stack bottom
}
  1. call callee指令从前
    此刻pc指向call指令,需求传递的参数已经放手传参寄存器,栈是caller的frame。

(8卡塔尔(قطر‎.开始化当前形式的栈帧

7.jpg

func:
    pushq   %rbp        //保存rbp(main函数栈的基址)
    .seh_pushreg    %rbp
    movq    %rsp, %rbp      //将main栈的栈顶指针作为被调用函数的栈基址
    .seh_setframe   %rbp, 0
    subq    $32, %rsp  //func栈需要32字节的栈空间
    .seh_stackalloc 32
    .seh_endprologue
    movl    %ecx, 16(%rbp)  //将4个参数移动到栈底偏移16-40的空间(main栈的shadow space)
    movss   %xmm1, 24(%rbp)
    movsd   %xmm2, 32(%rbp)
    movl    %r9d, 40(%rbp)

    movabsq $4613937818241073152, %rax //本地变量local_d2,即浮点数3.0
    movq    %rax, -8(%rbp)  //5个局部变量
    movl    16(%rbp), %eax
    movl    %eax, -12(%rbp)
    movl    40(%rbp), %eax
    movl    %eax, -16(%rbp)
    movl    24(%rbp), %eax
    movl    %eax, -20(%rbp)
    movq    32(%rbp), %rax
    movq    %rax, -32(%rbp)
param # param name register
1 a rdi
2 b rsi
3 c rdx
4 d rcx
5 e r8
6 f r9

4.call_stub的参数保存着Java方法的参数,今后就需求将参数压入call_stub栈中

4.jpg

(4卡塔尔国.顾客定义的归来值类型长度必需是1、2、4、8、16、32、64

  1. pushq %rbp指令之后
    把当下rbp的值压入栈,并且pc向前挪动到下一条指令。

(9卡塔尔国.增添方法的调用计数


赢得传入参数数量到rcx中:

1.jpg
// do call
  { JavaCallWrapper link(method, receiver, result, CHECK);
    { HandleMark hm(thread);  // HandleMark used by HandleMarkCleaner

      StubRoutines::call_stub()(
        (address)&link,
        // (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
        result_val_address,          // see NOTE above (compiler problem)
        result_type,
        method(),
        entry_point,
        args->parameters(),
        args->size_of_parameters(),
        CHECK
      );

      result = link.result();  // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
      // Preserve oop return value across possible gc points
      if (oop_result_flag) {
        thread->set_vm_result((oop) result->get_jobject());
      }
    }
  }

骨子里栈的源委和前段时间未有call
sub的栈内容是相仿的,只是调治了%rsp的指针,因为callee已经不是卡片函数了,它须要调用sub函数,那个进程中是有栈的操作的,所以必得把%rsp指向准确的职位。然后在函数重回的时候leave指令能够再把%rsp重新调节到%rbp的岗位。

// get return address
  __ pop(rax);

(2).浮点数项指标参数通过xmm0-xmm3传递,注意分歧门类的参数占用的寄存器序号是基于参数的序号来调整的,比如add(int,double,float,intState of Qatar就各自笔者保护存在rcx、xmm1、xmm2、r9贮存器中

    pushq   %rbp
    movq    %rsp, %rbp
    movq    %rdi, -24(%rbp)
    movq    %rsi, -32(%rbp)
    movq    $3333, -24(%rbp)
    movq    $4444, -32(%rbp)
    movq    $5555, -16(%rbp)
    movq    $6666, -8(%rbp)
    movq    $9999, %rax
    leave
    ret

(4卡塔尔国.获取再次回到地址,保存在rax中(注意当时栈顶为调用函数call指令后下一条指令的地址卡塔尔国

8 ret指令之后
ret指令也是完毕两件业务,1:栈中弹出再次回到地址,2:并且跳转的回到地址。

除此以外,还也许有十七个1二十10位的XMM存放器,分别为xmm0-15,x84-64的寄放器服从调用约定(Calling
ConventionsState of Qatar:

long callee(long i, long j) {
    long k;
    long l;
    i = 3333L;
    j = 4444L;
    k = 5555L;
    l = 6666L;
    return 9999L;
}

void caller() {
    long r = foo(...);
}
;; long 类型返回结果保存:  
  0x00000000024006b7: mov    %rax,(%rcx)
  0x00000000024006ba: jmp    0x0000000002400647
  ;; float 类型返回结果保存:  
  0x00000000024006bc: vmovss %xmm0,(%rcx)
  0x00000000024006c0: jmp    0x0000000002400647
  ;; double 类型返回结果保存:  
  0x00000000024006c2: vmovsd %xmm0,(%rcx)
  0x00000000024006c6: jmpq   0x0000000002400647
    movq    $2222, %rsi
    movq    $1111, %rdi
    call    callee
    movq    %rax, -4(%rbp)
//该部分为一个loop循环
// rdx - # of additional locals
  // allocate space for locals
  // explicitly initialize locals
  {
    Label exit, loop;
    __ testl(rdx, rdx);
    __ jcc(Assembler::lessEqual, exit); // do nothing if rdx <= 0
    __ bind(loop);
    __ push((int) NULL_WORD); // initialize local variables
    __ decrementl(rdx); // until everything initialized
    __ jcc(Assembler::greater, loop);
    __ bind(exit);
  }
long ret = foo(1111L, 2222L);   

这儿栈的层系如下:

咱俩得以看来那儿栈结商谈函数进来以前是一成不改变的,进而确认保证callee再次回到现在caller可以继续实施。

(3卡塔尔.对栈空间尺寸实行反省,判定是不是会发生栈溢出

5 实践函数体作用指令,比方:
movq %rdi, -24(%rbp)
movq %rsi, -32(%rbp)
其一时候大家得以领略的观看,callee是怎么分配栈空间的,rbp往下第一是一些变量,然后是参数预先流出空间。

调用者保存寄放器:rax、rcx、rdx、r8-r11都以为是易失型寄放器(Volatile卡塔尔国,那个贮存器随即恐怕被用到,这个贮存器将由调用者
自行维护,当调用其余函数时,被调用函数对那些贮存器的操作并不会耳熟能详调用函数(即那么些寄存器的职能范围只限于当前函数卡塔尔。

3.jpg

6.预备保存重回结果,这里须要先依据不一样的归来类型收取重回结果,然后保留到重返结果指针所指向的职位

被调用者保存贮存器:rbx、rbp、rdi、rsi、r12-r15、xmm6-xmm15都以非易失型贮存器(non-volatile卡塔尔国,调用别的函数时,那几个存放器的值可能在调用再次来到时还索要用,那么被调用函数就亟须将那一个寄放器的值保存起来,当要再次来到时,复苏这几个贮存器的值(即这么些寄存器的功力范围是跨函数调
用的卡塔尔。

main函数调用func以前的汇编代码如下:

;; prepare to save result:
  //栈底指针的0x18和0x20偏移分别保存着返回结果的指针和结果类型
  0x000000000240061a: mov    0x18(%rbp),%rcx
  0x000000000240061e: mov    0x20(%rbp),%edx

  ;; handle result accord to different result_type:
  0x0000000002400621: cmp    $0xc,%edx
  0x0000000002400624: je     0x00000000024006b7
  0x000000000240062a: cmp    $0xb,%edx
  0x000000000240062d: je     0x00000000024006b7
  0x0000000002400633: cmp    $0x6,%edx
  0x0000000002400636: je     0x00000000024006bc
  0x000000000240063c: cmp    $0x7,%edx
  0x000000000240063f: je     0x00000000024006c2
  ;; save result for the other result_type:
  0x0000000002400645: mov    %eax,(%rcx)

call_stub(卡塔尔(قطر‎定义在/hotspot/src/share/vm/runtime/stubRoutines.h中,实际上重返的正是CallStub函数指针_call_stub_entry,该指针指向call_stub的汇编实现的对象代码指令地址,即call_stub的例程
入口。

(method entry
point卡塔尔来思量栈帧,这里以较常被利用的zerolocals方法入口为例,深入分析Java方法的栈帧构造与调用进程,入口例程目的代码的发出在InterpreterGenerator::generate_normal_entry()中:

StubRoutines::call_stub [0x0000000002400567, 0x00000000024006cb[ (356 bytes)
  //保存bp
  0x0000000002400567: push   %rbp
  //更新栈顶地址            
  0x0000000002400568: mov    %rsp,%rbp

  //call_stub需要的栈空间大小为0xd8
  0x000000000240056b: sub    $0xd8,%rsp

(6卡塔尔国.为除参数以外的有的变量分配栈空间,若那些有个别变量数量为0,那么就跳过这一部分管理,不然,将压入
maxlocals – param_size个0,以初始化这个部分变量

main:
    pushq   %rbp            //保存rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp      //更新栈基址
    .seh_setframe   %rbp, 0
    subq    $80, %rsp      
    .seh_stackalloc 80      //main栈需要80字节的栈空间
    .seh_endprologue
    call    __main
    movabsq $4611686018427387904, %rdx //0x4000000000000000,即浮点数2.0
    movabsq $4613937818241073152, %rax //0x3000000000000000,即浮点数3.0
    movq    %rax, 32(%rsp)          //第5个参数3.0,即param_d2保存在栈空间上
    movl    $3, %r9d               //第4个参数3,即param_i2保存在r9d中(r9的低32位)
    movq    %rdx, -24(%rbp)         
    movsd   -24(%rbp), %xmm2        //第3个参数2.0,即param_d1保存在xmm2中
    movss   .LC2(%rip), %xmm1       //第2个参数1.0(0x3f800000),保存在xmm1中
    movl    $1, %ecx               //第1个参数1,保存在ecx中(rcx的低32位)
    call    func
;; prepare entry:
  //栈底指针的0x28和0x30偏移分别保存着被调用Java方法的methodOop指针和解释代码的入口地址
  0x000000000240060d: mov    0x28(%rbp),%rbx
  0x0000000002400611: mov    0x30(%rbp),%rdx
  0x0000000002400615: mov    %rsp,%r13  //保存栈顶指针
  ;; jump to run Java method:
  0x0000000002400618: callq  *%rdx

澳门新葡萄京官网首页 4

java应用程序的启航在在/hotspot/src/share/tools/launcher/java.c的main(State of Qatar函数中,而在设想机开头化进度中,将开创并运转Java的Main线程。最终将调用JNIEnv的CallStaticVoidMethod(卡塔尔国来进行main方法。

澳门新葡萄京官网首页 5

// initialize fixed part of activation frame
  generate_fixed_frame(false);

将methodData以0为开头值压入栈,依照methodOop的ConstantPoolOop成员将常量池缓冲地址压入栈,r14中保存着
局地变量区(第二个参数的地址卡塔尔国指针,将其压入栈,别的如若调用的是native调用,那么字节码指针部分为0,不然平常将字节码指针压入栈,最终为栈留
出叁个字的表明式栈底空间,并改良rsp

//分别使用rcx、rdx、r8、r9来保存第1、2、3、4个参数,多出来的其他参数用栈空间来传递
  //使用xmm0-4来传递第1-4个浮点数参数
  //这里将参数复制到栈空间,这样call_stub的所有参数就在rbp + 0x10 ~ 0x48栈空间上
  0x0000000002400572: mov    %r9,0x28(%rbp)
  0x0000000002400576: mov    %r8d,0x20(%rbp)
  0x000000000240057a: mov    %rdx,0x18(%rbp)
  0x000000000240057e: mov    %rcx,0x10(%rbp)
// rbx: methodOop
  // rcx: size of parameters
  // r13: sender_sp (could differ from sp+wordSize if we were called via c2i )

  __ load_unsigned_short(rdx, size_of_locals); // get size of locals in words
  __ subl(rdx, rcx); // rdx = no. of additional locals

封存再次回到地址,为被调用的Java方法打算栈帧,并将sender
sp指针、last_sp(设置为0卡塔尔(قطر‎压入栈,依据methodOop的constMethodOop成员将字节码指针保存到r13贮存器中,并将methodOop压入栈

 __ dispatch_next(vtos);

(2卡塔尔(قطر‎.
获取局地变量区的高低,保存在rdx中,并减去参数数量,将除参数以外的一对变量数量保存在rdx中(固然参数作为局地变量是艺术的一片段,但参数由调用
者提供,那些参数应有调用者栈帧而非被调用者栈帧维护,即被调用者栈帧只需求保证局地变量中除去参数的局地就可以卡塔尔

// increment invocation count & check for overflow
  Label invocation_counter_overflow;
  Label profile_method;
  Label profile_method_continue;
  if (inc_counter) {
    generate_counter_incr(&invocation_counter_overflow,
                          &profile_method,
                          &profile_method_continue);
    if (ProfileInterpreter) {
      __ bind(profile_method_continue);
    }
  }

(11卡塔尔.JVM工具接口部分

5.调用Java方法的疏解代码

(3卡塔尔.对于陆九个人以上的重临值,将由调用函数在栈上为其分配空间,并将其指针保存在rcx中作为”第七个参数”,而流传参数将次第右移,最终函数调用完后,由rax重临该空间的指针

CallStaticVoidMethod()对应的jni函数为jni_CallStaticVoidMethod,定义在/hotspot/src/share/vm/prims/jni.cpp中,而jni_CallStaticVoidMethod(卡塔尔(قطر‎又调用了jni_invoke_static(),jni_invoke_static(卡塔尔国通过JavaCalls的call(卡塔尔国发起对Java方法的调用

(12卡塔尔(قطر‎.跳转到第一条字节码的本土代码处实践

澳门新葡萄京官网首页 6

call_stub由generate_call_stub(State of Qatar解释成汇编代码,有意思味的能够一而再一而再读书call_stub的汇编代码进行深入分析。
下面对call_stub的汇编部分实行深入分析:

// Call stubs are used to call Java from C
  //    return_from_Java 是紧跟在call *%eax后面的那条指令的地址
  //     [ return_from_Java      ] <--- rsp
  // -28 [ arguments             ] <-- rbp - 0xe8
  // -26 [ saved xmm15           ] <-- rbp - 0xd8
  // -24 [ saved xmm14           ] <-- rbp - 0xc8
  // -22 [ saved xmm13           ] <-- rbp - 0xb8
  // -20 [ saved xmm12           ] <-- rbp - 0xa8
  // -18 [ saved xmm11           ] <-- rbp - 0x98
  // -16 [ saved xmm10           ] <-- rbp - 0x88
  // -14 [ saved xmm9            ] <-- rbp - 0x78
  // -12 [ saved xmm8            ] <-- rbp - 0x68
  // -10 [ saved xmm7            ] <-- rbp - 0x58
  // -9  [ saved xmm6            ] <-- rbp - 0x48 
  // -7  [ saved r15             ] <-- rbp - 0x38
  // -6  [ saved r14             ] <-- rbp - 0x30
  // -5  [ saved r13             ] <-- rbp - 0x28
  // -4  [ saved r12             ] <-- rbp - 0x20
  // -3  [ saved rdi             ] <-- rbp - 0x18
  // -2  [ saved rsi             ] <-- rbp - 0x10  
  // -1  [ saved rbx             ] <-- rbp - 0x8
  //  0  [ saved rbp             ] <--- rbp,
  //  1 [ return address       ]  <--- rbp + 0x08
  //  2 [ ptr. to call wrapper ]  <--- rbp + 0x10
  //  3 [ result               ]  <--- rbp + 0x18
  //  4 [ result_type          ]  <--- rbp + 0x20
  //  5 [ method               ]  <--- rbp + 0x28
  //  6 [ entry_point          ]  <--- rbp + 0x30
  //  7 [ parameters           ]  <--- rbp + 0x38
  //  8 [ parameter_size       ]  <--- rbp + 0x40
  //  9 [ thread               ]  <--- rbp + 0x48
if (synchronized) {
    // Allocate monitor and lock method
    lock_method();

最后栈的半空中协会如下:

double func(int param_i1, float param_f1, double param_d1, int param_i2, double param_d2)

{
    int local_i1, local_i2;
    float local_f1;
    double local_d1;
    double local_d2 = 3.0;
    local_i1 = param_i1;
    local_i2 = param_i2;
    local_f1 = param_f1;
    local_d1 = param_d1;
    return local_d1 + local_f1 * (local_i2 - local_i1) - param_d2 + local_d2;
}

int main()

{
    double res;
    res = func(1, 1.0, 2.0, 3, 3.0);
    return 0;
}

func函数重回后,main函数将从xmm0中抽出再次回到结果

1.参数字传送递:

(2).对于_m128(i/dState of Qatar以致浮点数类型将动用xmm0传递

(3卡塔尔国.8/16/32/64门类的构造体或共用体和_m64类型将运用rcx、rdx、r8、r9直接传送,而别的品种将会经过指针引用的艺术在此4个寄放器中传递

;; save registers:
  //依次保存rbx、rsi、rdi这三个被调用者保存的寄存器,随后保存r12-r15、XMM寄存器组xmm6-xmm15
  0x0000000002400582: mov    %rbx,-0x8(%rbp)
  0x0000000002400586: mov    %r12,-0x20(%rbp)
  0x000000000240058a: mov    %r13,-0x28(%rbp)
  0x000000000240058e: mov    %r14,-0x30(%rbp)
  0x0000000002400592: mov    %r15,-0x38(%rbp)
  0x0000000002400596: vmovdqu %xmm6,-0x48(%rbp)
  0x000000000240059b: vmovdqu %xmm7,-0x58(%rbp)
  0x00000000024005a0: vmovdqu %xmm8,-0x68(%rbp)
  0x00000000024005a5: vmovdqu %xmm9,-0x78(%rbp)
  0x00000000024005aa: vmovdqu %xmm10,-0x88(%rbp)
  0x00000000024005b2: vmovdqu %xmm11,-0x98(%rbp)
  0x00000000024005ba: vmovdqu %xmm12,-0xa8(%rbp)
  0x00000000024005c2: vmovdqu %xmm13,-0xb8(%rbp)
  0x00000000024005ca: vmovdqu %xmm14,-0xc8(%rbp)
  0x00000000024005d2: vmovdqu %xmm15,-0xd8(%rbp)
  0x00000000024005da: mov    %rsi,-0x10(%rbp)
  0x00000000024005de: mov    %rdi,-0x18(%rbp)
  //栈底指针的0x48偏移保存着thread对象,0x6d01a2c3(%rip)为异常处理入口
  0x00000000024005e2: mov    0x48(%rbp),%r15
  0x00000000024005e6: mov    0x6d01a2c3(%rip),%r12        # 0x000000006f41a8b0

3.将被调用者保存寄存器的值压入call_stub栈中:

// see if we've got enough room on the stack for locals plus overhead.
  generate_stack_overflow_check();

先来看下call_stub的调用栈布局:(注:本文实验是在windows_陆12个人平台上落到实处的State of Qatar

;; restore registers:
  0x0000000002400647: lea    -0xd8(%rbp),%rsp
  0x000000000240064e: vmovdqu -0xd8(%rbp),%xmm15
  0x0000000002400656: vmovdqu -0xc8(%rbp),%xmm14
  0x000000000240065e: vmovdqu -0xb8(%rbp),%xmm13
  0x0000000002400666: vmovdqu -0xa8(%rbp),%xmm12
  0x000000000240066e: vmovdqu -0x98(%rbp),%xmm11
  0x0000000002400676: vmovdqu -0x88(%rbp),%xmm10
  0x000000000240067e: vmovdqu -0x78(%rbp),%xmm9
  0x0000000002400683: vmovdqu -0x68(%rbp),%xmm8
  0x0000000002400688: vmovdqu -0x58(%rbp),%xmm7
  0x000000000240068d: vmovdqu -0x48(%rbp),%xmm6
  0x0000000002400692: mov    -0x38(%rbp),%r15
  0x0000000002400696: mov    -0x30(%rbp),%r14
  0x000000000240069a: mov    -0x28(%rbp),%r13
  0x000000000240069e: mov    -0x20(%rbp),%r12
  0x00000000024006a2: mov    -0x8(%rbp),%rbx
  0x00000000024006a6: mov    -0x18(%rbp),%rdi
  0x00000000024006aa: mov    -0x10(%rbp),%rsi

  ;; back to old(caller) stack frame:
  0x00000000024006ae: add    $0xd8,%rsp //栈顶指针复位
  0x00000000024006b5: pop    %rbp //栈底指针复位
  0x00000000024006b6: retq

在分析call_stub的汇编代码此前,先精通下x86存放器和栈帧以至函数调用的有关文化。

(1State of Qatar.对于能够填充为陆十四个人的再次回到值(包涵_m64卡塔尔将使用rax举行传递

(5卡塔尔(قطر‎.由于参数在栈中由低地址向高地址是以相反的逐个寄存的,所以率先个参数的地点应该是
rsp+rcx*8-8(第二个参数地址范围为 rsp+rcx*8-8 ~
rsp+rcx*8卡塔尔(قطر‎,将其保存在r14中

call    func
    movq    %xmm0, %rax             //保存结果
    movq    %rax, -8(%rbp)          
    movl    $0, %eax               //清空eax,回收main栈,恢复栈顶地址
    addq    $80, %rsp
    popq    %rbp
    ret

如上分析可能略显复杂,但首要的是清楚方法的输入例程是怎样为Java方法组织新的栈帧,进而为字节码的运作提供调用栈境况。

x86-64的富有寄存器都以与
机器字长(数据总线位宽卡塔尔相仿,即63人的,x86-64将x86的8个叁15个人通用寄存器增加为陆12人(eax、ebx、ecx、edx、eci、
edi、ebp、esp卡塔尔国,况兼扩展了8个新的六15位存放器(r8-r15卡塔尔国,在命有名的模特式上,也从”exx”变为”rxx”,但仍保存”exx”举行叁九人操作,下表描述了各寄放器的命名和效果

富有来自设想机对Java函数的调用最后都将由JavaCalls模块来成功,JavaCalls将由此call_helper(卡塔尔国来实践Java方法并回到调用结果,并最终调用StubRoutines::call_stub()来执行Java方法:

那边未有虚构func函数再一次调用其余函数而希图操作数的栈内容的情况,但结合main函数栈,大概能够得出栈的通用布局如下:

澳门新葡萄京官网首页 7

method entry point汇编代码的解析能够参谋随后的一篇小说。

2.rcx、rdx、r8d、r9d分别保存着传播call_stub的前4个参数,现在亟需将其复制到栈上的shadow
space中

里头methodOop指针被保存在rbx中,调用Java方法的sender
sp被封存在r13中,参数大小保存在rcx中

// Calls to Java
  typedef void (*CallStub)(
    address   link,
    intptr_t* result,
    BasicType result_type,
    methodOopDesc* method,
    address   entry_point,
    intptr_t* parameters,
    int       size_of_parameters,
    TRAPS
  );
  static CallStub call_stub()   { return CAST_TO_FN_PTR(CallStub, _call_stub_entry); }

(1State of Qatar.前4个参数的int类型分别通过rcx、rdx、r8、r9传递,多余的在栈空间上传递(从右向左依次入栈State of Qatar,贮存器全数的参数都以向右对齐的(低位对齐卡塔尔(قطر‎

发表评论

电子邮件地址不会被公开。 必填项已用*标注