MSVC在x64模式下如何傳參?

由於我需要寫一個編譯器,爲了彙編程序和MSVC生成的C++進行交互,必須瞭解MSVC在x64下如何運作。查了一下基本沒有中文的資料,MSDN是有文檔的,但是講的也不太清楚,這裏根據這篇 進行一些試驗,關鍵是弄懂 struct 是如何作爲參數傳遞的。

首先你需要閱讀上面那個連接,瞭解 Windows 下 64 位的基本調用規範,包括整數、浮點數參數如何傳遞等等,然後這篇文章是用來解答上面的文檔中需要驗證的地方。

實驗1:

驗證:

Structs/unions of size 8, 16, 32, or 64 bits and __m64 are passed as
if they were integers of the same size.

驗證代碼:// 我搞錯了大小的單位,以爲文檔說的是 byte ,所以這四個 struct 只有第一個能驗證 64 bit 的情況,不過讓我們繼續往下看。。

struct size8
{
    char pad[8];
};

struct size16
{
    char pad[16];
};

struct size32
{
    char pad[32];
};

struct size64
{
    char pad[64];
};

void size8_callee(struct size8 size_8_param)
{
    size_8_param.pad[0] = '8';
}

void size16_callee(struct size16 size_16_param)
{
    size_16_param.pad[0] = '6';
}

void size32_callee(struct size32 size_32_param)
{
    size_32_param.pad[0] = '2';
}

void size64_callee(struct size64 size_64_param)
{
    size_64_param.pad[0] = '4';
}

void caller()
{
    size8 size_8_argument;
    size16 size_16_argument;
    size32 size_32_argument;
    size64 size_64_argument;

    size8_callee(size_8_argument);
    size16_callee(size_16_argument);
    size32_callee(size_32_argument);
    size64_callee(size_64_argument);
}

這個時候我還沒有試着編譯,現在編譯一下,並且要注意是x64,注意要輸出列表文件:

這裏寫圖片描述

然後編譯沒有通過,報了一個ERROR(MSVC你多管閒事)

使用了未初始化的局部變量“size_32_argument”

爲了讓生成出來得彙編代碼簡明扼要,我們不需要初始化,初始化了反而很麻煩。這個 error 其實是個 warning,只是它太嚴重了,但是有它在其實還可以編譯。我發現可以把它 suppress 掉(錯誤編號是4700,輸入4700到禁用特定警告即可):
這裏寫圖片描述

這樣就編譯通過了。我們來找一找生成的彙編文件,

這裏寫圖片描述

屎它!test1.cod。由於 VS 裝插件比較麻煩,我們用 VS Code 來查看它。

我們先看一下 caller 的頭部:

; Function compile flags: /Odtp /RTCsu /ZI
; File c:\users\kiritsugu emiya\source\repos\test_param\test_param\test1.cpp
;   COMDAT ?caller@@YAXXZ
_TEXT   SEGMENT
size_8_argument$ = 8
size_16_argument$ = 40
size_32_argument$ = 88
size_64_argument$ = 160
$T7 = 640
$T8 = 688
$T9 = 752
$T10 = 836
$T11 = 868
$T12 = 900
$T13 = 932

這裏可以看到我們剛纔在代碼中出現的幾個變量的名字,和幾個叫 $T{x} 的名字。後面幾個 $T 是幹嘛的我還不太清楚,先看看我們關心的這幾個參數 size_8_argumentsize_64_argument是怎麼處理的。(我現在還沒有看)
根據我的猜測,這應該是一系列的偏移量,因爲我們把這些數字相減
40-8 = 3288-40 = 48160 - 88 = 72
32 = 16 + 16 48 = 32 + 16 72 = 64 + 8
恰好比結構實際的 size 大一些,多餘的部分可能會填充一些 debug 信息。

可以略過跟這幾個 $T 相關的變量,看看和第一個 arugment 相關的部分:

