1. 程式人生 > 實用技巧 >CallStub棧幀

CallStub棧幀

receiver表示方法的接收者,如A.main()呼叫中,A就是方法的接收者。

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

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

上面的方法是自解釋的,對應各自的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)linklink變數的型別為JavaCallWrapper,定義在/src/share/vm/runtime/javaCalls.cpp檔案中,如下:

// A JavaCallWrapper is constructed before each JavaCall and destructed after the call.
// Its purpose is to allocate/deallocate a new handle block and to save/restore the last
// Java fp/sp. A pointer to the JavaCallWrapper is stored on the stack.

class JavaCallWrapper: StackObj {
  friend class VMStructs;
private:
  JavaThread*      _thread;                 // the thread to which this call belongs
  JNIHandleBlock*  _handles;                // the saved handle block
  Method*          _callee_method;          // to be able to collect arguments if entry frame is top frame
  oop              _receiver;               // the receiver of the call (if a non-static call)

  JavaFrameAnchor  _anchor;                 // last thread anchor state that we must restore

  JavaValue*       _result;                 // result value

public:
  // Construction/destruction
   JavaCallWrapper(methodHandle callee_method, Handle receiver, JavaValue* result, TRAPS);
  ~JavaCallWrapper();

  // Accessors
  JavaThread*      thread() const           { return _thread; }
  JNIHandleBlock*  handles() const          { return _handles; }

  JavaFrameAnchor* anchor(void)             { return &_anchor; }

  JavaValue*       result() const           { return _result; }
  // GC support
  Method*          callee_method()          { return _callee_method; }
  oop              receiver()               { return _receiver; }
  void             oops_do(OopClosure* f);

  bool             is_first_frame() const   { return _anchor.last_Java_sp() == NULL; }

};

通過link可以實現堆疊追蹤,可以得到整個方法的呼叫鏈路。

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

(3)result_type函式返回型別。

(4)method()當前方法在JVM中的表示物件。每一個方法在被載入的時候,JVM都會為其建一個模型,儲存該方法所有的原始描述資訊,主要包括:

  • 方法的名稱,所屬的類;
  • 方法的入參資訊,包括入參型別,入參引數名,入引數量,順序等;
  • 方法編譯後的位元組碼資訊,包括對應的位元組碼指令等;
  • 方法的註釋資訊;
  • 方法的繼承資訊;
  • 方法的返回資訊。
method()引數的意義就是為了讓JVM可以通過method()物件獲取到Java方法編譯後的位元組碼資訊,JVM在拿到位元組碼後就可以解釋執行這個方法。

(5)entry_pointJVM每次在呼叫Java函式時,必然會呼叫CallStub函式指標,這個函式指標的值為_call_stub_entry,JVM通過_call_stub_entry指向被呼叫函式地址,最終呼叫函式。在呼叫函式之前,必須要先經過entry_point,JVM實際是通過entry_point從method()物件上拿到Java方法對應的第一個位元組碼命令,這也是整個函式的呼叫入口。

(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()函式中,_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()方法,方法的實現如下:(針對linux的實現邏輯)

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的定義如下:

原始碼位置:globalDefinitions.hpp

typedef   u_char*       address;

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 {
   ...
}

// Call stubs are used to call Java from C
//
// Linux Arguments:
//    c_rarg0:   call wrapper address                   address
//    c_rarg1:   result                                 address
//    c_rarg2:   result type                            BasicType
//    c_rarg3:   method                                 Method*
//    c_rarg4:   (interpreter) entry point              address
//    c_rarg5:   parameters                             intptr_t*
//    16(rbp): parameter size (in words)              int
//    24(rbp): thread                                 Thread*
//
//     [ return_from_Java     ] <--- rsp
//     [ argument word n      ]
//      ...
// -12 [ argument word 1      ]
// -11 [ saved r15            ] <--- rsp_after_call
// -10 [ saved r14            ]
//  -9 [ saved r13            ]
//  -8 [ saved r12            ]
//  -7 [ saved rbx            ]
//  -6 [ call wrapper         ]
//  -5 [ result               ]
//  -4 [ result type          ]
//  -3 [ method               ]
//  -2 [ entry point          ]
//  -1 [ parameters           ]
//   0 [ saved rbp            ] <--- rbp
//   1 [ return address       ]
//   2 [ parameter size       ]
//   3 [ thread               ]

// Call stub stack layout word offsets from rbp
enum call_stub_layout {
    rsp_after_call_off = -12,
    mxcsr_off          = rsp_after_call_off,
    r15_off            = -11,
    r14_off            = -10,
    r13_off            = -9,
    r12_off            = -8,
    rbx_off            = -7,
    call_wrapper_off   = -6,
    result_off         = -5,
    result_type_off    = -4,
    method_off         = -3,
    entry_point_off    = -2,
    parameters_off     = -1,
    rbp_off            =  0,
    retaddr_off        =  1,
    parameter_size_off =  2,
    thread_off         =  3
};

如果要看generate_call_stub()方法生成的彙編,可以在匯入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);
} 

呼叫的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 // 第1個為源運算元,第2個為目地運算元
  0x00007fdf45000723: sub    $0x60,%rsp  // 0x60 = -rsp_after_call_of * wordSize

