目錄
1. 匯編代碼
1.1 debug編譯
1.2 release編譯
2. 匯編分析
2.1 浮點參數傳遞規則
2.2 棧幀rsp的變化時序
2.3 參數的訪問邏輯
2.4 返回值XMM0寄存器
3. 匯編轉化
3.1 Debug編譯
3.2 Release 編譯
3.3 C語言轉化
1. 匯編代碼
上一節介紹了整型的函數傳參,那么浮點型有什么不同,將在這一節介紹,包含float和double的浮點型都是類似的傳參方式。這一節將不再區分少數參數和多個參數,直接使用多個參數浮點型的匯編代碼例子。
1.1 debug編譯
many_double_params:0000000000000A20: F2 0F 11 5C 24 20 movsd mmword ptr [rsp+20h],xmm30000000000000A26: F2 0F 11 54 24 18 movsd mmword ptr [rsp+18h],xmm20000000000000A2C: F2 0F 11 4C 24 10 movsd mmword ptr [rsp+10h],xmm10000000000000A32: F2 0F 11 44 24 08 movsd mmword ptr [rsp+8],xmm00000000000000A38: 57 push rdi0000000000000A39: 48 83 EC 10 sub rsp,10h0000000000000A3D: 48 8B FC mov rdi,rsp0000000000000A40: B9 04 00 00 00 mov ecx,40000000000000A45: B8 CC CC CC CC mov eax,0CCCCCCCCh0000000000000A4A: F3 AB rep stos dword ptr [rdi]0000000000000A4C: F2 0F 10 44 24 20 movsd xmm0,mmword ptr [rsp+20h]0000000000000A52: F2 0F 58 44 24 28 addsd xmm0,mmword ptr [rsp+28h]0000000000000A58: F2 0F 58 44 24 30 addsd xmm0,mmword ptr [rsp+30h]0000000000000A5E: F2 0F 58 44 24 38 addsd xmm0,mmword ptr [rsp+38h]0000000000000A64: F2 0F 58 44 24 40 addsd xmm0,mmword ptr [rsp+40h]0000000000000A6A: F2 0F 58 44 24 48 addsd xmm0,mmword ptr [rsp+48h]0000000000000A70: F2 0F 5E 05 00 00 divsd xmm0,mmword ptr [__real@4018000000000000]00 000000000000000A78: 48 83 C4 10 add rsp,10h0000000000000A7C: 5F pop rdi0000000000000A7D: C3 ret0000000000000A7E: CC int 30000000000000A7F: CC int 30000000000000A80: CC int 3
1.2 release編譯
many_double_params:0000000000000000: F2 0F 58 C1 addsd xmm0,xmm10000000000000004: F2 0F 58 C2 addsd xmm0,xmm20000000000000008: F2 0F 58 C3 addsd xmm0,xmm3000000000000000C: F2 0F 58 44 24 28 addsd xmm0,mmword ptr [rsp+28h]0000000000000012: F2 0F 58 44 24 30 addsd xmm0,mmword ptr [rsp+30h]0000000000000018: F2 0F 5E 05 00 00 divsd xmm0,mmword ptr [__real@4018000000000000]00 000000000000000020: C3 ret
2. 匯編分析
2.1 浮點參數傳遞規則
在 Windows x64 調用約定中:
- 前 4 個浮點參數??通過?
XMM0-XMM3
?寄存器傳遞 - 第 5 個及以上參數??通過棧傳遞(從右向左壓棧)
- 調用者需預留 ??32 字節影子空間??(Shadow Space),用于被調函數保存寄存器參數
2.2 棧幀rsp的變化時序
在棧幀變化上浮點型與整型保持著完全一致的邏輯
; 1. 保存前4個浮點參數到影子空間
movsd [rsp+20h], xmm3 ; 參數4 → [影子空間+0x20]
movsd [rsp+18h], xmm2 ; 參數3 → [影子空間+0x18]
movsd [rsp+10h], xmm1 ; 參數2 → [影子空間+0x10]
movsd [rsp+8], xmm0 ; 參數1 → [影子空間+0x08]; 2. 保存非易失寄存器
push rdi ; RSP -= 8; 3. 分配局部變量空間
sub rsp, 10h ; RSP -= 16 (0x10); 4. 初始化局部空間(調試版行為)
mov rdi, rsp
mov ecx, 4
mov eax, 0CCCCCCCCh ; 填充未初始化數據標記
rep stosd ; 用 0xCC 填充 16 字節
?在call指令調用函數時,棧幀rsp-=8, 所以影子空間的位置都要+0x08, 由[rsp]-[rsp+0x20]變化為[rsp+8]-[rsp+0x28]所以movsd [rsp+8], xmm0 ; 參數1 → [影子空間+0x08],第一個參數是放在[rsp+8]。
在push rdi 后, 再次 rsp-=8, 此時所有參數位置要再次+ 0x08
在sub rsp 10h之后,再次 rsp -= 0x10, 所以所有參數位置再次+ 0x10h
這里也充分說明了局部變量的地址是向下增長的
2.3 參數的訪問邏輯
; 加載并累加參數(偏移基于當前 RSP)
movsd xmm0, [rsp+20h] ; 加載參數1 (當前 RSP+0x20 = 原始影子空間+0x08)
addsd xmm0, [rsp+28h] ; + 參數2 (原始影子空間+0x10)
addsd xmm0, [rsp+30h] ; + 參數3 (原始影子空間+0x18)
addsd xmm0, [rsp+38h] ; + 參數4 (原始影子空間+0x20)
addsd xmm0, [rsp+40h] ; + 參數5 (調用者壓棧的第1個額外參數)
addsd xmm0, [rsp+48h] ; + 參數6 (調用者壓棧的第2個額外參數)
由于棧是向下增長的,從參數六開始壓棧,參數1 的地址其實是最低的
2.4 返回值XMM0寄存器
在x64架構下,浮點計算的返回值統一使用 ??XMM0 寄存器??作為傳遞載體。這一規則適用于單精度(float
)和雙精度(double
)浮點數,且是跨操作系統(如 Windows 和 Linux)的標準約定
浮點類型??(包括?float
、double
):結果存儲在?XMM0
?的低位部分:
- ??單精度??(32位):使用?
XMM0
?的低32位 - 雙精度??(64位):使用?
XMM0
?的低64位
3. 匯編轉化
3.1 Debug編譯
movsd [rsp+20h], xmm3 ; 保存第4個參數(xmm3 → [影子空間+0x20])[1](@ref)
movsd [rsp+18h], xmm2 ; 保存第3個參數(xmm2 → [影子空間+0x18])
movsd [rsp+10h], xmm1 ; 保存第2個參數(xmm1 → [影子空間+0x10])
movsd [rsp+8], xmm0 ; 保存第1個參數(xmm0 → [影子空間+0x08])
push rdi ; 保存非易失寄存器 RDI(RSP -= 8)
sub rsp, 10h ; 分配 16 字節局部空間(RSP -= 16)
mov rdi, rsp
mov ecx, 4 ; 循環4次(4×4字節=16字節)
mov eax, 0CCCCCCCCh ; 未初始化內存標記(調試用)
rep stosd ; 用 0xCC 填充局部空間
movsd xmm0, [rsp+20h] ; 加載第1個參數(原始 xmm0)
addsd xmm0, [rsp+28h] ; + 第2個參數(原始 xmm1)
addsd xmm0, [rsp+30h] ; + 第3個參數(原始 xmm2)
addsd xmm0, [rsp+38h] ; + 第4個參數(原始 xmm3)
addsd xmm0, [rsp+40h] ; + 第5個參數(棧傳遞)
addsd xmm0, [rsp+48h] ; + 第6個參數(棧傳遞)
divsd xmm0, [__real@4018000000000000] ; xmm0 /= 6.0
add rsp, 10h ; 釋放局部空間(RSP += 16)
pop rdi ; 恢復 RDI(RSP += 8)
ret ; 返回結果(xmm0 作為返回值)
3.2 Release 編譯
; 函數入口點,參數通過寄存器和棧傳遞
0000000000000000: 8D 04 11 lea eax, [rcx+rdx] ; EAX = 參數1(RCX) + 參數2(RDX)
0000000000000003: 41 03 C0 add eax, r8d ; EAX += 參數3(R8D)
0000000000000006: 41 03 C1 add eax, r9d ; EAX += 參數4(R9D)
0000000000000009: 03 44 24 28 add eax, dword ptr [rsp+28h] ; EAX += 參數5(棧偏移0x28)
000000000000000D: 03 44 24 30 add eax, dword ptr [rsp+30h] ; EAX += 參數6(棧偏移0x30)
0000000000000011: 03 44 24 38 add eax, dword ptr [rsp+38h] ; EAX += 參數7(棧偏移0x38)
0000000000000015: 03 44 24 40 add eax, dword ptr [rsp+40h] ; EAX += 參數8(棧偏移0x40)
0000000000000019: 03 44 24 48 add eax, dword ptr [rsp+48h] ; EAX += 參數9(棧偏移0x48)
000000000000001D: 03 44 24 50 add eax, dword ptr [rsp+50h] ; EAX += 參數10(棧偏移0x50)
0000000000000021: C3 ret ; 返回結果(EAX為返回值)
3.3 C語言轉化
#include <stdint.h>// 函數原型(6個 double 參數)
double many_double_params(double p1, double p2, double p3, double p4,double p5, double p6
) {// 累加所有參數double sum = p1 + p2 + p3 + p4 + p5 + p6;// 除以 6.0 后返回return sum / 6.0;
}