函数调用过程的汇编代码分析(arm64指令集),顺便关注一下栈空间的分配与回收。

下述C语言程序包含了一个简单的add()函数,该函数接受两个整型参数,计算并返回两者的和。

main()函数调用执行了add()函数来计算3+2的和,并将计算结果打印出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int add(int a, int b){
int t = a + b;
return t;
}

int main(){
int x = 3;
x = add(x,2);
printf("%d",x);
return 1;
}

为了从机器指令层面理解上述函数调用过程中的传参、跳转、返回等行为,我们在浏览器中访问了下述网站:

image-20220514225243460

在粘贴相关代码并选择ARM64 gcc 9.3选项后,我们得到下述汇编语言指令序列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
add(int, int):
sub sp, sp, #32
str w0, [sp, 12]
str w1, [sp, 8]
ldr w1, [sp, 12]
ldr w0, [sp, 8]
add w0, w1, w0
str w0, [sp, 28]
ldr w0, [sp, 28]
add sp, sp, 32
ret
.LC0:
.string "%d"
main:
stp x29, x30, [sp, -32]!
mov x29, sp
mov w0, 3
str w0, [sp, 28]
mov w1, 2
ldr w0, [sp, 28]
bl add(int, int)
str w0, [sp, 28]
ldr w1, [sp, 28]
adrp x0, .LC0
add x0, x0, :lo12:.LC0
bl printf
mov w0, 1
ldp x29, x30, [sp], 32
ret

第2 ~ 11行:函数add()的实体

第2行:将栈指针(stack pointer)寄存器sp的值减去32,结果仍存储到sp中。显然,该指令事实上为自动变量分配了栈空间。

第10行:在函数返回前,将栈指针(stack pointer)寄存器的值增加32,结果仍存储到sp中。显然,该指令事实上回收了栈空间,回收的数量与分配的数量一致。

第3行:将寄存器w0中的32位数值存储到内存地址sp+12处,结合后续代码可以知道,sp+12处存储的即是形参a。

第4行:将寄存器w1中的32位数值存储到内存地址sp+8处,结合后续代码可以知道,sp+8处存储的即是形参b。

第5行:将存储于sp+12处的形参a值装入寄存器w1。

第6行:将存储于sp+8处的形参b值装入寄存器w0。

第7行:将寄存器w1和w0的值相加,结果存入w0,此处事实上相加的即是形参a和b。

第8行:将寄存器w0中的32位数值存入地址sp+28处。可以推测,sp+28处存储的即为自动变量t。

第9行:将sp+28处的自动变量t装入寄存器w0, 结合后续代码可知,w0寄存器被用于向函数的调用者返回计算结果。

第11行:返回x30寄存器所标识的地址继续执行,此时,x30寄存器所标识的地址为上述代码的第22行。

第14 ~ 29行:函数main()的实体

第17行:将整数3存入w0寄存器。

第18行:将w0中的32位值存入sp+28处。合理推测,sp+28处为变量x的存放位置。

第19行:将整数2存入w1寄存器,结合add()代码可知,add()通过w1寄存器获得参数b。

第20行:将sp+28处的变量x的值存入寄存器w0。结合add()代码可知,add()通过w0寄存器获得参数a。

第 21行:在准备好函数调用的参数(a,b分别在w0和w1寄存器内)后,branch & link指令完成两件工作:

  • 将pc+4的值存入x30寄存器,其中,pc表示当前指令地址(第21行),pc+4即指向当前指令的下一条,也就是第22行。
  • 跳转到add()函数函数,即第2行。

当add()函数完成任务后,第11行ret指令将返回x30寄存器标识的地址继续执行,即返回代码的第22行。

第22行:将w0寄存器的23位数值装入内存地址sp+28处。结合add()函数代码可知,此时的w0寄存器事实上存储的是add()函数的计算结果。这行代码事实上取得了add()函数的返回值,并将其存入x变量(sp+28)。

在代码的后续部分,我们还看到main()函数对printf()函数的调用… 略。

总结

  • 函数执行过程中,确实通过修改sp(栈顶指针)的值来分配和回收栈空间,栈空间用于存储自动变量。
  • 在寄存器够用的情况下,程序会尽量通过寄存器传参,并获取返回值。因为寄存器的访问速度比内存快得多。