之前多次提到接觸到調用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的源代碼
13、類加載器
14、類的雙親委派機制
15、核心類的預裝載
16、Java主類的裝載
17、觸發類的裝載
18、類文件介紹
19、文件流
20、解析Class文件
21、常量池解析(1)
22、常量池解析(2)
23、字段解析(1)
24、字段解析之偽共享(2)
25、字段解析(3)
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引用類型
作者持續維護的個人博客 classloading.com。
關注公眾號,有HotSpot源碼剖析系列文章!
參考文章: