在前一篇 第3篇-CallStub新棧幀的創建 中我們介紹了generate_call_stub()函數的部分實現,完成了向CallStub棧幀中壓入參數的操作,此時的狀態如下圖所示。

繼續看generate_call_stub()函數的實現,接來下會加載線程寄存器,代碼如下:
__ movptr(r15_thread, thread);
__ reinit_heapbase();
生成的匯編代碼如下:
mov 0x18(%rbp),%r15
mov 0x1764212b(%rip),%r12 # 0x00007fdf5c6428a8
對照着上面的棧幀可看一下0x18(%rbp)這個位置存儲的是thread,將這個參數存儲到%r15寄存器中。
如果在調用函數時有參數的話需要傳遞參數,代碼如下:
Label parameters_done;
// parameter_size拷貝到c_rarg3即rcx寄存器中
__ movl(c_rarg3, parameter_size);
// 校驗c_rarg3的數值是否合法。兩操作數作與運算,僅修改標志位,不回送結果
__ testl(c_rarg3, c_rarg3);
// 如果不合法則跳轉到parameters_done分支上
__ jcc(Assembler::zero, parameters_done);
// 如果執行下面的邏輯,那么就表示parameter_size的值不為0,也就是需要為
// 調用的java方法提供參數
Label loop;
// 將地址parameters包含的數據即參數對象的指針拷貝到c_rarg2寄存器中
__ movptr(c_rarg2, parameters);
// 將c_rarg3中值拷貝到c_rarg1中,即將參數個數復制到c_rarg1中
__ movl(c_rarg1, c_rarg3);
__ BIND(loop);
// 將c_rarg2指向的內存中包含的地址復制到rax中
__ movptr(rax, Address(c_rarg2, 0));
// c_rarg2中的參數對象的指針加上指針寬度8字節,即指向下一個參數
__ addptr(c_rarg2, wordSize);
// 將c_rarg1中的值減一
__ decrementl(c_rarg1);
// 傳遞方法調用參數
__ push(rax);
// 如果參數個數大於0則跳轉到loop繼續
__ jcc(Assembler::notZero, loop);
這里是個循環,用於傳遞參數,相當於如下代碼:
while(%esi){
rax = *arg
push_arg(rax)
arg++; // ptr++
%esi--; // counter--
}
生成的匯編代碼如下:
// 將棧中parameter size送到%ecx中
mov 0x10(%rbp),%ecx
// 做與運算,只有當%ecx中的值為0時才等於0
test %ecx,%ecx
// 沒有參數需要傳遞,直接跳轉到parameters_done即可
je 0x00007fdf4500079a
// -- loop --
// 匯編執行到這里,說明paramter size不為0,需要傳遞參數
mov -0x8(%rbp),%rdx
mov %ecx,%esi
mov (%rdx),%rax
add $0x8,%rdx
dec %esi
push %rax
// 跳轉到loop
jne 0x00007fdf4500078e
因為要調用Java方法,所以會為Java方法壓入實際的參數,也就是壓入parameter size個從parameters開始取的參數。壓入參數后的棧如下圖所示。

當把需要調用Java方法的參數准備就緒后,接下來就會調用Java方法。這里需要重點提示一下Java解釋執行時的方法調用約定,不像C/C++在x86下的調用約定一樣,不需要通過寄存器來傳遞參數,而是通過棧來傳遞參數的,說的更直白一些,是通過局部變量表來傳遞參數的,所以上圖CallStub()函數棧幀中的argument word1 ... argument word n其實是被調用的Java方法局部變量表的一部分。
下面接着看調用Java方法的代碼,如下:
// 調用Java方法
// -- parameters_done --
__ BIND(parameters_done);
// 將method地址包含的數據接Method*拷貝到rbx中
__ movptr(rbx, method);
// 將解釋器的入口地址拷貝到c_rarg1寄存器中
__ movptr(c_rarg1, entry_point);
// 將rsp寄存器的數據拷貝到r13寄存器中
__ mov(r13, rsp);
// 調用解釋器的解釋函數,從而調用Java方法
// 調用的時候傳遞c_rarg1,也就是解釋器的入口地址
__ call(c_rarg1);
生成的匯編代碼如下:
// 將Method*送到%rbx中
mov -0x18(%rbp),%rbx
// 將entry_point送到%rsi中
mov -0x10(%rbp),%rsi
// 將調用者的棧頂指針保存到%r13中
mov %rsp,%r13
// 調用Java方法
callq *%rsi
注意調用callq指令后,會將callq指令的下一條指令的地址壓棧,再跳轉到第1操作數指定的地址,也就是*%rsi表示的地址。壓入下一條指令的地址是為了讓函數能通過跳轉到棧上的地址從子函數返回。
callq指令調用的是entry_point。entry_point在后面會詳細介紹。
公眾號 深入剖析Java虛擬機HotSpot 已經更新虛擬機源代碼剖析相關文章到60+,歡迎關注,如果有任何問題,可加作者微信mazhimazh,拉你入虛擬機群交流