; 44   :    size8 size_8_argument;
; 45   :    size16 size_16_argument;
; 46   :    size32 size_32_argument;
; 47   :    size64 size_64_argument;
; 48   : 
; 49   :    size8_callee(size_8_argument);

  0003b 80 bd 44 03 00
    00 00        cmp     BYTE PTR $T10[rbp], 0
  00042 75 0c        jne     SHORT $LN3@caller
  00044 48 8d 0d 00 00
    00 00        lea     rcx, OFFSET FLAT:?caller@@YAXXZ$rtcName$0
  0004b e8 00 00 00 00   call    _RTC_UninitUse
$LN3@caller:
  00050 48 8b 4d 08  mov     rcx, QWORD PTR size_8_argument$[rbp]
  00054 e8 00 00 00 00   call    ?size8_callee@@YAXUsize8@@@Z ; size8_callee

這裏我直接把彙編碼複製過來,可以看到有一些奇奇怪怪的東西,可能是 Debug 模式下生成的用於調試的東西,現在管不了,我們可以看到,確實如同微軟所言,直接放到了對應的整數的位置。

The first four integer arguments are passed in registers. 
Integer values are passed (in order left to right) in RCX, RDX, R8, and R9. 
Arguments five and higher are passed on the stack.

然後我們看看 callee 怎麼接住這個參數:

; Function compile flags: /Odtp /RTCsu /ZI
; File c:\users\kiritsugu emiya\source\repos\test_param\test_param\test1.cpp
;   COMDAT ?size8_callee@@YAXUsize8@@@Z
_TEXT   SEGMENT
size_8_param$ = 224
?size8_callee@@YAXUsize8@@@Z PROC           ; size8_callee, COMDAT

; 23   : {

$LN3:
  00000 48 89 4c 24 08   mov     QWORD PTR [rsp+8], rcx
  00005 55       push    rbp
  00006 57       push    rdi
  00007 48 81 ec c8 00
    00 00        sub     rsp, 200       ; 000000c8H
  0000e 48 8b ec     mov     rbp, rsp
  00011 48 8b fc     mov     rdi, rsp
  00014 b9 32 00 00 00   mov     ecx, 50            ; 00000032H
  00019 b8 cc cc cc cc   mov     eax, -858993460        ; ccccccccH
  0001e f3 ab        rep stosd
  00020 48 8b 8c 24 e8
    00 00 00     mov     rcx, QWORD PTR [rsp+232]

; 24   :    size_8_param.pad[0] = '8';

  00028 b8 01 00 00 00   mov     eax, 1
  0002d 48 6b c0 00  imul    rax, rax, 0
  00031 c6 84 05 e0 00
    00 00 38     mov     BYTE PTR size_8_param$[rbp+rax], 56 ; 00000038H

; 25   : }

  00039 48 8d a5 c8 00
    00 00        lea     rsp, QWORD PTR [rbp+200]
  00040 5f       pop     rdi
  00041 5d       pop     rbp
  00042 c3       ret     0

我們別的不看,追蹤作爲傳參媒介的 rcx 的動向:

(1) mov QWORD PTR [rsp+8], rcx // 把 rcx 保存起來,可能一會要用
(2) mov rcx, QWORD PTR [rsp+232] // 把 rcx 取回來,注意 (1) 到 (2)rsp 變了,因此取地址的算式也變了,我們可以嘗試驗證一下這裏的 rsp(new)+232 == rsp(old)+8

rsp 的操作有:
(1) push rbp // rsp -= 8
(2) push rdi // rsp -= 8
(3) sub rsp, 200 // rsp -= 200
因此 rsp(new) == rsp(old) - 216

這裏令人疑惑的是,rsp(new)+232 == rsp(old)+8 並不成立,我查了一下手冊,發現(1) 到 (2)中間應該不會有別的指令去改變 rsp 的值。我們接着往後看。

