1. 程式人生 > >[inside hotspot] java方法調用的StubCode

[inside hotspot] java方法調用的StubCode

dep 是個 觸發 comment his nature 內部 store mar

[inside hotspot] java方法調用的StubCode

眾所周知jvm有invokestatic,invokedynamic,invokestatic,invokespecial,invokevirtual幾條方法調用指令,每個負責調用不同的方法,
而這些方法調用落實到hotspot上都位於hotspot\src\share\vm\runtime\javaCalls.hppJavaCalls :

1. JavaCalls

class JavaCalls: AllStatic {
  static void call_helper(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS);
 public:
  // call_special
  // ------------
  // The receiver must be first oop in argument list
  static void call_special(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, JavaCallArguments* args, TRAPS);
  static void call_special(JavaValue* result, Handle receiver, KlassHandle klass, Symbol* name, Symbol* signature, TRAPS); // No args
  static void call_special(JavaValue* result, Handle receiver, KlassHandle klass, Symbol* name, Symbol* signature, Handle arg1, TRAPS);
  static void call_special(JavaValue* result, Handle receiver, KlassHandle klass, Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS);

  // virtual call
  // ------------
  // The receiver must be first oop in argument list
  static void call_virtual(JavaValue* result, KlassHandle spec_klass, Symbol* name, Symbol* signature, JavaCallArguments* args, TRAPS);
  static void call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass, Symbol* name, Symbol* signature, TRAPS); // No args
  static void call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass, Symbol* name, Symbol* signature, Handle arg1, TRAPS);
  static void call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass, Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS);

  // Static call
  // -----------
  static void call_static(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, JavaCallArguments* args, TRAPS);
  static void call_static(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, TRAPS);
  static void call_static(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, Handle arg1, TRAPS);
  static void call_static(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS);
  static void call_static(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, Handle arg1, Handle arg2, Handle arg3, TRAPS);

  // Low-level interface
  static void call(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS);
};

上面的方法是自解釋的,對應各自的invoke*指令,這些call_static,call_virtual內部調用了call()函數:

void JavaCalls::call(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS) {
  assert(THREAD->is_Java_thread(), "only JavaThreads can make JavaCalls");
  os::os_exception_wrapper(call_helper, result, method, args, THREAD);
}

call()只是簡單檢查了一下線程信息,以及根據平臺比如windows會使用結構化異常(SEH)包裹call_helper,最終執行方法調用的還是call_helper

void JavaCalls::call_helper(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS) {
  ...
 
  // 如果當前方法為空,則直接返回
  if (method->is_empty_method()) {
    assert(result->get_type() == T_VOID, "an empty method must return a void value");
    return;
  }

  ...

  //根據情況決定是否編譯該方法,JIT和-Xcomp都有可能觸發它
  CompilationPolicy::compile_if_required(method, CHECK);

  // 解釋器入口點
  address entry_point = method->from_interpreted_entry();
  if (JvmtiExport::can_post_interpreter_events() && thread->is_interp_only_mode()) {
    entry_point = method->interpreter_entry();
  }

  // 確定返回值類型
  BasicType result_type = runtime_type_from(result);
  bool oop_result_flag = (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY);

  // 返回值地址
  intptr_t* result_val_address = (intptr_t*)(result->get_value_addr());

  // 確定receiver,如果是static函數就沒有receiver
  Handle receiver = (!method->is_static()) ? args->receiver() : Handle();

  if (!thread->stack_guards_enabled()) {
    thread->reguard_stack();
  }

  // 確認當前sp是否到達ShadowPages,即是否會觸發棧溢出錯誤
  address sp = os::current_stack_pointer();
  if (!os::stack_shadow_pages_available(THREAD, method, sp)) {
    // Throw stack overflow exception with preinitialized exception.
    Exceptions::throw_stack_overflow_exception(THREAD, __FILE__, __LINE__, method);
    return;
  } else {
    // Touch pages checked if the OS needs them to be touched to be mapped.
    os::map_stack_shadow_pages(sp);
  }


  // 執行調用
  { 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());
      }
    }
  } 


  // 設置返回值
  if (oop_result_flag) {
    result->set_jobject((jobject)thread->vm_result());
    thread->set_vm_result(NULL);
  }
}

