1. bl指令
- CPU从何处执行指令是由pc中的内容决定的,我们可以通过改变pc的内容来控制CPU执行目标指令
- ARM64提供了一个mov指令(传送指令),可以用来修改大部分寄存器的值,比如
mov x0,#10、mov x1,#20
,但是,mov指令不能用于设置pc的值,ARM64没有提供这样的功能 - ARM64提供了另外的指令来修改PC的值,这些指令统称为转移指令,最简单的是bl指令
2. bl指令练习
2.1. 在xocode中新建一个汇编文件
![](https://img.haomeiwen.com/i1187684/40308433a458c133.png)
汇编是.s结尾
![](https://img.haomeiwen.com/i1187684/f91ba647c940e9df.png)
2.2. 汇编代码
这是arm64的汇编,一定要在5s以上的真机上运行
.text //存储在代码段
.global _A,_B //暴露A,B给外部调用
_A:
mov x0,#0xa //x0=0xa
mov x1,#0x00 //x1=0x00
add x1,x0,#0xff //x1=x0+0xff
mov x0,x1 //x0=x1
bl _B //跳转_B
mov x0,#0x00
ret
_B:
add x0,x0,#0x10 //x0=x0+ 0x10
ret
汇编在Xcode中是会参与编译的
![](https://img.haomeiwen.com/i1187684/4e59c94f799f76f7.png)
main.m中调用
![](https://img.haomeiwen.com/i1187684/ddcda8162c78e382.png)
这样打断点是断不住的
![](https://img.haomeiwen.com/i1187684/ec36a419ccafbdb1.png)
main.m中打断点。
Debug->Debug Workflow->Always Show Disaseembly
显示汇编![](https://img.haomeiwen.com/i1187684/8a889e2ddc6433d6.png)
再运行,会断在这里
![](https://img.haomeiwen.com/i1187684/7933ae545acbd2ce.png)
在lldb中s进入函数中
![](https://img.haomeiwen.com/i1187684/2c13411a5d2716bb.png)
![](https://img.haomeiwen.com/i1187684/061297f765794896.png)
选中这个可以看到寄存器相关的
![](https://img.haomeiwen.com/i1187684/00dce630bea0e511.png)
n单步往下执行可以看到寄存器的值已经改变
![](https://img.haomeiwen.com/i1187684/5b6b4d2658ef49b5.png)
![](https://img.haomeiwen.com/i1187684/1d0da8849d45ed99.png)
3. 函数调用栈
3.1. 栈:是一种具有特殊的访问方式的存储空间(后进先出,LIFO)
![](https://img.haomeiwen.com/i1187684/97461e8c890f8f5b.png)
3.1. SP和FP寄存器
- sp寄存器在任意时刻会保存我们栈顶的地址.
- fp寄存器也称为x29寄存器属于通用寄存器,但是在某些时刻我们利用它保存栈底的地址!
3.2. 函数调用栈
.text
.global _A,_B
_A:
mov x0,#0xa
mov x1,#0x00
add x1,x0,#0xff
mov x0,x1
bl _B
mov x0,#0x00
ret
_B:
sub sp,sp,#0x20 //拉伸0x20(32字节)空间
stp x0,x1,[sp,#0x10] //将x0,x1写入sp+#0x10内存中
ldp x1,x0,[sp,#0x10] //将sp+#0x10这段内存的数据加载到x1,x0中
add sp,sp,#0x20 //栈平衡
ret
断点查看,memory read sp,读取的32字节的数据
![](https://img.haomeiwen.com/i1187684/a1e9e7272ef174d6.png)
n走一步,拉伸栈空间,读取sp的值,sp的值已经改变,里面的数据可能是以前用过的,等会就会被覆盖
![](https://img.haomeiwen.com/i1187684/615eb0a6867a335b.png)
发现数据已经写入了,前面x0,后面x1
![](https://img.haomeiwen.com/i1187684/f1c7dbf902f9f1bf.png)
n下一步,x0,x1的值已经交换
![](https://img.haomeiwen.com/i1187684/0db5ba00e0de4499.png)
栈空间平衡
![](https://img.haomeiwen.com/i1187684/41076bb797af9229.png)
4. bl和ret指令
- bl标号
将下一条指令的地址放入lr(x30)寄存器
转到标号处执行指令 - ret
默认使用lr(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址! - lr(x30)寄存器存放的是函数的返回地址.当ret指令执行时刻,会寻找x30寄存器保存的地址值!pc存储的是cpu将要执行的指令
上面写的bl指令是有问题的,main函数中这样执行一下,发现没有打印B,
![](https://img.haomeiwen.com/i1187684/998c04179d3b7a41.png)
打断点进去汇编里面看看,注意这个lr和pc
![](https://img.haomeiwen.com/i1187684/5b51805b4efef6cd.png)
bl进B函数后,发现lr的值是上面A中bl的下一条指令,pc是将要执行的下一条指令
![](https://img.haomeiwen.com/i1187684/3e6d399b46ca25bf.png)
继续ni,直到ret后回到A函数,发现lr和sp的值一样,由此可以发现bl指令之所以能够跳转回去,是lr记录了跳转之前的bl的下一句指令
![](https://img.haomeiwen.com/i1187684/c2d009509115de1e.png)
继续执行ni,发现lr的值始终始终不变,,执行完ret后,pc的值又跟lr一样了,程序在这一直死循环了,原因是main函数bl进A的时候lr存的值被覆盖掉了,
![](https://img.haomeiwen.com/i1187684/9ae8c8ef6b8bd693.png)
至此得出结论,bl进新函数后,要保存下lr的值,bl其他函数回来后要把lr的值重新取一下,把代码改成这样
.text
.global _A,_B
_A:
mov x0,#0xa
mov x1,#0x00
add x1,x0,#0xff
mov x0,x1
//存储lr(x30)
sub sp,sp,#0x10
str x30,[sp]
////////////
bl _B
mov x0,#0x00
//取出lr
ldr x30,[sp]
add sp,sp,#0x10
////////////
ret
_B:
sub sp,sp,#0x20
stp x0,x1,[sp,#0x10] //将x0,x1写入sp+#0x10内存中
ldp x1,x0,[sp,#0x10] //将sp+#0x10这段内存的数据加载到x1,x0中
add sp,sp,#0x20
ret
执行,输出AB了,问题解决
![](https://img.haomeiwen.com/i1187684/515be1ae61ac4472.png)
上面的存储和取出的代码还可以优化成1句,表达的意思都是一样的
//存储lr(x30)
sub sp,sp,#0x10
str x30,[sp]
可以改成
str x30,[sp,#-0x10]!
//取出lr
ldr x30,[sp]
add sp,sp,#0x10
可以改成
ldr x30,[sp],#0x10
5. 函数的参数和返回值
- ARM64下,函数的参数是存放在X0到X7(W0到W7)这8个寄存器里面的.如果超过8个参数,就会入栈,如果是浮点数就会用浮点寄存器。
- 函数的返回值是放在X0 寄存器里面的.
5.1. main函数中定义一个9个参数的函数,然后断点查看汇编
![](https://img.haomeiwen.com/i1187684/36ff27f5ca4b26fa.png)
来分析一下栈内存
![](https://img.haomeiwen.com/i1187684/73082b48ee817c65.png)
![](https://img.haomeiwen.com/i1187684/df169c5557d20809.png)
将上面的栈结构画成这张图更直观
![](https://img.haomeiwen.com/i1187684/e802d0d6d4aa57c6.png)
函数的返回值进入到test函数里面
![](https://img.haomeiwen.com/i1187684/c83e1c8eaadf074e.png)
5.1. main函数中定义一个返回结构体的函数来看返回值是操作的
![](https://img.haomeiwen.com/i1187684/2d9c820356fa2321.png)
main函数栈空间
![](https://img.haomeiwen.com/i1187684/7e20bb9675170930.png)
getStr函数栈
![](https://img.haomeiwen.com/i1187684/9875d5cf59b28465.png)
6. 总结
上面主要学习了bl指令和函数的堆栈,通过一些堆栈练习了解寄存器的存储,学习了函数的参数和返回值是如何进行操作。
网友评论