为啥添加到 esp 是 0x10?
Posted
技术标签:
【中文标题】为啥添加到 esp 是 0x10?【英文标题】:Why added to esp is 0x10?为什么添加到 esp 是 0x10? 【发布时间】:2021-08-17 16:10:47 【问题描述】:我正在阅读Wikipedia 文章,但无法理解为什么在以下代码块的末尾会有add esp, 0x10
。我将放弃我自己的假设并简单地问 - 为什么?
printnums:
; stack setup
push ebp
mov ebp, esp
sub esp, 0x08
mov [ebp-0x04], ecx ; in x86, ecx = first argument.
mov [ebp-0x08], edx ; arg2
push [ebp+0x08] ; arg3 is pushed to stack.
push [ebp-0x08] ; arg2 is pushed
push [ebp-0x04] ; arg1 is pushed
push 0x8065d67 ; "The numbers you sent are %d %d %d"
call printf
; stack cleanup
add esp, 0x10
nop
leave
retn 0x04
【问题讨论】:
因为printf
的caller
必须清理堆栈,并且在 printf 0x10 被添加到 ESP 之后(0x10 = 16 十进制和4*4=16)。 printf
作为 C 库的一部分被假定使用 CDECL 调用约定(调用者清理)
这是未优化的代码。如果启用优化,这可能会消失。查看编译器的未优化汇编输出通常没有帮助。
@MichaelPetch;推送的 4 个 32 位值是什么?
@psprint:call printf
之前的四个push
指令,将四个参数推送给printf
,每个参数都是32 位(可能是整数或指针)。
@prl:如果我们启用优化,leave
更有可能会消失(省略帧指针)而add esp, 0x10
会保留。
【参考方案1】:
这里没有意义,因为leave
会在它当前指向的任何地方恢复 ESP。
正如***所说,这是从正上方显示的 C 函数中反汇编 GCC 输出
(https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_fastcall)。我们可以从 __attribute__((fastcall))
看出它不是 MSVC,nop
和 leave
看起来像未优化的 GCC 输出。
这就是为什么它将 2 个传入的寄存器参数存储到堆栈空间,然后用 push
重新加载它们。
函数调用语句本身的代码块以弹出实际传递给 printf 的 4 个 dword 参数(这是 cdecl,而不是 fastcall)结束。那是 0x10 = 16 个字节。
它不会弹出sub esp, 8
为本地变量保留的空间;留给leave
。
您可以在 Godbolt 编译器资源管理器 (https://godbolt.org/z/vdM4cxM7q) 上看到相同的内容,并且通过查看编译器 asm 输出(而不是反汇编),您可以获得一个符号名称,而不是像 0x8065d67 这样的数字地址。更重要的是,它将用颜色突出显示 C 源代码行,以将它们与 asm 行匹配。
(实际的gcc -O0 -m32
输出使用sub esp, 24
,而不是 8。也许***的输出来自更早的 GCC 版本。或者它来自针对 Windows 或 *BSD 的 GCC 版本,其中 16 字节堆栈对齐不是对 32 位代码的要求;似乎空间浪费错误随着 -mpreferred-stack-boundary=2
消失了,所以 GCC5.4 https://godbolt.org/z/KWzK6zdrj 完全再现了 asm 输出。GCC 4.9 及更早版本不会浪费带有 NOP 的指令。)
【讨论】:
以上是关于为啥添加到 esp 是 0x10?的主要内容,如果未能解决你的问题,请参考以下文章