CallStub棧幀


之前多次提到接觸到調用JavaCalls::call()函數來執行Java方法,如:

(1)Java主類裝載時,調用JavaCalls::call()方法執行的Java方法checkAndLoadMain()方法;

(2)類的初始化過程中,調用JavaCalls::call()方法執行的Java方法<clinit>方法,可以查看JavaCalls::call_default_constructor()函數,有對<clinit>函數的調用邏輯;

(3)我們先省略main方法的執行流程(其實main方法的執行也是先啟動一個JavaMain線程,套路都是一樣的),單看某個JavaThread的啟動過程。JavaThread的啟動最終都要通過一個native方法java.lang.Thread#start0方法完成的,這個方法經過解釋器的native_entry入口,調用到了JVM_StartThread函數。其中的static void thread_entry(JavaThread* thread, TRAPS)函數中會調用JavaCalls::call_virtual()函數。JavaThread最終會通過JavaCalls::call_virtual來調用字節碼中的run()方法。

可以看出,JavaCalls::call()函數為虛擬機調用Java方法提供了便利,Java虛擬機有invokestatic、invokedynamic、invokestatic、invokespecial、invokevirtual幾種方法調用指令,每個負責調用不同的方法,而這些方法都定義在JavaCalls類中,如下:

源代碼位置:/src/share/vm/runtime/javaCalls.hpp
// All calls to Java have to go via JavaCalls. Sets up the stack frame
// and makes sure that the last_Java_frame pointers are chained correctly.

class JavaCalls: AllStatic {
  static void call_helper(JavaValue* result, methodHandle* method, JavaCallArguments* args, TRAPS);
 public:
  // Optimized Constuctor call
  static void call_default_constructor(JavaThread* thread, methodHandle method, Handle receiver, TRAPS);

  // call_special
  // ------------
  // The receiver must be first oop in argument list
  // receiver表示方法的接收者,如A.main()調用中,A就是方法的接收者
  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);

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

在SystemDictionary::load_instance_class()這個能體現雙親委派的函數中,如果類加載器對象不為空,則會調用這個類加載器的loadClass()函數(通過call_virtual()函數來調用)來加載類。或者調用線程的run()方法時,也會通過call_virtual()函數調用JavaCalls::call()函數。

調用<cinit>函數初始化對象時,部分會通過JavaCalls::call_special()函數調用JavaCalls::call()函數。

其它就不一一列舉了。

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

void JavaCalls::call(JavaValue* result, methodHandle method, JavaCallArguments* args, TRAPS) {
  // Check if we need to wrap a potential OS exception handler around thread
  // This is used for e.g. Win32 structured exception handlers
  assert(THREAD->is_Java_thread(), "only JavaThreads can make JavaCalls");
  // Need to wrap each and everytime, since there might be native code down the
  // stack that has installed its own exception handlers
  // 通過傳入call_helper函數指針,在call_helper上面封裝了異常的處理,典型的回調函數用法
  os::os_exception_wrapper(call_helper, result, &method, args, THREAD); 
}

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

JavaCalls::call_helper()                      javaCalls.cpp
os::os_exception_wrapper()                    os_linux.cpp
JavaCalls::call()                             javaCalls.cpp
InstanceKlass::call_class_initializer_impl()  instanceKlass.cpp
InstanceKlass::call_class_initializer()       instanceKlass.cpp	
InstanceKlass::initialize_impl()              instanceKlass.cpp
InstanceKlass::initialize()                   instanceKlass.cpp
InstanceKlass::initialize_impl()              instanceKlass.cpp	
InstanceKlass::initialize()                   instanceKlass.cpp	
initialize_class()                            thread.cpp	
Threads::create_vm()                          thread.cpp
JNI_CreateJavaVM()                            jni.cpp
InitializeJVM()                               java.c
JavaMain()                                    java.c

 JavaCalls::helper()函數的實現如下:

