建议先看《CSAPP》的3.7节,讲的很细。我们这里就直接看例子来分析了。
例子
static int func(int a, int b, int c, int d, int e, int f, int g, int h, int i)
{
printf("%s\n", "add all");
int x = a + b;
return a + b + c + d + e + f + g + h + i;
}
int main()
{
getchar();
int result = func(1, 2, 3, 4, 5, 6, 7, 8, 9);
printf("result = %d\n", result);
return 0;
}
上面的程序,我们写了一个 func 的调用,主要是观察其参数传递时,寄存器的使用以及栈的变化情况。
在ARM64体系结构中,栈是从高地址往低地址生长。栈在函数调用过程中起到非常重要的作用,包括存储函数使用的局部变量、传递参数等。在函数调用过程中,栈是逐步生成的。为单个函数分配的栈空间,即从该函数栈底(高地址)到栈顶(低地址)这段空间称为栈帧(Stack Frame)。
ARM64在函数参数传递时,参数1~参数8 分别保存到 X0~X7 寄存器中 ,剩下的参数从右往左依次入栈,被调用者实现栈平衡,返回值存放在 X0 中。
ARM在参数传递时,参数1~参数4 分别保存到 R0~R3 寄存器中 ,剩下的参数从右往左依次入栈,被调用者实现栈平衡,返回值存放在 R0 中。
我们看下上面程序的反汇编代码,有个地方需要注意,为了观察最原始的汇编代码需要在Applicataion.mk
里面添加一个选项,否则编译器会优化一些逻辑:
APP_OPTIM := debug
.text:0000000000000784 A8 03 5F B8 LDUR W8, [X29,#var_10]
.text:0000000000000788 E0 03 08 2A MOV W0, W8 ; a
.text:000000000000078C A1 C3 5E B8 LDUR W1, [X29,#var_14] ; b
.text:0000000000000790 A2 83 5E B8 LDUR W2, [X29,#var_18] ; c
.text:0000000000000794 A3 43 5E B8 LDUR W3, [X29,#var_1C] ; d
.text:0000000000000798 E4 23 40 B9 LDR W4, [SP,#0x40+e] ; e
.text:000000000000079C E5 1F 40 B9 LDR W5, [SP,#0x40+f] ; f
.text:00000000000007A0 E6 1B 40 B9 LDR W6, [SP,#0x40+g] ; g
.text:00000000000007A4 E7 17 40 B9 LDR W7, [SP,#0x40+h] ; h
.text:00000000000007A8 EA 03 00 91 MOV X10, SP
.text:00000000000007AC E9 13 40 B9 LDR W9, [SP,#0x40+var_30]
.text:00000000000007B0 49 01 00 B9 STR W9, [X10,#0x40+i] ; i
.text:00000000000007B4 0A 00 00 94 BL func
可以看到 a b c d e f g h,这8个参数是存放到了 W0 - W7 中,W 是 X 的32 位形式。
对于参数 i 的处理分3步:
MOV X10, SP ;将sp的值给X10
LDR W9, [SP,#0x40+var_30] ;将9赋值给W9
STR W9, [X10,#0x40+i] ;将9储存到 sp+offset 中
是先将栈地址赋值给了 X10,然后将 i 的值存放到 W9 指向的地址里面,结合起来理解就是将 i 的值放到了栈里面。
我们动态的调试一下,观察栈变化:
前面的X0到X7的寄存器变化是符合预期的。
继续往下看,断点走到BL处,发现栈变化:
发现,将 9 的值储存到了 sp 的位置,偏移量为0。说明 [X10,#0x40+i]
计算出来的值就是 [X10],我们可以在这里按下Q键,看真实的汇编:
继续往下看,看跳转到 func 函数里面后栈的变化情况。一直走到 func 的 ret 指令,看栈的内容:
0000007FE24A0AE8 0000000900000003
0000007FE24A0AF0 0000000700000008
0000007FE24A0AF8 0000000500000006
0000007FE24A0B00 0000000300000004
0000007FE24A0B08 0000000100000002
0000007FE24A0B10 0000007FE24A0B60 [stack]:0000007FE24A0B60
0000007FE24A0B18 000000632CC977B8 main+A4
0000007FE24A0B20 0000000000000009
可以看到,栈底首先储存的是第9个参数。
然后是返回地址,就是函数执行完之后需要执行的下一条指令的地址。
然后接着是栈指针,这个栈指针是 main 函数的栈地址,因为func函数执行完之后,需要将栈指针给还原,mian才好继续执行。
然后接着是参数,这是因为函数的逻辑可能需要使用到 X0 到 X7寄存器。所以先将寄存器的值储存到栈中,方便后续获取。
最后是局部变量,它的值也是放到了栈中。