call_helper又可以分為兩步,第一步判斷一下方法是否為空,是否可以JIT編譯,是否還有棧空間可以等,第二步StubRoutines::call_stub()實際調用os+cpu限定的方法。
這個StubRoutines::call_stub()返回的是一個函數指針,指向的是平臺特定的方法,所以這段代碼:

      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
      );

call_stub()返回一個函數指針,指向依賴於操作系統和cpu架構的特定的方法,原因很簡單,要執行native代碼,得看看是什麽cpu架構以便確定寄存器,看看什麽os以便確定ABI。
然後傳遞8個參數到這個方法裏面並執行這個方法。那麽這個方法是什麽呢?進入stubRoutines.cpp便知是StubRoutines::_call_stub_entry

2. windows+x86_64的stubGenerator

以x64為例,hotspot\src\cpu\x86\vm\stubGenerator_x86_64.cppgenerate_call_stub()會負責初始化StubRoutines::_call_stub_entry函數,使用參數命令
-XX:+UnlockDiagnosticVMOptions -XX:+PrintStubCode可以輸出generate_call_stub方法生成的匯編,對照著看非常舒服:

 address generate_call_stub(address& return_address) {
    assert((int)frame::entry_frame_after_call_words == -(int)rsp_after_call_off + 1 &&
           (int)frame::entry_frame_call_wrapper_offset == (int)call_wrapper_off,
           "adjust this code");
    StubCodeMark mark(this, "StubRoutines", "call_stub");
    address start = __ pc();

    // same as in generate_catch_exception()!
    const Address rsp_after_call(rbp, rsp_after_call_off * wordSize);

    const Address call_wrapper  (rbp, call_wrapper_off   * wordSize);
    const Address result        (rbp, result_off         * wordSize);
    const Address result_type   (rbp, result_type_off    * wordSize);
    const Address method        (rbp, method_off         * wordSize);
    const Address entry_point   (rbp, entry_point_off    * wordSize);
    const Address parameters    (rbp, parameters_off     * wordSize);
    const Address parameter_size(rbp, parameter_size_off * wordSize);

    // same as in generate_catch_exception()!
    const Address thread        (rbp, thread_off         * wordSize);

    const Address r15_save(rbp, r15_off * wordSize);
    const Address r14_save(rbp, r14_off * wordSize);
    const Address r13_save(rbp, r13_off * wordSize);
    const Address r12_save(rbp, r12_off * wordSize);
    const Address rbx_save(rbp, rbx_off * wordSize);

    // stub code
    __ enter();
    __ subptr(rsp, -rsp_after_call_off * wordSize);
StubRoutines::call_stub [0x0000026b0a5d09d7, 0x0000026b0a5d0b44[ (365 bytes)
  0x0000026b0a5d09d7: push   %rbp
  0x0000026b0a5d09d8: mov    %rsp,%rbp
  0x0000026b0a5d09db: sub    $0x1d8,%rsp
    // save register parameters
#ifndef _WIN64
    __ movptr(parameters,   c_rarg5); // parameters
    __ movptr(entry_point,  c_rarg4); // entry_point
#endif

    __ movptr(method,       c_rarg3); // method
    __ movl(result_type,  c_rarg2);   // result type
    __ movptr(result,       c_rarg1); // result
    __ movptr(call_wrapper, c_rarg0); // call wrapper
  // r9方法,r8d返回值類型,rdx,返回值,rcx即JavaCallsWrapper
  0x0000026b0a5d09e2: mov    %r9,0x28(%rbp)
  0x0000026b0a5d09e6: mov    %r8d,0x20(%rbp)
  0x0000026b0a5d09ea: mov    %rdx,0x18(%rbp)
  0x0000026b0a5d09ee: mov    %rcx,0x10(%rbp)
    // save regs belonging to calling function
    __ movptr(rbx_save, rbx);
    __ movptr(r12_save, r12);
    __ movptr(r13_save, r13);
    __ movptr(r14_save, r14);
    __ movptr(r15_save, r15);
    if (UseAVX > 2) {
      __ movl(rbx, 0xffff);
      __ kmovwl(k1, rbx);
    }
#ifdef _WIN64
    int last_reg = 15;
    if (UseAVX > 2) {
      last_reg = 31;
    }
    if (VM_Version::supports_evex()) {
      for (int i = xmm_save_first; i <= last_reg; i++) {
        __ vextractf32x4(xmm_save(i), as_XMMRegister(i), 0);
      }
    } else {
      for (int i = xmm_save_first; i <= last_reg; i++) {
        __ movdqu(xmm_save(i), as_XMMRegister(i));
      }
    }

  // caller-save 寄存器
  0x0000026b0a5d09f2: mov    %rbx,-0x8(%rbp)
  0x0000026b0a5d09f6: mov    %r12,-0x20(%rbp)
  0x0000026b0a5d09fa: mov    %r13,-0x28(%rbp)
  0x0000026b0a5d09fe: mov    %r14,-0x30(%rbp)
  0x0000026b0a5d0a02: mov    %r15,-0x38(%rbp)

  0x0000026b0a5d0a06: vmovdqu %xmm6,-0x48(%rbp)
  0x0000026b0a5d0a0b: vmovdqu %xmm7,-0x58(%rbp)
  0x0000026b0a5d0a10: vmovdqu %xmm8,-0x68(%rbp)
  0x0000026b0a5d0a15: vmovdqu %xmm9,-0x78(%rbp)
  0x0000026b0a5d0a1a: vmovdqu %xmm10,-0x88(%rbp)
  0x0000026b0a5d0a22: vmovdqu %xmm11,-0x98(%rbp)
  0x0000026b0a5d0a2a: vmovdqu %xmm12,-0xa8(%rbp)
  0x0000026b0a5d0a32: vmovdqu %xmm13,-0xb8(%rbp)
  0x0000026b0a5d0a3a: vmovdqu %xmm14,-0xc8(%rbp)
  0x0000026b0a5d0a42: vmovdqu %xmm15,-0xd8(%rbp)
    const Address rdi_save(rbp, rdi_off * wordSize);
    const Address rsi_save(rbp, rsi_off * wordSize);
    __ movptr(rsi_save, rsi);
    __ movptr(rdi_save, rdi);
  // rsi rdi
  0x0000026b0a5d0a4a: mov    %rsi,-0x10(%rbp)
  0x0000026b0a5d0a4e: mov    %rdi,-0x18(%rbp)
    // Load up thread register
    __ movptr(r15_thread, thread);
    __ reinit_heapbase();
  // 線程寄存器
  0x0000026b0a5d0a52: mov    0x48(%rbp),%r15
  0x0000026b0a5d0a56: movabs $0x7ffe4c5b2be8,%r10
  0x0000026b0a5d0a60: mov    (%r10),%r12
    // pass parameters if any
    BLOCK_COMMENT("pass parameters if any");
    Label parameters_done;
    __ movl(c_rarg3, parameter_size);
    __ testl(c_rarg3, c_rarg3);
    __ jcc(Assembler::zero, parameters_done);

    Label loop;
    __ movptr(c_rarg2, parameters);       // parameter pointer
    __ movl(c_rarg1, c_rarg3);            // parameter counter is in c_rarg1
    __ BIND(loop);
    __ movptr(rax, Address(c_rarg2, 0));// get parameter
    __ addptr(c_rarg2, wordSize);       // advance to next parameter
    __ decrementl(c_rarg1);             // decrement counter
    __ push(rax);                       // pass parameter
    __ jcc(Assembler::notZero, loop);
  // 這裏是個循環,用於傳遞參數,相當於
  // while(r9d){
  //    rax = *arg
  //    push_arg(rax)
  //    arg++;  // ptr++
  //    r9d--;  // counter--
  // }
  0x0000026b0a5d0a63: mov    0x40(%rbp),%r9d
  0x0000026b0a5d0a67: test   %r9d,%r9d
  0x0000026b0a5d0a6a: je     0x0000026b0a5d0a83
  0x0000026b0a5d0a70: mov    0x38(%rbp),%r8
  0x0000026b0a5d0a74: mov    %r9d,%edx
  0x0000026b0a5d0a77: mov    (%r8),%rax
  0x0000026b0a5d0a7a: add    $0x8,%r8
  0x0000026b0a5d0a7e: dec    %edx
  0x0000026b0a5d0a80: push   %rax
  0x0000026b0a5d0a81: jne    0x0000026b0a5d0a77 
    // call Java function
    __ BIND(parameters_done);
    __ movptr(rbx, method);             // get Method*
    __ movptr(c_rarg1, entry_point);    // get entry_point
    __ mov(r13, rsp);                   // set sender sp
    BLOCK_COMMENT("call Java function");
    __ call(c_rarg1);
  // [!!]調用java方法
  0x0000026b0a5d0a83: mov    0x28(%rbp),%rbx
  0x0000026b0a5d0a87: mov    0x30(%rbp),%rdx
  0x0000026b0a5d0a8b: mov    %rsp,%r13
  0x0000026b0a5d0a8e: callq  *%rdx
    BLOCK_COMMENT("call_stub_return_address:");
    return_address = __ pc();

    // store result depending on type (everything that is not
    // T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT)
    __ movptr(c_rarg0, result);
    Label is_long, is_float, is_double, exit;
    __ movl(c_rarg1, result_type);
    __ cmpl(c_rarg1, T_OBJECT);
    __ jcc(Assembler::equal, is_long);
    __ cmpl(c_rarg1, T_LONG);
    __ jcc(Assembler::equal, is_long);
    __ cmpl(c_rarg1, T_FLOAT);
    __ jcc(Assembler::equal, is_float);
    __ cmpl(c_rarg1, T_DOUBLE);
    __ jcc(Assembler::equal, is_double);

    // handle T_INT case
    __ movl(Address(c_rarg0, 0), rax);

    __ BIND(exit);

    // pop parameters
    __ lea(rsp, rsp_after_call);
  // 儲存java方法返回值並彈出參數,這裏彈出操作即移動一下rsp指針
  0x0000026b0a5d0a90: mov    0x18(%rbp),%rcx
  0x0000026b0a5d0a94: mov    0x20(%rbp),%edx
  0x0000026b0a5d0a97: cmp    $0xc,%edx
  0x0000026b0a5d0a9a: je     0x0000026b0a5d0b30
  0x0000026b0a5d0aa0: cmp    $0xb,%edx
  0x0000026b0a5d0aa3: je     0x0000026b0a5d0b30
  0x0000026b0a5d0aa9: cmp    $0x6,%edx
  0x0000026b0a5d0aac: je     0x0000026b0a5d0b35
  0x0000026b0a5d0ab2: cmp    $0x7,%edx
  0x0000026b0a5d0ab5: je     0x0000026b0a5d0b3b
  0x0000026b0a5d0abb: mov    %eax,(%rcx)
  0x0000026b0a5d0abd: lea    -0x1d8(%rbp),%rsp
    // restore regs belonging to calling function
#ifdef _WIN64
    // emit the restores for xmm regs
    if (VM_Version::supports_evex()) {
      for (int i = xmm_save_first; i <= last_reg; i++) {
        __ vinsertf32x4(as_XMMRegister(i), as_XMMRegister(i), xmm_save(i), 0);
      }
    } else {
      for (int i = xmm_save_first; i <= last_reg; i++) {
        __ movdqu(as_XMMRegister(i), xmm_save(i));
      }
    }
#endif
    __ movptr(r15, r15_save);
    __ movptr(r14, r14_save);
    __ movptr(r13, r13_save);
    __ movptr(r12, r12_save);
    __ movptr(rbx, rbx_save);

    __ movptr(rdi, rdi_save);
    __ movptr(rsi, rsi_save);
  // 恢復之前保存的caller-save寄存器
  0x0000026b0a5d0ac4: vmovdqu -0x48(%rbp),%xmm6
  0x0000026b0a5d0ac9: vmovdqu -0x58(%rbp),%xmm7
  0x0000026b0a5d0ace: vmovdqu -0x68(%rbp),%xmm8
  0x0000026b0a5d0ad3: vmovdqu -0x78(%rbp),%xmm9
  0x0000026b0a5d0ad8: vmovdqu -0x88(%rbp),%xmm10
  0x0000026b0a5d0ae0: vmovdqu -0x98(%rbp),%xmm11
  0x0000026b0a5d0ae8: vmovdqu -0xa8(%rbp),%xmm12
  0x0000026b0a5d0af0: vmovdqu -0xb8(%rbp),%xmm13
  0x0000026b0a5d0af8: vmovdqu -0xc8(%rbp),%xmm14
  0x0000026b0a5d0b00: vmovdqu -0xd8(%rbp),%xmm15
  0x0000026b0a5d0b08: mov    -0x38(%rbp),%r15
  0x0000026b0a5d0b0c: mov    -0x30(%rbp),%r14
  0x0000026b0a5d0b10: mov    -0x28(%rbp),%r13
  0x0000026b0a5d0b14: mov    -0x20(%rbp),%r12
  0x0000026b0a5d0b18: mov    -0x8(%rbp),%rbx
  0x0000026b0a5d0b1c: mov    -0x18(%rbp),%rdi
  0x0000026b0a5d0b20: mov    -0x10(%rbp),%rsi
    // restore rsp
    __ addptr(rsp, -rsp_after_call_off * wordSize);

    // return
    __ pop(rbp);
    __ ret(0);
  // 結束__call_stub_entry這個函數
  0x0000026b0a5d0b24: add    $0x1d8,%rsp
  0x0000026b0a5d0b2b: vzeroupper 
  0x0000026b0a5d0b2e: pop    %rbp
  0x0000026b0a5d0b2f: retq   

下面這段代碼邏輯上屬於之前的存儲java方法的返回值,隨便舉個例子0x0000026b0a5d0b30這個地址正是之前存放java方法的代碼段je 0x0000026b0a5d0b30所跳之處,只是放到了最後而已:(不過我也不知道為什麽要放到這後面)

    // handle return types different from T_INT
    __ BIND(is_long);
    __ movq(Address(c_rarg0, 0), rax);
    __ jmp(exit);

    __ BIND(is_float);
    __ movflt(Address(c_rarg0, 0), xmm0);
    __ jmp(exit);

    __ BIND(is_double);
    __ movdbl(Address(c_rarg0, 0), xmm0);
    __ jmp(exit);

    return start;
  }
  0x0000026b0a5d0b30: mov    %rax,(%rcx)
  0x0000026b0a5d0b33: jmp    0x0000026b0a5d0abd
  0x0000026b0a5d0b35: vmovss %xmm0,(%rcx)
  0x0000026b0a5d0b39: jmp    0x0000026b0a5d0abd
  0x0000026b0a5d0b3b: vmovsd %xmm0,(%rcx)
  0x0000026b0a5d0b3f: jmpq   0x0000026b0a5d0abd

對照匯編看非常清晰,不過也可以看到它建立了棧幀結構,但它還是沒有執行java代碼,而是使用callq *rdx進行的,
這也是為什麽它叫做stub的原因。
另外上面的棧幀裏面內容比較多,[rsp+xx]存放什麽內容啊這些比較難記,已經歸納好的結構可以參見代碼註釋:

  // Windowsx86_64平臺
  //
  //    註意c_rarg\d 表示寄存器,method/result表示內存地址[rbp+\d]
  //
  //    c_rarg0:   call wrapper address                   address
  //    c_rarg1:   result                                 address
  //    c_rarg2:   result type                            BasicType
  //    c_rarg3:   method                                 Method*
  //    48(rbp): (interpreter) entry point              address
  //    56(rbp): parameters                             intptr_t*
  //    64(rbp): parameter size (in words)              int
  //    72(rbp): thread                                 Thread*
  //
  //     [ return_from_Java     ] <--- 這裏執行callq調用java方法。壓入返回地址,跳轉到java方法,也就是說↑上面的部分就是java方法使用的棧幀了
  //     [ argument word n      ] <--- 循環傳遞的java方法實參
  //      ...
  // -60 [ argument word 1      ] 
  // -59 [ saved xmm31          ] <--- rsp after_call
  //     [ saved xmm16-xmm30    ] 
  // -27 [ saved xmm15          ]
  //     [ saved xmm7-xmm14     ]
  //  -9 [ saved xmm6           ] 
  //  -7 [ saved r15            ]
  //  -6 [ saved r14            ]
  //  -5 [ saved r13            ]
  //  -4 [ saved r12            ]
  //  -3 [ saved rdi            ]
  //  -2 [ saved rsi            ]
  //  -1 [ saved rbx            ]
  //   0 [ saved rbp            ] <--- rbp
  //   1 [ return address       ] <--- last rbp
  //   2 [ call wrapper         ] <--- arg0
  //   3 [ result               ] <--- arg1
  //   4 [ result type          ] <--- arg2
  //   5 [ method               ] <--- arg3
  //   6 [ entry point          ] <--- arg4 
  //   7 [ parameters           ] <--- arg5
  //   8 [ parameter size       ] <--- arg6
  //   9 [ thread               ] <--- arg7

這8個arg正是之前傳遞給函數指針指向的函數的實參:

     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
      );

[inside hotspot] java方法調用的StubCode