; 24   :    size_8_param.pad[0] = '8';

  00028 b8 01 00 00 00   mov     eax, 1
  0002d 48 6b c0 00  imul    rax, rax, 0
  00031 c6 84 05 e0 00
    00 00 38     mov     BYTE PTR size_8_param$[rbp+rax], 56 ; 00000038H

這裏就非常明白了,mov eax, 1 是在把單位的 char 的偏移量加載進來,然後 imul rax, rax, 0 是計算 offset * size (顯然這裏沒有經過優化),然後把我們需要修改的值賦在內存裏:

mov BYTE PTR size_8_param$[rbp+rax]

這裏的 rbp 是多少?我們從開頭看:

  00005 55       push    rbp
  00006 57       push    rdi
  00007 48 81 ec c8 00
    00 00        sub     rsp, 200       ; 000000c8H
  0000e 48 8b ec     mov     rbp, rsp

可以看到 rbp == rsp(new) ,也就是 rbp == rsp(old) - 216
我們回顧一下之前有 size_8_param$ = 224
因此,這裏的地址是 size_8_param$ + rbp + rax == 224 + rsp(old) - 216 + 0 ,剛好等於 rsp(old) + 8

回顧一下開頭:mov QWORD PTR [rsp+8], rcx,完 全 一 致
因此,這裏我們可以看到彙編程序確實執行了 size_8_param.pad[0] = '8'; 這個任務

那麼問題回到 mov rcx, QWORD PTR [rsp+232] 這句話是幹嘛的呢?
我也不知道。因爲剩下的程序 rcx 的值並未被使用過。

  00039 48 8d a5 c8 00
    00 00        lea     rsp, QWORD PTR [rbp+200]
  00040 5f       pop     rdi
  00041 5d       pop     rbp
  00042 c3       ret     0

接下來就非常普通,這三條指令釋放了函數運行棧的空間,恢復了 rdi, rbp, 的值。這個恢復操作驗證了
https://docs.microsoft.com/zh-cn/cpp/build/register-usage

同時這裏有個 ret 0,表示清理棧上的參數和影子空間是 caller 的責任。清理棧上的參數我們無法確定,因爲這裏直接用寄存器傳參了,但是確實影子空間是 caller 分配但是 callee 並沒有去管的。

好不容易看完了 size8_callee(size_8_argument); 的調用過程,我們可以簡單驗證一下剩下的:

; 50   :    size16_callee(size_16_argument);

  00059 80 bd 64 03 00
    00 00        cmp     BYTE PTR $T11[rbp], 0
  00060 75 0c        jne     SHORT $LN4@caller
  00062 48 8d 0d 00 00
    00 00        lea     rcx, OFFSET FLAT:?caller@@YAXXZ$rtcName$1
  00069 e8 00 00 00 00   call    _RTC_UninitUse
$LN4@caller:
  0006e 48 8d 85 80 02
    00 00        lea     rax, QWORD PTR $T7[rbp]
  00075 48 8d 4d 28  lea     rcx, QWORD PTR size_16_argument$[rbp]
  00079 48 8b f8     mov     rdi, rax
  0007c 48 8b f1     mov     rsi, rcx
  0007f b9 10 00 00 00   mov     ecx, 16
  00084 f3 a4        rep movsb
  00086 48 8d 8d 80 02
    00 00        lea     rcx, QWORD PTR $T7[rbp]
  0008d e8 00 00 00 00   call    ?size16_callee@@YAXUsize16@@@Z ; size16_callee

我靠,在逗我嗎?這裏明明是 lea rcx, QWORD PTR $T7[rbp] ,Load Effective Address,參數 rcx 是一個地址!
簡單看一下這段,可以看到它執行了一個複製的操作,把 size16_argument 這個對象從 size_16_argument$[rbp] 複製到了 $T7[rbp] ,然後把複製後的對象的地址傳遞給了 callee。

我們再看下 callee