繼續看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)
0x00007fdf45000752: stmxcsr -0x60(%rbp)  // stmxcsr是將MXCSR暫存器中的值儲存到-0x60(%rbp)中
0x00007fdf45000756: mov    -0x60(%rbp),%eax
0x00007fdf45000759: and    $0xffc0,%eax
// cmp通過第2個運算元減去第1個運算元的差,根據結果來設定eflags中的標誌位。本質上和sub指令相同,但是不會改變運算元的值
0x00007fdf4500075f: cmp    0x1762cb5f(%rip),%eax  # 0x00007fdf5c62d2c4 
0x00007fdf45000765: je     0x00007fdf45000772  // 當ZF=1時跳轉到dest
0x00007fdf4500076b: ldmxcsr 0x1762cb52(%rip)      # 0x00007fdf5c62d2c4   // 將m32載入到MXCSR暫存器中

MXCSR狀態管理指令,LDMXCSR與STMXCSR,用於控制MXCSR暫存器(表示SSE指令的運算狀態的暫存器)狀態。LDMXCSR指令從儲存器中載入MXCSR暫存器狀態;STMXCSR指令將MXCSR暫存器狀態儲存到儲存器中。

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

由於call_helper()函式在呼叫CallStub()函式時,傳遞的引數多於6個,所以最後2個引數size_of_parameters與TRAPS(當前執行緒)要通過call_helper()的棧幀來傳遞,剩下的可以根據呼叫約定通過暫存器來傳遞。

可以看到在傳遞引數時會遵守呼叫約定,當x64中函式呼叫時,以下暫存器用於引數:

  • 第一個引數:rdi c_rarg0
  • 第二個引數:rsi c_rarg1
  • 第三個引數:rdx c_rarg2
  • 第四個引數:rcx c_rarg3
  • 第五個引數:r8 c_rarg4
  • 第六個引數:r9 c_rarg5

如果引數超過六個,那麼程式呼叫棧就會被用來傳遞那些額外的引數。

載入執行緒暫存器,程式碼如下:

// 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
BLOCK_COMMENT("pass parameters if any"); // _masm-> 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(%esi){
   rax = *arg
   push_arg(rax)
   arg++;   // ptr++
   %esi--;  // counter--
}

生成的彙編程式碼如下:

0x00007fdf4500077d: mov    0x10(%rbp),%ecx
0x00007fdf45000780: test   %ecx,%ecx
0x00007fdf45000782: je     0x00007fdf4500079a

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

因為要呼叫Java方法,所以 會為Java方法壓入實際的引數,也就是壓入parameter size個從parameters開始取的引數。棧的圖如下:

呼叫Java函式,如下:

// call Java function
__ BIND(parameters_done); // bind(parameters_done); _masm-> block_comment("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);

生成的彙編程式碼如下:

0x00007fdf4500079a: mov    -0x18(%rbp),%rbx
0x00007fdf4500079e: mov    -0x10(%rbp),%rsi
0x00007fdf450007a2: mov    %rsp,%r13
0x00007fdf450007a5: callq  *%rsi

注意呼叫callq指令後,會將callq指令的下一條指令的地址壓棧,再跳轉到第1運算元指定的地址。這樣函式就能通過跳轉到棧上的地址從子函式返回。 

callq指令呼叫的是entry point。在呼叫entry point時的棧佈局如下圖所示。

entry point的詳細資訊參考文章:https://www.cnblogs.com/mazhimazhi/p/11428094.html

下面開始處理呼叫方法後的返回值與返回型別。

// 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,都當做INT處理
// 將result地址的值拷貝到c_rarg0中
__ movptr(c_rarg0, result);   // 方法呼叫的結果儲存在rdi暫存器中,注意result為函式返回值的地址
Label is_long, is_float, is_double, exit;
// 將result_type地址的值拷貝到c_rarg1
__ movl(c_rarg1, result_type);  // 方法呼叫的結果返回的型別儲存在esi暫存器中
// 根據結果型別的不同跳轉到不同的處理分支
__ 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
// 處理結果型別是int的情形,將rax中的值寫入c_rarg0對應地址的記憶體中
__ movl(Address(c_rarg0, 0), rax); // 呼叫函式後返回值都是儲存在EAX中,c_rarg0中儲存的是一個指標,指示的地方應該儲存方法的返回值

__ BIND(exit);

// pop parameters
// rsp_after_call的有效地址拷貝到rsp中,即將rsp往高地址方向移動了,原來的方法呼叫引數相當於pop掉了
__ lea(rsp, rsp_after_call); // lea指令將地址載入到暫存器中

生成的彙編程式碼如下:

0x00007fdf450007a7: mov    -0x28(%rbp),%rdi //  棧中的-0x20位置儲存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寫到棧中-0x20的位置

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)

 

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

生成的彙編程式碼如下:

0x00007fdf450007f0: add    $0x60,%rsp

0x00007fdf450007f4: pop    %rbp
0x00007fdf450007f5: retq

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

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

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

  

參考文章:

(1)JVM系列之 _call_stub_entry初始化

(2)[Inside HotSpot] Java的方法呼叫