ARM64汇编学习笔记二(寄存器和函数本质)
寄存器
CPU除了有控制器、运算器还有寄存器。其中寄存器的作用就是进行数据的临时存储。
CPU的运算速度是非常快的,为了性能CPU在内部开辟一小块临时存储区域,并在进行运算时先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小快临时存储区域内进行。我们称这一小块临时存储区域为寄存器。
对于arm64系的CPU来说, 如果寄存器以x开头则表明的是一个64位的寄存器,如果以w开头则表明是一个32位的寄存器,其中32位的寄存器是64位寄存器的低32位部分并不是独立存在的。注意在系统中没有提供16位和8位的寄存器供访问和使用。
高速缓存(了解)
iPhoneX上搭载的ARM处理器A11它的1级缓存的容量是64KB,2级缓存的容量8M。
CPU每执行一条指令前都需要从内存中将指令读取到CPU内并执行。而寄存器的运行速度相比内存读写要快很多,为了性能,CPU还集成了一个高速缓存存储区域.当程序在运行时,先将要执行的指令代码以及数据复制到高速缓存中去(由操作系统完成).CPU直接从高速缓存依次读取指令来执行.
数据地址寄存器
数据地址寄存器通常用来做数据计算的临时存储、做累加、计数、地址保存等功能。定义这些寄存器的作用主要是用于在CPU指令中保存操作数,在CPU中当做一些常规变量来使用。
ARM64中
- 64位的数据: X0-X30, XZR(零寄存器)
-
32位的数据: W0-W30, WZR(零寄存器)
浮点寄存器
因为浮点数的存储以及其运算的特殊性,CPU中专门提供浮点数寄存器来处理浮点数
ARM64中
- 64位的数据: D0 - D31
-
32位的数据: S0 - S31
向量寄存器
现在的CPU支持向量运算.(向量运算在图形处理相关的领域用得非常的多)为了支持向量计算,系统也提供了众多的向量寄存器
128位的数据:V0 - V31
栈
栈是一种具有特殊访问方式的存储空间(特性:先进后出 - First In Last Out,后进先出 - Last In First Out)
函数调用会开辟一段内存空间(栈空间)用于函数的局部变量、参数和寄存器的保护
sub sp, sp,#0x20 ;拉伸栈空间
函数调用完毕之后也需要释放空间
add sp, sp, #0x20 ;栈平衡
补充:sp拉伸空间的大小是根据什么?
函数的参数和局部变量的内存空间大小 + x29、x30的内存空间大小
拉伸空间的大小最少为16字节,并且必须是16字节的倍数
sp 和 fp 寄存器
- sp寄存器在任意时刻会保存栈顶的地址
- fp寄存器也称为x29寄存器属于通用寄存器,但是在某些时刻我们利用它保存栈底的地址
注意:
1.ARM64 开始就取消了32位的 LDM,STM,PUSH,POP指令,取而代之的是str\stp、ldr\ldp
2.ARM64 里面对栈的操作是16字节对齐
内存读写指令
注意:读/写数据都是往高地址读/写
str(store register)指令
将数据从寄存器中读出来,存到内存中
ldr(load register)指令
将数据从内存中读出来,存到寄存器中
此 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)寄存器,保存回家的地址
- 跳转到标号处执行指令
rec 指令--返回
- 返回到 lr 寄存器所保存的地址
- pc 寄存器指向此地址,并执行下一步指令
注意:x30寄存器存放的是函数的返回地址,当ret指令执行时刻,会寻找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 函数
}
funcA 汇编代码
funcB 汇编代码
明显可以发现叶子函数并没有做任何对 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]));
}
}