[inside hotspot] java方法呼叫的StubCode
[inside hotspot] java方法呼叫的StubCode
眾所周知jvm有invokestatic
,invokedynamic
,invokestatic
,invokespecial
,invokevirtual
幾條方法呼叫指令,每個負責呼叫不同的方法,
而這些方法呼叫落實到hotspot上都位於hotspot\src\share\vm\runtime\javaCalls.hpp
的JavaCalls
:
1. JavaCalls
class JavaCalls: AllStatic { static void call_helper(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS); public: // call_special // ------------ // The receiver must be first oop in argument list static void call_special(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, JavaCallArguments* args, TRAPS); static void call_special(JavaValue* result, Handle receiver, KlassHandle klass, Symbol* name, Symbol* signature, TRAPS); // No args static void call_special(JavaValue* result, Handle receiver, KlassHandle klass, Symbol* name, Symbol* signature, Handle arg1, TRAPS); static void call_special(JavaValue* result, Handle receiver, KlassHandle klass, Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS); // virtual call // ------------ // The receiver must be first oop in argument list static void call_virtual(JavaValue* result, KlassHandle spec_klass, Symbol* name, Symbol* signature, JavaCallArguments* args, TRAPS); static void call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass, Symbol* name, Symbol* signature, TRAPS); // No args static void call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass, Symbol* name, Symbol* signature, Handle arg1, TRAPS); static void call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass, Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS); // Static call // ----------- static void call_static(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, JavaCallArguments* args, TRAPS); static void call_static(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, TRAPS); static void call_static(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, Handle arg1, TRAPS); static void call_static(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS); static void call_static(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, Handle arg1, Handle arg2, Handle arg3, TRAPS); // Low-level interface static void call(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS); };
上面的方法是自解釋的,對應各自的invoke*
指令,這些call_static
,call_virtual
內部呼叫了call()
函式:
void JavaCalls::call(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS) { assert(THREAD->is_Java_thread(), "only JavaThreads can make JavaCalls"); os::os_exception_wrapper(call_helper, result, method, args, THREAD); }
call()
只是簡單檢查了一下執行緒資訊,以及根據平臺比如windows會使用結構化異常(SEH)包裹call_helper,最終執行方法呼叫的還是call_helper
。
void JavaCalls::call_helper(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS) { ... // 如果當前方法為空,則直接返回 if (method->is_empty_method()) { assert(result->get_type() == T_VOID, "an empty method must return a void value"); return; } ... //根據情況決定是否編譯該方法,JIT和-Xcomp都有可能觸發它 CompilationPolicy::compile_if_required(method, CHECK); // 直譯器入口點 address entry_point = method->from_interpreted_entry(); if (JvmtiExport::can_post_interpreter_events() && thread->is_interp_only_mode()) { entry_point = method->interpreter_entry(); } // 確定返回值型別 BasicType result_type = runtime_type_from(result); bool oop_result_flag = (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY); // 返回值地址 intptr_t* result_val_address = (intptr_t*)(result->get_value_addr()); // 確定receiver,如果是static函式就沒有receiver Handle receiver = (!method->is_static()) ? args->receiver() : Handle(); if (!thread->stack_guards_enabled()) { thread->reguard_stack(); } // 確認當前sp是否到達ShadowPages,即是否會觸發棧溢位錯誤 address sp = os::current_stack_pointer(); if (!os::stack_shadow_pages_available(THREAD, method, sp)) { // Throw stack overflow exception with preinitialized exception. Exceptions::throw_stack_overflow_exception(THREAD, __FILE__, __LINE__, method); return; } else { // Touch pages checked if the OS needs them to be touched to be mapped. os::map_stack_shadow_pages(sp); } // 執行呼叫 { JavaCallWrapper link(method, receiver, result, CHECK); { HandleMark hm(thread);// HandleMark used by HandleMarkCleaner StubRoutines::call_stub()( (address)&link, // (intptr_t*)&(result->_value), // see NOTE above (compiler problem) result_val_address,// see NOTE above (compiler problem) result_type, method(), entry_point, args->parameters(), args->size_of_parameters(), CHECK ); result = link.result();// circumvent MS C++ 5.0 compiler bug (result is clobbered across call) // Preserve oop return value across possible gc points if (oop_result_flag) { thread->set_vm_result((oop) result->get_jobject()); } } } // 設定返回值 if (oop_result_flag) { result->set_jobject((jobject)thread->vm_result()); thread->set_vm_result(NULL); } }
call_helper
又可以分為兩步,第一步判斷一下方法是否為空,是否可以JIT編譯,是否還有棧空間可以等,第二步StubRoutines::call_stub()
實際呼叫os+cpu限定的方法。
這個StubRoutines::call_stub()
返回的是一個函式指標,指向的是平臺特定的方法,所以這段程式碼:
StubRoutines::call_stub()( (address)&link, // (intptr_t*)&(result->_value), // see NOTE above (compiler problem) result_val_address,// see NOTE above (compiler problem) result_type, method(), entry_point, args->parameters(), args->size_of_parameters(), CHECK );
call_stub()
返回一個函式指標,指向依賴於作業系統和cpu架構
的特定的方法,原因很簡單,要執行native程式碼,得看看是什麼cpu架構以便確定暫存器,看看什麼os以便確定ABI。
然後傳遞8個引數到這個方法裡面並執行這個方法。那麼這個方法是什麼呢?進入stubRoutines.cpp
便知是StubRoutines::_call_stub_entry
。
2. windows+x86_64的stubGenerator
以x64為例,hotspot\src\cpu\x86\vm\stubGenerator_x86_64.cpp
的generate_call_stub()
會負責初始化StubRoutines::_call_stub_entry
函式,使用引數命令
-XX:+UnlockDiagnosticVMOptions -XX:+PrintStubCode
可以輸出generate_call_stub方法生成的彙編,對照著看非常舒服:
address generate_call_stub(address& return_address) { assert((int)frame::entry_frame_after_call_words == -(int)rsp_after_call_off + 1 && (int)frame::entry_frame_call_wrapper_offset == (int)call_wrapper_off, "adjust this code"); StubCodeMark mark(this, "StubRoutines", "call_stub"); address start = __ pc(); // same as in generate_catch_exception()! const Address rsp_after_call(rbp, rsp_after_call_off * wordSize); const Address call_wrapper(rbp, call_wrapper_off* wordSize); const Address result(rbp, result_off* wordSize); const Address result_type(rbp, result_type_off* wordSize); const Address method(rbp, method_off* wordSize); const Address entry_point(rbp, entry_point_off* wordSize); const Address parameters(rbp, parameters_off* wordSize); const Address parameter_size(rbp, parameter_size_off * wordSize); // same as in generate_catch_exception()! const Address thread(rbp, thread_off* wordSize); const Address r15_save(rbp, r15_off * wordSize); const Address r14_save(rbp, r14_off * wordSize); const Address r13_save(rbp, r13_off * wordSize); const Address r12_save(rbp, r12_off * wordSize); const Address rbx_save(rbp, rbx_off * wordSize); // stub code __ enter(); __ subptr(rsp, -rsp_after_call_off * wordSize);
StubRoutines::call_stub [0x0000026b0a5d09d7, 0x0000026b0a5d0b44[ (365 bytes) 0x0000026b0a5d09d7: push%rbp 0x0000026b0a5d09d8: mov%rsp,%rbp 0x0000026b0a5d09db: sub$0x1d8,%rsp
// save register parameters #ifndef _WIN64 __ movptr(parameters,c_rarg5); // parameters __ movptr(entry_point,c_rarg4); // entry_point #endif __ movptr(method,c_rarg3); // method __ movl(result_type,c_rarg2);// result type __ movptr(result,c_rarg1); // result __ movptr(call_wrapper, c_rarg0); // call wrapper
// r9方法,r8d返回值型別,rdx,返回值,rcx即JavaCallsWrapper 0x0000026b0a5d09e2: mov%r9,0x28(%rbp) 0x0000026b0a5d09e6: mov%r8d,0x20(%rbp) 0x0000026b0a5d09ea: mov%rdx,0x18(%rbp) 0x0000026b0a5d09ee: mov%rcx,0x10(%rbp)
// save regs belonging to calling function __ movptr(rbx_save, rbx); __ movptr(r12_save, r12); __ movptr(r13_save, r13); __ movptr(r14_save, r14); __ movptr(r15_save, r15); if (UseAVX > 2) { __ movl(rbx, 0xffff); __ kmovwl(k1, rbx); } #ifdef _WIN64 int last_reg = 15; if (UseAVX > 2) { last_reg = 31; } if (VM_Version::supports_evex()) { for (int i = xmm_save_first; i <= last_reg; i++) { __ vextractf32x4(xmm_save(i), as_XMMRegister(i), 0); } } else { for (int i = xmm_save_first; i <= last_reg; i++) { __ movdqu(xmm_save(i), as_XMMRegister(i)); } }
// caller-save 暫存器 0x0000026b0a5d09f2: mov%rbx,-0x8(%rbp) 0x0000026b0a5d09f6: mov%r12,-0x20(%rbp) 0x0000026b0a5d09fa: mov%r13,-0x28(%rbp) 0x0000026b0a5d09fe: mov%r14,-0x30(%rbp) 0x0000026b0a5d0a02: mov%r15,-0x38(%rbp) 0x0000026b0a5d0a06: vmovdqu %xmm6,-0x48(%rbp) 0x0000026b0a5d0a0b: vmovdqu %xmm7,-0x58(%rbp) 0x0000026b0a5d0a10: vmovdqu %xmm8,-0x68(%rbp) 0x0000026b0a5d0a15: vmovdqu %xmm9,-0x78(%rbp) 0x0000026b0a5d0a1a: vmovdqu %xmm10,-0x88(%rbp) 0x0000026b0a5d0a22: vmovdqu %xmm11,-0x98(%rbp) 0x0000026b0a5d0a2a: vmovdqu %xmm12,-0xa8(%rbp) 0x0000026b0a5d0a32: vmovdqu %xmm13,-0xb8(%rbp) 0x0000026b0a5d0a3a: vmovdqu %xmm14,-0xc8(%rbp) 0x0000026b0a5d0a42: vmovdqu %xmm15,-0xd8(%rbp)
const Address rdi_save(rbp, rdi_off * wordSize); const Address rsi_save(rbp, rsi_off * wordSize); __ movptr(rsi_save, rsi); __ movptr(rdi_save, rdi);
// rsi rdi 0x0000026b0a5d0a4a: mov%rsi,-0x10(%rbp) 0x0000026b0a5d0a4e: mov%rdi,-0x18(%rbp)
// Load up thread register __ movptr(r15_thread, thread); __ reinit_heapbase();
// 執行緒暫存器 0x0000026b0a5d0a52: mov0x48(%rbp),%r15 0x0000026b0a5d0a56: movabs $0x7ffe4c5b2be8,%r10 0x0000026b0a5d0a60: mov(%r10),%r12
// pass parameters if any BLOCK_COMMENT("pass parameters if any"); Label parameters_done; __ movl(c_rarg3, parameter_size); __ testl(c_rarg3, c_rarg3); __ jcc(Assembler::zero, parameters_done); Label loop; __ movptr(c_rarg2, parameters);// parameter pointer __ movl(c_rarg1, c_rarg3);// parameter counter is in c_rarg1 __ BIND(loop); __ movptr(rax, Address(c_rarg2, 0));// get parameter __ addptr(c_rarg2, wordSize);// advance to next parameter __ decrementl(c_rarg1);// decrement counter __ push(rax);// pass parameter __ jcc(Assembler::notZero, loop);
// 這裡是個迴圈,用於傳遞引數,相當於 // while(r9d){ //rax = *arg //push_arg(rax) //arg++;// ptr++ //r9d--;// counter-- // } 0x0000026b0a5d0a63: mov0x40(%rbp),%r9d 0x0000026b0a5d0a67: test%r9d,%r9d 0x0000026b0a5d0a6a: je0x0000026b0a5d0a83 0x0000026b0a5d0a70: mov0x38(%rbp),%r8 0x0000026b0a5d0a74: mov%r9d,%edx 0x0000026b0a5d0a77: mov(%r8),%rax 0x0000026b0a5d0a7a: add$0x8,%r8 0x0000026b0a5d0a7e: dec%edx 0x0000026b0a5d0a80: push%rax 0x0000026b0a5d0a81: jne0x0000026b0a5d0a77
// call Java function __ BIND(parameters_done); __ movptr(rbx, method);// get Method* __ movptr(c_rarg1, entry_point);// get entry_point __ mov(r13, rsp);// set sender sp BLOCK_COMMENT("call Java function"); __ call(c_rarg1);
// [!!]呼叫java方法 0x0000026b0a5d0a83: mov0x28(%rbp),%rbx 0x0000026b0a5d0a87: mov0x30(%rbp),%rdx 0x0000026b0a5d0a8b: mov%rsp,%r13 0x0000026b0a5d0a8e: callq*%rdx
BLOCK_COMMENT("call_stub_return_address:"); return_address = __ pc(); // store result depending on type (everything that is not // T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT) __ movptr(c_rarg0, result); Label is_long, is_float, is_double, exit; __ movl(c_rarg1, result_type); __ cmpl(c_rarg1, T_OBJECT); __ jcc(Assembler::equal, is_long); __ cmpl(c_rarg1, T_LONG); __ jcc(Assembler::equal, is_long); __ cmpl(c_rarg1, T_FLOAT); __ jcc(Assembler::equal, is_float); __ cmpl(c_rarg1, T_DOUBLE); __ jcc(Assembler::equal, is_double); // handle T_INT case __ movl(Address(c_rarg0, 0), rax); __ BIND(exit); // pop parameters __ lea(rsp, rsp_after_call);
// 儲存java方法返回值並彈出引數,這裡彈出操作即移動一下rsp指標 0x0000026b0a5d0a90: mov0x18(%rbp),%rcx 0x0000026b0a5d0a94: mov0x20(%rbp),%edx 0x0000026b0a5d0a97: cmp$0xc,%edx 0x0000026b0a5d0a9a: je0x0000026b0a5d0b30 0x0000026b0a5d0aa0: cmp$0xb,%edx 0x0000026b0a5d0aa3: je0x0000026b0a5d0b30 0x0000026b0a5d0aa9: cmp$0x6,%edx 0x0000026b0a5d0aac: je0x0000026b0a5d0b35 0x0000026b0a5d0ab2: cmp$0x7,%edx 0x0000026b0a5d0ab5: je0x0000026b0a5d0b3b 0x0000026b0a5d0abb: mov%eax,(%rcx) 0x0000026b0a5d0abd: lea-0x1d8(%rbp),%rsp
// restore regs belonging to calling function #ifdef _WIN64 // emit the restores for xmm regs if (VM_Version::supports_evex()) { for (int i = xmm_save_first; i <= last_reg; i++) { __ vinsertf32x4(as_XMMRegister(i), as_XMMRegister(i), xmm_save(i), 0); } } else { for (int i = xmm_save_first; i <= last_reg; i++) { __ movdqu(as_XMMRegister(i), xmm_save(i)); } } #endif __ movptr(r15, r15_save); __ movptr(r14, r14_save); __ movptr(r13, r13_save); __ movptr(r12, r12_save); __ movptr(rbx, rbx_save); __ movptr(rdi, rdi_save); __ movptr(rsi, rsi_save);
// 恢復之前儲存的caller-save暫存器 0x0000026b0a5d0ac4: vmovdqu -0x48(%rbp),%xmm6 0x0000026b0a5d0ac9: vmovdqu -0x58(%rbp),%xmm7 0x0000026b0a5d0ace: vmovdqu -0x68(%rbp),%xmm8 0x0000026b0a5d0ad3: vmovdqu -0x78(%rbp),%xmm9 0x0000026b0a5d0ad8: vmovdqu -0x88(%rbp),%xmm10 0x0000026b0a5d0ae0: vmovdqu -0x98(%rbp),%xmm11 0x0000026b0a5d0ae8: vmovdqu -0xa8(%rbp),%xmm12 0x0000026b0a5d0af0: vmovdqu -0xb8(%rbp),%xmm13 0x0000026b0a5d0af8: vmovdqu -0xc8(%rbp),%xmm14 0x0000026b0a5d0b00: vmovdqu -0xd8(%rbp),%xmm15 0x0000026b0a5d0b08: mov-0x38(%rbp),%r15 0x0000026b0a5d0b0c: mov-0x30(%rbp),%r14 0x0000026b0a5d0b10: mov-0x28(%rbp),%r13 0x0000026b0a5d0b14: mov-0x20(%rbp),%r12 0x0000026b0a5d0b18: mov-0x8(%rbp),%rbx 0x0000026b0a5d0b1c: mov-0x18(%rbp),%rdi 0x0000026b0a5d0b20: mov-0x10(%rbp),%rsi
// restore rsp __ addptr(rsp, -rsp_after_call_off * wordSize); // return __ pop(rbp); __ ret(0);
// 結束__call_stub_entry這個函式 0x0000026b0a5d0b24: add$0x1d8,%rsp 0x0000026b0a5d0b2b: vzeroupper 0x0000026b0a5d0b2e: pop%rbp 0x0000026b0a5d0b2f: retq
下面這段程式碼邏輯上屬於之前的儲存java方法的返回值,隨便舉個例子0x0000026b0a5d0b30
這個地址正是之前存放java方法的程式碼段je0x0000026b0a5d0b30
所跳之處,只是放到了最後而已:(不過我也不知道為什麼要放到這後面)
// handle return types different from T_INT __ BIND(is_long); __ movq(Address(c_rarg0, 0), rax); __ jmp(exit); __ BIND(is_float); __ movflt(Address(c_rarg0, 0), xmm0); __ jmp(exit); __ BIND(is_double); __ movdbl(Address(c_rarg0, 0), xmm0); __ jmp(exit); return start; }
0x0000026b0a5d0b30: mov%rax,(%rcx) 0x0000026b0a5d0b33: jmp0x0000026b0a5d0abd 0x0000026b0a5d0b35: vmovss %xmm0,(%rcx) 0x0000026b0a5d0b39: jmp0x0000026b0a5d0abd 0x0000026b0a5d0b3b: vmovsd %xmm0,(%rcx) 0x0000026b0a5d0b3f: jmpq0x0000026b0a5d0abd
對照彙編看非常清晰,不過也可以看到它建立了棧幀結構,但它還是沒有執行java程式碼,而是使用callq *rdx
進行的,
這也是為什麼它叫做 stub 的原因。
另外上面的棧幀裡面內容比較多,[rsp+xx]存放什麼內容啊這些比較難記,已經歸納好的結構可以參見程式碼註釋:
// Windowsx86_64平臺 // //注意c_rarg\d 表示暫存器,method/result表示記憶體地址[rbp+\d] // //c_rarg0:call wrapper addressaddress //c_rarg1:resultaddress //c_rarg2:result typeBasicType //c_rarg3:methodMethod* //48(rbp): (interpreter) entry pointaddress //56(rbp): parametersintptr_t* //64(rbp): parameter size (in words)int //72(rbp): threadThread* // //[ return_from_Java] <--- 這裡執行callq呼叫java方法。壓入返回地址,跳轉到java方法,也就是說↑上面的部分就是java方法使用的棧幀了 //[ argument word n] <--- 迴圈傳遞的java方法實參 //... // -60 [ argument word 1] // -59 [ saved xmm31] <--- rsp after_call //[ saved xmm16-xmm30] // -27 [ saved xmm15] //[ saved xmm7-xmm14] //-9 [ saved xmm6] //-7 [ saved r15] //-6 [ saved r14] //-5 [ saved r13] //-4 [ saved r12] //-3 [ saved rdi] //-2 [ saved rsi] //-1 [ saved rbx] //0 [ saved rbp] <--- rbp //1 [ return address] <--- last rbp //2 [ call wrapper] <--- arg0 //3 [ result] <--- arg1 //4 [ result type] <--- arg2 //5 [ method] <--- arg3 //6 [ entry point] <--- arg4 //7 [ parameters] <--- arg5 //8 [ parameter size] <--- arg6 //9 [ thread] <--- arg7
這8個arg正是之前傳遞給函式指標指向的函式的實參:
StubRoutines::call_stub()( (address)&link, // (intptr_t*)&(result->_value), // see NOTE above (compiler problem) result_val_address,// see NOTE above (compiler problem) result_type, method(), entry_point, args->parameters(), args->size_of_parameters(), CHECK );