void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
  methodHandle method = *m;
  JavaThread* thread = (JavaThread*)THREAD;
  assert(thread->is_Java_thread(), "must be called by a java thread");
  assert(method.not_null(), "must have a method to call");
  assert(!SafepointSynchronize::is_at_safepoint(), "call to Java code during VM operation");
  assert(!thread->handle_area()->no_handle_mark_active(), "cannot call out to Java here");


  // Ignore call if method is empty
  if (method->is_empty_method()) {
    assert(result->get_type() == T_VOID, "an empty method must return a void value");
    return;
  }

  assert(!thread->is_Compiler_thread(), "cannot compile from the compiler");
  if (CompilationPolicy::must_be_compiled(method)) {
    CompileBroker::compile_method(method, InvocationEntryBci,
                                  CompilationPolicy::policy()->initial_compile_level(),
                                  methodHandle(), 0, "must_be_compiled", CHECK);
  }

  //獲取的entry_point就是為Java方法調用准備棧楨,並把代碼調用指針指向method的第一個字節碼的內存地址。
  //entry_point相當於是method的封裝,不同的method類型有不同的entry_point。
  // Since the call stub sets up like the interpreter we call the from_interpreted_entry
  // so we can go compiled via a i2c. Otherwise initial entry method will always
  // run interpreted.
  address entry_point = method->from_interpreted_entry();
  if (JvmtiExport::can_post_interpreter_events() && thread->is_interp_only_mode()) {
    entry_point = method->interpreter_entry();
  }

  // Figure out if the result value is an oop or not (Note: This is a different value
  // than result_type. result_type will be T_INT of oops. (it is about size)
  BasicType result_type = runtime_type_from(result);
  bool oop_result_flag = (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY);

  // NOTE: if we move the computation of the result_val_address inside
  // the call to call_stub, the optimizer produces wrong code.
  intptr_t* result_val_address = (intptr_t*)(result->get_value_addr());

  // Find receiver
  Handle receiver = (!method->is_static()) ? args->receiver() : Handle();

  // When we reenter Java, we need to reenable the yellow zone which
  // might already be disabled when we are in VM.
  if (thread->stack_yellow_zone_disabled()) {
    thread->reguard_stack();
  }

  // Check that there are shadow pages available before changing thread state
  // to Java
  if (!os::stack_shadow_pages_available(THREAD, method)) {
    // 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::bang_stack_shadow_pages();
  }

  // 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());
      }
    }
  } // Exit JavaCallWrapper (can block - potential return oop must be preserved)

  // Check if a thread stop or suspend should be executed
  // The following assert was not realistic.  Thread.stop can set that bit at any moment.
  //assert(!thread->has_special_runtime_exit_condition(), "no async. exceptions should be installed");

  // Restore possible oop return
  if (oop_result_flag) {
    result->set_jobject((jobject)thread->vm_result());
    thread->set_vm_result(NULL);
  }
}

我們需要關注此函數做的如下幾件事:

1、檢查目標方法是否“首次執行前就必須被編譯”,是的話調用JIT編譯器去編譯目標方法

2、獲取目標方法的解釋模式入口from_interpreted_entry,也就是entry_point的值。獲取的entry_point就是為Java方法調用准備棧楨,並把代碼調用指針指向method的第一個字節碼的內存地址。entry_point相當於是method的封裝,不同的method類型有不同的entry_point

3、調用call_stub()函數。call_helper又可以分為兩步,第一步判斷一下方法是否為空,是否可以JIT編譯,是否還有棧空間等,第二步StubRoutines::call_stub()實際調用os+cpu限定的方法。

調用CallStub函數的是/src/share/vm/runtime/javaCalls.cpp文件中的call_helper()函數,調用CallStub函數指針所指的函數時,需要傳遞8個參數,如下:

(1)link 此變量的類型為JavaCallWrapper,這個變量需要保存的信息很重要,后面將詳細介紹。

(2)result_val_address 函數返回值地址。

(3)result_type 函數返回類型。 

(4)method() 當前要執行的方法。通過此參數可以獲取到Java方法所有的元數據信息,包括最重要的字節碼信息,這樣就可以根據字節碼信息解釋執行這個方法了。

(5)entry_point HotSpot每次在調用Java函數時,必然會調用CallStub函數指針,這個函數指針的值取自_call_stub_entry,HotSpot通過_call_stub_entry指向被調用函數地址。在調用函數之前,必須要先經過entry_point,HotSpot實際是通過entry_point從method()對象上拿到Java方法對應的第1個字節碼命令,這也是整個函數的調用入口。

(6)args->parameters()  描述Java函數的入參信息。

(7)args->size_of_parameters()  描述Java函數的入參數量。

(8)CHECK 當前線程對象。  

來源:/src/share/vm/runtime/stubRoutines.hpp

static CallStub  call_stub() { 
    return CAST_TO_FN_PTR(CallStub, _call_stub_entry); 
}

call_stub()函數返回一個函數指針,指向依賴於操作系統和cpu架構的特定的方法,原因很簡單,要執行native代碼,得看看是什么cpu架構以便確定寄存器,看看什么os以便確定ABI。

其中CAST_TO_FN_PTR是宏,具體定義如下:

源代碼位置:/src/share/vm/runtime/utilities/globalDefinitions.hpp
#define CAST_TO_FN_PTR(func_type, value) ((func_type)(castable_address(value)))

對call_stub()函數進行宏替換和展開后會變為如下的形式:

static CallStub call_stub(){
    return (CallStub)( castable_address(_call_stub_entry) );
}

CallStub的定義如下:

源代碼位置:/src/share/vm/runtime/stubRoutines.hpp

// Calls to Java
typedef void (*CallStub)(
    address   link,    // 連接器
    intptr_t* result, // 函數返回值地址
    BasicType result_type, //函數返回類型 
    Method* method, // JVM內部所表示的Java方法對象
    // JVM調用Java方法的例程入口。JVM內部的每一段例程都是在JVM啟動過程中預先生成好的一段機器指令。要調用Java方法,
    // 必須經過本例程,即需要先執行這段機器指令,然后才能跳轉到Java方法字節碼所對應的機器指令去執行
    address   entry_point, 
    intptr_t* parameters,
    int       size_of_parameters,
    TRAPS
); 

如上定義了一種函數指針類型,指向的函數聲明了8個形式參數。 

在call_stub()函數中調用的castable_address()函數定義在globalDefinitions.hpp文件中,具體實現如下:

inline address_word  castable_address(address x)  { 
    return address_word(x) ; 
}

address_word是一定自定義的類型,在globalDefinitions.hpp文件中的定義如下:

// unsigned integer which will hold a pointer
// except for some implementations of a C++
// linkage pointer to function. Should never
// need one of those to be placed in this type anyway.
typedef   uintptr_t     address_word;

其中uintptr_t也是一種自定義的類型,在Linux內核的操作系統下使用globalDefinitions_gcc.hpp文件中的定義,具體定義如下:

typedef  unsigned int  uintptr_t;

這樣call_stub()函數其實等同於如下的實現形式:

static CallStub call_stub(){
    return (CallStub)( unsigned int(_call_stub_entry) );
}

將_call_stub_entry強制轉換為unsigned int類型,然后以強制轉換為CallStub類型。CallStub是一個函數指針,所以_call_stub_entry應該也是一個函數指針,而不應該是一個普通的無符號整數。  

在call_stub()函數中,_call_stub_entry的定義如下:

address StubRoutines::_call_stub_entry = NULL; 

_call_stub_entry的初始化在在/src/cpu/x86/vm/stubGenerator_x86_64.cpp文件下的generate_initial()函數,調用鏈如下:

StubGenerator::generate_initial()   stubGenerator_x86_64.cpp	
StubGenerator::StubGenerator()      stubGenerator_x86_64.cpp
StubGenerator_generate()            stubGenerator_x86_64.cpp	
StubRoutines::initialize1()         stubRoutines.cpp	
stubRoutines_init1()                stubRoutines.cpp	
init_globals()                      init.cpp
Threads::create_vm()                thread.cpp
JNI_CreateJavaVM()                  jni.cpp
InitializeJVM()                     java.c
JavaMain()                          java.c

其中的StubGenerator類定義在src/cpu/x86/vm目錄下的stubGenerator_x86_64.cpp文件中,這個文件中的generate_initial()方法會初始化call_stub_entry變量,如下:

StubRoutines::_call_stub_entry = generate_call_stub(StubRoutines::_call_stub_return_address);

調用的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);

    // save register parameters
    __ movptr(parameters,   c_rarg5); // parameters
    __ movptr(entry_point,  c_rarg4); // entry_point


    __ movptr(method,       c_rarg3); // method
    __ movl(result_type,  c_rarg2);   // result type
    __ movptr(result,       c_rarg1); // result
    __ movptr(call_wrapper, c_rarg0); // call wrapper

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

    const Address mxcsr_save(rbp, mxcsr_off * wordSize);
    {
      Label skip_ldmx;
      __ stmxcsr(mxcsr_save);
      __ movl(rax, mxcsr_save);
      __ andl(rax, MXCSR_MASK);    // Only check control and mask bits
      ExternalAddress mxcsr_std(StubRoutines::addr_mxcsr_std());
      __ cmp32(rax, mxcsr_std);
      __ jcc(Assembler::equal, skip_ldmx);
      __ ldmxcsr(mxcsr_std);
      __ bind(skip_ldmx);
    }


    // Load up thread register
    __ movptr(r15_thread, thread);
    __ reinit_heapbase();


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

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

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


    __ movptr(r15, r15_save);
    __ movptr(r14, r14_save);
    __ movptr(r13, r13_save);
    __ movptr(r12, r12_save);
    __ movptr(rbx, rbx_save);

    __ ldmxcsr(mxcsr_save);

    // restore rsp
    __ addptr(rsp, -rsp_after_call_off * wordSize);

    // return
    __ pop(rbp);
    __ ret(0);

    // 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;
  }

這個函數實現的邏輯有點多,而且最終會生成一段機器碼,由於機器碼很難讀懂,所以我們可以通過方法的源代碼和匯編代碼來解讀。

首先簡單介紹一下address和Address類型。

address是u_char*類型的別名,定義如下:

源代碼位置:globalDefinitions.hpp

typedef   u_char*       address;

address是指針類型,指向的是u_char類型的值。一般在c/c++中,char是8位二進制位,所以是占一個字節。u_char是無符號的char,可以將address看作是u_char數組類型。

Address類的定義如下:

源代碼位置:/x86/vm/assembler_x86.hpp

// Address is an abstraction used to represent a memory location
// using any of the amd64 addressing modes with one object.
//
// Note: A register location is represented via a Register, not
//       via an address for efficiency & simplicity reasons.

class Address VALUE_OBJ_CLASS_SPEC {
   ...
}

Address代表內存地址,而寄存器使用Register代表。

如果要看generate_call_stub()方法生成的匯編,可以在導入hsdis-amd64.so(放入的路徑為/build/linux-x86_64-normal-server-slowdebug/jdk/lib/amd64/server/hsdis-amd64.so)的情況下,輸入如下命令:

-XX:+PrintStubCode -XX:+UnlockDiagnosticVMOptions    com.test/CompilationDemo1

首先看generate_call_stub()函數的如下兩句代碼:

// stub code
__ enter();
__ subptr(rsp, -rsp_after_call_off * wordSize);

調用macroAssembler_x86.cpp文件中的enter()方法,用來保存調用方棧基址,並將call_stub棧基址更新為當前棧頂地址。實現如下:

void MacroAssembler::enter() {
  push(rbp);
  mov(rbp, rsp);
}

調用的push()方法如下:

void Assembler::push(Register src) {
  int encode = prefix_and_encode(src->encoding());

  emit_int8(0x50 | encode);
} 

Assembler中定義的一些方法通常難以讀懂,這是因為需要我們知道x86體系下機器碼,並且要對Opcode編碼規則有所掌握,這一部分后面會詳細介紹,這里暫時不介紹,有興趣的可以自行學習Intel開發者手冊,里面對Intel cpu指令集有詳細介紹。我們這里只簡單認識一下生成機器碼的相關方法即可。

調用的src->encoding()返回自身,而prefix_and_encode()方法的實現如下:

int Assembler::prefix_and_encode(int reg_enc, bool byteinst) {
  if (reg_enc >= 8) {
    prefix(REX_B);
    reg_enc -= 8;
  } else if (byteinst && reg_enc >= 4) {
    prefix(REX);
  }
  return reg_enc;
}

enter()方法中調用的mov()方法的實現如下:

