【问题标题】:What are the "extra" 32 bytes on the Windows stack?Windows 堆栈上的“额外”32 个字节是什么?
【发布时间】:2020-07-07 00:22:45
【问题描述】:

我正在 Windows 上学习汇编,并试图弄清楚堆栈中的值是什么。
Visual C++ documentation 表示 RSP 以上的值为:

  • 分配空间
  • 保存的 RBP
  • 退货地址
  • 注册家(RCX、RDX、R8、R9)
  • 函数参数

问题是文档中没有提到的堆栈中有 32 个额外字节。

memory snapshot RSP 中从 0x0000000000DAF5E0 开始。彩色框是:

  • 黄色:两个 64 位变量,值为 9
  • 白色:保存旧 RBP + 返回地址
  • 蓝色:函数参数
  • 绿色:注册主页
  • 红色:?

那些红色的字节可能是什么?

使用 VS2019、MASM64 构建并在 x64 调试模式下运行的 MASM 源代码。

C++ 标志:/JMC /permissive- /GS /W3 /Zc:wchar_t /ZI /Gm- /Od /sdl /Fd"x64\Debug\vc142.pdb" /Zc:inline /fp:precise /D" _DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /MDd /FC /Fa"x64\Debug\" /EHsc /nologo /Fo "x64\Debug\" /Fp"x64\Debug\ConsoleApplication1.pch" /diagnostics:column

.code

; int64_t StackFrameDemo_(int8_t a, int16_t b, int32_t c, int64_t d, int8_t e, int16_t f, int32_t g, int64_t h)
StackFrameDemo_ proc frame

; prolog
push rbp
.pushreg rbp

; allocate 16 bytes on the stack
sub rsp, 16
.allocstack 16
.endprolog

; save registers to register home
mov qword ptr [rbp+8], rcx
mov qword ptr [rbp+16], rdx
mov qword ptr [rbp+24], r8
mov qword ptr [rbp+32], r9

; save the two variables
mov rax, 9
mov [rsp], rax
mov [rsp+type qword], rax

nop ; set the break point here to view memory

; epilog
add rsp, 16 ; release local stack space
pop rbp     ; restore caller's rbp register

ret

StackFrameDemo_ endp

end

【问题讨论】:

  • 我没有阅读您的问题,但您是在谈论为溢出参数寄存器保留的空间吗? IIRC 被称为“归巢/阴影空间/区域”之类的东西。确实有据可查。
  • 您的图片与您的描述完全不符。 x86 堆栈向下增长。另外,不要写入野指针,这是一个糟糕的主意。
  • @EOF:我很确定这段代码不是有意通过野指针 (RBP) 编写的;没有注意到导致混乱的错误。另请注意,描述从位于最低地址的事物开始,因此它以地址递增的顺序描述它,与增长顺序相反。我想这就是图表的绘制方式?注意到缺少 RBP 初始化后,我没有仔细检查;我认为这是唯一的问题。

标签: assembly x86-64 masm callstack stack-frame


【解决方案1】:

您忘记使用mov rbp, rsp(在push rbp 之后)使RBP 成为您的 堆栈帧的帧指针。

你的“家庭空间”又名影子空间比你的返回地址高 32 个字节,你只是没有使用它。(而是通过存储违反调用约定相对于可能具有任何值的某个寄存器。在这种情况下,您的调用者可能还使用 RBP 作为传统帧指针,因此您可能只是踩到了调用者的主空间。)


请注意,0xCC 是 MSVC 调试模式用来毒化堆栈的值,有助于检测未初始化内存的读取。 (如果你不小心用这些内容执行了内存,那就是 x86 int3 调试断点指令。)


顺便说一句,当您使用 RBP 作为传统帧指针时,mov rsp, rbp / pop rbp / retadd rsp, 16 / pop rbp 更有效。代码大小略小,一些 CPU 会进行 mov-elimination 以避免需要 mov 的执行单元。这会给您带来更大的噪音,例如可能从您的呼叫者的返回地址返回,您可能在单步执行时已经注意到了!

(leave = mov/pop,因此您可以将其用于更小的代码大小。与enter 不同,这对性能来说是可以的;GCC 在函数中使用leave,其帧指针不以 RSP 结尾已经指向保存的 RBP。其他一些编译器更喜欢 mov/pop。但编译器通常只在根本不使用帧指针时才使用add rsp, n。)

帧指针是可选的,不是堆栈帧布局的必需部分。像 .allocstack 16 这样的指令创建的元数据使得堆栈展开成为可能,而无需传统的帧指针链表。

【讨论】:

  • Peter,mov rbp, rsp 成功了,内存布局现在有意义了!我必须在 mov qword ptr [rbp+8] 指令上添加 8 个字节,因为它们正在覆盖返回值; rsp,在分配 16 个字节之前在推送的 rbp 值之前开始,因此 +8 个字节。谢谢!
  • @gfurtadoalmeida:哦,是的,您从 RBP 到家庭空间的偏移量也是错误的。这是正确的修复,我只是没有注意到其他错误。对于传统的帧指针,返回地址上方的第一个堆栈槽位是帧指针(指向保存的 RBP)上方的 2 个槽位。
  • 彼得,你有什么链接或书籍建议可以让我了解更多关于堆栈操作和 rbp、rsp 关系的信息吗?
  • @gfurtadoalmeida:this Q&A 中的大多数重复链接都是相关的。 (32 位代码有 4 字节而不是 8 字节堆栈槽)。我用site:stackoverflow.com esp ebp difference 找到了它们。我建议查看优化的编译器输出,了解编译器如何使用堆栈,例如仅在使用alloca() 或可变长度数组或过度对齐堆栈的函数中设置旧堆栈指针。或者在未优化的代码中,它们如何为本地人保留空间,但你已经在你的函数中这样做了。
猜你喜欢
  • 1970-01-01
  • 2018-07-20
  • 2011-02-04
  • 2016-12-11
  • 1970-01-01
  • 2020-11-01
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多