为啥添加到 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

【问题讨论】:

因为printfcaller 必须清理堆栈,并且在 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,nopleave 看起来像未优化的 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?的主要内容,如果未能解决你的问题,请参考以下文章

ESP-IDF 添加 arduino作为component

怎么向esp分区添加引导文件

怎样向esp分区添加引导文件?

ESP32/ESP8266 Web Service HTTP添加身份验证

ESP8266通过程序向闪存文件系统文件添加信息

添加自己的component