注册

汇编-函数本质(下)

篇幅限制,分为2篇


返回值



函数的返回值一般是一个指针,不会超过8字节。X0寄存器就完全够用了。如果要返回一个结构体类型超过8字节。
下面的例子(str结构体占用24字节):

struct str {
int a;
int b;
int c;
int d;
int e;
int f;
};

struct str getStr(int a, int b, int c, int d, int e, int f) {
struct str str1;
str1.a = a;
str1.b = b;
str1.c = c;
str1.d = d;
str1.e = e;
str1.f = f;
return str1;
}

- (void)viewDidLoad {
[super viewDidLoad];
struct str str2 = getStr(1,2,3,4,5,6);
}

汇编代码:

TestDemo`-[ViewController viewDidLoad]:
0x1042b5e58 <+0>: sub sp, sp, #0x50 ; =0x50
0x1042b5e5c <+4>: stp x29, x30, [sp, #0x40]
0x1042b5e60 <+8>: add x29, sp, #0x40 ; =0x40
0x1042b5e64 <+12>: stur x0, [x29, #-0x8]
0x1042b5e68 <+16>: stur x1, [x29, #-0x10]
0x1042b5e6c <+20>: ldur x8, [x29, #-0x8]
0x1042b5e70 <+24>: add x9, sp, #0x20 ; =0x20
0x1042b5e74 <+28>: str x8, [sp, #0x20]
0x1042b5e78 <+32>: adrp x8, 4
0x1042b5e7c <+36>: add x8, x8, #0x418 ; =0x418
0x1042b5e80 <+40>: ldr x8, [x8]
0x1042b5e84 <+44>: str x8, [x9, #0x8]
0x1042b5e88 <+48>: adrp x8, 4
0x1042b5e8c <+52>: add x8, x8, #0x3e8 ; =0x3e8
0x1042b5e90 <+56>: ldr x1, [x8]
0x1042b5e94 <+60>: mov x0, x9
0x1042b5e98 <+64>: bl 0x1042b6564 ; symbol stub for: objc_msgSendSuper2
//x8指向栈空间的区域,预留足够的空间
0x1042b5e9c <+68>: add x8, sp, #0x8 ; =0x8
0x1042b5ea0 <+72>: mov w0, #0x1
0x1042b5ea4 <+76>: mov w1, #0x2
0x1042b5ea8 <+80>: mov w2, #0x3
0x1042b5eac <+84>: mov w3, #0x4
0x1042b5eb0 <+88>: mov w4, #0x5
0x1042b5eb4 <+92>: mov w5, #0x6
0x1042b5eb8 <+96>: bl 0x1042b5e04 ; getStr at ViewController.m:59
-> 0x1042b5ebc <+100>: ldp x29, x30, [sp, #0x40]
0x1042b5ec0 <+104>: add sp, sp, #0x50 ; =0x50
0x1042b5ec4 <+108>: ret
str函数:

    TestDemo`getStr:
