Grz*_*ski 8 linux x86 assembly gcc memory-alignment
我试图清楚地了解谁(调用者或被调用者)负责堆栈对齐.64位汇编的情况相当清楚,它是由调用者.
参考System V AMD64 ABI,第3.2.2节"堆栈帧":
输入参数区域的末尾应在16(32,如果在堆栈上传递__m256)字节边界上对齐.
换句话说,应该可以安全地假设,对于被调用函数的每个入口点:
16 | (%rsp + 8)
保持(额外八个是因为call隐含地在堆栈上推送返回地址).
它在32位世界中的表现(假设是cdecl)?我注意到,使用以下构造gcc将对齐放置在被调用函数内:
and esp, -16
Run Code Online (Sandbox Code Playgroud)
这似乎表明,这是被召唤者的责任.
为了更清楚,请考虑以下代码:
global main
extern printf
extern scanf
section .rodata
s_fmt db "%d %d", 0
s_res db `%d with remainder %d\n`, 0
section .text
main:
start 0, 0
sub esp, 8
mov DWORD [ebp-4], 0 ; dividend
mov DWORD [ebp-8], 0 ; divisor
lea eax, [ebp-8]
push eax
lea eax, [ebp-4]
push eax
push s_fmt
call scanf
add esp, 12
mov eax, [ebp-4]
cdq
idiv DWORD [ebp-8]
push edx
push eax
push s_res
call printf
xor eax, eax
leave
ret
Run Code Online (Sandbox Code Playgroud)
是否需要在调用之前对齐堆栈scanf?如果是这样,那么%esp在将这两个参数推送到之前,这将需要减少四个字节scanf:
4 bytes (return address)
4 bytes (%ebp of previous stack frame)
8 bytes (for two variables)
12 bytes (three arguments for scanf)
= 28
Run Code Online (Sandbox Code Playgroud)
Pet*_*des 10
gcc只是采取防御性方法main,而不是假设alignas(32)使用正确的16B对齐堆栈调用.
多年来,i386 System V ABI保证/要求ESP + 4在进入功能时进行16B对齐.(即ESP必须在 CALL指令之前进行16B对齐,因此堆栈上的args从16B边界开始.这与x86-64系统V相同.)
ABI还保证新的32位进程以ESP在16B边界上对齐开始(例如-m32,在ELF入口点,ESP指向argc,而不是返回地址),并且glibc CRT代码保持该对齐.
就调用约定而言,EBP只是另一个调用保留寄存器.但是,编译器输出main确实-mpreferred-stack-boundary=4在其他调用保留寄存器(如EBX)之前完成,并且即使函数不需要使用EBP也这样做,因此保存的EBP值形成链表.
也许gcc是防御性的,因为一个非常古老的Linux内核(从i386 ABI修订之前,当所需的对齐只有4B时)可能违反了这个假设,并且它只是在生命周期中运行一次的额外几个指令.进程(假设程序没有_start递归调用).
与gcc不同,clang假设堆栈在进入main时正确对齐.(clang还假设窄args已经签名或零扩展到32位,即使当前的ABI版本没有指定该行为(尚未).gcc和clang都发出在调用者端执行的代码,但只是铿锵取决于它在被调用者.这发生在64位代码,但我没有检查32位.)
如果你很好奇,请查看http://gcc.godbolt.org/上的编译器输出,了解main以外的main和函数.
我刚刚更新了x86标签wiki中的ABI链接. http://x86-64.org/仍然死了,似乎没有回来,所以我更新了System V链接以指向HJ Lu的github repo中当前版本的PDF,以及带有链接的页面.
请注意,SCO站点上的最新版本不是当前版本,并且不包括16B堆栈对齐要求.
| 归档时间: |
|
| 查看次数: |
4196 次 |
| 最近记录: |