正确调用 printf 的堆栈对齐?
Posted
技术标签:
【中文标题】正确调用 printf 的堆栈对齐?【英文标题】:Correct stack alignment for call to printf? 【发布时间】:2021-03-03 13:55:00 【问题描述】:我已经看到堆栈指针/esp
在调用printf
之前由4
递减并在调用printf
之后由12
重新调整的示例:
section .text
global main
extern printf
main:
sub esp, 4
push msg
push format_str
call printf
add esp, 12
ret
section .data:
msg db "print me!", 0
format_str db "%s", 0
我已经看到堆栈指针/esp
在调用printf
之前由8
递减并在调用printf
之后由16
重新调整的示例:
section .text
global main
extern printf
main:
sub esp, 8
push msg
push format_str
call printf
add esp, 16
ret
section .data:
msg db "print me!", 0
format_str db "%s", 0
从我读到的内容来看,esp
应该减少8
,然后在从 libc 调用任何函数之前重新调整/增加16
。
这些示例中的差异让我感到困惑,哪个堆栈对齐示例是正确的,为什么?是否可以解释这个递增/递减过程以减少混淆?
【问题讨论】:
第二个例子实际上是错误的,堆栈没有在 16 字节边界上对齐。并不是网上所有的例子都是正确的。 【参考方案1】:我已经看到堆栈指针/esp 在调用 printf 之前减 4 并在调用 printf 后重新调整 12 的示例:
根据another question 中的评论,在可以使用 SSE 指令的系统(例如库、操作系统)上,堆栈应按 16 字节对齐。
假设调用函数 (main
) 时堆栈指针正确对齐,call
指令从 esp
中减去 4 个字节,因此 sub
和 push
指令必须正好减去 12、28、 40 ... 字节来自esp
,以保持堆栈指针正确对齐。
sub esp, 8
显然,在这种情况下,编译器不会被告知要注意 16 字节的堆栈对齐。
显然,在这种情况下,编译器分配的堆栈比必要的多。
我刚刚告诉编译器为堆栈生成一个 8 字节和 16 字节对齐;所有其他编译器选项(当然还有源代码)都是相同的。
不同的是,在8字节对齐的情况下,编译器生成sub esp, 4
,在16字节对齐的情况下生成sub esp, 20
。
很明显,这是编译器优化的问题:
如果sub esp,20
将堆栈对齐到 16 个字节,sub esp, 4
也将对齐到 16 个字节。
使用“对齐到 8 字节”选项表明绝对可以使用 sub esp, 4
而不是 sub esp, 20
。
这表明一些编译器为某些未知目的保留了比所需更多的堆栈。
【讨论】:
sub esp,8
示例确实 not 看起来像编译器输出。一方面,它是 NASM 语法,所以这已经是一些非标准的编译器了。 C 源代码几乎不可能使用static char msg[] = "print me!";
等等来在读写.data
部分(而不是.rodata
)中获取这些字符串,或者玩具编译器可能已经够糟糕了,总是这样做.此外,如果您不打算将堆栈重新对齐 16,则 sub esp
是不必要的。这个 main
不会返回 0 (xor eax,eax
),所以我猜 C89 可能是?
当我们为业余项目编译器找到足够多的借口来生成此代码时,它与那些并不真正知道自己在做什么的人的手写 asm 处于同一水平。所以我们不妨直接说“谁写了这段代码”,因为不管他们是直接写还是写了一个不符合 ABI 的编译器都没有关系。 (公平地说,只有 Linux 版本的 i386 Sys V ABI 需要 16 字节堆栈对齐;其他操作系统将其保留为 4 以用于 32 位模式。)以上是关于正确调用 printf 的堆栈对齐?的主要内容,如果未能解决你的问题,请参考以下文章