HotSpot的執行引擎-CallStub棧幀
之前多次提到接觸到呼叫JavaCalls::call()方法來執行Java方法,如:
(1)Java主類裝載時,呼叫JavaCalls::call()方法執行的Java方法checkAndLoadMain()方法
(2)類的初始化過程中,呼叫JavaCalls::call()方法執行的Java方法<clinit>方法
可以看出,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); }; |
上面的方法是自解釋的,對應各自的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類的定義如下:
原始碼位置:/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 {
... } |
如果要看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); } |
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 // 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()的棧幀來傳遞,剩下的可以根據呼叫約定透過暫存器來傳遞。
可以看到在傳遞引數時會遵守呼叫約定,當x64中函式呼叫時,以下暫存器用於引數:
- 第1個引數:rdi c_rarg0
- 第2個引數:rsi c_rarg1
- 第3個引數:rdx c_rarg2
- 第4個引數:rcx c_rarg3
- 第5個引數:r8 c_rarg4
- 第6個引數:r9 c_rarg5
在函式呼叫時,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<br>// -- 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則由呼叫函式負責釋放。
相關文章的連結如下:
來自 “ https://www.cnblogs.com/mazhimazhi/p/13516197.html ”, 原文作者:歸去來兮;原文連結:https://www.cnblogs.com/mazhimazhi/p/13516197.html,如有侵權,請聯絡管理員刪除。
相關文章
- JVM — 執行時棧幀結構簡介JVM
- JVM -- 執行時棧幀結構簡介JVM
- [深入理解Java虛擬機器]第八章 位元組碼執行引擎-執行時棧幀結構Java虛擬機
- JS引擎、執行時與呼叫棧概述JS
- JavaScript 如何工作系列: 引擎、執行時、呼叫棧概述JavaScript
- 深入理解python虛擬機器:程式執行的載體——棧幀Python虛擬機
- 【譯】JavaScript如何工作的:一覽引擎、執行時和呼叫棧JavaScript
- 譯—JavaScript是如何工作的(1):js引擎、執行時和呼叫棧的概述JavaScriptJS
- Java HotSpot效能引擎的體系結構 (轉)JavaHotSpot
- python棧幀沙箱逃逸Python
- python 棧幀沙箱逃逸Python
- 執行上下文和執行棧
- JavaScript執行環境與執行棧JavaScript
- [深入理解Java虛擬機器]第八章 位元組碼執行引擎-基於棧的位元組碼解釋執行引擎Java虛擬機
- JavaScript 中的執行上下文和執行棧JavaScript
- JS引擎的執行機制JS
- javascript引擎執行的過程的理解--執行階段JavaScript
- 函式棧幀(呼叫過程)函式
- 併發程式設計 棧幀程式設計
- 詳解GaussDB(DWS)中的行執行引擎
- 1-1 JavaScript執行環境 執行棧JavaScript
- (譯文)JavaScript中的執行上下文和執行棧JavaScript
- [譯] 理解 JavaScript 中的執行上下文和執行棧JavaScript
- JS引擎執行緒的執行過程的三個階段JS執行緒
- 【譯】理解 Javascript 執行上下文和執行棧JavaScript
- 結合執行棧、執行上下文,理解this的指向問題
- SQL Server中的執行引擎入門SQLServer
- 深入淺出JVM(七)之執行引擎的解釋執行與編譯執行JVM編譯
- JavaScript中的JS引擎的執行機制JavaScriptJS
- ARM下C語言棧幀機制C語言
- 深入分析JVM執行引擎JVM
- 深入理解JavaScript執行上下文和執行棧JavaScript
- 深入理解 JavaScript 執行上下文和執行棧JavaScript
- Hotspot VM 執行時資料區記憶體結構劃分HotSpot記憶體
- MySQL innodb引擎的事務執行過程MySql
- 微軟開源.NET Core的執行引擎CoreCLR微軟
- 詳解數倉的向量化執行引擎
- JavaScript中執行上下文和執行棧是什麼?JavaScript