JVM作为一个C/C++编写的java运行时,它是如何调用Java函数的呢?本篇看下!
Main->__clone3->JavaMain->call_helper->StubRoutines::call_stub看下最后调用的这个call_stub,它里面调用了Java函数的函数头其实地址(当前你运行哪个函数,它里面就调用哪个函数的函数头进行机器码运行)。
static CallStub call_stub() (return CAST_TO_FN_PTR(CallStub, _call_stub_entry); }这个CAST_TO_FN_PTR到底干嘛的呢?看下它的assembly
0x7ffff6547da4 <+0>: endbr64 0x7ffff6547da8 <+4>: push rbp 0x7ffff6547da9 <+5>: mov rbp, rsp 0x7ffff6547dac <+8>: lea rax, [rip + 0x17dc405] 0x7ffff6547db3 <+15>: mov rax, qword ptr [rax] 0x7ffff6547db6 <+18>: pop rbp 0x7ffff6547db7 <+19>: ret上面代码很清楚了,把_call_stub_entry的函数指针([rip + 0x17dc405])
(lldb) di -l libjvm.so`JavaCalls::call_helper: 0x7ffff65474fb <+1389>: call 0x7ffff6547da4 ; StubRoutines::call_stub at stubRoutines.hpp:305:19 -> 0x7ffff6547500 <+1394>: mov rbx, rax继续看
(lldb) si (lldb) di -l 0x7ffff6547503 <+1397>: mov rax, qword ptr [rbp - 0x108] 0x7ffff654750a <+1404>: mov rdi, rax 0x7ffff654750d <+1407>: call 0x7ffff6547f1e ; JavaCallArguments::size_of_parameters at javaCalls.hpp:205:9 0x7ffff6547512 <+1412>: mov r12d, eax 0x7ffff6547515 <+1415>: mov rax, qword ptr [rbp - 0x100] 0x7ffff654751c <+1422>: mov rdi, rax 0x7ffff654751f <+1425>: call 0x7ffff5bda5f2 ; methodHandle::operator() at handles.hpp:174:1 0x7ffff6547524 <+1430>: mov rcx, rax 0x7ffff6547527 <+1433>: mov r8, qword ptr [rbp - 0xb0] 0x7ffff654752e <+1440>: mov rdi, qword ptr [rbp - 0xd8] 0x7ffff6547535 <+1447>: mov edx, dword ptr [rbp - 0xec] 0x7ffff654753b <+1453>: mov rsi, qword ptr [rbp - 0xb8] 0x7ffff6547542 <+1460>: lea rax, [rbp - 0x60] 0x7ffff6547546 <+1464>: push qword ptr [rbp - 0x110] 0x7ffff654754c <+1470>: push r12 0x7ffff654754e <+1472>: mov r9, r8 0x7ffff6547551 <+1475>: mov r8, rdi 0x7ffff6547554 <+1478>: mov rdi, rax 0x7ffff6547557 <+1481>: call rbx看它最后一句:call rbx。这个rbx就是上面的_call_stub_entry函数的指针,它直接跳转。而call_stub的几个参数分别存入寄存器,这里简单看下。
(lldb) b 0x7ffff6547557 (lldb) c (lldb) di -l (lldb) register read rdi rsi rdx rcx r8 r9 rdi = 0x00007ffff576e5c0 rsi = 0x00007ffff576e6e8 rdx = 0x000000000000000a rcx = 0x00007fffc40301b0 r8 = 0x00007fffe85564e0 r9 = 0x00007ffff576e740 (lldb) p &link (JavaCallWrapper *) $21 = 0x00007ffff576e5c0 (lldb) p result_val_address (intptr_t *) $22 = 0x00007ffff576e6e8 (lldb) p result_type (BasicType) $23 = T_INT (lldb) p method() (Method *) $24 = 0x00007fffc40301b0 (lldb) p entry_point (address) $25 = 0x00007fffe85564e0 "H\x8bS\U00000010\U0000000f\xb7J,\U0000000f\xb7R*+с\xfa\xf5\U00000001" (lldb) p parameter_address (intptr_t *) $26 = 0x00007ffff576e740我们看到
rdi=&plink rsi=result_val_address rdx=result_type rcx=method() r8=entry_point r9=parameter_address这里着重关注下,r8寄存器。它保存的是当前需要运行的函数的函数头的起始地址。比如运行java源码的Main函数。上面运行到了call rbx此处,而rbx则是_call_stub_entry函数的函数头其实地址。进入里面。
(lldb) si Process 511497 stopped * thread #2, name = 'java', stop reason = instruction step into frame #0: 0x00007fffe8537c9c -> 0x7fffe8537c9c: push rbp 0x7fffe8537c9d: mov rbp, rsp 0x7fffe8537ca0: sub rsp, 0x60 0x7fffe8537ca4: mov qword ptr [rbp - 0x8], r9 (lldb) di -s 0x7fffe8537c9c -c80 -> 0x7fffe8537c9c: push rbp 0x7fffe8537c9d: mov rbp, rsp 0x7fffe8537ca0: sub rsp, 0x60 0x7fffe8537ca4: mov qword ptr [rbp - 0x8], r9 0x7fffe8537ca8: mov qword ptr [rbp - 0x10], r8 //中间太多,省略....... 0x7fffe8537d3e: mov rsi, qword ptr [rbp - 0x10] 0x7fffe8537d42: mov r13, rsp 0x7fffe8537d45: call rsi看下最后一条指令:call rsi。rsi保存的是什么呢?
JavaMain -》InitializeJVM-》generate_all-》set_interpreter_entry看下set_interpreter_entry函数原型:
505 void set_interpreter_entry(address entry) { 506 if (_i2i_entry != entry) { 507 _i2i_entry = entry; 508 } 509 if (_from_interpreted_entry != entry) { 510 _from_interpreted_entry = entry; 511 } 512 }也就是说在JVM初始化的时候调用了generate_all把当前运行的函数编译成机器码,然后通过set_interpreter_entry设置当前函数的函数头地址,也就是_from_interpreted_entry。在调用的时候直接获取到entry_point ,跳转到当前正在运行的函数的函数头执行。