AMD has an ABI specification that describes the calling convention to use on x86-64. All OSes follow it, except for Windows which has it's own x86-64 calling convention. Why?
Does anyone know the technical, historical, or political reasons for this difference, or is it purely a matter of NIHsyndrome?
I understand that different OSes may have different needs for higher level things, but that doesn't explain why for example the register parameter passing order on Windows is rcx - rdx …
我一直在努力深入了解编译器如何生成机器代码,更具体地说是GCC如何处理堆栈.在这样做的过程中,我一直在编写简单的C程序,将它们编译成汇编并尽力理解结果.这是一个简单的程序及其生成的输出:
asmtest.c:
void main() {
char buffer[5];
}
Run Code Online (Sandbox Code Playgroud)
asmtest.s:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
leave
ret
Run Code Online (Sandbox Code Playgroud)
让我感到困惑的是为什么要为堆栈分配24个字节.我知道由于处理器如何寻址内存,堆栈必须以4为增量进行分配,但如果是这种情况,我们应该只将堆栈指针移动8个字节而不是24个.作为参考,缓冲区为17 bytes产生一个移动40个字节的堆栈指针,并且根本没有缓冲区移动堆栈指针8. 1到16个字节之间的缓冲区移动ESP24个字节.
现在假设8个字节是必要的常量(它需要什么?),这意味着我们分配16个字节的块.为什么编译器会以这种方式对齐?我正在使用x86_64处理器,但即使是64位字也只需要8字节对齐.为什么会出现差异?
作为参考,我正在使用gcc 4.0.1运行10.5的Mac上进行编译,并且未启用任何优化.
什么是堆栈对齐?为什么用它?可以通过编译器设置来控制吗?
这个问题的细节来自于尝试将ffmpeg库与msvc一起使用时遇到的问题,但我真正感兴趣的是对"堆栈对齐"的解释.
细节:
谢谢,
担
我可以理解旧PPC RISC系统的这个要求,甚至是x86-64,但是对于旧的,经过验证的x86?在这种情况下,堆栈只需要在4字节边界上对齐.是的,某些MMX/SSE指令需要16字节对齐,但如果这是被调用者的要求,则应确保对齐正确.为什么要为每个来电者增加这项额外要求?这实际上可能会导致性能下降,因为每个呼叫站点都必须管理此要求.我错过了什么吗?
更新:在对此进行一些调查并与一些内部同事进行一些咨询后,我对此有一些理论:
我对最后一项的问题是,对于依赖于被调用者清理堆栈的调用约定,上述要求实际上 "uglify"了codegen.例如,某些编译器决定为自己的内部使用实现更快的基于寄存器的调用样式(即任何不打算从其他语言或源调用的代码)?这种堆栈对齐可能会通过在寄存器中传递一些参数来抵消一些性能提升.
更新:到目前为止,唯一真正的答案是一致性,但对我来说,答案有点太容易了.我有超过20年的x86架构经验,如果一致性,而不是性能,或其他具体的东西,那么我真的是因为我恭敬地建议开发人员要求它有点天真.他们忽略了近三十年的工具和支持.特别是如果他们期望工具供应商能够快速轻松地为他们的平台调整他们的工具(可能不是......这是 Apple ......),而不必跳过几个看似不必要的箍.
我会在另一天左右给出这个话题,然后关闭它......
int read_val();
long read_and_process(int n) {
long vals[n];
for (int i = 0; i < n; i++)
vals[i] = read_val();
return vals[n-1];
}
Run Code Online (Sandbox Code Playgroud)
x86-64 GCC 5.4编译的汇编语言代码为:
read_and_process(int):
pushq %rbp
movslq %edi, %rax
>>> leaq 22(,%rax,8), %rax
movq %rsp, %rbp
pushq %r14
pushq %r13
pushq %r12
pushq %rbx
andq $-16, %rax
leal -1(%rdi), %r13d
subq %rax, %rsp
testl %edi, %edi
movq %rsp, %r14
jle .L3
leal -1(%rdi), %eax
movq %rsp, %rbx
leaq 8(%rsp,%rax,8), %r12
movq %rax, %r13
.L4:
call read_val()
cltq …Run Code Online (Sandbox Code Playgroud) 有没有办法将打包的双字整数从XMM寄存器推送到堆栈?然后在需要时弹出它?
理想情况下,我正在寻找像PUSH或POP这样的通用寄存器,我已经检查了英特尔手册,但我要么错过了命令,要么没有一个...
或者我是否必须将值解压缩到通用寄存器然后推送它们?
在Kip Irvine 的《Assembly Language, Seventh Edition for x86 Processors》第 211 页中,它在5.53 The x86 Calling Convention下说,它解决了 Microsoft x64 Calling Convention,
- 调用子程序时,堆栈指针 (
RSP) 必须在 16 字节边界(16 的倍数)上对齐。该CALL指令将 8 字节返回地址压入堆栈,因此调用程序除了已经为影子空间减去 32 之外,还必须从堆栈指针中减去 8 。
它继续显示一些sub rsp, 8在 之前带有 的程序集sub rsp, 20h(对于 32 字节的影子空间)。
但这是一个安全的约定吗?Microsoft 堆栈是否保证在指令之前按 16 字节对齐CALL?或者,这本书假设堆栈是错误的
CALLCALLsub rsp, 8;才能恢复到 16 字节对齐吗?我正在阅读一本教科书,其中显示了基于 C 代码的汇编代码:
代码:
void echo()
{
char buf[8];
otherFunction(buf);
}
Run Code Online (Sandbox Code Playgroud)
汇编代码:
echo:
subq $24, %rsp //Allocate 24 bytes on stack, but why allocate 24 instead of 8 bytes?
movq %rsp, %rdi //Compute buf as %rsp
call otherFunction
Run Code Online (Sandbox Code Playgroud)
我不明白为什么堆栈指针%rsp会减少 24 个字节。我只将 8 个字节的缓冲区分配为char buf[8];,并且没有被调用者保存的寄存器压入堆栈,指令不应该是
subq $8, %rsp
Run Code Online (Sandbox Code Playgroud) 我试图清楚地了解谁(调用者或被调用者)负责堆栈对齐.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 …Run Code Online (Sandbox Code Playgroud) 我正在阅读“计算机系统:程序员的视角,3/E”(CS:APP3e),以下代码是书中的示例:
long call_proc() {
long x1 = 1;
int x2 = 2;
short x3 = 3;
char x4 = 4;
proc(x1, &x1, x2, &x2, x3, &x3, x4, &x4);
return (x1+x2)*(x3-x4);
}
Run Code Online (Sandbox Code Playgroud)
书中给出了GCC生成的汇编代码:
long call_proc()
call_proc:
; Set up arguments to proc
subq $32, %rsp ; Allocate 32-byte stack frame
movq $1, 24(%rsp) ; Store 1 in &x1
movl $2, 20(%rsp) ; Store 2 in &x2
movw $3, 18(%rsp) ; Store 3 in &x3
movb $4, 17(%rsp) ; Store 4 in …Run Code Online (Sandbox Code Playgroud)