闽公网安备 35020302035485号
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 ,跳转到当前正在运行的函数的函数头执行。