回顾之前
static CallStub call_stub(){
return (CallStub)(castable_address(_call_stub_entry));
}
JVM 通过call_stub()函数返回一个CallStub类型函数指针,即_call_stub_entry,再通过_call_stub_entry指向某个函数地址从而调用函数
StubRoutines::_call_stub_entry = generate_call_stub(StubRoutines::_call_stub_return_address)
_call_stub_entry初始化由generate_call_stub(),顾名思义就是产生函数首地址
address generate_call_stub(address& return_address) {
StubCodeMark mark(this, "StubRoutines", "call_stub");
address start = __ pc();//获取当前入口的内存地址,因为后续会继续向代码空间在写入汇编指令,后面返回start即可
// stub code parameters / addresses
assert(frame::entry_frame_call_wrapper_offset == 2, "adjust this code");
bool sse_save = false;
const Address rsp_after_call(rbp, -4 * wordSize); // same as in generate_catch_exception()!
const int locals_count_in_bytes (4*wordSize);
//...
const Address result (rbp, 3 * wordSize);
const Address result_type (rbp, 4 * wordSize);
const Address method (rbp, 5 * wordSize);
const Address entry_point (rbp, 6 * wordSize);
const Address parameters (rbp, 7 * wordSize);
const Address parameter_size(rbp, 8 * wordSize);
const Address thread (rbp, 9 * wordSize); // same as in generate_catch_exception()!
sse_save = UseSSE > 0;
// stub code
__ enter();//实现:push(rbp);mov(rbp,rsp);均调用emit()写入机器码
__ movptr(rcx, parameter_size);//类似 // parameter counter
__ shlptr(rcx, Interpreter::logStackElementSize); // convert parameter count to bytes
__ addptr(rcx, locals_count_in_bytes); // reserve space for register saves
__ subptr(rsp, rcx);
__ andptr(rsp, -(StackAlignmentInBytes)); // Align stack
// save rdi, rsi, & rbx, according to C calling conventions
__ movptr(saved_rdi, rdi);
__ movptr(saved_rsi, rsi);
__ movptr(saved_rbx, rbx);
// save and initialize %mxcsr
//...
// interpreter uses xmm0 for return values
if (UseSSE >= 1) {
__ movflt(Address(rdi, 0), xmm0);
} else {
__ fstp_s(Address(rdi, 0));
}
__ jmp(exit);
__ BIND(is_double);
// interpreter uses xmm0 for return values
if (UseSSE >= 2) {
__ movdbl(Address(rdi, 0), xmm0);
} else {
__ fstp_d(Address(rdi, 0));
}
__ jmp(exit);
return start;
}
第一行代码 address start = ___pc();保存当前例程所写入对应的机器码的起始位置
address pc() const {
return _code_pos;
}
JVM启动过程中会生成很多机器码(例程)如函数调用,字节码例程,异常处理等
每一个例程都在一段连续的内存中,生成第一个例程时pc()返回0,生成了长度为20字节的机器码,而当生成第二个例程时,pc()返回20
JVM中每一个例程都对应一个generate()来生成机器码,通过全局的_code_pos来记录例程的相应信息
// stub code parameters / addresses
assert(frame::entry_frame_call_wrapper_offset == 2, "adjust this code");
bool sse_save = false;
const Address rsp_after_call(rbp, -4 * wordSize); // same as in generate_catch_exception()!
const int locals_count_in_bytes (4*wordSize);
//...
const Address result (rbp, 3 * wordSize);
const Address result_type (rbp, 4 * wordSize);
const Address method (rbp, 5 * wordSize);
const Address entry_point (rbp, 6 * wordSize);
先回顾汇编调用call时堆栈模型(基础)
JVM在调用CallStub时传递8个参数,推断一下入参根据ebp的相对位置
参数 | 位置 |
(address)&link | 8(%ebp) |
result_val_address | 12(%ebp) |
result_type | 16(%ebp) |
... | ... |
__ enter();//实现:push(rbp);mov(rbp,rsp);均调用emit()写入机器码
__ movptr(rcx, parameter_size);//类似 // parameter counter
__ shlptr(rcx, Interpreter::logStackElementSize); // convert parameter count to bytes
__ addptr(rcx, locals_count_in_bytes); // reserve space for register saves
__ subptr(rsp, rcx);
__ andptr(rsp, -(StackAlignmentInBytes)); // Align stack
JVM在对被调用的java函数的堆栈大小计算,最终如下
根据parameters开始将参数压栈(汇编基础)
经过堆栈之间的处理,函数调用的资源已经准备充足,正式通过entry_point开始调用函数
// call Java function
__ BIND(parameters_done);
__ movptr(rbx, method); // get Method*
__ movptr(rax, entry_point); // get entry_point
__ mov(rsi, rsp); // set sender sp
BLOCK_COMMENT("call Java function");
__ call(rax);
BLOCK_COMMENT("call_stub_return_address:");
return_address = __ pc();
简单,将java函数的method对象首地址保存在ebx,entry_point到eax中,总体如下
开始call entry_point.