SP和FP寄存器
sp
寄存器在任意时刻会保存我们栈顶的地址。fp
寄存器也称为x29
寄存器属于通用寄存器,但是在某些时刻我们利用它保存栈底的地址!(没有出现函数嵌套调用的时候不需要fp
,相当于分界点)undefined⚠️:ARM64
开始,取消32
位的LDM,STM,PUSH,POP
指令! 取而代之的是ldrldp strstp
。ARM64
里面 对栈的操作是16
字节对齐的!!
ARM64
是先开辟一段栈空间,fp
移动到栈顶再往栈中存放内容(编译期就已经确定大小)。不存在push
操作。在iOS
中栈是往低地址开辟空间
image.png
函数调用栈
常见的函数调用开辟和恢复的栈空间:
代码语言:txt复制//开辟栈空间
代码语言:txt复制sub sp, sp, #0x40 ; 拉伸0x40(64字节)空间
代码语言:txt复制stp x29, x30, [sp, #0x30] ;x29x30 寄存器入栈保护
代码语言:txt复制add x29, sp, #0x30 ; x29指向栈帧的底部
代码语言:txt复制...
代码语言:txt复制//恢复栈空间
代码语言:txt复制ldp x29, x30, [sp, #0x30] ;恢复x29/x30 寄存器的值
代码语言:txt复制add sp, sp, #0x40 ;栈平衡
代码语言:txt复制ret
恢复后数据并不销毁,拉伸栈空间后会先覆盖再读取。
内存读写指令
⚠️:读/写
数据都是往高地址读/写,也就是放数据从高地址往低地址放。比如读取16
字节的数据,给的地址是0x02
,那么读取的就是0x02
和0x03
。
str(store register)指令
将数据从寄存器中读出来,存到内存中。
ldr(load register)指令
将数据从内存中读出来,存到寄存器中。
ldr
和 str
的变种 ldp
和 stp
还可以操作2
个寄存器。
堆栈操作案例
使用32
个字节空间作为这段程序的栈空间,然后利用栈将x0
和x1
的值进行交换。
.text
代码语言:txt复制.global _C
代码语言:txt复制_C:
代码语言:txt复制 sub sp, sp, #0x20 ;拉伸栈空间32个字节
代码语言:txt复制 stp x0, x1, [sp, #0x10] ;sp 偏移 16字节存放 x0和x1 []的意思是寻址。这sp并没有改变
代码语言:txt复制 ldp x1, x0, [sp, #0x10] ;将sp偏移16个字节的值取出来,放入x1 和 x0。这里内存相当于temp 交换了 x0 和 x1。寄存器中的值交换了,内存中的值不变。
代码语言:txt复制 add sp, sp, #0x20 ;恢复栈空间
代码语言:txt复制 ret
这段代码相当于 x0,x1遍历,sp和内存没有变。
栈空间分配:
image.png
断点调试
在0x102e6e518
断点处对x0
和x1
分别赋值0xa
和0xb
。然后单步执行:
image.png
拉伸后sp
也变了。
(lldb) register write x0 0xa
代码语言:txt复制(lldb) register write x1 0xb
代码语言:txt复制(lldb) register read sp
代码语言:txt复制 sp = 0x000000016cf95b30
代码语言:txt复制(lldb) register read sp
代码语言:txt复制 sp = 0x000000016cf95b10
代码语言:txt复制(lldb)
看下0x000000016cf95b10
的空间:
image.png
目前还没有写入内存,是脏数据。接着单步执行:
image.png
数据写入了内存。接着单步执行数据读取放入x0
和x1
:
image.png
这个时候x0
和x1
的数据完成了交换。内存的数据并没有变化。
继续单步执行:
代码语言:txt复制(lldb) register write x0 0xa
代码语言:txt复制(lldb) register write x1 0xb
代码语言:txt复制(lldb) register read sp
代码语言:txt复制 sp = 0x000000016cf95b30
代码语言:txt复制(lldb) register read sp
代码语言:txt复制 sp = 0x000000016cf95b10
代码语言:txt复制(lldb) register read sp
代码语言:txt复制 sp = 0x000000016cf95b30
代码语言:txt复制(lldb)
sp
还原了,栈空间释放,这时候0xa
,0xb
还依然存在内存中,等待下次拉伸栈空间写数据覆盖:
image.png
bl和ret指令
bl标号
- 将下一条指令的地址放入
lr(x30)
寄存器 - 转到标号处执行指令
b
就是跳转,l
将下一条指令的地址放入lr(x30)
寄存器。
image.png
lr
相当于保存的”回家的路“。
ret
- 默认使用lr(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址!
ret
只会看lr
。
ARM64平台的特色指令,它面向硬件做了优化处理。
x30寄存器
x30寄存器存放的是函数的返回地址.当ret指令执行时刻,会寻找x30寄存器保存的地址值!
一个嵌套调用的案例,汇编代码如下:
代码语言:txt复制.text
代码语言:txt复制.global _C, _D
代码语言:txt复制_C:
代码语言:txt复制 mov x0,#0xaaaa
代码语言:txt复制 bl _D
代码语言:txt复制 mov x0,#0xaaaa
代码语言:txt复制 ret
代码语言:txt复制_D:
代码语言:txt复制 mov x0,#0xbbbb
代码语言:txt复制 ret
ViewController.m
中调用:
int C();
代码语言:txt复制int D();
代码语言:txt复制- (void)viewDidLoad {
[super viewDidLoad];
printf("C");
C();
printf("D");
}
在C();
打断点执行,进入C
中:
image.png
这个时候lr
指向viewDidLoad
下一条指令,接着执行跳入D
中:
image.png
这个时候lr
指向C
的下一条指令0x104c8e4f8
,执行完D
返回C
可以看到lr
仍然指向0x104c8e4f8
没有改变
image.png
继续执行发现一直在0x104c8e4f8
和0x104c8e4fc
中跳转返不回去viewDidLoad
中了,发生了死循环。
-> 0x104c8e4f8 < 8>: mov x0, #0xaaaa
代码语言:txt复制 0x104c8e4fc < 12>: ret
那么如果要返回,就必须将viewDidLoad
中下一条指令告诉lr
,这个时候就必须在bl
之前保护lr
寄存器(遇到bl
,lr
就会改变。需要保护“回家的路”)。那么这个时候能不能把lr
保存到其它寄存器?这里我们没法保证其它寄存器不会被使用。这个时候唯一属于当前函数的也就是自己的栈区了。保存到栈区应该就能解决了。
可以看下系统是怎么实现的,写一个c
函数断点调试看下:
void c() {
代码语言:txt复制 d();
代码语言:txt复制 return;;
代码语言:txt复制}
代码语言:txt复制void d() {
代码语言:txt复制}
代码语言:txt复制- (void)viewDidLoad {
[super viewDidLoad];
c();
}
系统的实现如下:
image.png
代码语言:txt复制TestDemo`c:
代码语言:txt复制//边开辟空间边写入 x29(fp) 和 x30(lr) 的值。[sp, #-0x10]! !代表赋值给sp,相当于 sp -= 0x10
代码语言:txt复制-> 0x102a21e84 < 0>: stp x29, x30, [sp, #-0x10]!
代码语言:txt复制 0x102a21e88 < 4>: mov x29, sp
代码语言:txt复制 0x102a21e8c < 8>: bl 0x102a21e98 ; d at ViewController.m:34:1
代码语言:txt复制//将sp所指向的地址读取给x29,x30。[sp], #0x10 等价于 sp = 0x10
代码语言:txt复制 0x102a21e90 < 12>: ldp x29, x30, [sp], #0x10
代码语言:txt复制 0x102a21e94 < 16>: ret
可以看到系统先开辟栈空间,然后将x29
和x30
寄存器的值存入栈区。在ret
之前恢复x29
和x30
的值。
stp x29, x30, [sp, #-0x10]!
:开辟空间并将x29
和x30
存入栈区。!
代表赋值给sp
,相当于sp -= 0x10
ldp x29, x30, [sp], #0x10
:将栈区的值给x29
,x30
并回收空间。[sp], #0x10
等价于sp = 0x10
。
那么对于C
和D
的案例自己实现下保存和恢复lr
寄存器。
.text
代码语言:txt复制.global _C, _D
代码语言:txt复制_C:
代码语言:txt复制 //sub sp,sp,#0x10
代码语言:txt复制 //str x30,[sp] ;等价
代码语言:txt复制 str x30, [sp,#-0x10]! ;16字节对齐,必须最小0x10。
代码语言:txt复制 mov x0,#0xaaaa
代码语言:txt复制 bl _D
代码语言:txt复制 mov x0,#0xaaaa
代码语言:txt复制 //ldr x30,[sp]
代码语言:txt复制 //add sp,#0x10 ;等价
代码语言:txt复制 ldr x30,[sp],#0x10
代码语言:txt复制 ret
代码语言:txt复制_D:
代码语言:txt复制 mov x0,#0xbbbb
代码语言:txt复制 ret
viewDidload中下一条指令
fp指向的内存
栈中存储的上一个函数对应lr
值
这个时候进入D
中lr
值已经发生变化。
D中lr值
返回C
中继续执行,这个时候执行到ret
的时候lr
已经恢复了。
C中恢复lr值
继续执行正常返回viewDidload
了,这个时候死循环就已经解决了。
⚠️:在函数嵌套调用的时候,需要将x30
入栈!开辟空间需要16
字节对齐。如果开辟8字节
再读的时候会坏地址访问。写的时候没问题。
image.png
函数的参数和返回值
先看下系统的实现:
代码语言:txt复制int sum(int a, int b) {
代码语言:txt复制 return a b;
代码语言:txt复制}
代码语言:txt复制- (void)viewDidLoad {
[super viewDidLoad];
sum(10,20);
}
image.png
可以看到变量10
和20
分别存入了w0
和w1
。
sum
调用如下(release
模式下编译器会优化):
TestDemo`sum:
代码语言:txt复制 //开辟空间
代码语言:txt复制-> 0x100121e68 < 0>: sub sp, sp, #0x10 ; =0x10
代码语言:txt复制 //w0 和 w1 存入栈中
代码语言:txt复制 0x100121e6c < 4>: str w0, [sp, #0xc]
代码语言:txt复制 0x100121e70 < 8>: str w1, [sp, #0x8]
代码语言:txt复制 //从栈中读取参数
代码语言:txt复制 0x100121e74 < 12>: ldr w8, [sp, #0xc]
代码语言:txt复制 0x100121e78 < 16>: ldr w9, [sp, #0x8]
代码语言:txt复制 //参数相加存入w0
代码语言:txt复制 0x100121e7c < 20>: add w0, w8, w9
代码语言:txt复制 //恢复栈空间
代码语言:txt复制 0x100121e80 < 24>: add sp, sp, #0x10 ; =0x10
代码语言:txt复制 //返回
代码语言:txt复制 0x100121e84 < 28>: ret
从上面可以看出返回值在w0
中。那么自己实现sum
函数的汇编代码:
.text
代码语言:txt复制.global _suma
代码语言:txt复制_suma:
代码语言:txt复制 add x0,x0,x1
代码语言:txt复制 ret
调用:
代码语言:txt复制int suma(int a, int b);
代码语言:txt复制- (void)viewDidLoad {
[super viewDidLoad];
printf("%d",suma(10,20));
}
image.png
⚠️ARM64
下,函数的参数是存放在X0
到X7
(W0
到W7
)这8
个寄存器里面的。如果超过8
个参数就会入栈。那么oc
的方法最好不要超过6
个(self
和cmd
)。
函数的返回值是放在X0
寄存器里面的。
参数超过8个
代码语言:txt复制int test(int a, int b, int c ,int d, int e, int f, int g, int h, int i) {
代码语言:txt复制 return a b c d e f g h i;
代码语言:txt复制}
代码语言:txt复制- (void)viewDidLoad {
[super viewDidLoad];
test(1, 2, 3, 4, 5, 6, 7, 8, 9);
}
image.png
可以看到前8
个参数分别保存在w0~w7
寄存器中,第9
个参数先保存在w10
中,然后写入x8
中(这个时候x8
指向sp
,相当于第9
个参数写入了当前函数栈中)。
TestDemo`-[ViewController viewDidLoad]:
代码语言:txt复制 //拉伸栈空间,保存fp lr
代码语言:txt复制 0x100f09e5c < 0>: sub sp, sp, #0x40 ; =0x40
代码语言:txt复制 0x100f09e60 < 4>: stp x29, x30, [sp, #0x30]
代码语言:txt复制 //fp指向 sp 0x30
代码语言:txt复制 0x100f09e64 < 8>: add x29, sp, #0x30 ; =0x30
代码语言:txt复制 //fp-0x8 存放x0
代码语言:txt复制 0x100f09e68 < 12>: stur x0, [x29, #-0x8]
代码语言:txt复制 //fp-0x10 存放x1
代码语言:txt复制 0x100f09e6c < 16>: stur x1, [x29, #-0x10]
代码语言:txt复制 //fp-0x8 给到 x8
代码语言:txt复制 0x100f09e70 < 20>: ldur x8, [x29, #-0x8]
代码语言:txt复制 //sp 0x10 指针给到 x9
代码语言:txt复制 0x100f09e74 < 24>: add x9, sp, #0x10 ; =0x10
代码语言:txt复制 //x8写入 sp 0x10
代码语言:txt复制 0x100f09e78 < 28>: str x8, [sp, #0x10]
代码语言:txt复制 //adrp = address page 内存中取数据
代码语言:txt复制 0x100f09e7c < 32>: adrp x8, 4
代码语言:txt复制 0x100f09e80 < 36>: add x8, x8, #0x418 ; =0x418
代码语言:txt复制 //x8所指向的内容去出来
代码语言:txt复制 0x100f09e84 < 40>: ldr x8, [x8]
代码语言:txt复制 //x8写入栈中,这个时候x9指向地址,这个时候是一个新的x8
代码语言:txt复制 0x100f09e88 < 44>: str x8, [x9, #0x8]
代码语言:txt复制 0x100f09e8c < 48>: adrp x8, 4
代码语言:txt复制 0x100f09e90 < 52>: add x8, x8, #0x3e8 ; =0x3e8
代码语言:txt复制 0x100f09e94 < 56>: ldr x1, [x8]
代码语言:txt复制 0x100f09e98 < 60>: mov x0, x9
代码语言:txt复制 0x100f09e9c < 64>: bl 0x100f0a568 ; symbol stub for: objc_msgSendSuper2
代码语言:txt复制 //sp 一直没有改变过,w0~w7 分别存放前8个参数
代码语言:txt复制 0x100f09ea0 < 68>: mov w0, #0x1
代码语言:txt复制 0x100f09ea4 < 72>: mov w1, #0x2
代码语言:txt复制 0x100f09ea8 < 76>: mov w2, #0x3
代码语言:txt复制 0x100f09eac < 80>: mov w3, #0x4
代码语言:txt复制 0x100f09eb0 < 84>: mov w4, #0x5
代码语言:txt复制 0x100f09eb4 < 88>: mov w5, #0x6
代码语言:txt复制 0x100f09eb8 < 92>: mov w6, #0x7
代码语言:txt复制 0x100f09ebc < 96>: mov w7, #0x8
代码语言:txt复制 //x8 指向 sp
代码语言:txt复制 -> 0x100f09ec0 < 100>: mov x8, sp
代码语言:txt复制 //参数 9 存入 w10
代码语言:txt复制 0x100f09ec4 < 104>: mov w10, #0x9
代码语言:txt复制 //w10 存入 x8地址中,也就是sp栈底中
代码语言:txt复制 0x100f09ec8 < 108>: str w10, [x8]
代码语言:txt复制 0x100f09ecc < 112>: bl 0x100f09de4 ; test at ViewController.m:41
代码语言:txt复制 0x100f09ed0 < 116>: ldp x29, x30, [sp, #0x30]
代码语言:txt复制 0x100f09ed4 < 120>: add sp, sp, #0x40 ; =0x40
代码语言:txt复制 0x100f09ed8 < 124>: ret
viewDidLoad栈空间变化
接着往下直接跳转到test
函数中:
TestDemo`test:
代码语言:txt复制 //开辟空间48字节
代码语言:txt复制 0x100f09de4 < 0>: sub sp, sp, #0x30 ; =0x30
代码语言:txt复制 //从viewDidLoad栈中取数据 第9个参数(读写往高地址)
代码语言:txt复制 0x100f09de8 < 4>: ldr w8, [sp, #0x30]
代码语言:txt复制 //参数入栈,分别占4个字节
代码语言:txt复制 0x100f09dec < 8>: str w0, [sp, #0x2c]
代码语言:txt复制 0x100f09df0 < 12>: str w1, [sp, #0x28]
代码语言:txt复制 0x100f09df4 < 16>: str w2, [sp, #0x24]
代码语言:txt复制 0x100f09df8 < 20>: str w3, [sp, #0x20]
代码语言:txt复制 0x100f09dfc < 24>: str w4, [sp, #0x1c]
代码语言:txt复制 0x100f09e00 < 28>: str w5, [sp, #0x18]
代码语言:txt复制 0x100f09e04 < 32>: str w6, [sp, #0x14]
代码语言:txt复制 0x100f09e08 < 36>: str w7, [sp, #0x10]
代码语言:txt复制 0x100f09e0c < 40>: str w8, [sp, #0xc]
代码语言:txt复制-> 0x100f09e10 < 44>: ldr w8, [sp, #0x2c]
代码语言:txt复制 0x100f09e14 < 48>: ldr w9, [sp, #0x28]
代码语言:txt复制 0x100f09e18 < 52>: add w8, w8, w9
代码语言:txt复制 0x100f09e1c < 56>: ldr w9, [sp, #0x24]
代码语言:txt复制 0x100f09e20 < 60>: add w8, w8, w9
代码语言:txt复制 0x100f09e24 < 64>: ldr w9, [sp, #0x20]
代码语言:txt复制 0x100f09e28 < 68>: add w8, w8, w9
代码语言:txt复制 0x100f09e2c < 72>: ldr w9, [sp, #0x1c]
代码语言:txt复制 0x100f09e30 < 76>: add w8, w8, w9
代码语言:txt复制 0x100f09e34 < 80>: ldr w9, [sp, #0x18]
代码语言:txt复制 0x100f09e38 < 84>: add w8, w8, w9
代码语言:txt复制 0x100f09e3c < 88>: ldr w9, [sp, #0x14]
代码语言:txt复制 0x100f09e40 < 92>: add w8, w8, w9
代码语言:txt复制 0x100f09e44 < 96>: ldr w9, [sp, #0x10]
代码语言:txt复制 0x100f09e48 < 100>: add w8, w8, w9
代码语言:txt复制 0x100f09e4c < 104>: ldr w9, [sp, #0xc]
代码语言:txt复制 //最终相加结果给 w0
代码语言:txt复制 0x100f09e50 < 108>: add w0, w8, w9
代码语言:txt复制 //栈平衡
代码语言:txt复制 0x100f09e54 < 112>: add sp, sp, #0x30 ; =0x30
代码语言:txt复制 0x100f09e58 < 116>: ret
test栈空间
最终函数返回值放入w0
中,如果在release
模式下test
不会被调用(被优化掉,因为没有意义,有没有对app没有影响。)
自己实现一个简单有参数并且嵌套调用的汇编:
代码语言:txt复制.text
代码语言:txt复制.global _func,_sum
代码语言:txt复制_func:
代码语言:txt复制 //sub sp,sp,#0x10
代码语言:txt复制 //stp x29,x30,[sp]
代码语言:txt复制 stp x29,x30,[sp, #-0x10]!
代码语言:txt复制 bl _sum
代码语言:txt复制 //ldp x29,x30,[sp]
代码语言:txt复制 //add sp,sp,#0x10
代码语言:txt复制 ldp x29,x30,[sp],#0x10
代码语言:txt复制 ret
代码语言:txt复制_sum:
代码语言:txt复制 add x0,x0,x1
代码语言:txt复制 ret
返回值
函数的返回值一般是一个指针,不会超过8字节。X0
寄存器就完全够用了。如果要返回一个结构体类型超过8
字节。
下面的例子(str
结构体占用24
字节):
struct str {
代码语言:txt复制 int a;
代码语言:txt复制 int b;
代码语言:txt复制 int c;
代码语言:txt复制 int d;
代码语言:txt复制 int e;
代码语言:txt复制 int f;
代码语言:txt复制};
代码语言:txt复制struct str getStr(int a, int b, int c, int d, int e, int f) {
代码语言:txt复制 struct str str1;
代码语言:txt复制 str1.a = a;
代码语言:txt复制 str1.b = b;
代码语言:txt复制 str1.c = c;
代码语言:txt复制 str1.d = d;
代码语言:txt复制 str1.e = e;
代码语言:txt复制 str1.f = f;
代码语言:txt复制 return str1;
代码语言:txt复制}
代码语言:txt复制- (void)viewDidLoad {
[super viewDidLoad];
struct str str2 = getStr(1,2,3,4,5,6);
}
汇编代码:
代码语言:txt复制TestDemo`-[ViewController viewDidLoad]:
代码语言:txt复制 0x1042b5e58 < 0>: sub sp, sp, #0x50 ; =0x50
代码语言:txt复制 0x1042b5e5c < 4>: stp x29, x30, [sp, #0x40]
代码语言:txt复制 0x1042b5e60 < 8>: add x29, sp, #0x40 ; =0x40
代码语言:txt复制 0x1042b5e64 < 12>: stur x0, [x29, #-0x8]
代码语言:txt复制 0x1042b5e68 < 16>: stur x1, [x29, #-0x10]
代码语言:txt复制 0x1042b5e6c < 20>: ldur x8, [x29, #-0x8]
代码语言:txt复制 0x1042b5e70 < 24>: add x9, sp, #0x20 ; =0x20
代码语言:txt复制 0x1042b5e74 < 28>: str x8, [sp, #0x20]
代码语言:txt复制 0x1042b5e78 < 32>: adrp x8, 4
代码语言:txt复制 0x1042b5e7c < 36>: add x8, x8, #0x418 ; =0x418
代码语言:txt复制 0x1042b5e80 < 40>: ldr x8, [x8]
代码语言:txt复制 0x1042b5e84 < 44>: str x8, [x9, #0x8]
代码语言:txt复制 0x1042b5e88 < 48>: adrp x8, 4
代码语言:txt复制 0x1042b5e8c < 52>: add x8, x8, #0x3e8 ; =0x3e8
代码语言:txt复制 0x1042b5e90 < 56>: ldr x1, [x8]
代码语言:txt复制 0x1042b5e94 < 60>: mov x0, x9
代码语言:txt复制 0x1042b5e98 < 64>: bl 0x1042b6564 ; symbol stub for: objc_msgSendSuper2
代码语言:txt复制 //x8指向栈空间的区域,预留足够的空间
代码语言:txt复制 0x1042b5e9c < 68>: add x8, sp, #0x8 ; =0x8
代码语言:txt复制 0x1042b5ea0 < 72>: mov w0, #0x1
代码语言:txt复制 0x1042b5ea4 < 76>: mov w1, #0x2
代码语言:txt复制 0x1042b5ea8 < 80>: mov w2, #0x3
代码语言:txt复制 0x1042b5eac < 84>: mov w3, #0x4
代码语言:txt复制 0x1042b5eb0 < 88>: mov w4, #0x5
代码语言:txt复制 0x1042b5eb4 < 92>: mov w5, #0x6
代码语言:txt复制 0x1042b5eb8 < 96>: bl 0x1042b5e04 ; getStr at ViewController.m:59
代码语言:txt复制-> 0x1042b5ebc < 100>: ldp x29, x30, [sp, #0x40]
代码语言:txt复制 0x1042b5ec0 < 104>: add sp, sp, #0x50 ; =0x50
代码语言:txt复制 0x1042b5ec4 < 108>: ret
str
函数:
TestDemo`getStr:
代码语言:txt复制-> 0x1001d1e04 < 0>: sub sp, sp, #0x20 ; =0x20
代码语言:txt复制 //参数分别放入栈中
代码语言:txt复制 0x1001d1e08 < 4>: str w0, [sp, #0x1c]
代码语言:txt复制 0x1001d1e0c < 8>: str w1, [sp, #0x18]
代码语言:txt复制 0x1001d1e10 < 12>: str w2, [sp, #0x14]
代码语言:txt复制 0x1001d1e14 < 16>: str w3, [sp, #0x10]
代码语言:txt复制 0x1001d1e18 < 20>: str w4, [sp, #0xc]
代码语言:txt复制 0x1001d1e1c < 24>: str w5, [sp, #0x8]
代码语言:txt复制 //取出来放入w9,
代码语言:txt复制 0x1001d1e20 < 28>: ldr w9, [sp, #0x1c]
代码语言:txt复制 //存入x8,也就是上一个栈中直到写完
代码语言:txt复制 0x1001d1e24 < 32>: str w9, [x8]
代码语言:txt复制 0x1001d1e28 < 36>: ldr w9, [sp, #0x18]
代码语言:txt复制 0x1001d1e2c < 40>: str w9, [x8, #0x4]
代码语言:txt复制 0x1001d1e30 < 44>: ldr w9, [sp, #0x14]
代码语言:txt复制 0x1001d1e34 < 48>: str w9, [x8, #0x8]
代码语言:txt复制 0x1001d1e38 < 52>: ldr w9, [sp, #0x10]
代码语言:txt复制 0x1001d1e3c < 56>: str w9, [x8, #0xc]
代码语言:txt复制 0x1001d1e40 < 60>: ldr w9, [sp, #0xc]
代码语言:txt复制 0x1001d1e44 < 64>: str w9, [x8, #0x10]
代码语言:txt复制 0x1001d1e48 < 68>: ldr w9, [sp, #0x8]
代码语言:txt复制 0x1001d1e4c < 72>: str w9, [x8, #0x14]
代码语言:txt复制 //栈平衡,这里没有以 x0 作为返回值,已经全部写入上一个函数栈x8中。
代码语言:txt复制 0x1001d1e50 < 76>: add sp, sp, #0x20 ; =0x20
代码语言:txt复制 0x1001d1e54 < 80>: ret
这里没有使用X0
作为返回值,而是使用了栈空间。
结构体返回值栈空间
如果返回值大于8
字节,也会保存在栈中返回(上一个函数栈空间)
那么结构体参数超过8个呢?
猜测参数和返回值都存在上一个函数的栈中,参数应该在低地址。返回值在高地址。
代码语言:txt复制struct str {
代码语言:txt复制 int a;
代码语言:txt复制 int b;
代码语言:txt复制 int c;
代码语言:txt复制 int d;
代码语言:txt复制 int e;
代码语言:txt复制 int f;
代码语言:txt复制 int g;
代码语言:txt复制 int h;
代码语言:txt复制 int i;
代码语言:txt复制 int j;
代码语言:txt复制};
代码语言:txt复制struct str getStr(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) {
代码语言:txt复制 struct str str1;
代码语言:txt复制 str1.a = a;
代码语言:txt复制 str1.b = b;
代码语言:txt复制 str1.c = c;
代码语言:txt复制 str1.d = d;
代码语言:txt复制 str1.e = e;
代码语言:txt复制 str1.f = f;
代码语言:txt复制 str1.g = g;
代码语言:txt复制 str1.h = h;
代码语言:txt复制 str1.i = i;
代码语言:txt复制 str1.j = j;
代码语言:txt复制 return str1;
代码语言:txt复制}
代码语言:txt复制- (void)viewDidLoad {
[super viewDidLoad];
struct str str2 = getStr(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
printf("%d",func(10,20));
}
⚠️: 有两个函数A
B
,A -> B
,在B
执行完后A
传递给B
的参数释放了么?
在上面的例子中9
和10
没有释放,相当于A
的局部变量。
对应的汇编代码:
代码语言:txt复制TestDemo`-[ViewController viewDidLoad]:
代码语言:txt复制 //函数开始
代码语言:txt复制 0x100c31ee4 < 0>: sub sp, sp, #0x60 ; =0x60
代码语言:txt复制 0x100c31ee8 < 4>: stp x29, x30, [sp, #0x50]
代码语言:txt复制 0x100c31eec < 8>: add x29, sp, #0x50 ; =0x50
代码语言:txt复制 //参数入栈
代码语言:txt复制 0x100c31ef0 < 12>: stur x0, [x29, #-0x8]
代码语言:txt复制 0x100c31ef4 < 16>: stur x1, [x29, #-0x10]
代码语言:txt复制 //x8获取参数x0
代码语言:txt复制 0x100c31ef8 < 20>: ldur x8, [x29, #-0x8]
代码语言:txt复制 //x9指向 x29 - 0x20
代码语言:txt复制 0x100c31efc < 24>: sub x9, x29, #0x20 ; =0x20
代码语言:txt复制 //x8 存入 x29 - 0x20
代码语言:txt复制 0x100c31f00 < 28>: stur x8, [x29, #-0x20]
代码语言:txt复制 //address page 内存中取数据
代码语言:txt复制 0x100c31f04 < 32>: adrp x8, 4
代码语言:txt复制 0x100c31f08 < 36>: add x8, x8, #0x418 ; =0x418
代码语言:txt复制 //x8 所指的内存取出来
代码语言:txt复制 0x100c31f0c < 40>: ldr x8, [x8]
代码语言:txt复制 0x100c31f10 < 44>: str x8, [x9, #0x8]
代码语言:txt复制 0x100c31f14 < 48>: adrp x8, 4
代码语言:txt复制 0x100c31f18 < 52>: add x8, x8, #0x3e8 ; =0x3e8
代码语言:txt复制 0x100c31f1c < 56>: ldr x1, [x8]
代码语言:txt复制 0x100c31f20 < 60>: mov x0, x9
代码语言:txt复制 0x100c31f24 < 64>: bl 0x100c32584 ; symbol stub for: objc_msgSendSuper2
代码语言:txt复制 //x8指向 sp 0x8
代码语言:txt复制 0x100c31f28 < 68>: add x8, sp, #0x8 ; =0x8
代码语言:txt复制 0x100c31f2c < 72>: mov w0, #0x1
代码语言:txt复制 0x100c31f30 < 76>: mov w1, #0x2
代码语言:txt复制 0x100c31f34 < 80>: mov w2, #0x3
代码语言:txt复制 0x100c31f38 < 84>: mov w3, #0x4
代码语言:txt复制 0x100c31f3c < 88>: mov w4, #0x5
代码语言:txt复制 0x100c31f40 < 92>: mov w5, #0x6
代码语言:txt复制 0x100c31f44 < 96>: mov w6, #0x7
代码语言:txt复制 0x100c31f48 < 100>: mov w7, #0x8
代码语言:txt复制 //sp的值给x9
代码语言:txt复制 0x100c31f4c < 104>: mov x9, sp
代码语言:txt复制 //9 给 w10
代码语言:txt复制 0x100c31f50 < 108>: mov w10, #0x9
代码语言:txt复制 //w10写入 x9 所指向的地址
代码语言:txt复制 0x100c31f54 < 112>: str w10, [x9]
代码语言:txt复制 //10 给 w10
代码语言:txt复制 0x100c31f58 < 116>: mov w10, #0xa
代码语言:txt复制 //w10写入 x9 所指向的地址 偏移4个字节
代码语言:txt复制 0x100c31f5c < 120>: str w10, [x9, #0x4]
代码语言:txt复制 //跳转getStr
代码语言:txt复制 0x100c31f60 < 124>: bl 0x100c31e58 ; getStr at ViewController.m:31
代码语言:txt复制 //函数结束
代码语言:txt复制-> 0x100c31f64 < 128>: ldp x29, x30, [sp, #0x50]
代码语言:txt复制 0x100c31f68 < 132>: add sp, sp, #0x60 ; =0x60
代码语言:txt复制 0x100c31f6c < 136>: ret
str
:
TestDemo`getStr:
代码语言:txt复制 //开辟空间
代码语言:txt复制 0x100c31e58 < 0>: sub sp, sp, #0x30 ; =0x30
代码语言:txt复制 //从上一个栈空间 获取9 和 10
代码语言:txt复制 0x100c31e5c < 4>: ldr w9, [sp, #0x30]
代码语言:txt复制 0x100c31e60 < 8>: ldr w10, [sp, #0x34]
代码语言:txt复制 //参数入栈
代码语言:txt复制 0x100c31e64 < 12>: str w0, [sp, #0x2c]
代码语言:txt复制 0x100c31e68 < 16>: str w1, [sp, #0x28]
代码语言:txt复制 0x100c31e6c < 20>: str w2, [sp, #0x24]
代码语言:txt复制 0x100c31e70 < 24>: str w3, [sp, #0x20]
代码语言:txt复制 0x100c31e74 < 28>: str w4, [sp, #0x1c]
代码语言:txt复制 0x100c31e78 < 32>: str w5, [sp, #0x18]
代码语言:txt复制 0x100c31e7c < 36>: str w6, [sp, #0x14]
代码语言:txt复制 0x100c31e80 < 40>: str w7, [sp, #0x10]
代码语言:txt复制 0x100c31e84 < 44>: str w9, [sp, #0xc]
代码语言:txt复制 0x100c31e88 < 48>: str w10,[sp, #0x8]
代码语言:txt复制 //获取参数分别存入上一个栈x8所指向的地址中
代码语言:txt复制-> 0x100c31e8c < 52>: ldr w9, [sp, #0x2c]
代码语言:txt复制 0x100c31e90 < 56>: str w9, [x8]
代码语言:txt复制 0x100c31e94 < 60>: ldr w9, [sp, #0x28]
代码语言:txt复制 0x100c31e98 < 64>: str w9, [x8, #0x4]
代码语言:txt复制 0x100c31e9c < 68>: ldr w9, [sp, #0x24]
代码语言:txt复制 0x100c31ea0 < 72>: str w9, [x8, #0x8]
代码语言:txt复制 0x100c31ea4 < 76>: ldr w9, [sp, #0x20]
代码语言:txt复制 0x100c31ea8 < 80>: str w9, [x8, #0xc]
代码语言:txt复制 0x100c31eac < 84>: ldr w9, [sp, #0x1c]
代码语言:txt复制 0x100c31eb0 < 88>: str w9, [x8, #0x10]
代码语言:txt复制 0x100c31eb4 < 92>: ldr w9, [sp, #0x18]
代码语言:txt复制 0x100c31eb8 < 96>: str w9, [x8, #0x14]
代码语言:txt复制 0x100c31ebc < 100>: ldr w9, [sp, #0x14]
代码语言:txt复制 0x100c31ec0 < 104>: str w9, [x8, #0x18]
代码语言:txt复制 0x100c31ec4 < 108>: ldr w9, [sp, #0x10]
代码语言:txt复制 0x100c31ec8 < 112>: str w9, [x8, #0x1c]
代码语言:txt复制 0x100c31ecc < 116>: ldr w9, [sp, #0xc]
代码语言:txt复制 0x100c31ed0 < 120>: str w9, [x8, #0x20]
代码语言:txt复制 0x100c31ed4 < 124>: ldr w9, [sp, #0x8]
代码语言:txt复制 0x100c31ed8 < 128>: str w9, [x8, #0x24]
代码语言:txt复制 //恢复栈
代码语言:txt复制 0x100c31edc < 132>: add sp, sp, #0x30 ; =0x30
代码语言:txt复制 0x100c31ee0 < 136>: ret
参数超过8个&返回值大于8字节
和之前的猜测相符。
函数的局部变量
代码语言:txt复制int func1(int a, int b) {
代码语言:txt复制 int c = 6;
代码语言:txt复制 return a b c;
代码语言:txt复制}
代码语言:txt复制- (void)viewDidLoad {
[super viewDidLoad];
func1(10, 20);
}
对应的汇编指令:
代码语言:txt复制TestDemo`func1:
代码语言:txt复制-> 0x104bc5e40 < 0>: sub sp, sp, #0x10 ; =0x10
代码语言:txt复制 0x104bc5e44 < 4>: str w0, [sp, #0xc]
代码语言:txt复制 0x104bc5e48 < 8>: str w1, [sp, #0x8]
代码语言:txt复制 //局部变量c存入自己的栈区
代码语言:txt复制 0x104bc5e4c < 12>: mov w8, #0x6
代码语言:txt复制 0x104bc5e50 < 16>: str w8, [sp, #0x4]
代码语言:txt复制 0x104bc5e54 < 20>: ldr w8, [sp, #0xc]
代码语言:txt复制 0x104bc5e58 < 24>: ldr w9, [sp, #0x8]
代码语言:txt复制 0x104bc5e5c < 28>: add w8, w8, w9
代码语言:txt复制 0x104bc5e60 < 32>: ldr w9, [sp, #0x4]
代码语言:txt复制 0x104bc5e64 < 36>: add w0, w8, w9
代码语言:txt复制 0x104bc5e68 < 40>: add sp, sp, #0x10 ; =0x10
代码语言:txt复制 0x104bc5e6c < 44>: ret
函数的局部变量放在栈里面!(自己的栈)
那么有嵌套调用呢?
代码语言:txt复制int func1(int a, int b) {
代码语言:txt复制 int c = 6;
代码语言:txt复制 int d = func2(a, b, c);
代码语言:txt复制 int e = func2(a, b, c);
代码语言:txt复制 return d e;
代码语言:txt复制}
代码语言:txt复制int func2(int a, int b, int c) {
代码语言:txt复制 int d = a b c;
代码语言:txt复制 printf("%d",d);
代码语言:txt复制 return d;
代码语言:txt复制}
代码语言:txt复制- (void)viewDidLoad {
[super viewDidLoad];
func1(10, 20);
}
对应的汇编:
代码语言:txt复制TestDemo`func1:
代码语言:txt复制 //函数的开始
代码语言:txt复制-> 0x100781d9c < 0>: sub sp, sp, #0x30 ; =0x30
代码语言:txt复制 0x100781da0 < 4>: stp x29, x30, [sp, #0x20]
代码语言:txt复制 0x100781da4 < 8>: add x29, sp, #0x20 ; =0x20
代码语言:txt复制 //参数入栈
代码语言:txt复制 0x100781da8 < 12>: stur w0, [x29, #-0x4]
代码语言:txt复制 0x100781dac < 16>: stur w1, [x29, #-0x8]
代码语言:txt复制 //局部变量入栈
代码语言:txt复制 0x100781db0 < 20>: mov w8, #0x6
代码语言:txt复制 0x100781db4 < 24>: stur w8, [x29, #-0xc]
代码语言:txt复制 //读取参数和局部变量
代码语言:txt复制 0x100781db8 < 28>: ldur w0, [x29, #-0x4]
代码语言:txt复制 0x100781dbc < 32>: ldur w1, [x29, #-0x8]
代码语言:txt复制 0x100781dc0 < 36>: ldur w2, [x29, #-0xc]
代码语言:txt复制 //执行func2
代码语言:txt复制 0x100781dc4 < 40>: bl 0x100781df8 ; func2 at ViewController.m:86
代码语言:txt复制 //func2 返回值入栈
代码语言:txt复制 0x100781dc8 < 44>: str w0, [sp, #0x10]
代码语言:txt复制 //读取参数和局部变量
代码语言:txt复制 0x100781dcc < 48>: ldur w0, [x29, #-0x4]
代码语言:txt复制 0x100781dd0 < 52>: ldur w1, [x29, #-0x8]
代码语言:txt复制 0x100781dd4 < 56>: ldur w2, [x29, #-0xc]
代码语言:txt复制 //第二次执行func2
代码语言:txt复制 0x100781dd8 < 60>: bl 0x100781df8 ; func2 at ViewController.m:86
代码语言:txt复制 //func2 返回值入栈
代码语言:txt复制 0x100781ddc < 64>: str w0, [sp, #0xc]
代码语言:txt复制 //读取两次 func2 返回值
代码语言:txt复制 0x100781de0 < 68>: ldr w8, [sp, #0x10]
代码语言:txt复制 0x100781de4 < 72>: ldr w9, [sp, #0xc]
代码语言:txt复制 //相加存入w0返回上层函数
代码语言:txt复制 0x100781de8 < 76>: add w0, w8, w9
代码语言:txt复制 //函数的结束
代码语言:txt复制 0x100781dec < 80>: ldp x29, x30, [sp, #0x20]
代码语言:txt复制 0x100781df0 < 84>: add sp, sp, #0x30 ; =0x30
代码语言:txt复制 0x100781df4 < 88>: ret
可以看到参数被保存到栈中。
⚠️:现场保护包含:FP
,LR
,参数
,返回值
。