SP和FP寄存器
sp寄存器 在任意时刻都会保存栈顶的地址。
fp 寄存器 也称为 x29 寄存器属于通用寄存器,但在某些时刻我们用它保存栈底的地址。
注意:ARM64 开始,取消32位的 LDM、STM、PUSH、POP指令,取而代之的是 ldr/ldp、 str/stp ARM64 里面对栈的操作是
16字节对齐。
内存读写指令
str(store register)指令
str w0, [sp, #0x1c] 将 w0 中的值,写到 sp向上 偏移16个字节的内存位置。
ldr(load register)指令
ldr w0, [sp, #0x18] 将 sp向上 偏移24个字节的内存位置中的值读到 w0 中。
注意:数据是都是往高地址读/写。 stp 和 ldp 为 str 和 ldr 的变种。
练习
使用32个字节空间作为这段程序的栈空间,然后利用栈将 x0 和 x1 的值进行交换。
sub sp, sp, #0x20 ;拉伸栈空间32个字节
stp x0, x1, [sp, #0x10] ;sp往上加16个字节,存放x0和x1
ldp x1, x0, [sp, #0x10] ;将sp偏移16个字节的值取出来,放入x1和x0
跳转和返回指令
bl
将下一条指令的地址放入 lr(x30)寄存器并跳转到标号处。
ret
默认使用 lr(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址。
x30寄存器(lr寄存器)
x30寄存器存放的是函数的返回地址。当 ret 指令执行时刻,会寻找 x30 寄存器保存的地址值。
函数的参数
函数的参数是存放在 X0 到 X7 (W0到W7)这8个寄存器里面的。如果超过8个参数,就会入栈. 函数的返回值是放在 X0 寄存器里面的。
// 通过其他函数的栈传递参数(大于8个参数时)
int test(int a,int b,int c,int d,int e,int f,int g,int h,int i, int j, int k){
return a+b+c+d+e+f+g+h+i+j+k;
}
// e.g. 11个参数
int main(int argc, char * argv[]) {
test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ,11);
}
查看 main 函数汇编:
查看 test 函数汇编:
函数的局部变量
函数的局部变量放在栈里面
分析如下函数:
int function(int a, int b) {
int c = 3;
return a + b + c;
}
- (void)viewDidLoad {
[super viewDidLoad];
function(10, 20);
}
查看 ViewDidLoad 函数汇编:
查看 function 函数汇编:
函数的全局变量与字符串常量
分析如下函数:
int g = 12;
int func(int a, int b){
printf("haha");
int c = a + g;
return c;
}
int main(int argc, char * argv[]) {
func(1, 2);
}
查看 func 函数汇编:
查看静态字符串:
查看全局变量:
其中重点分析 adrp x0,1 和 add x0,x0,#0xfa5 两句:
adrp指令(address page 按页寻址):
- 将1的值左移12位,此时的1是二进制
- 加上pc寄存器的值(先需要将pc的低12位清零)
pageSize的大小是4k即十进制4096,十六进制0x1000。