; 29   :    size_16_param.pad[0] = '6';

  00028 b8 01 00 00 00   mov     eax, 1
  0002d 48 6b c0 00  imul    rax, rax, 0
  00031 48 8b 8d e0 00
    00 00        mov     rcx, QWORD PTR size_16_param$[rbp]
  00038 c6 04 01 36  mov     BYTE PTR [rcx+rax], 54 ; 00000036H

很顯然 callee 也不含糊,它知道這是個地址,直接完成了操作。

讓我們驗證看下

    size32_callee(size_32_argument);
    size64_callee(size_64_argument);

是怎麼調用的。
這裏就不再贅述,和size16_callee是一樣的道理。這就有問題了。

__m128 types, arrays and strings are never passed by immediate value but rather a pointer is passed to memory allocated by the caller. Structs/unions of size 8, 16, 32, or 64 bits and __m64 are passed as if they were integers of the same size. Structs/unions other than these sizes are passed as a pointer to memory allocated by the caller.

難道編譯器自動把

struct size16
{
    char pad[16];
};

這樣的結構當做傳遞了一個 “array” ?那好,我們不傳遞 “array” 再試一次,看看能不能和

Structs/unions of size 8, 16, 32, or 64 bits and __m64 are passed as if they were integers of the same size.

對應上。

struct size16
{
    int a;
    int b;
    int c;
    int d;
};

void size16_callee(struct size16 size_16_param)
{
    size_16_param.a = 233;
}

編譯了以後看到這樣的結果:

; 53   :    size16_callee(size_16_argument);

  00059 80 bd 64 03 00
    00 00        cmp     BYTE PTR $T11[rbp], 0
  00060 75 0c        jne     SHORT $LN4@caller
  00062 48 8d 0d 00 00
    00 00        lea     rcx, OFFSET FLAT:?caller@@YAXXZ$rtcName$1
  00069 e8 00 00 00 00   call    _RTC_UninitUse
$LN4@caller:
  0006e 48 8d 85 80 02
    00 00        lea     rax, QWORD PTR $T7[rbp]
  00075 48 8d 4d 28  lea     rcx, QWORD PTR size_16_argument$[rbp]
  00079 48 8b f8     mov     rdi, rax
  0007c 48 8b f1     mov     rsi, rcx
  0007f b9 10 00 00 00   mov     ecx, 16
  00084 f3 a4        rep movsb
  00086 48 8d 8d 80 02
    00 00        lea     rcx, QWORD PTR $T7[rbp]
  0008d e8 00 00 00 00   call    ?size16_callee@@YAXUsize16@@@Z ; size16_callee

lea rcx, QWORD PTR $T7[rbp]
可以看到,還是傳了個地址,這裏 MSVC 的文檔似乎和實際不相符。

然後我發現我犯了個愚蠢的錯誤,文檔裏說的是:

Structs/unions of size 8, 16, 32, or 64 bits and __m64 are passed as if they were integers of the same size.

它用的是 bit … 不是 byte,我習慣性的把 size 的單位當做了 byte。
讓我們再試一次。。


struct size8
{
    char pad[1];
};

struct size16
{
    char pad[2];
};

struct size32
{
    char pad[4];
};

struct size64
{
    char pad[8];
};

void size8_callee(struct size8 size_8_param)
{
    size_8_param.pad[0] = '8';
}

void size16_callee(struct size16 size_16_param)
{
    size_16_param.pad[0] = '6';
}

void size32_callee(struct size32 size_32_param)
{
    size_32_param.pad[0] = '2';
}

void size64_callee(struct size64 size_64_param)
{
    size_64_param.pad[0] = '4';
}

void caller()
{
    size8 size_8_argument;
    size16 size_16_argument;
    size32 size_32_argument;
    size64 size_64_argument;

    size8_callee(size_8_argument);
    size16_callee(size_16_argument);
    size32_callee(size_32_argument);
    size64_callee(size_64_argument);
}

然後一切都合乎標準。具體和第一次試驗是差不多的,大家可以自己嘗試一下。

