细说Shadow Space

汇编yulabu2026-09-07 40 次阅读

细说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 这块内存不停地存东西、取东西

评论

正在加载评论区…