栈
- 栈:是一种具有特殊的访问方式的存储空间(后进先出, Last In Out Firt,LIFO)
SP和FP寄存器
- sp寄存器在任意时刻会保存我们栈顶的地址.
- fp寄存器也称为x29寄存器属于通用寄存器,但是在某些时刻(比如说嵌套调用)我们利用它保存栈底的地址!
注意:ARM64开始,取消32位的 LDM,STM,PUSH,POP指令! 取而代之的是ldr\ldp str\stp ARM64里面 对栈的操作是16字节对齐的!!
函数调用栈
常见的函数调用开辟和恢复的栈空间
sub sp, sp, #0x40 ; 拉伸0x40(64字节)空间
stp x29, x30, [sp, #0x30] ;x29\x30 寄存器入栈保护
add x29, sp, #0x30 ; x29指向栈帧的底部
...
ldp x29, x30, [sp, #0x30] ;恢复x29/x30 寄存器的值
add sp, sp, #0x40 ; 栈平衡
ret
复制代码
关于内存读写指令
注意:读/写 数据是都是往高地址读/写
str(store register)指令
将数据从寄存器中读出来,存到内存中.
ldr(load register)指令
将数据从内存中读出来,存到寄存器中
此ldr 和 str 的变种ldp 和 stp 还可以操作2个寄存器.
###堆栈操作练习 使用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
add sp, sp, #0x20 ;栈平衡
复制代码
[]
是寻址,中间逗号可以加上后面的偏移量,可以拿到该地址的值或者放值到该地址,有点像swift中,UnsafePointer
的pointee
。
我们可以通过View Memory查看内存的数据状况(个人更喜欢x/8g)
View Memory刷新数据需要Page来回切
bl和ret指令
bl标号
- 将下一条指令的地址放入lr(x30)寄存器
- 转到标号处执行指令
ret
- 默认使用lr(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址!
ARM64平台的特色指令,它面向硬件做了优化处理的
x30寄存器
x30寄存器存放的是函数的返回地址.当ret指令执行时刻,会寻找x30寄存器保存的地址值!
注意:在函数嵌套调用的时候.需要讲x30入栈!
.text
.global _A,_B
_A:
str x30, [sp, #-0x10]! ;x30入栈,并且sp指针开辟栈空间
mov x0, #0xaaaa
bl _B
mov x0, #0xaaaa
ldr x30, [sp], #0x10 ;x30恢复,栈恢复,平衡
ret
_B:
mov x0, #0xbbbb
ret
复制代码
这里有两条简写语句:
str x30, [sp, #-0x10]!
相当于
sub sp, sp, #0x10
str x30, [sp]
复制代码
ldr x30, [sp], #0x10
相当于
ldr x30, [sp]
add sp, sp, #0x10
复制代码
这里有点a++
和++a
的意思
函数的参数和返回值
ARM64下,函数的参数是存放在X0到X7(W0到W7)这8个寄存器里面的.如果超过8个参数,就会入栈。OC的方法最好不要超过6个,因为默认会有self
和cmd
这两个参数
函数的返回值是放在X0 寄存器里面的.
实现一个sum函数
// 调用前声明
int sum(int a, int b);
// 汇编代码
.text
.global _sum
_sum:
add x0, x0, x1
ret
复制代码
函数的局部变量
函数的局部变量放在栈里面!