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

understanding aarch64 assembly function call, how is stack operated

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 = %d\n", 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 main1 (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 的程序应该把它的 return 地址(在 main 之后)放在 x30 中。由于 main 本身应该调用另一个函数,它应该修改 x30,所以它将 x30 保存在它的堆栈中。但为什么它存储在 sp - #0x20 中?为什么变量 x、y、z 存储在 sp + #20、sp + #24、sp + #28 中?如果主函数调用 printf,我猜 sp 和 x29 会减少一些。这个数量是否取决于被调用函数(这里是 printf)使用了多少堆栈区域?还是恒定的?以及如何确定 main 中的 x29、x30 存储位置?是否确定这两个值位于被调用函数(printf)的堆栈区域的正上方?抱歉问题太多。

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

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

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

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

这就是您提到的 32 或 0x20 的来源。请注意,它完全用于 main 本身使用的堆栈内存。它不是一个普遍的常数;如果您从 main.

添加或删除足够的局部变量,您会看到它发生变化

它与 printf 的任何需求无关。如果 printf 需要堆栈 space 作为它自己的局部变量,那么 printf 中的代码将不得不相应地调整堆栈指针。编译器在编译 main 时不知道 space 是多少,也不关心。

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

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

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

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