ARM64汇编0B - 函数调用约定

建议先看《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寄存器。所以先将寄存器的值储存到栈中,方便后续获取。

最后是局部变量,它的值也是放到了栈中。

  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值