所以實驗一一共試了7種情況(第二次的 size64 就是 第一次的 size8):
struct 的大小是 1, 2, 3, 4, 8, 16, 32, 64 byte,和文檔敘述是一致的。
注意值傳遞的時候有一步複製的操作,並且傳參的臨時空間分配全部是caller搞定。

實驗2:

驗證:

Structs/unions other than these sizes are passed as a pointer to memory allocated by the caller. For these aggregate types passed as a pointer (including __m128), the caller-allocated temporary memory will be 16-byte aligned.

讓我們簡單實驗試一下其他 size 的情況。

struct size48
{
    char pad[6];
};
...
void size48_callee(struct size48 size_48_param)
{
    size_48_param.pad[0] = '8';
}
...
    size48_callee(size_48_argument);
...

然後爲了防止對齊的影響,我們編譯選項選擇按 1 字節對齊。

結果如下:

; 65   : 
; 66   :    size48_callee(size_48_argument);

  0009d 80 bd f4 01 00
    00 00        cmp     BYTE PTR $T12[rbp], 0
  000a4 75 0c        jne     SHORT $LN6@caller
  000a6 48 8d 0d 00 00
    00 00        lea     rcx, OFFSET FLAT:?caller@@YAXXZ$rtcName$3
  000ad e8 00 00 00 00   call    _RTC_UninitUse
$LN6@caller:
  000b2 48 8d 85 70 01
    00 00        lea     rax, QWORD PTR $T8[rbp]
  000b9 48 8d 8d 84 00
    00 00        lea     rcx, QWORD PTR size_48_argument$[rbp]
  000c0 48 8b f8     mov     rdi, rax
  000c3 48 8b f1     mov     rsi, rcx
  000c6 b9 06 00 00 00   mov     ecx, 6
  000cb f3 a4        rep movsb
  000cd 48 8d 8d 70 01
    00 00        lea     rcx, QWORD PTR $T8[rbp]
  000d4 e8 00 00 00 00   call    ?size48_callee@@YAXUsize48@@@Z ; size48_callee

顯然這裏也是傳了個地址過去。所以,除了 1,2,4,8字節這些恰好能湊在一個寄存器裏面的 struct,是使用整數的方式傳遞,其他都是按照指針傳遞。

實驗3

驗證 struct 參數與整數參數混合的情況。
混合情況有兩種,一種是 1, 2, 4, 8 字節的 struct 和整數參數混合,以及非 1 ,2,4,8字節的 struct 和參數混合。第二種我比較感興趣,所以先看看第二種

非 1 ,2,4,8字節的 struct 和參數混合:

void size48_mixed_callee1(int a, struct size48 size_48_param)
{
    a = size_48_param.pad[0];
}

void size48_mixed_callee2(struct size48 size_48_param, int b)
{
    b = size_48_param.pad[0];
}
...
    size48_mixed_callee1(1, size_48_argument);
    size48_mixed_callee2(size_48_argument, 2);
...

編譯結果爲:

; 77   : 
; 78   :    size48_mixed_callee1(1, size_48_argument);

  000d9 80 bd 54 02 00
    00 00        cmp     BYTE PTR $T14[rbp], 0
  000e0 75 0c        jne     SHORT $LN7@caller
  000e2 48 8d 0d 00 00
    00 00        lea     rcx, OFFSET FLAT:?caller@@YAXXZ$rtcName$3
  000e9 e8 00 00 00 00   call    _RTC_UninitUse
$LN7@caller:
  000ee 48 8d 85 a0 01
    00 00        lea     rax, QWORD PTR $T9[rbp]
  000f5 48 8d 8d 84 00
    00 00        lea     rcx, QWORD PTR size_48_argument$[rbp]
  000fc 48 8b f8     mov     rdi, rax
  000ff 48 8b f1     mov     rsi, rcx
  00102 b9 06 00 00 00   mov     ecx, 6
  00107 f3 a4        rep movsb
  00109 48 8d 95 a0 01
    00 00        lea     rdx, QWORD PTR $T9[rbp]
  00110 b9 01 00 00 00   mov     ecx, 1
  00115 e8 00 00 00 00   call    ?size48_mixed_callee1@@YAXHUsize48@@@Z ; size48_mixed_callee1

