据我所知,x86-64要求堆栈在调用前为16字节对齐,而gcc with -m32
doesn't require this for main。
我有以下测试代码:
.data
intfmt: .string "int: %d\n"
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的方式打印ints并浮动。
进入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
我对生成的推送指令非常困惑。
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。
答案 0 :(得分:2)
i386 System V ABI做保证/要求call
之前必须进行16字节的堆栈对齐,就像我在回答的开头回答的那样。 (除非您要调用私有帮助器函数,否则可以编写自己的对齐,arg传递规则以及该函数的寄存器被破坏。)
如果您违反此ABI要求,但允许崩溃或行为不正常,。例如, x86-64 Ubuntu glibc(由最新的gcc编译)中的scanf
只是最近才开始这样做:scanf Segmentation faults when called from a function that doesn't change RSP
函数的性能可能取决于堆栈对齐方式(对齐double
或double
的数组以避免访问时发生高速缓存行拆分)。
通常,唯一的函数依赖于正确性的堆栈对齐的情况是编译为使用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?。)