You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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字节对齐,否则可能触发异常。

验证修改后的执行流程

  1. main执行BL label2:LR = #(3),跳转到label2;
  2. label2执行PUSH {LR}:将#(3)压入栈;
  3. label2执行BL label1:LR被覆盖为#(2),跳转到label1;
  4. label1执行RET:跳转到#(2),回到label2;
  5. label2执行POP {LR}:从栈中恢复LR为#(3);
  6. label2执行RET:跳转到#(3),正确回到main的调用点。

内容的提问来源于stack exchange,提问作者lafinur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 14:33:36