ARM在压栈出栈使用局部变量中,R0,R1这些通用寄存器是这么使用的,有什么特殊的地方

在ARM架构中,R0、R1等通用寄存器(R0-R3)在压栈、出栈和局部变量使用上的“特殊性”,源于 ARM程序调用标准(ATPCS) 对寄存器的分工约定,核心区别于R4-R11:

一、寄存器的核心分工(ATPCS规则

寄存器范围

主要用途

保存责任(压栈出栈约束)

R0 - R3

函数间参数传递(前4个参数)、返回值传递(32位返回值用R0,64位用R0+R1)

「调用者保存」:被调用函数可自由修改,无需主动压栈恢复(调用者若需保留原值,由调用者自己压栈)

R4 - R11

函数内局部变量存储也称为(“变量寄存器”)

「被调用者保存」:被调用函数若使用这些寄存器,必须主动压栈保存原值,退出时出栈恢复

二、压栈、出栈的特殊规则

1. R0-R3:“调用者保存”,约束更宽松
  • 局部变量的临时使用:R0-R3可临时充当“局部变量”(存储函数内的临时计算结果),且由于是“调用者保存”(也即是函数A调用函数B时,函数A进行保存),被调用函数无需主动压栈保存它们的原始值(因为调用者若依赖R0-R3的原值,由调用者自己负责压栈)。

  • ARM 函数返回值的存储遵循 “小值用寄存器,大值用栈” 的原则:

    • 32 位 → R0;

    • 64 位 → R0+R1;

  • 嵌套调用时的压栈:只有当函数内部有嵌套调用(比如当前函数又调用其他函数,且后续需要复用当前R0-R3的值)时,才需要手动将R0-R3压栈,用完后出栈恢复。

2. R4-R11:“被调用者保存”,约束更严格
  • 局部变量的专用性:R4-R11是局部变量的“专用寄存器”。如果函数使用了这些寄存器存储局部变量,必须在函数入口处压栈保存它们的原始值,并在函数退出前出栈恢复——否则会破坏“调用者”的上下文(调用者可能依赖R4-R11的原值)。

我们通过 “正常情况”与“异常情况”的对比,举例说明“被调用函数不保存R4-R11”如何破坏调用者上下文。核心场景:funcA(调用者)依赖R4存储关键数据,funcB(被调用者)用R4存局部变量却不压栈恢复,导致funcA后续逻辑出错。

前提约定
  • R4-R11是 “被调用者保存”寄存器:按规则,被调用者(funcB)若使用这些寄存器,必须在入口压栈保存原值,退出时出栈恢复。

  • 示例中用 R4 代表R4-R11(其他寄存器逻辑完全相同)。

场景1:正常情况(funcB遵守规则,保存恢复R4)

funcA用R4存储重要数据(如0x1234),调用funcB时,funcB按规则保存R4,使用后恢复,funcA上下文无破坏。

.global funcA, funcB

funcA:  ; 调用者
    ; 步骤1:funcA用R4存储关键数据(后续需复用)
    MOV R4, #0x1234  ; R4 = 0x1234(比如是计算结果、配置参数)
    
    ; 步骤2:调用funcB(BL指令自动将返回地址存入LR)
    BL funcB         ; 跳转到funcB执行
    
    ; 步骤4:funcB返回后,funcA继续使用R4的原始值
    ADD R0, R4, #1   ; 依赖R4=0x1234,计算0x1234+1=0x1235(结果正确)
    BX LR            ; funcA返回,逻辑正常


funcB:  ; 被调用者(遵守规则,保存恢复R4)
    ; 步骤3-1:入口压栈保存R4原值(被调用者责任)
    PUSH {R4, LR}    ; ① 保存R4(此时R4=0x1234)和LR(返回地址)
    ; 步骤3-2:用R4存储funcB的局部变量
    MOV R4, #0x5678  ; R4 = 0x5678(funcB的局部变量,覆盖原值)
    ; 步骤3-3:funcB的业务逻辑(比如用R4做计算)
    ADD R4, R4, #2   ; R4 = 0x5678+2=0x567A(局部变量使用)
    ; 步骤3-4:退出前出栈恢复R4原值
    POP {R4, LR}     ; ② 恢复R4=0x1234,恢复LR
    BX LR            ; funcB返回funcA
结果:

funcA调用funcB后,R4仍为0x1234,后续计算0x1234+1=0x1235正确,上下文无破坏。

场景2:异常情况(funcB不遵守规则,不保存R4)

funcB用R4存储局部变量,但不压栈保存原值,也不出栈恢复,直接修改R4,导致funcA的R4被永久篡改。

汇编代码与执行流程
.global funcA, funcB

funcA:  ; 调用者(逻辑与场景1完全相同)
    MOV R4, #0x1234  ; R4 = 0x1234(关键数据)
    BL funcB         ; 调用funcB
    ADD R0, R4, #1   ; 依赖R4的原始值,但此时R4已被篡改!
    BX LR            ; 返回


funcB:  ; 被调用者(违规,不保存R4)
    ; 步骤3-1:直接用R4存储局部变量,不保存原值
    MOV R4, #0x5678  ; R4 = 0x5678(覆盖funcA的0x1234,无保存)
    ; 步骤3-2:funcB的业务逻辑
    ADD R4, R4, #2   ; R4 = 0x567A
    ; 步骤3-3:直接返回,不恢复R4
    BX LR            ; funcB返回,R4仍为0x567A(未恢复)
结果:

funcA调用funcB后,R4从0x1234变成了0x567A,后续计算0x567A+1=0x567B——与预期的0x1235完全不符,调用者上下文被破坏!

funcA的R4存储的是“数组地址”“配置寄存器值”等关键数据,这种破坏会导致更严重的问题(如数组越界、硬件配置错误、程序崩溃)。

三、局部变量的灵活使用差异

  • R0-R3:虽以“参数/返回值传递”为主,但也可灵活充当“临时局部变量”,且无需严格遵守“压栈-恢复”的强制约束(除非跨函数调用需要保留值)。

  • R4-R11:作为“专用局部变量寄存器”,使用时必须严格执行“压栈保存、出栈恢复”,以保证上下文安全性。

总结

R0-R3的特殊性在于:既是参数/返回值的核心通道,又属于“调用者保存”寄存器,因此压栈出栈的约束更宽松;而R4-R11是“被调用者保存”的局部变量专用寄存器,对压栈出栈的约束更严格。这种分工平衡了“参数传递效率”与“上下文安全性”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值