Visual Studio C++ 的函数序言
Function Prologue by Visual Studio C++
我在VS2019社区版写了一个很简单的C++函数,对应的反汇编有问题
函数:
void manip(char* a, char* b, long* mc) {
long lena = 0;
long lenb = 0;
long lmc;
long i, j;
for (; a[lena] != NULL; lena++);
for (; b[lenb] != NULL; lenb++);
lmc = lena + lenb + *mc;
for (i=0; i < lena; i++) a[lena] = a[lena] + lmc;
for (j=0; j < lenb; j++) b[lenb] = b[lenb] + lmc;
}
反汇编(节选):
void manip(char* a, char* b, long* mc) {
00007FF720DE1910 mov qword ptr [rsp+18h],r8
00007FF720DE1915 mov qword ptr [rsp+10h],rdx
00007FF720DE191A mov qword ptr [rsp+8],rcx
00007FF720DE191F push rbp
00007FF720DE1920 push rdi
00007FF720DE1921 sub rsp,188h
00007FF720DE1928 lea rbp,[rsp+20h]
00007FF720DE192D mov rdi,rsp
00007FF720DE1930 mov ecx,62h
00007FF720DE1935 mov eax,0CCCCCCCCh
00007FF720DE193A rep stos dword ptr [rdi]
在前三行中,我们将参数放入堆栈中的帧指针之前。之后帧 rbp 指针被压入。困扰我的是以下三行:
00007FF720DE1921 sub rsp,188h
00007FF720DE1928 lea rbp,[rsp+20h]
00007FF720DE192D mov rdi,rsp
在上面的三行中,据我了解,第一行在堆栈上保留了 space。
问题:
- I do not understand why this huge space (188h) is reserved while we need just enough to save 5 longs, which are no more than 5*4=20 (16h)
bytes.
- Second line is calculation of new frame pointer, but I don't understand how did we get 20h(32).
- I also don't get the significance of 3rd line.
这是MSVC调试模式;它保留额外的 space 并用 0xCC
毒化它(在罐头中使用 rep stosd
又名 memset,因此 mov rdi,rsp
设置目的地)以帮助检测越界访问错误。 (即使 none 的当地人的地址被占用并且 none 是数组...)
额外的堆栈数量惊人space;我不知道 MSVC 是如何选择保留多少的。在 Release 模式下(-O2
优化 https://godbolt.org/z/GY7xTYWKq),它当然根本不会触及堆栈。
调试模式必须添加一些不是 MSVC 命令行默认设置的额外选项,因为我无法在 https://godbolt.org/z/nGo9516b7 上使用 MSVC2015 19.10 或 19.28 重现此代码生成。在将传入的寄存器参数溢出到影子 space 之后,我只是得到 sub rsp, 40
,甚至没有将 RBP 设置为帧指针。 (我猜是因为它是叶函数。)
lea rbp,[rsp+20h]
似乎将 RBP 设置为可能的帧指针,但它并不指向 return 地址正下方的已保存 RBP。通过一些代码显示它是如何使用它的,也许我们可以弄清楚。 (查看它的 asm 输出,而不是反汇编,因此您可以获得局部变量的符号名称)。
顺便说一句,如果您想真正了解循环逻辑的工作原理,那么针对循环优化的 asm 更易读。
您的代码充满了从堆栈重新加载指针和 movsxd
符号扩展,因为您使用有符号整数作为不是指针宽度的数组索引,并且编译器没有优化到指针增量或至少到 64 位整数。 (Signed-overflow being UB allows this optimization.)
的大部分内容都适用于编写优化后有趣的函数。
我在VS2019社区版写了一个很简单的C++函数,对应的反汇编有问题
函数:
void manip(char* a, char* b, long* mc) {
long lena = 0;
long lenb = 0;
long lmc;
long i, j;
for (; a[lena] != NULL; lena++);
for (; b[lenb] != NULL; lenb++);
lmc = lena + lenb + *mc;
for (i=0; i < lena; i++) a[lena] = a[lena] + lmc;
for (j=0; j < lenb; j++) b[lenb] = b[lenb] + lmc;
}
反汇编(节选):
void manip(char* a, char* b, long* mc) {
00007FF720DE1910 mov qword ptr [rsp+18h],r8
00007FF720DE1915 mov qword ptr [rsp+10h],rdx
00007FF720DE191A mov qword ptr [rsp+8],rcx
00007FF720DE191F push rbp
00007FF720DE1920 push rdi
00007FF720DE1921 sub rsp,188h
00007FF720DE1928 lea rbp,[rsp+20h]
00007FF720DE192D mov rdi,rsp
00007FF720DE1930 mov ecx,62h
00007FF720DE1935 mov eax,0CCCCCCCCh
00007FF720DE193A rep stos dword ptr [rdi]
在前三行中,我们将参数放入堆栈中的帧指针之前。之后帧 rbp 指针被压入。困扰我的是以下三行:
00007FF720DE1921 sub rsp,188h
00007FF720DE1928 lea rbp,[rsp+20h]
00007FF720DE192D mov rdi,rsp
在上面的三行中,据我了解,第一行在堆栈上保留了 space。
问题:
- I do not understand why this huge space (188h) is reserved while we need just enough to save 5 longs, which are no more than 5*4=20 (16h) bytes.
- Second line is calculation of new frame pointer, but I don't understand how did we get 20h(32).
- I also don't get the significance of 3rd line.
这是MSVC调试模式;它保留额外的 space 并用 0xCC
毒化它(在罐头中使用 rep stosd
又名 memset,因此 mov rdi,rsp
设置目的地)以帮助检测越界访问错误。 (即使 none 的当地人的地址被占用并且 none 是数组...)
额外的堆栈数量惊人space;我不知道 MSVC 是如何选择保留多少的。在 Release 模式下(-O2
优化 https://godbolt.org/z/GY7xTYWKq),它当然根本不会触及堆栈。
调试模式必须添加一些不是 MSVC 命令行默认设置的额外选项,因为我无法在 https://godbolt.org/z/nGo9516b7 上使用 MSVC2015 19.10 或 19.28 重现此代码生成。在将传入的寄存器参数溢出到影子 space 之后,我只是得到 sub rsp, 40
,甚至没有将 RBP 设置为帧指针。 (我猜是因为它是叶函数。)
lea rbp,[rsp+20h]
似乎将 RBP 设置为可能的帧指针,但它并不指向 return 地址正下方的已保存 RBP。通过一些代码显示它是如何使用它的,也许我们可以弄清楚。 (查看它的 asm 输出,而不是反汇编,因此您可以获得局部变量的符号名称)。
顺便说一句,如果您想真正了解循环逻辑的工作原理,那么针对循环优化的 asm 更易读。
您的代码充满了从堆栈重新加载指针和 movsxd
符号扩展,因为您使用有符号整数作为不是指针宽度的数组索引,并且编译器没有优化到指针增量或至少到 64 位整数。 (Signed-overflow being UB allows this optimization.)