• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

SOC/IP验证工程师

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

ARM V8R中汇编指令PUSH的详细用法

好的,我们来详细解析 ARMv8-R 架构(通常运行在 AArch32 状态)中 PUSH 指令的用法。这是一条极其重要且常用的指令,它是实现函数调用、中断处理等功能的基石,用于在栈上动态分配内存并保存寄存器的值。

🧠 核心功能与概述

PUSH 指令的核心功能是:将指定的一个或多个寄存器的值,存储到栈内存中,并自动更新栈指针 (SP)。

  • 操作本质:它是一种“压栈” (Push onto the stack) 操作。栈是一种后进先出 (LIFO) 的数据结构,从高地址向低地址增长。
  • 关键特性:它是一条伪指令。这意味着汇编器在编译时,会将其转换为一条等效的核心指令。理解其等效指令是深入理解其行为的关键。
  • 主要用途:
    1. 保存上下文:在进入一个函数(子程序)时,保存 caller 需要保留的寄存器(即被调用者保存寄存器),以便在函数返回时可以恢复现场。
    2. 传递参数:当函数参数过多,无法全部通过寄存器 (R0-R3) 传递时,将多余的参数压入栈中。
    3. 分配局部变量:在栈上为函数的局部变量预留空间。

PUSH 指令的本质是 STMDB SP!, {reglist} (Store Multiple Decrement Before)。下图清晰地展示了其工作原理:

flowchart TD A["执行前: SP = 0x8000<br>要保存: R1, R2, R3"] --> B[指令: PUSH {R1-R3}] B --> C[等价于: STMDB SP!, {R1-R3}] C --> D[操作: 先递减指针, 后存储数据] subgraph Operation[具体操作步骤] direction LR O1[Step 1: SP = SP - 4 → 0x7FFC] O2[Step 2: 存储 R3 → Mem[0x7FFC]] O3[Step 3: SP = SP - 4 → 0x7FF8] O4[Step 4: 存储 R2 → Mem[0x7FF8]] O5[Step 5: SP = SP - 4 → 0x7FF4] O6[Step 6: 存储 R1 → Mem[0x7FF4]] end D --> Operation Operation --> E[执行后: SP = 0x7FF4] E --> F[内存布局:] G[0x7FF4: R1<br>0x7FF8: R2<br>0x7FFC: R3<br>0x8000: ...] -.-> F

⚙️ 语法与操作数格式

PUSH 指令的语法如下:

PUSH{cond} {reglist}
  • {cond}:可选的条件码后缀(如 EQ, NE),允许条件地执行压栈操作。
  • {reglist}:寄存器列表。指定要压入栈中的寄存器集合,用大括号 {} 括起来。
    • 寄存器可以连续地使用 - 连接(如 {R4-R6})。
    • 或不连续地用 , 分隔(如 {R1, R3, R5, LR})。
    • 重要:寄存器总是编号小的寄存器存入低地址(即栈顶),编号大的寄存器存入高地址。

🛠️ 详细用法与示例

1. 基本用法:保存寄存器

这是最经典的用途,在函数开头保存需要使用的寄存器。

my_function:
    PUSH {R4, R5, R6, LR}  @ 将 R4, R5, R6 和链接寄存器 LR 压栈保存
                           @ 等价于: STMDB SP!, {R4, R5, R6, LR}
    @ ... 函数体代码 ...
    @ 这里可以安全地使用 R4, R5, R6,而不会破坏调用者的值

    POP {R4, R5, R6, PC}   @ 函数结束时,弹出值恢复 R4, R5, R6,
                           @ 并直接将返回地址 LR 弹出到 PC 中,实现函数返回

关键点:

  • 保存了 R4-R6,因为这些是被调用者需要保存的寄存器(根据 AAPCS 标准)。
  • 保存了 LR(链接寄存器),因为函数内部可能会调用其他函数(使用 BL 指令),这会覆盖 LR 的值。
  • POP {..., PC} 是一种高效的函数返回技巧。

2. 传递参数

当参数多于 4 个时,超出部分通过栈传递。

; Caller (调用者) 端:
MOV R0, #1      @ 第一个参数
MOV R1, #2      @ 第二个参数
MOV R2, #3      @ 第三个参数
MOV R3, #4      @ 第四个参数
PUSH {R5}       @ 假设第五个参数在 R5 中,将其压栈
MOV R5, #5
BL my_function
ADD SP, SP, #4  @ 调用完成后,调用者负责平衡栈指针 (清空参数)

; Callee (被调用函数) 端:
my_function:
    PUSH {R4, LR}        @ 保存寄存器
    LDR R4, [SP, #8]     @ 从栈上读取第五个参数
                         @ (SP 指向 R4, 跳过 R4 和 LR 各4字节,共8字节)
    @ ... 使用 R0-R3 和 R4 (第五个参数) ...
    POP {R4, PC}         @ 恢复并返回

3. 为局部变量分配空间

通过移动栈指针来预留空间。

my_function:
    PUSH {R4, R5, LR}    @ 先保存寄存器
    SUB SP, SP, #8       @ 在栈上为两个局部变量 (8字节) 分配空间
    @ ...                @ 可以使用 [SP] 和 [SP, #4] 来访问局部变量
    ADD SP, SP, #8       @ 释放局部变量空间
    POP {R4, R5, PC}     @ 恢复寄存器并返回

⚠️ 重要注意事项与原理

  1. 栈指针对齐:PUSH 指令要求栈指针 (SP) 是 8 字节对齐的。这是 ARM 架构的标准调用约定 (AAPCS)。非对齐访问可能会导致性能下降或触发异常。

  2. 写回操作:PUSH 指令总是包含写回(即 ! 后缀)。这意味着栈指针 SP 会在操作完成后自动更新为新的栈顶地址。你不需要手动去修改 SP。

  3. Thumb 指令集的限制:在 Thumb 状态下(例如使用 .thumb 指令),PUSH 指令的寄存器列表有一定的限制:

    • 只能访问寄存器 R0-R7 和 LR。
    • 列表长度受限。
      而在 ARM 状态下(例如使用 .arm 指令),可以压入包括高寄存器 (R8-R12) 在内的任何组合。
  4. 与 POP 的对称性:

    • PUSH 和 POP 必须成对出现,以保证栈的平衡。
    • POP 的等效指令是 LDMIA SP!, {reglist} (Load Multiple Increment After)。
    • 通常,函数内 PUSH 了多少个寄存器,返回时就要 POP 多少个寄存器(除非你手动调整了 SP)。
  5. 寄存器顺序:无论你在 {} 中以何种顺序列出寄存器,编号小的寄存器总是被压入(和弹出)到更低地址的栈位置。

💎 总结

PUSH 指令是 ARM 汇编中管理栈和实现函数调用的核心指令。

它的核心价值在于:

  • 简洁性:一条指令替代了多条 STR 指令,极大地简化了代码。
  • 原子性:架构保证这些存储操作是原子的,这对于上下文安全至关重要。
  • 符合标准:它是实现 ARM 架构过程调用标准 (AAPCS) 的基础。

主要应用场景:

  • 函数开场白 (Prologue):在函数开始时保存上下文。
  • 参数传递:处理多余参数的传递。
  • 中断/异常处理:快速保存处理器状态。

简单来说:只要你需要临时将寄存器的值保存到内存中,以便腾出这些寄存器供后续操作使用,并且在之后还需要恢复它们,就应该使用 PUSH / POP 指令对。 它是编写任何结构化汇编代码的必备工具。

posted on 2025-09-05 20:23  SOC验证工程师  阅读(62)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3