我试图清楚地了解谁(调用方或被调用方)负责堆栈对齐。64位汇编的情况很清楚,它是由 caller进行的 。
参考系统V AMD64 ABI,第3.2.2节 堆栈框架 :
输入参数区域的末尾应在16(如果在堆栈上通过__m256,则为32)字节边界对齐。
换句话说, 应该 安全地假设,对于被调用函数的每个入口点:
16 | (%rsp + 8)
保持(额外的八个是因为call
隐式将返回地址压入堆栈)。
在32位世界中的外观(假设cdecl)?我注意到使用以下构造gcc
将对齐方式 放置 在调用的函数 内 :
and esp, -16
这似乎表明,这是 被叫方的 责任。
更清楚地说,请考虑以下NASM代码:
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
在调用之前是否需要对齐堆栈scanf
?如果是这样,那么%esp
在将这两个参数推为scanf
as 之前,这需要减少四个字节。
4 bytes (return address)
4 bytes (%ebp of previous stack frame)
8 bytes (for two variables)
12 bytes (three arguments for scanf)
= 28
GCC 仅 在中进行此额外的堆栈对齐main
;该功能很特别。
如果您查看其他任何函数的代码源,除非您拥有带有alignas(32)
或的局部语言,否则您将看不到它。
GCC只是采取了一种防御性的方法-m32
,不假设main
使用正确的16B对齐堆栈来调用该方法。还是从当初-mpreferred-stack- boundary=4
只是一个好主意,而不是法律而留下的特殊待遇。
多年以来,i386 System V ABI一直保证/要求ESP + 4在功能上进行16B对齐。(即,ESP必须 在 CALL指令 之前 对齐16B
,因此堆栈上的args从16B边界开始。这与x86-64 System V相同。)
ABI还保证新的32位进程以在16B边界上对齐的ESP开始(例如_start
,在ELF入口点,ESP指向argc,而不是返回地址),并且glibc
CRT代码保持该对齐。
就调用约定而言,EBP只是另一个保留呼叫的寄存器。但是,是的,编译器的输出-fno-omit-frame-pointer
确实push ebp
要先于其他保留呼叫的寄存器(例如EBX),因此保存的EBP值会形成一个链表。(因为mov ebp, esp
在完成该推送之后,它还负责设置帧指针。)
也许gcc是防御性的,因为一个非常古老的Linux内核(从i386
ABI修订版之前的版本开始,当时所需的对齐方式仅为4B)可能违反了这一假设,而且这只是一条额外的指令,在运行期间存在一次流程(假设程序没有main
递归调用)。
与gcc不同,clang假设堆栈在进入main时已正确对齐。clang还[假设狭窄的args已被符号化或零扩展为32位,即使当前的ABI修订版尚未指定该行为(尚未)。gcc和clang都发出在调用方执行的代码,但仅clang取决于被调用方。这发生在64位代码中,但我没有检查32位。)
如果您有兴趣,可以在http://gcc.godbolt.org/上查看编译器输出,以获取main和main以外的功能。
前几天,我刚刚更新了x86标签Wiki中的ABI链接。
http://x86-64.org/仍然死了,似乎还没有回来,所以我更新了System V链接,以指向HJ
Lu的github repo中当前版本的PDF以及他的带有links的页面。
请注意,SCO网站上的最新版本 不是
当前版本,并且不包括16B堆栈对齐要求。
我认为某些BSD版本仍然不需要/保持16字节堆栈对齐。
我试图清楚地了解谁(调用者或被调用者)负责堆栈对齐。64位程序集的情况相当清楚,它是由调用者完成的。 参考System V AMD64 ABI,第3.2.2节堆栈框架: 输入参数区域的末尾应在16(32,如果__m256在堆栈上传递)字节边界上对齐。 换句话说,应该可以安全地假设,对于被调用函数的每个切入点: 保持(额外的8是因为调用隐式地将返回地址推送到堆栈上)。 它在32位世界中看起来如何(假
我在不同的地方读到过这样做是出于“性能原因”,但我仍然想知道这种16字节对齐方式在哪些特定情况下提高了性能。或者,无论如何,选择它的原因是什么。 编辑:我认为我写这个问题的方式有误导性。我不是在问为什么处理器使用16字节对齐的内存会更快,这在文档中随处都有解释。相反,我想知道的是,强制的16字节对齐如何优于在需要时让程序员自己对齐堆栈。我这样问是因为根据我在汇编方面的经验,堆栈强制有两个问题:它只
本文向大家介绍Intel x86 Assembly& Microarchitecture 32位寄存器,包括了Intel x86 Assembly& Microarchitecture 32位寄存器的使用技巧和注意事项,需要的朋友参考一下 示例 英特尔生产80386时,他们从16位处理器升级到了32位处理器。32位处理意味着两件事:要处理的数据都是32位,而要访问的内存地址是32位。为此,他们仍然
我在一个堆栈中有小部件,所以我想将我的按钮栏放在堆栈的底部中心,但什么都不起作用。小部件只是粘在左边。这是我的密码。 我已经试过了所有的中心对齐,请帮忙
我来自C/C++背景,在这里一个进程内存分为: null 我想把我的注意力集中在这一点上,当我阅读JVM中的堆和堆栈时,我们是在谈论堆栈和堆的概念吗?并且整个JVM的实际内存驻留在堆上(这里指的是堆的C++概念)?
libdyld.dylib`STACK_NOT_16_BYTE_ALIGNED_ERROR:->0x7FFFC12DA2FA<+0>:movdqa%xMM0,(%RSP)0x7FFFC12DA2FF<+5>:int3 libdyld.dylib`_dyld_func_lookup:0x7fffc12da300<+0>:pushq%rbp 0x7fffc12da301<+1>:movq%rsp,%r