可以看到,是 rcx 存儲了第一個參數常量 1 ,第二個參數 rdx 存儲了到 size_48_argument 的指針。

; 79   :    size48_mixed_callee2(size_48_argument, 2);

  0011a 80 bd 54 02 00
    00 00        cmp     BYTE PTR $T14[rbp], 0
  00121 75 0c        jne     SHORT $LN8@caller
  00123 48 8d 0d 00 00
    00 00        lea     rcx, OFFSET FLAT:?caller@@YAXXZ$rtcName$3
  0012a e8 00 00 00 00   call    _RTC_UninitUse
$LN8@caller:
  0012f 48 8d 85 d0 01
    00 00        lea     rax, QWORD PTR $T10[rbp]
  00136 48 8d 8d 84 00
    00 00        lea     rcx, QWORD PTR size_48_argument$[rbp]
  0013d 48 8b f8     mov     rdi, rax
  00140 48 8b f1     mov     rsi, rcx
  00143 b9 06 00 00 00   mov     ecx, 6
  00148 f3 a4        rep movsb
  0014a ba 02 00 00 00   mov     edx, 2
  0014f 48 8d 8d d0 01
    00 00        lea     rcx, QWORD PTR $T10[rbp]
  00156 e8 00 00 00 00   call    ?size48_mixed_callee2@@YAXUsize48@@H@Z ; size48_mixed_callee2

這裏剛好反過來,rcx 存儲了到 size_48_argument 的地址,rdx 存儲了第二個參數 2

這是參數少於 4 的情況,多於 4 我們先不討論,因爲暫時沒有這個需求。

再來看看第一種情況。

1, 2, 4, 8 字節的 struct 和整數參數混合:

void size64_mixed_callee1(int a, struct size64 size_64_param)
{
    a = size_64_param.pad[0];
}

void size64_mixed_callee2(struct size64 size_64_param, int a)
{
    a = size_64_param.pad[0];
}
...
    size64_mixed_callee1(1, size_64_argument);
    size64_mixed_callee2(size_64_argument, 2);
...
; 90   : 
; 91   :    size64_mixed_callee1(1, size_64_argument);

  0015b 80 bd 34 02 00
    00 00        cmp     BYTE PTR $T13[rbp], 0
  00162 75 0c        jne     SHORT $LN9@caller
  00164 48 8d 0d 00 00
    00 00        lea     rcx, OFFSET FLAT:?caller@@YAXXZ$rtcName$2
  0016b e8 00 00 00 00   call    _RTC_UninitUse
$LN9@caller:
  00170 48 8b 55 68  mov     rdx, QWORD PTR size_64_argument$[rbp]
  00174 b9 01 00 00 00   mov     ecx, 1
  00179 e8 00 00 00 00   call    ?size64_mixed_callee1@@YAXHUsize64@@@Z ; size64_mixed_callee1

這裏 rcx ,rdx 分別裝了參數 1 和 參數 2。

; 92   :    size64_mixed_callee2(size_64_argument, 2);

  0017e 80 bd 34 02 00
    00 00        cmp     BYTE PTR $T13[rbp], 0
  00185 75 0c        jne     SHORT $LN10@caller
  00187 48 8d 0d 00 00
    00 00        lea     rcx, OFFSET FLAT:?caller@@YAXXZ$rtcName$2
  0018e e8 00 00 00 00   call    _RTC_UninitUse
$LN10@caller:
  00193 ba 02 00 00 00   mov     edx, 2
  00198 48 8b 4d 68  mov     rcx, QWORD PTR size_64_argument$[rbp]
  0019c e8 00 00 00 00   call    ?size64_mixed_callee2@@YAXUsize64@@H@Z ; size64_mixed_callee2

