ARMv8中BL与RET嵌套调用的返回异常问题及解决咨询
ARM汇编嵌套BL/RET调用的正确实现
问题场景
假设label1、label2是两组以RET结尾的ARM汇编指令序列,label2通过BL调用label1,代码结构如下:
label1: # 若干操作... # (1) RET label2: # 若干操作 BL label1 #(2) RET
在main中调用label2:
main: # 若干操作... BL label2 #(3)
异常原因
执行main里的BL label2时,LR寄存器会保存返回点#(3);进入label2后执行BL label1,LR会被覆盖为#(2)。label1的RET能正常回到#(2),但label2的RET此时会使用已经被覆盖的LR(值还是#(2)),导致执行流陷入循环异常,无法返回main的#(3)位置。
解决方案:用栈保存/恢复LR
ARM的LR是单个寄存器,嵌套调用必须手动借助栈来维护上层调用的返回地址,避免被覆盖。以下是两种常用实现方式:
方式1:仅保存LR到栈
修改label2,在调用label1前先把当前LR(返回main的地址)压入栈,执行完子函数后再恢复LR:
label2: ; 把LR压入栈(假设栈向下生长,SP为栈指针) PUSH {LR} ; 若干操作 BL label1 ;(2) ; 从栈中恢复LR POP {LR} RET
方式2:规范栈帧(保存多寄存器)
如果函数需要修改R4-R12这类调用者保存寄存器,可以一次性把LR和需要保留的寄存器压入栈,函数结束时一起恢复,符合ARM函数调用约定:
label2: ; 保存LR和需要保留的寄存器,构建栈帧 PUSH {R4-R5, LR} ; 若干操作(可安全使用R4-R5) BL label1 ;(2) ; 恢复寄存器和LR POP {R4-R5, LR} RET
补充说明
RET本质是BX LR的别名指令,两者功能完全一致;- 在Thumb-2架构下,要确保栈保持8字节对齐,否则可能触发异常。
验证修改后的执行流程
main执行BL label2:LR =#(3),跳转到label2;label2执行PUSH {LR}:将#(3)压入栈;label2执行BL label1:LR被覆盖为#(2),跳转到label1;label1执行RET:跳转到#(2),回到label2;label2执行POP {LR}:从栈中恢复LR为#(3);label2执行RET:跳转到#(3),正确回到main的调用点。
内容的提问来源于stack exchange,提问作者lafinur
相关产品推荐
相关产品推荐

