gcc x86-32堆栈对齐并调用printf
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了gcc x86-32堆栈对齐并调用printf相关的知识,希望对你有一定的参考价值。
据我所知,x86-64要求堆栈在调用之前是16字节对齐,而gcc with -m32
doesn't require this for main。
我有以下测试代码:
.data
intfmt: .string "int: %d
"
testint: .int 20
.text
.globl main
main:
mov %esp, %ebp
push testint
push $intfmt
call printf
mov %ebp, %esp
ret
用as --32 test.S -o test.o && gcc -m32 test.o -o test
构建。我知道syscall写存在,但据我所知它不能打印整数和浮动printf的方式。
进入main后,堆栈上有一个4字节的返回地址。然后天真地解释这个代码,两个push调用各自在堆栈上放置4个字节,因此调用需要另一个4字节值推送对齐。
这是gas和gcc生成的二进制文件的objdump:
0000053d <main>:
53d: 89 e5 mov %esp,%ebp
53f: ff 35 1d 20 00 00 pushl 0x201d
545: 68 14 20 00 00 push $0x2014
54a: e8 fc ff ff ff call 54b <main+0xe>
54f: 89 ec mov %ebp,%esp
551: c3 ret
552: 66 90 xchg %ax,%ax
554: 66 90 xchg %ax,%ax
556: 66 90 xchg %ax,%ax
558: 66 90 xchg %ax,%ax
55a: 66 90 xchg %ax,%ax
55c: 66 90 xchg %ax,%ax
55e: 66 90 xchg %ax,%ax
我对生成的推送指令非常困惑。
- 如果按下两个4字节值,如何实现对齐?
- 为什么要推0x2014而不是0x14?什么是0x201d?
call 54b
甚至实现了什么?hd
的输出与objdump
匹配。为什么这在gdb中有所不同?这是动态链接器吗?
B+>│0x5655553d <main> mov %esp,%ebp │
│0x5655553f <main+2> pushl 0x5655701d │
│0x56555545 <main+8> push $0x56557014 │
│0x5655554a <main+13> call 0xf7e222d0 <printf> │
│0x5655554f <main+18> mov %ebp,%esp │
│0x56555551 <main+20> ret
关于实际执行二进制文件时所发生的事情的资源是值得赞赏的,因为我不知道实际发生了什么以及我读过的教程没有涵盖它。我正在通过How programs get run: ELF binaries阅读。
i386 System V ABI在call
之前保证/需要16字节堆栈对齐,就像我在你回答的链接中所说的那样。 (除非您正在调用私有帮助程序函数,在这种情况下,您可以组成自己的对齐规则,arg传递以及哪些寄存器被该函数破坏。)
如果违反此ABI要求,则允许函数崩溃或行为异常,但不是必需的。例如scanf
在x86-64 Ubuntu glibc(由最近的gcc编译)最近才开始这样做:scanf Segmentation faults when called from a function that doesn't change RSP
函数可以依赖于堆栈对齐的性能(对齐double
或double
s数组以避免访问时的缓存行拆分)。
通常,函数依赖于堆栈对齐以确保正确性的唯一情况是在编译时使用SSE / SSE2,因此它可以使用16字节对齐所需的加载/存储来复制结构或数组(movaps
或movdqa
),或实际在本地数组上自动矢量化循环。
我认为Ubuntu不会使用SSE编译他们的32位库(除了使用运行时调度的memcpy
之类的函数),因此他们仍然可以使用像Pentium II这样的古老CPU。 x86-64系统上的多树库应该采用SSE2,但是使用4字节指针时,32位函数不太可能有16字节结构要复制。
无论如何,无论什么原因,显然你的32位构建的glibc中的printf
实际上并不依赖于16字节堆栈对齐的正确性,因此即使你的堆栈不对齐也不会出错。
为什么要推0x2014而不是0x14?什么是0x201d?
0x14
(十进制20)是该位置的内存中的值。它将在运行时加载,因为你使用的是push r/m32
,而不是push $20
(或像.equ testint, 20
或testint = 20
那样的汇编时间常数)。
您使用gcc -m32
创建一个PIE(位置无关可执行文件),它在运行时重新定位,因为这是Ubuntu的gcc上的默认值。
0x2014
是相对于文件开头的偏移量。如果在运行程序后在运行时进行反汇编,则会看到一个真实的地址。
call 54b
也是如此。它是对PLT的调用(它靠近文件/文本段的开头,因此是低地址)。
如果你用objdump -drwC
反汇编,你会看到符号重定位信息。 (我也喜欢-Mintel
,但要注意它是MASM,而不是NASM)。
您可以链接gcc -m32 -no-pie
以制作经典的位置相关可执行文件。我肯定建议特别是对于32位代码,特别是如果你正在编译C,使用gcc -m32 -no-pie -fno-pie
来获取非PIE代码以及链接到非PIE可执行文件。 (有关PIE的更多信息,请参阅32-bit absolute addresses no longer allowed in x86-64 Linux?。)
以上是关于gcc x86-32堆栈对齐并调用printf的主要内容,如果未能解决你的问题,请参考以下文章
使用 GCC 但没有使用 Clang 的堆栈帧太大(过度对齐?)
为啥 gcc 4.x 在调用方法时默认为 linux 上的堆栈保留 8 个字节?