You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

x64 DLL栈访问越界疑问:VS2017编译代码反汇编异常分析

理解Windows x64调用栈中的影子空间(Shadow Space)

你看到的这个现象其实是Windows x64调用约定的正常行为,核心在于**影子空间(Shadow Space)**的机制。让我们一步步拆解:

1. Windows x64调用约定的基础规则

在Windows x64平台上,调用者在调用函数前必须为被调用者预留32字节的影子空间(也称为“寄存器参数区”)。这片空间的作用包括:

  • 被调用者可用来保存RCX、RDX、R8、R9这四个参数寄存器的值(如果后续操作会覆盖这些寄存器的话)
  • 被调用者也能自由使用它来保存非易失性寄存器(比如RBX、RDI、RSI等)或临时数据

调用者预留影子空间后执行call指令,此时会把返回地址(RIP)压入栈中(占8字节),进入被调用函数时的栈布局大致为:

[调用者栈帧]
...
[影子空间起始地址] ← 调用者rsp - 32
[影子空间:32字节]
[返回地址] ← 进入函数时的rsp

2. 你的函数栈帧计算

看你的反汇编代码:

000007FEFAD01000 | 40 57 | push rdi  ; 保存非易失性寄存器RDI,rsp减少8字节
000007FEFAD01002 | 48 83 EC 20 | sub rsp,20  ; 分配32字节栈空间(20为十六进制=32),rsp再减少32字节

执行这两步后,当前rsp的位置是:
进入函数时的rsp -8(push rdi) -32(sub rsp,20) = 调用者rsp -32(影子空间) -8(返回地址) -8 -32 = 调用者rsp -80

再看写入[rsp+30]的指令:

000007FEFAD0102B | 48 89 5C 24 30 | mov qword ptr ss:[rsp+30],rbx

这里的rsp+30(30为十六进制=48字节)对应的绝对地址是:
调用者rsp -80 +48 = 调用者rsp -32
这个地址正好是调用者预留的影子空间的起始位置!

3. 为什么编译器要这么做?

RBX是Windows x64调用约定中的非易失性寄存器——函数必须保证调用前后RBX的值不变,所以你的函数需要先保存RBX的值,退出时再恢复。

编译器选择把RBX保存在影子空间里,而非自己分配的栈空间,原因很简单:影子空间已经被调用者分配好,属于可用内存区域,不需要额外分配栈空间,能稍微优化栈空间的使用。

后面的指令也验证了这一点:

000007FEFAD01043 | 48 8B 5C 24 30 | mov rbx,qword ptr ss:[rsp+30]  ; 恢复RBX的值

总结

你看到的[rsp+30]并不是父函数的私有栈空间,而是调用者为当前函数预留的影子空间,当前函数完全有权限使用这片区域。这是VS2017编译器针对Windows x64平台的正常优化行为,不属于异常现象。

内容的提问来源于stack exchange,提问作者Chin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:52:15