bl指令
bl指令也成为跳转指令,执行这个指令之前会做两个操作:
- 将下一条指令的地址放入
lr(x30)寄存器
- 转到标号处执行指令
ret指令
默认使用lr(x30)寄存器的值
,通过底层指令提示CPU此处作为下条指令地址! 也就是说,当执行ret指令
时,CPU会执行lr(x30)这个寄存器存的内存地址指向的命令。
x30寄存器
x30寄存器
存放的是函数的返回地址
.当ret指令执行时刻,会寻找x30寄存器保存的地址值
!
注意:在函数嵌套调用的时候.
需要将x30入栈
! 否则会造成死循环。
关于内存读写指令
注意:读/写 数据是都是往高地址读/写
str(store register)
指令
将数据从
寄存器
中读出来,存到内存中.
ldr(load register)
指令
将数据从
内存
中读出来,存到寄存器中
此ldr 和 str
的变种ldp 和 stp
可以操作2个寄存器。
栈
bl 和ret的实践
新建一个工程,创建一个汇编的文件,如下图:
随便取名为:asm.s, 在该文件中,添加如下的代码:
.text
.global _A, _B
_A:
mov x0, #0xaaaa
bl _B
mov x0, #0xcccc
ret
_B:
mov x0, #0xbbbb
ret
在main函数中,定义一个A的函数,并调用,如下图:
执行代码,打开断点时用汇编显示的设置,显示如下:
如上图所示,查看pc寄存器
的值,是0x10452a1c0
,正好是断点的位置;
bl
表示即将跳转到0x104529d0c
这个内存地址所存储的函数;
注意:bl 下面的那一行的地址是 0x10452a1c4。
接下来,按住controller 键 + 点击断点的下一步
,进入bl 要跳转的函数:
查看进入函数后,lr寄存器
的值已经修改成“0x10452a1c4”
,也就是进入A函数之前的下一句地址。
在控制台中,执行”ni
“即下一步的指令,
这边又有一个bl的指令
,那么bl执行之前,又会把下一条指令的地址,存放到lr寄存器上,再进入bl要跳转的函数。contrller+下一步,进入函数,查看:
此时,lr寄存器的值,已经被修改了。
继续执行,遇到ret
指令,该指令会取出lr寄存器的地址,然后跳转到该地址下
。此时,lr寄存器的地址是0x104529d14
, 即A函数跳转B函数后的下一条指令,如下图:
所以,执行完B函数后,会返回到A函数中;
继续单步执行,又会遇到ret
,那么又会继续取出lr寄存器的地址值,执行。但是此时,lr寄存器的值为0x104529d14。所以,就会陷入死循环。
因此,如果只是按照上面的那段汇编代码的话,执行main函数的时候,只会打印”1111“,然后陷入死循环。
那么如何优化呢?这边就需要用到栈。即,在跳转B函数之前,先保存一份lr寄存器的值,再跳转。
优化的代码如下:
.text
.global _A, _B
_A:
mov x0, #0xaaaa
str x30, [sp, #-0x10]!
bl _B
mov x0, #0xcccc
ldr x30, [sp], #0x10
ret
_B:
mov x0, #0xbbbb
ret
其中, str x30, [sp, #-0x10]!
相当于如下的代码:
sub sp, sp, #0x10 //把sp的值,减去 10, 在将值放到sp中
str x30, [sp] //把x30的值,存放到sp的位置中
ldr x30, [sp], #0x10
相当于如下的代码:
ldr x30, [sp]
add sp, sp, #0x10
整体的意思是:
在跳转b函数之前,将lr的地址保存到sp中,
跳转函数之后,lr的值修改成跳回函数A的地址
执行完B的代码,返回到A后,此时若执行完A的代码时,
将保存在sp的lr的值取出来,然后跳回到这个lr的地址的函数。
这样,在跳转函数A之前保存的lr的值,就不会被改变了。
执行的效果如下:
上图是,进入函数A之后,lr寄存器保存的值;
上图是,进入函数B后,lr寄存器保存的值;
单步执行,遇到ret,取出lr寄存器的值,跳转:
当执行完ldr这条指令后,之前保存到sp的值又存储到了lr寄存器上。
而这个值正好是,跳转到A函数后的下一条指令:
这样就可以避免死循环了。