由于我需要写一个编译器,为了汇编程序和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_argument
… size_64_argument
是怎么处理的。(我现在还没有看)
根据我的猜测,这应该是一系列的偏移量,因为我们把这些数字相减
40-8 = 32
, 88-40 = 48
,160 - 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