栈
是一种具有特殊的访问方式的存储空间(后进先出, Last In Out Firt,LIFO)SP(stack pointer)和FP寄存器
sp寄存器在任意时刻会保存我们栈底的地址.fp寄存器也称为x29寄存器属于通用寄存器,但是在某些时刻我们利用它保存栈顶的地址!
函数调用会开辟一段内存空间(栈空间)用于函数的局部变量、参数和寄存器的保护
sub sp, sp,#0x20 ;拉伸栈空间
函数调用完毕之后也需要释放空间
add sp, sp, #0x20 ;栈平衡
注意:ARM64开始,取消32位的 LDM,STM,PUSH,POP指令! 取而代之的是ldr\ldp str\stp
ARM64里面 对栈的操作是16字节操作的!!
关于内存读写指令
注意:读/写 数据是都是往高地址读/写st指令
stur指令把寄存器的值(32位)写进内存
stp/str指令
把寄存器的值(128位/64位)写进内存
ld指令
ldr指令从内存地址取一个单个的32位值
dlp指令
从内存地址取一个单个的64位值
此 ldr 和 str 的变种 ldp 和 stp 还可以操作2个寄存器
栈操作
练习:使用32个字节空间作为这段程序的栈空间,然后利用栈将x0和x1的值进行交换
.text
.global _A
_A:
mov x0, #0xa ;x0=10
mov x1, #0xc ;x1=12
sub sp, sp,#0x20 ;拉伸栈空间
stp x0, x1, [sp, #0x10] ;sp栈偏移16字节写入x0,x1
ldp x1, x0, [sp, #0x10] ;sp栈偏移16字节读取x1,x0
add sp, sp, #0x20 ;栈平衡(每个函数调用完毕之后,将拉伸的栈空间平衡(将sp加回去))
ret
bl和ret指令
bl标号
将下一条指令的地址放入lr(x30)寄存器转到标号处执行指令
ret
默认使用lr(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址!ARM64平台的特色指令,它面向硬件做了优化处理的
x30寄存器
x30寄存器存放的是函数的返回地址.当ret指令执行时刻,会寻找x30寄存器保存的地址值!注意:在函数嵌套调用的时候.需要将x30入栈!
函数的参数
在 ARM64 中,函数的参数是保存在X0-X7(W0-W7)这8个寄存器里面,如果超过8个参数,超过的参数会入栈,保存在栈里面。
调用函数一:(这种情况是参数个数不超过8)
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int sum1(int a, int b) {
return a + b;
}
int main(int argc, char * argv[]) {
sum1(10, 20);
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- 未调用函数时的汇编代码
- 进入函数后的汇编代码
调用函数二:(这种情况是参数个数超过8)
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int sum2(int a, int b, int c, int d, int e, int f, int g, int h, int i) {
int sum = a + b + c + d + e + f + g + h + i;
return sum;
}
int main(int argc, char * argv[]) {
sum2(1, 2, 3, 4, 5, 6, 7, 8, 9);
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- 未调用函数时的汇编代码
- 进入函数后的汇编代码
函数的局部变量
在 ARM64 中,函数的局部变量保存在栈里面。
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int sum3() {
int a = 10;
int b = 20;
return a + b;
}
int main(int argc, char * argv[]) {
sum3();
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- 未调用函数时的汇编代码
- 进入函数后的汇编代码
通过调用函数的前后汇编代码可以发现函数的局部变量保存在栈里面,当函数调用完之后,保持栈平衡,就是我们所谓的释放内存空间,其实真正意义上是此数据仍在内存中,只是此时没法直接访问到,等待下一次其他数据的覆盖。
函数的返回值
在 ARM64 中,函数的返回值保存在 x0寄存器
函数的嵌套调用
注意事项:
1.当此函数为叶子函数时,就不需要在对 x29 和 x30 寄存器的保护。
叶子函数:函数里面不再调用其他函数。
2.当函数中的参数还有其他函数的引用时,需要对参数入栈,进行保护,以防引起数据错误。
叶子函数
//叶子函数
int funcA(int a, int b) {
return a+b;
}
//非叶子函数
void funcB() {
printf("haha"); //调用 printf 函数
}
明显可以发现叶子函数并没有做任何对 x29 和 x30 寄存器的保护。
函数嵌套
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int funcA(int a, int b) {
return a+b;
}
int funcC(int a, int b) {
int c = funcA(a, b);
int d = funcA(a, b);
return c+d;
}
int main(int argc, char * argv[]) {
funcC(10, 20);
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}