MSVC X64 函式中的 RSP, RBP 和 Calling Convention
上一篇 博文提到了 X64 下 MSVC 如何傳遞引數,但是沒有涉及到當引數個數大於 4 的時候如何分配記憶體空間的問題,接下來我們來探究這個問題。
RSP 和 RBP
按照上面提到的博文,我們進行如下實驗:
- 所有引數都是
struct Arg
, 並且sizeof(Arg) == 16
。 - 實驗中,引數的個數
k = 6, 7, 8
,先觀察彙編後rsp
和rbp
暫存器的變化。
實驗:
1. k = 6
; 28 : void Call() {
$LN3:
00000 40 55 push rbp
00002 56 push rsi
00003 57 push rdi
00004 48 81 ec f0 03
00 00 sub rsp, 1008 ; 000003f0H
0000b 48 8d 6c 24 30 lea rbp, QWORD PTR [rsp+48]
00010 48 8b fc mov rdi, rsp
- k = 7
; 28 : void Call() {
$LN3:
00000 40 55 push rbp
00002 56 push rsi
00003 57 push rdi
00004 48 81 ec 60 04
00 00 sub rsp, 1120 ; 00000460H
0000b 48 8d 6c 24 40 lea rbp, QWORD PTR [rsp+64]
00010 48 8b fc mov rdi, rsp
- k = 8
; 28 : void CallFuck() {
$LN3:
00000 40 55 push rbp
00002 56 push rsi
00003 57 push rdi
00004 48 81 ec c0 04
00 00 sub rsp, 1216 ; 000004c0H
0000b 48 8d 6c 24 40 lea rbp, QWORD PTR [rsp+64]
這裡我們需要明確一點,就是 rbp
的意義和 rsp
的意義是什麼。
rbp
是屬於當前函式的棧空間基地址,rsp
是包含當前函式為被呼叫函式準備的棧空間的基地址。
這點可以在彙編程式碼中看出來。
我們可以看出,當 k=6 的時候,MSVC 利用 lea rbp, QWORD PTR [rsp+48]
使得 rbp == rsp + 48
k = 7, 8 時 rbp == rsp + 64
根據 X64 下的傳參規則,當 sizeof(struct)
不為 8, 16, 32, 64 bits 時,將指向引數本體的指標放在對應的位置,因此,一個引數(這裡指這個指標,64 bits = 8 bytes)在棧上所佔用的記憶體應該為 M = 8 * (#arg - 4)
。
所以, k = 6, M = 16; k = 7, M = 24; k = 8, M = 32; 再加上分配的 32 bytes 影子空間,那麼應該是
k = 6, rbp == rsp + 48
k = 7, rbp == rsp + 56
k = 8, rbp == rsp + 64·
實際情況呢,k = 7 我們和編譯器結果不一樣,實際情況是 rbp == rsp + 64
,原因在於
為這些聚合型別作為指標的傳遞 (包括__m128),呼叫方分配的臨時記憶體將是 16 位元組對齊。
因此這些指標雖然單個大小是 8,所以在奇數個的時候為了對齊要額外增加 8 !
Calling Convention
根據這個簡單的呼叫約定,我們可以畫出64位下函式呼叫時到底是個怎麼樣的記憶體結構。(時機應該是 call
指令執行完畢)
這張圖片認真理解,就解決了所有呼叫時對於記憶體空間如何分配的疑問。
See also:
本篇基於實驗得出,除了某些叫法不同,和微軟的官方文件是一致的。