void Assembler::mov(Register dst, Register src) {
  LP64_ONLY(movq(dst, src)) NOT_LP64(movl(dst, src));
}

對於64位來說,調用movq()方法,如下:

void Assembler::movq(Register dst, Register src) {
  int encode = prefixq_and_encode(dst->encoding(), src->encoding());
  emit_int8((unsigned char)0x8B);
  emit_int8((unsigned char)(0xC0 | encode));
}

調用prefixq_and_encode()方法的實現如下:

int Assembler::prefixq_and_encode(int dst_enc, int src_enc) {
  if (dst_enc < 8) {
    if (src_enc < 8) {
      prefix(REX_W);
    } else {
      prefix(REX_WB);
      src_enc -= 8;
    }
  } else {
    if (src_enc < 8) {
      prefix(REX_WR);
    } else {
      prefix(REX_WRB);
      src_enc -= 8;
    }
    dst_enc -= 8;
  }
  return dst_enc << 3 | src_enc;
}

dst_enc的值為5,src_enc的值為4。 

generate_call_stub()方法中調用的subptr()方法的實現如下:

void MacroAssembler::subptr(Register dst, int32_t imm32) {
  LP64_ONLY(subq(dst, imm32)) NOT_LP64(subl(dst, imm32));
}

調用的 subq()方法的實現如下:

void Assembler::subq(Register dst, int32_t imm32) {
  (void) prefixq_and_encode(dst->encoding());
  emit_arith(0x81, 0xE8, dst, imm32);
}

調用的prefixq_and_encode()方法的實現如下:

int Assembler::prefixq_and_encode(int reg_enc) {
  if (reg_enc < 8) {
    prefix(REX_W);
  } else {
    prefix(REX_WB);
    reg_enc -= 8;
  }
  return reg_enc;
}

subq()方法中調用的emit_arith()方法的實現如下:

void Assembler::emit_arith(int op1, int op2, Register dst, int32_t imm32) {
  assert(isByte(op1) && isByte(op2), "wrong opcode");
  assert((op1 & 0x01) == 1, "should be 32bit operation");
  assert((op1 & 0x02) == 0, "sign-extension bit should not be set");

  if (is8bit(imm32)) {
    emit_int8(op1 | 0x02); // set sign bit
    emit_int8(op2 | encode(dst));
    emit_int8(imm32 & 0xFF);
  } else {
    emit_int8(op1);
    emit_int8(op2 | encode(dst));
    emit_int32(imm32);
  }
}

使用參數命令:

-XX:+UnlockDiagnosticVMOptions -XX:+PrintStubCode

可以輸出generate_call_stub方法生成的匯編,生成的匯編代碼如下:

StubRoutines::call_stub [0x00007fdf4500071f, 0x00007fdf45000807[ (232 bytes)
  0x00007fdf4500071f: push   %rbp         
  0x00007fdf45000720: mov    %rsp,%rbp 
  0x00007fdf45000723: sub    $0x60,%rsp  // 0x60 = -rsp_after_call_of * wordSize

如上匯編第1個為源操作數,第2個為目地操作數。如上3句匯編通常是開辟一個新棧幀使用的最常用寫法。

繼續看generate_call_stub()方法的實現,如下:

// save register parameters
__ movptr(parameters,   c_rarg5); // parameters
__ movptr(entry_point,  c_rarg4); // entry_point
__ movptr(method,       c_rarg3); // method
__ movl(result_type,    c_rarg2); // result type
__ movptr(result,       c_rarg1); // result
__ movptr(call_wrapper, c_rarg0); // call wrapper

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

const Address mxcsr_save(rbp, mxcsr_off * wordSize);
{
      Label skip_ldmx;
      __ stmxcsr(mxcsr_save);
      __ movl(rax, mxcsr_save);
      __ andl(rax, MXCSR_MASK);    // Only check control and mask bits
      ExternalAddress mxcsr_std(StubRoutines::addr_mxcsr_std());
      __ cmp32(rax, mxcsr_std);
      __ jcc(Assembler::equal, skip_ldmx);
      __ ldmxcsr(mxcsr_std);
      __ bind(skip_ldmx);
} 

生成的匯編代碼如下:

0x00007fdf45000727: mov      %r9,-0x8(%rbp)
0x00007fdf4500072b: mov      %r8,-0x10(%rbp)
0x00007fdf4500072f: mov      %rcx,-0x18(%rbp)
0x00007fdf45000733: mov      %edx,-0x20(%rbp)
0x00007fdf45000736: mov      %rsi,-0x28(%rbp)
0x00007fdf4500073a: mov      %rdi,-0x30(%rbp)
0x00007fdf4500073e: mov      %rbx,-0x38(%rbp)
0x00007fdf45000742: mov      %r12,-0x40(%rbp)
0x00007fdf45000746: mov      %r13,-0x48(%rbp)
0x00007fdf4500074a: mov      %r14,-0x50(%rbp)
0x00007fdf4500074e: mov      %r15,-0x58(%rbp)
// stmxcsr是將MXCSR寄存器中的值保存到-0x60(%rbp)中
0x00007fdf45000752: stmxcsr  -0x60(%rbp)  
0x00007fdf45000756: mov      -0x60(%rbp),%eax
0x00007fdf45000759: and      $0xffc0,%eax // MXCSR_MASK = 0xFFC0
// cmp通過第2個操作數減去第1個操作數的差,根據結果來設置eflags中的標志位。
// 本質上和sub指令相同,但是不會改變操作數的值
0x00007fdf4500075f: cmp      0x1762cb5f(%rip),%eax  # 0x00007fdf5c62d2c4 
// 當ZF=1時跳轉到目標地址
0x00007fdf45000765: je       0x00007fdf45000772 
// 將m32加載到MXCSR寄存器中
0x00007fdf4500076b: ldmxcsr  0x1762cb52(%rip)      # 0x00007fdf5c62d2c4   

MXCSR狀態管理指令,ldmxcsr與stmxcsr,用於控制MXCSR寄存器(表示SSE指令的運算狀態的寄存器)狀態。ldmxcsr指令從存儲器中加載MXCSR寄存器狀態;stmxcsr指令將MXCSR寄存器狀態保存到存儲器中。

最終的棧幀狀態如下圖所示。

 

由於call_helper()函數在調用CallStub()函數時,傳遞的參數多於6個,所以最后2個參數size_of_parameters與TRAPS(當前線程)要通過call_helper()的棧幀來傳遞,剩下的可以根據調用約定通過寄存器來傳遞。

當調用的目標函數為C/C++函數時,需要遵守調用約定,調用約定主要規定傳遞參數的方式以及返回值的方式。當在x64下進行函數調用時,以下寄存器用於傳遞參數: 

  • 第1個參數:rdi    c_rarg0   6
  • 第2個參數:rsi    c_rarg1   2
  • 第3個參數:rdx   c_rarg2  1
  • 第4個參數:rcx   c_rarg3  8 
  • 第5個參數:r8     c_rarg4  9
  • 第6個參數:r9     c_rarg5  7

在函數調用時,6個及小於6個用如下寄存器來傳遞,在HotSpot中通過更易理解的別名c_rarg*來使用對應的寄存器。如果參數超過六個,那么程序調用棧就會被用來傳遞那些額外的參數。 

繼續看generate_call_stub()方法的實現,接來下會加載線程寄存器,代碼如下:

// Load up thread register
__ movptr(r15_thread, thread);
__ reinit_heapbase();

生成的匯編代碼如下:

0x00007fdf45000772: mov    0x18(%rbp),%r15
0x00007fdf45000776: mov    0x1764212b(%rip),%r12   # 0x00007fdf5c6428a8

如果在調用函數時有參數的話需要傳遞參數,代碼如下:

// 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(%esi){
   rax = *arg
   push_arg(rax)
   arg++;   // ptr++
   %esi--;  // counter--
}

生成的匯編代碼如下:

0x00007fdf4500077d: mov    0x10(%rbp),%ecx    // 將棧中parameter size送到%ecx中
0x00007fdf45000780: test   %ecx,%ecx          // 做與運算,只有當%ecx中的值為0時才等於0
0x00007fdf45000782: je     0x00007fdf4500079a // 沒有參數需要傳遞,直接跳轉到parameters_done即可
// -- loop --
// 匯編執行到這里,說明paramter size不為0,需要傳遞參數
0x00007fdf45000788: mov    -0x8(%rbp),%rdx
0x00007fdf4500078c: mov    %ecx,%esi
0x00007fdf4500078e: mov    (%rdx),%rax
0x00007fdf45000791: add    $0x8,%rdx
0x00007fdf45000795: dec    %esi
0x00007fdf45000797: push   %rax
0x00007fdf45000798: jne    0x00007fdf4500078e  // 跳轉到loop

因為要調用Java方法,所以會為Java方法壓入實際的參數,也就是壓入parameter size個從parameters開始取的參數。壓入參數后的棧如下圖所示。

調用Java函數,如下:

// call Java function
// -- parameters_done -- __ BIND(parameters_done); __ movptr(rbx, method); // get Method* __ movptr(c_rarg1, entry_point); // get entry_point __ mov(r13, rsp); // set sender sp __ call(c_rarg1); // 調用Java方法

生成的匯編代碼如下:

0x00007fdf4500079a: mov     -0x18(%rbp),%rbx  // 將Method*送到%rbx中
0x00007fdf4500079e: mov     -0x10(%rbp),%rsi  // 將entry_point送到%rsi中
0x00007fdf450007a2: mov     %rsp,%r13         // 將調用者的棧頂指針保存到%r13中
0x00007fdf450007a5: callq   *%rsi             // 調用Java方法

注意調用callq指令后,會將callq指令的下一條指令的地址壓棧,再跳轉到第1操作數指定的地址,也就是*%rsi表示的地址。壓入下一條指令的地址是為了讓函數能通過跳轉到棧上的地址從子函數返回。 

callq指令調用的是entry point。entry point在后面會詳細介紹。

接下來在generate_call_stub()方法中會處理調用Java方法后的返回值與返回類型,而且還需要執行退棧操作,也就是將棧恢復到調用Java方法之前的狀態。代碼實現如下:

// store result depending on type (everything that is not
// T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT)
// 保存方法調用結果依賴於結果類型,只要不是T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE,都當做T_INT處理
// 將result地址的值拷貝到c_rarg0中,也就是將方法調用的結果保存在rdi寄存器中,注意result為函數返回值的地址
__ movptr(c_rarg0, result);     
Label is_long, is_float, is_double, exit;
// 將result_type地址的值拷貝到c_rarg1中,也就是將方法調用的結果返回的類型保存在esi寄存器中


__ 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
// 當執行到這里時,處理的就是T_INT類型,將rax中的值寫入c_rarg0保存的地址指向的內存中
__ movl(Address(c_rarg0, 0), rax); // 調用函數后返回值根據調用約定會存儲在eax中

__ BIND(exit);

// pop parameters
// 將rsp_after_call中保存的有效地址拷貝到rsp中,即將rsp往高地址方向移動了,
// 原來的方法調用參數argument1、...、argumentn相當於從棧中彈出
__ lea(rsp, rsp_after_call);  // lea指令將地址加載到寄存器中

生成的匯編代碼如下:

0x00007fdf450007a7: mov    -0x28(%rbp),%rdi  //  棧中的-0x28位置保存result
0x00007fdf450007ab: mov    -0x20(%rbp),%esi  //  棧中的-0x20位置保存result type
0x00007fdf450007ae: cmp    $0xc,%esi         // 是否為T_OBJECT類型
0x00007fdf450007b1: je     0x00007fdf450007f6
0x00007fdf450007b7: cmp    $0xb,%esi         // 是否為T_LONG類型
0x00007fdf450007ba: je     0x00007fdf450007f6
0x00007fdf450007c0: cmp    $0x6,%esi         // 是否為T_FLOAT類型
0x00007fdf450007c3: je     0x00007fdf450007fb
0x00007fdf450007c9: cmp    $0x7,%esi         // 是否為T_DOUBLE類型
0x00007fdf450007cc: je     0x00007fdf45000801

0x00007fdf450007d2: mov    %eax,(%rdi)       // 如果是T_INT類型,直接將返回結果%eax寫到棧中-0x28的位置

// -- exit --
0x00007fdf450007d4: lea    -0x60(%rbp),%rsp  // 將rsp_after_call的有效地址拷到rsp中

恢復之前保存的caller-save寄存器:

// restore regs belonging to calling function
__ movptr(r15, r15_save);
__ movptr(r14, r14_save);
__ movptr(r13, r13_save);
__ movptr(r12, r12_save);
__ movptr(rbx, rbx_save);

__ ldmxcsr(mxcsr_save); 

生成的匯編代碼如下:

0x00007fdf450007d8: mov      -0x58(%rbp),%r15
0x00007fdf450007dc: mov      -0x50(%rbp),%r14
0x00007fdf450007e0: mov      -0x48(%rbp),%r13
0x00007fdf450007e4: mov      -0x40(%rbp),%r12
0x00007fdf450007e8: mov      -0x38(%rbp),%rbx
0x00007fdf450007ec: ldmxcsr  -0x60(%rbp)

在彈出了為調用Java方法保存的調用參數及恢復caller-save寄存器后,繼續執行退棧操作,實現如下:

// restore rsp
__ addptr(rsp, -rsp_after_call_off * wordSize);

// return
__ pop(rbp);
__ ret(0);

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

生成的匯編代碼如下:

// %rsp加上0x60,也就是執行退棧操作,也就相當於彈出了callee_save寄存器和壓棧的那6個參數
0x00007fdf450007f0: add    $0x60,%rsp 
0x00007fdf450007f4: pop    %rbp
0x00007fdf450007f5: retq  // 方法返回,指令中的q表示64位操作數,就是指的棧中存儲的return address是64位的

// -- is_long --
0x00007fdf450007f6: mov    %rax,(%rdi)
0x00007fdf450007f9: jmp    0x00007fdf450007d4

// -- is_float --
0x00007fdf450007fb: vmovss %xmm0,(%rdi)
0x00007fdf450007ff: jmp    0x00007fdf450007d4

// -- is_double --
0x00007fdf45000801: vmovsd %xmm0,(%rdi)
0x00007fdf45000805: jmp    0x00007fdf450007d4

在執行完add指令后的棧狀態如下圖所示。  

然后恢復%rsp的值后,調用retq使用return address返回調用call_helper()函數的那個調用函數,至於paramter size與thread則由調用函數負責釋放。 

相關文章的鏈接如下:

1、在Ubuntu 16.04上編譯OpenJDK8的源代碼 

2、調試HotSpot源代碼

3、HotSpot項目結構 

4、HotSpot的啟動過程 

5、HotSpot二分模型(1)

6、HotSpot的類模型(2)  

7、HotSpot的類模型(3) 

8、HotSpot的類模型(4)

9、HotSpot的對象模型(5)  

10、HotSpot的對象模型(6) 

11、操作句柄Handle(7)

12、句柄Handle的釋放(8)

13、類加載器 

14、類的雙親委派機制 

15、核心類的預裝載

16、Java主類的裝載  

17、觸發類的裝載  

18、類文件介紹 

19、文件流 

20、解析Class文件 

21、常量池解析(1) 

22、常量池解析(2)

23、字段解析(1)

24、字段解析之偽共享(2) 

25、字段解析(3)  

26、字段解析之OopMapBlock(4)

27、方法解析之Method與ConstMethod介紹  

28、方法解析

29、klassVtable與klassItable類的介紹  

30、計算vtable的大小 

31、計算itable的大小 

32、解析Class文件之創建InstanceKlass對象 

33、字段解析之字段注入 

34、類的連接  

35、類的連接之驗證 

36、類的連接之重寫(1) 

37、類的連接之重寫(2)

38、方法的連接  

39、初始化vtable 

40、初始化itable  

41、類的初始化 

42、對象的創建  

43、Java引用類型 

44、Java引用類型之軟引用(1)

45、Java引用類型之軟引用(2)

46、Java引用類型之弱引用與幻像引用  

47、Java引用類型之最終引用

48、HotSpot的垃圾回收算法  

49、HotSpot的垃圾回收器   

作者持續維護的個人博客  classloading.com

關注公眾號,有HotSpot源碼剖析系列文章!

   

參考文章:

(1)JVM系列之 _call_stub_entry初始化

(2)[Inside HotSpot] Java的方法調用

(3)Hotspot JVM の Frame について

 

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM