细说Shadow Space
细说Shadow Space
前面我们讲了hadow Space 是 Windows x64 ABI 规定的 32 字节栈空间,但是这32字节有啥用,怎么用,依旧是个问题
Shadow Space是什么?
Shadow Space,也叫 Home Space(参数寄存区),是 Windows x64 ABI 规定的、由调用者(caller)在调用函数前预留的 32 字节栈空间
这些是在之前就了解过的,但是为什么有32个字节?
简单来说,因为 Windows x64 最多有 4 个通过寄存器传递的参数位置:
复制代码
第1个参数 → RCX
第2个参数 → RDX
第3个参数 → R8
第4个参数 → R9
其中每个位置对应8字节,四个加起来就是32字节
**不过要注意:**参数刚传进来时通常只在寄存器里,Shadow Space 只是提前给它们准备好的存放位置
Shadow Space有什么用?
让**被调用函数(callee)**可以把寄存器参数“搬到栈上”
举个简单的例子:
asm
复制代码
mov ecx, 10
mov edx, 20
call add
进入add时:
复制代码
RCX = 10
RDX = 20
此时调用者已经准备好了32字节的shadow space,如果add觉得需要把a、b放入栈,就可以:
asm
复制代码
mov [rsp+8], rcx
mov [rsp+10h], rdx
为啥不一直用寄存器呢?
上一篇文章的末尾我们讲了寄存器的保存规则,我们知道寄存器是区分为易失寄存器volatile和不可变寄存器non-volatile
而我们的参数寄存器属于易失寄存器
所以当我们的函数内部再出现函数调用的时候,本来存参数的寄存器又会被调出去用,所以需要这么一个空间来兜底,把原本放在易失寄存器的参数放在安全的位置
等函数调用完之后,再从栈中取出参数到寄存器
诶🤔,这怎么感觉和之前学32位的时候有点像
Shadow space给谁用?
让我们来捋一捋,这个Shadow space和之前学的局部变量空间有啥区别:
局部变量空间
假设有个函数:
c
复制代码
int foo()
{
int a = 10;
int b = 20;
return a + b;
}
编译器可能:
asm
复制代码
sub rsp, 20h
[RSP+0] a
[RSP+4] b
这块局部变量空间由当前函数调给自己用
Shadow Space
但是在这个影子空间中的调用有点不一样;
asm
复制代码
sub rsp, 20h
call foo
add rsp, 20h
这个空间是在call之前就分配好的,是调用foo的函数给foo分配的,而之前的局部变量空间是foo自己给自己分配的
Shadow Space 属于调用边界
他是caller个callee准备的,而且用途也相当不同:
- 局部变量空间放的是临时变量
c
复制代码
int a
char buf[100]
临时变量
- Shadow Space 为前四个寄存器参数提供了对应的 Home 位置,callee 可以根据需要将寄存器中的参数保存到这里
复制代码
RCX 的 home
RDX 的 home
R8 的 home
R9 的 home
可以说,两者本质上都是函数运行时使用的栈空间
区别在于局部变量空间主要由 callee 为自己使用而分配,而 Shadow Space 是 caller 按 ABI 约定提前为 callee 准备的参数 Home 空间
为啥不直接push?
64 位 ABI 的设计理念:参数放在寄存器以减少内存访问和指令数量
Shadow Space的作用相当于一个兜底的作用,函数本身基本可以做到完全不用访问内存
而且这份空间是一次性预留、并且归属于caller的,它的所有callee在需要用易失寄存器的时候将寄存器的内容存在同一片空间,不会每次需要都开辟空间,当前caller的所有调用结束后由caller统一收回
最后我们发现:Windows x64 的函数调用,本质其实就是一群人在围着 RSP 这块内存不停地存东西、取东西
评论