-> 0x1001d1e04 <+0>: sub sp, sp, #0x20 ; =0x20
//参数分别放入栈中
0x1001d1e08 <+4>: str w0, [sp, #0x1c]
0x1001d1e0c <+8>: str w1, [sp, #0x18]
0x1001d1e10 <+12>: str w2, [sp, #0x14]
0x1001d1e14 <+16>: str w3, [sp, #0x10]
0x1001d1e18 <+20>: str w4, [sp, #0xc]
0x1001d1e1c <+24>: str w5, [sp, #0x8]

//取出来放入w9,
0x1001d1e20 <+28>: ldr w9, [sp, #0x1c]
//存入x8,也就是上一个栈中直到写完
0x1001d1e24 <+32>: str w9, [x8]
0x1001d1e28 <+36>: ldr w9, [sp, #0x18]
0x1001d1e2c <+40>: str w9, [x8, #0x4]
0x1001d1e30 <+44>: ldr w9, [sp, #0x14]
0x1001d1e34 <+48>: str w9, [x8, #0x8]
0x1001d1e38 <+52>: ldr w9, [sp, #0x10]
0x1001d1e3c <+56>: str w9, [x8, #0xc]
0x1001d1e40 <+60>: ldr w9, [sp, #0xc]
0x1001d1e44 <+64>: str w9, [x8, #0x10]
0x1001d1e48 <+68>: ldr w9, [sp, #0x8]
0x1001d1e4c <+72>: str w9, [x8, #0x14]
//栈平衡,这里没有以 x0 作为返回值,已经全部写入上一个函数栈x8中。
0x1001d1e50 <+76>: add sp, sp, #0x20 ; =0x20
0x1001d1e54 <+80>: ret
这里没有使用X0作为返回值,而是使用了栈空间。

9f46ca18ca4429433fb2fb09537ee205.png

如果返回值大于8字节,也会保存在栈中返回(上一个函数栈空间)

那么结构体参数超过8个呢?
猜测参数和返回值都存在上一个函数的栈中,参数应该在低地址。返回值在高地址。


struct str {
int a;
int b;
int c;
int d;
int e;
int f;
int g;
int h;
int i;
int j;
};

struct str getStr(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) {
struct str str1;
str1.a = a;
str1.b = b;
str1.c = c;
str1.d = d;
str1.e = e;
str1.f = f;
str1.g = g;
str1.h = h;
str1.i = i;
str1.j = j;
return str1;
}

- (void)viewDidLoad {
[super viewDidLoad];
struct str str2 = getStr(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
printf("%d",func(10,20));
}

⚠️:有两个函数 A BA -> B,在B执行完后A传递给B的参数释放了么?
在上面的例子中910没有释放,相当于A的局部变量。

对应的汇编代码:

TestDemo`-[ViewController viewDidLoad]:
//函数开始
0x100c31ee4 <+0>: sub sp, sp, #0x60 ; =0x60
0x100c31ee8 <+4>: stp x29, x30, [sp, #0x50]
0x100c31eec <+8>: add x29, sp, #0x50 ; =0x50

//参数入栈
0x100c31ef0 <+12>: stur x0, [x29, #-0x8]
0x100c31ef4 <+16>: stur x1, [x29, #-0x10]
//x8获取参数x0
0x100c31ef8 <+20>: ldur x8, [x29, #-0x8]
//x9指向 x29 - 0x20
0x100c31efc <+24>: sub x9, x29, #0x20 ; =0x20
//x8 存入 x29 - 0x20
0x100c31f00 <+28>: stur x8, [x29, #-0x20]

//address page 内存中取数据
0x100c31f04 <+32>: adrp x8, 4
0x100c31f08 <+36>: add x8, x8, #0x418 ; =0x418
//x8 所指的内存取出来
0x100c31f0c <+40>: ldr x8, [x8]
0x100c31f10 <+44>: str x8, [x9, #0x8]
0x100c31f14 <+48>: adrp x8, 4
0x100c31f18 <+52>: add x8, x8, #0x3e8 ; =0x3e8
0x100c31f1c <+56>: ldr x1, [x8]
0x100c31f20 <+60>: mov x0, x9
0x100c31f24 <+64>: bl 0x100c32584 ; symbol stub for: objc_msgSendSuper2
//x8指向 sp + 0x8
0x100c31f28 <+68>: add x8, sp, #0x8 ; =0x8
0x100c31f2c <+72>: mov w0, #0x1
0x100c31f30 <+76>: mov w1, #0x2
0x100c31f34 <+80>: mov w2, #0x3
0x100c31f38 <+84>: mov w3, #0x4
0x100c31f3c <+88>: mov w4, #0x5
0x100c31f40 <+92>: mov w5, #0x6
0x100c31f44 <+96>: mov w6, #0x7
0x100c31f48 <+100>: mov w7, #0x8
//sp的值给x9
0x100c31f4c <+104>: mov x9, sp
//9 w10
0x100c31f50 <+108>: mov w10, #0x9
//w10写入 x9 所指向的地址
0x100c31f54 <+112>: str w10, [x9]
//10 w10
0x100c31f58 <+116>: mov w10, #0xa
//w10写入 x9 所指向的地址 偏移4个字节
0x100c31f5c <+120>: str w10, [x9, #0x4]
//跳转getStr
0x100c31f60 <+124>: bl 0x100c31e58 ; getStr at ViewController.m:31

//函数结束
-> 0x100c31f64 <+128>: ldp x29, x30, [sp, #0x50]
0x100c31f68 <+132>: add sp, sp, #0x60 ; =0x60
0x100c31f6c <+136>: ret
str:

TestDemo`getStr:
//开辟空间
0x100c31e58 <+0>: sub sp, sp, #0x30 ; =0x30
//从上一个栈空间 获取9 10
0x100c31e5c <+4>: ldr w9, [sp, #0x30]
0x100c31e60 <+8>: ldr w10, [sp, #0x34]
//参数入栈
0x100c31e64 <+12>: str w0, [sp, #0x2c]
0x100c31e68 <+16>: str w1, [sp, #0x28]
0x100c31e6c <+20>: str w2, [sp, #0x24]
0x100c31e70 <+24>: str w3, [sp, #0x20]
0x100c31e74 <+28>: str w4, [sp, #0x1c]
0x100c31e78 <+32>: str w5, [sp, #0x18]
0x100c31e7c <+36>: str w6, [sp, #0x14]
0x100c31e80 <+40>: str w7, [sp, #0x10]
0x100c31e84 <+44>: str w9, [sp, #0xc]
0x100c31e88 <+48>: str w10,[sp, #0x8]

//获取参数分别存入上一个栈x8所指向的地址中
-> 0x100c31e8c <+52>: ldr w9, [sp, #0x2c]
0x100c31e90 <+56>: str w9, [x8]
0x100c31e94 <+60>: ldr w9, [sp, #0x28]
0x100c31e98 <+64>: str w9, [x8, #0x4]
0x100c31e9c <+68>: ldr w9, [sp, #0x24]
0x100c31ea0 <+72>: str w9, [x8, #0x8]
0x100c31ea4 <+76>: ldr w9, [sp, #0x20]
0x100c31ea8 <+80>: str w9, [x8, #0xc]
0x100c31eac <+84>: ldr w9, [sp, #0x1c]
0x100c31eb0 <+88>: str w9, [x8, #0x10]
0x100c31eb4 <+92>: ldr w9, [sp, #0x18]
0x100c31eb8 <+96>: str w9, [x8, #0x14]
0x100c31ebc <+100>: ldr w9, [sp, #0x14]
0x100c31ec0 <+104>: str w9, [x8, #0x18]
0x100c31ec4 <+108>: ldr w9, [sp, #0x10]
0x100c31ec8 <+112>: str w9, [x8, #0x1c]
0x100c31ecc <+116>: ldr w9, [sp, #0xc]
0x100c31ed0 <+120>: str w9, [x8, #0x20]
0x100c31ed4 <+124>: ldr w9, [sp, #0x8]
0x100c31ed8 <+128>: str w9, [x8, #0x24]
//恢复栈
0x100c31edc <+132>: add sp, sp, #0x30 ; =0x30
0x100c31ee0 <+136>: ret
cf4de764a62ff525bb57e1d8854d7fce.png


和之前的猜测相符。

函数的局部变量


int func1(int a, int b) {
int c = 6;
return a + b + c;
}

- (void)viewDidLoad {
[super viewDidLoad];
func1(10, 20);
}

对应的汇编指令:

TestDemo`func1:
-> 0x104bc5e40 <+0>: sub sp, sp, #0x10 ; =0x10
0x104bc5e44 <+4>: str w0, [sp, #0xc]
0x104bc5e48 <+8>: str w1, [sp, #0x8]
//局部变量c存入自己的栈区
0x104bc5e4c <+12>: mov w8, #0x6
0x104bc5e50 <+16>: str w8, [sp, #0x4]
0x104bc5e54 <+20>: ldr w8, [sp, #0xc]
0x104bc5e58 <+24>: ldr w9, [sp, #0x8]
0x104bc5e5c <+28>: add w8, w8, w9
0x104bc5e60 <+32>: ldr w9, [sp, #0x4]
0x104bc5e64 <+36>: add w0, w8, w9
0x104bc5e68 <+40>: add sp, sp, #0x10 ; =0x10
0x104bc5e6c <+44>: ret
函数的局部变量放在栈里面!(自己的栈)
那么有嵌套调用呢?

int func1(int a, int b) {
int c = 6;
int d = func2(a, b, c);
int e = func2(a, b, c);
return d + e;
}

int func2(int a, int b, int c) {
int d = a + b + c;
printf("%d",d);
return d;
}

- (void)viewDidLoad {
[super viewDidLoad];
func1(10, 20);
}
对应的汇编:

TestDemo`func1:
//函数的开始
-> 0x100781d9c <+0>: sub sp, sp, #0x30 ; =0x30
0x100781da0 <+4>: stp x29, x30, [sp, #0x20]
0x100781da4 <+8>: add x29, sp, #0x20 ; =0x20

//参数入栈
0x100781da8 <+12>: stur w0, [x29, #-0x4]
0x100781dac <+16>: stur w1, [x29, #-0x8]

//局部变量入栈
0x100781db0 <+20>: mov w8, #0x6
0x100781db4 <+24>: stur w8, [x29, #-0xc]

//读取参数和局部变量
0x100781db8 <+28>: ldur w0, [x29, #-0x4]
0x100781dbc <+32>: ldur w1, [x29, #-0x8]
0x100781dc0 <+36>: ldur w2, [x29, #-0xc]

//执行func2
0x100781dc4 <+40>: bl 0x100781df8 ; func2 at ViewController.m:86
//func2 返回值入栈
0x100781dc8 <+44>: str w0, [sp, #0x10]

//读取参数和局部变量
0x100781dcc <+48>: ldur w0, [x29, #-0x4]
0x100781dd0 <+52>: ldur w1, [x29, #-0x8]
0x100781dd4 <+56>: ldur w2, [x29, #-0xc]

//第二次执行func2
0x100781dd8 <+60>: bl 0x100781df8 ; func2 at ViewController.m:86

//func2 返回值入栈
0x100781ddc <+64>: str w0, [sp, #0xc]

//读取两次 func2 返回值
0x100781de0 <+68>: ldr w8, [sp, #0x10]
0x100781de4 <+72>: ldr w9, [sp, #0xc]
//相加存入w0返回上层函数
0x100781de8 <+76>: add w0, w8, w9

//函数的结束
0x100781dec <+80>: ldp x29, x30, [sp, #0x20]
0x100781df0 <+84>: add sp, sp, #0x30 ; =0x30
0x100781df4 <+88>: ret
可以看到参数被保存到栈中。
⚠️:现场保护包含:FPLR参数返回值

总结

    • 是一种具有特殊的访问方式的存储空间(后进先出,LIFO)
    • SP和FP寄存器
      • sp寄存器在任意时刻保存栈顶的地址
      • fp(x29)寄存器属于通用寄存器,在某些时刻利用它保存栈底的地址(嵌套调用)
    • ARM64里面栈的操作16字节对齐
    • 栈读写指令
      • 读:ldr(load register)指令LDR、LDP
      • 写:str(store register)指令STR、STP
    • 汇编练习
      • 指令:
        • sub sp, sp,#0x10 ;拉伸栈空间16个字节
        • stp x0,x1,[sp];往sp所在位置存放x0和x1
        • ldp x0,x1,[sp];读取sp存入x0和x1
        • add sp,#0x10;恢复栈空间
      • 简写:
        • stp x0, x1,[sp,#-0x10]!;前提条件是正好开辟的空间放满栈。先开辟空间,存入值,再改变sp的值。
        • ldp x0,x1,[sp],#0x10
  • bl指令
    • 跳转指令:bl标号,转到标号处执行指令并将下一条指令的地址保存到lr寄存器
    • B代表跳转
    • L代表lr(x30)寄存器
  • ret指令
    • 类似函数中的return
    • 让CPU执行lr寄存器所指向的指令
    • 有跳转需要“保护现场”
  • 函数
    • 函数调用栈
      • ARM64中栈是递减栈,向低地址延伸的栈
      • SP寄存器指向栈顶的位置
      • X29(FP)寄存器指向栈底的位置
    • 函数的参数
      • ARM64中,默认情况下参数是放在X0~X7的8个寄存器中
      • 如果是浮点数,会用浮点寄存器
      • 如果超过8个参数会用栈传递(多过8个的参数在函数调用结束后参数不会释放,相当于局部变量,属于调用方,只有调用方函数执行结束栈平衡后才释放。)
    • 函数的返回值
      • 一般情况下函数的返回值使用X0寄存器保存
      • 如果返回值大于了8个字节(放不下),就会利用内存。写入上一个调用栈内部,用X8寄存器作为参照。
    • 函数的局部变量
      • 使用栈保存局部变量
    • 函数的嵌套调用
      • 会将X29,X30寄存器入栈保护。
      • 同时现场保护的还有:FP,LR,参数,返回值。


0 个评论

要回复文章请先登录注册