這裏 rcx 裝了參數 1,rdx 裝了參數 2。

我們還可以試一下四個參數的情況,例如:

mixed_callee1(size_48_argument, size_64_argument, size_48_argument, 3);
; 98   : 
; 99   :    mixed_callee1(size_48_argument, size_64_argument, size_48_argument, 3);

  001a1 80 bd 74 03 00
    00 00        cmp     BYTE PTR $T16[rbp], 0
  001a8 75 0c        jne     SHORT $LN11@caller
  001aa 48 8d 0d 00 00
    00 00        lea     rcx, OFFSET FLAT:?caller@@YAXXZ$rtcName$3
  001b1 e8 00 00 00 00   call    _RTC_UninitUse
$LN11@caller:
  001b6 80 bd 54 03 00
    00 00        cmp     BYTE PTR $T15[rbp], 0
  001bd 75 0c        jne     SHORT $LN12@caller
  001bf 48 8d 0d 00 00
    00 00        lea     rcx, OFFSET FLAT:?caller@@YAXXZ$rtcName$2
  001c6 e8 00 00 00 00   call    _RTC_UninitUse
$LN12@caller:
  001cb 80 bd 74 03 00
    00 00        cmp     BYTE PTR $T16[rbp], 0
  001d2 75 0c        jne     SHORT $LN13@caller
  001d4 48 8d 0d 00 00
    00 00        lea     rcx, OFFSET FLAT:?caller@@YAXXZ$rtcName$3
  001db e8 00 00 00 00   call    _RTC_UninitUse
$LN13@caller:
  001e0 48 8d 85 f0 02
    00 00        lea     rax, QWORD PTR $T12[rbp]
  001e7 48 8d 8d 84 00
    00 00        lea     rcx, QWORD PTR size_48_argument$[rbp]
  001ee 48 8b f8     mov     rdi, rax
  001f1 48 8b f1     mov     rsi, rcx
  001f4 b9 06 00 00 00   mov     ecx, 6
  001f9 f3 a4        rep movsb
  001fb 48 8d 85 c0 02
    00 00        lea     rax, QWORD PTR $T11[rbp]
  00202 48 8d 8d 84 00
    00 00        lea     rcx, QWORD PTR size_48_argument$[rbp]
  00209 48 8b f8     mov     rdi, rax
  0020c 48 8b f1     mov     rsi, rcx
  0020f b9 06 00 00 00   mov     ecx, 6
  00214 f3 a4        rep movsb
  00216 41 b9 03 00 00
    00       mov     r9d, 3
  0021c 4c 8d 85 f0 02
    00 00        lea     r8, QWORD PTR $T12[rbp]
  00223 48 8b 55 68  mov     rdx, QWORD PTR size_64_argument$[rbp]
  00227 48 8d 8d c0 02
    00 00        lea     rcx, QWORD PTR $T11[rbp]
  0022e e8 00 00 00 00   call    ?mixed_callee1@@YAXUsize48@@Usize64@@0H@Z ; mixed_callee1

仔細閱讀的話會發現第一個參數 rcx 是最後計算的,因爲 rcx 中間因爲其它需要被覆蓋了。
和預期一樣,rcx 存儲了 size_48_argument 的指針,rdx 直接存儲了 size_64_argumentr8 存儲了 size_48_argument 的指針,r9: mov r9d, 3 存儲了 3

這樣我們得到一個結論:

當參數爲整數或者 struct,且小於等於 4 個, MSVC 按照以下規則進行 struct 或整數的值傳遞

regs = [rcx, rdx, r8, r9]
for i, argument in enumerate(arguments):
    if argument 是整數: 
         regs[i] <- argument
    elif argument 是結構:
        if argument 大小爲 1, 2, 4, 8 字節:
            regs[i] <- argument
        else:
            //也許會複製 argument;
            regs[i] <- &argument // 取地址

謝謝大家,祝您身體健康!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章