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都會為其建一個模型,儲存該方法所有的原始描述資訊,主要包括:
- 方法的名稱,所屬的類;
- 方法的入參資訊,包括入參型別,入參引數名,入引數量,順序等;
- 方法編譯後的位元組碼資訊,包括對應的位元組碼指令等;
- 方法的註釋資訊;
- 方法的繼承資訊;
- 方法的返回資訊。
(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
參考文章: