ARM V8R中汇编指令PUSH的详细用法
好的,我们来详细解析 ARMv8-R 架构(通常运行在 AArch32 状态)中 PUSH 指令的用法。这是一条极其重要且常用的指令,它是实现函数调用、中断处理等功能的基石,用于在栈上动态分配内存并保存寄存器的值。
🧠 核心功能与概述
PUSH 指令的核心功能是:将指定的一个或多个寄存器的值,存储到栈内存中,并自动更新栈指针 (SP)。
- 操作本质:它是一种“压栈” (Push onto the stack) 操作。栈是一种后进先出 (LIFO) 的数据结构,从高地址向低地址增长。
- 关键特性:它是一条伪指令。这意味着汇编器在编译时,会将其转换为一条等效的核心指令。理解其等效指令是深入理解其行为的关键。
- 主要用途:
- 保存上下文:在进入一个函数(子程序)时,保存 caller 需要保留的寄存器(即被调用者保存寄存器),以便在函数返回时可以恢复现场。
- 传递参数:当函数参数过多,无法全部通过寄存器 (R0-R3) 传递时,将多余的参数压入栈中。
- 分配局部变量:在栈上为函数的局部变量预留空间。
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} @ 恢复寄存器并返回
⚠️ 重要注意事项与原理
-
栈指针对齐:
PUSH指令要求栈指针 (SP) 是 8 字节对齐的。这是 ARM 架构的标准调用约定 (AAPCS)。非对齐访问可能会导致性能下降或触发异常。 -
写回操作:
PUSH指令总是包含写回(即!后缀)。这意味着栈指针 SP 会在操作完成后自动更新为新的栈顶地址。你不需要手动去修改 SP。 -
Thumb 指令集的限制:在 Thumb 状态下(例如使用
.thumb指令),PUSH指令的寄存器列表有一定的限制:- 只能访问寄存器
R0-R7和LR。 - 列表长度受限。
而在 ARM 状态下(例如使用.arm指令),可以压入包括高寄存器 (R8-R12) 在内的任何组合。
- 只能访问寄存器
-
与
POP的对称性:PUSH和POP必须成对出现,以保证栈的平衡。POP的等效指令是LDMIA SP!, {reglist}(Load Multiple Increment After)。- 通常,函数内
PUSH了多少个寄存器,返回时就要POP多少个寄存器(除非你手动调整了 SP)。
-
寄存器顺序:无论你在
{}中以何种顺序列出寄存器,编号小的寄存器总是被压入(和弹出)到更低地址的栈位置。
💎 总结
PUSH 指令是 ARM 汇编中管理栈和实现函数调用的核心指令。
它的核心价值在于:
- 简洁性:一条指令替代了多条
STR指令,极大地简化了代码。 - 原子性:架构保证这些存储操作是原子的,这对于上下文安全至关重要。
- 符合标准:它是实现 ARM 架构过程调用标准 (AAPCS) 的基础。
主要应用场景:
- 函数开场白 (Prologue):在函数开始时保存上下文。
- 参数传递:处理多余参数的传递。
- 中断/异常处理:快速保存处理器状态。
简单来说:只要你需要临时将寄存器的值保存到内存中,以便腾出这些寄存器供后续操作使用,并且在之后还需要恢复它们,就应该使用 PUSH / POP 指令对。 它是编写任何结构化汇编代码的必备工具。
浙公网安备 33010602011771号