理解aarch64汇编函数调用,栈是如何操作的?



test.c(裸机)

#include <stdio.h>
int add1(int a, int b)
{
int c;
c = a + b;
return c;
}
int main()
{
int x, y, z;
x = 3;
y = 4;
z = add1(x,y);
printf("z = %dn", z);
}

我做aarch64-none-elf-gcc test.c -specs=rdimon.specs,得到a.out。我做了aarch64-none-elf-objdump -d a.out,得到了汇编代码。下面是主要功能:

00000000004002e0 <add1>:
4002e0:   d10083ff    sub sp, sp, #0x20       <-- reduce sp by 0x20 (just above it are saved fp and lr of main)
4002e4:   b9000fe0    str w0, [sp, #12]       <-- save first param x at sp + 12
4002e8:   b9000be1    str w1, [sp, #8]        <-- save second param y at sp + 8
4002ec:   b9400fe1    ldr w1, [sp, #12]       <-- load w1 with x
4002f0:   b9400be0    ldr w0, [sp, #8]        <-- load w0 with y
4002f4:   0b000020    add w0, w1, w0          <-- w0 = w1 + w0
4002f8:   b9001fe0    str w0, [sp, #28]       <-- store x0 to sp+28
4002fc:   b9401fe0    ldr w0, [sp, #28]       <-- load w0 with the result (seems redundant)
400300:   910083ff    add sp, sp, #0x20       <-- increment sp by 0x20
400304:   d65f03c0    ret
0000000000400308 <main>:
400308:   a9be7bfd    stp x29, x30, [sp, #-32]!   <-- save x29(fp) and x30(lr) at sp - 0x20
40030c:   910003fd    mov x29, sp                 <-- set fp to new sp, the base of stack growth(down)
400310:   52800060    mov w0, #0x3                    // #3
400314:   b9001fe0    str w0, [sp, #28]           <-- x is assigned in sp + #28
400318:   52800080    mov w0, #0x4                    // #4
40031c:   b9001be0    str w0, [sp, #24]           <-- y is assiged in sp + #24
400320:   b9401be1    ldr w1, [sp, #24]            <-- load func param for y
400324:   b9401fe0    ldr w0, [sp, #28]           <-- load func param for x
400328:   97ffffee    bl  4002e0 <add1>           <-- call add1 (args are in w0, w1)
40032c:   b90017e0    str w0, [sp, #20]           <-- store x0(result z) to sp+20
400330:   b94017e1    ldr w1, [sp, #20]           <-- load w1 with the result (why? seems redundant. it's already in w0)
400334:   d0000060    adrp    x0, 40e000 <__sfp_handle_exceptions+0x28>
400338:   91028000    add x0, x0, #0xa0  <-- looks like loading param x0 for printf
40033c:   940000e7    bl  4006d8 <printf>
400340:   52800000    mov w0, #0x0                    // #0 <-- for main's return value..
400344:   a8c27bfd    ldp x29, x30, [sp], #32  <-- recover x29 and x30 (look's like values in x29, x30 was used in the fuction who called main)
400348:   d65f03c0    ret
40034c:   d503201f    nop

我用<--标记添加了我的理解。有人能看到代码并给我一些修改吗?任何小小的评论都将不胜感激。(请参阅<main>)

补充:谢谢你的评论。我想我忘记问我真正的问题了。在main的开始,调用main的程序应该把它的返回地址(在main之后)放在x30中。由于main本身应该调用另一个函数,它应该修改x30,因此它将x30保存在堆栈中。但为什么要把它存储在sp - #0x20中呢?为什么变量x y z存储在sp + #20 sp + #24 sp + #28中?如果main函数调用printf,我猜sp和x29将减少一些量。这个数量是否取决于被调用的函数(这里是printf)使用了多少堆栈面积?还是常数?x29、x30在main中的存储位置是如何确定的?是否确定这两个值位于被调用函数(printf)的堆栈区域上方?对不起,问题太多了。

在为main布局堆栈时,编译器必须满足以下约束:

  • x29x30需要保存在堆栈上。每个占用8字节。

  • 局部变量x,y,z需要堆栈空间,每个4字节。(如果你正在优化,你会看到它们被保存在寄存器中,或者优化后完全不存在。)这使我们总共有8+8+4+4+4=28个字节。

  • 堆栈指针sp必须始终保持对齐为16字节;这是一个架构和ABI约束(操作系统可以选择放宽这个要求,但通常不会)。所以我们不能用sp减去28;我们必须四舍五入到16的下一个倍数,即32。

这就是你提到的32或0x20的来源。请注意,它完全用于main本身使用的堆栈内存。它不是一个普遍常数;如果你从main中添加或删除足够的局部变量,你会看到它的变化。

这与printf的需求无关。如果printf需要为自己的局部变量提供堆栈空间,那么printf中的代码将不得不相应地调整堆栈指针。编译器在编译main时不知道它有多大的空间,也不关心。

现在编译器需要在它为自己创建的32字节的堆栈空间中组织这五个对象x29, x30, x, y, z。除了下面这一点之外,在哪里放什么几乎完全可以任意选择。

函数的序言需要从堆栈指针中减去32,并将寄存器x29, x30存储在分配的空间中的某个地方。这一切都可以通过使用预索引存储对指令stp x29, x30, [sp, #-32]!在一条指令中完成。它从sp中减去32,然后将x29x30存储在从sp现在所在的地址开始的16字节中。因此,为了使用这条指令,我们必须接受将x29x30放置在分配空间的底部,[sp+0][sp+8]相对于spnew值的偏移量。把它们放在其他地方需要额外的指令,而且效率较低。

(实际上,因为这是最方便的方式,ABI实际上要求以这种方式设置堆栈帧,当它们被使用时,x29, x30以这种顺序在堆栈上连续(5.2.3)。)

我们仍然有16个字节从[sp+16]开始玩,x,y,z必须放在其中。编译器选择将它们分别放在地址[sp+28], [sp+24], [sp+20][sp+16]处的4个字节仍然未使用,但请记住,为了实现正确的堆栈对齐,我们必须在某个地方浪费4个字节。选择这些对象的排列方式,以及不使用哪个槽,完全是任意的,任何其他的排列方式都一样有效。

最新更新