arm的v8r中汇编指令STM的详细用法
ARMv8-R 架构中的 STM(Store Multiple)指令用于将多个寄存器的值存储到一片连续的内存空间中。这对于批量数据写入、上下文保存(如进入异常处理程序时的寄存器保存)、堆栈操作等场景非常有用。由于搜索结果中关于 ARMv8-R specifically 的信息较少,我会结合 ARM 架构的通用知识为你详细解释。
📝 STM 指令格式
STM 指令的基本语法格式如下:
STM{<cond>}<addressing_mode> <Rn>{!}, <registers>{^}
{<cond>}:条件执行后缀(可选)。例如EQ(等于)、NE(不等于)等。如果省略,指令将无条件执行。<addressing_mode>:寻址模式。决定了基址寄存器Rn的值如何变化,以及数据存储的顺序。这是STM指令的关键所在,详细说明见下文表格。<Rn>:基址寄存器。其值是内存操作的起始地址。Rn不能是 PC(R15)。{!}:写回后缀(可选)。如果使用!,则在数据传送完毕后,将最终的内存地址写回基址寄存器Rn。否则,Rn的值保持不变。<registers>:寄存器列表。指定要存储的寄存器,用大括号{}括起来。寄存器可以用逗号分隔(如{R0, R2, R5}),也可以用连字符表示范围(如{R4-R8})。无论列表中寄存器的书写顺序如何,在实际存储时,编号小的寄存器总是对应更低的内存地址。{^}:特殊后缀(可选)。含义较为复杂,主要用于特权模式和用户模式切换以及异常返回。- 在
STM指令中,使用^表示操作的是用户模式下的寄存器,而非当前模式的寄存器。 - 更多情况下与
LDM指令和异常返回相关,STM中较少使用。
- 在
🔍 寻址模式
STM 指令支持多种寻址模式,决定了地址的增长方向和操作顺序。它们主要分为两类:数据块传输和堆栈操作。为了帮助你快速理解和选择,我将它们总结在下面的表格中:
| 模式 | 名称 | 含义 | 地址变化方向 | 常见应用场景 |
|---|---|---|---|---|
| IA | Increment After | 每次传输后地址增加 | ↑ | 数据块存储 |
| IB | Increment Before | 每次传输前地址增加 | ↑ | |
| DA | Decrement After | 每次传输后地址减少 | ↓ | |
| DB | Decrement Before | 每次传输前地址减少 | ↓ | |
| FD | Full Descending | 满递减堆栈 | ↓ | ARM默认堆栈模式,压栈 |
| ED | Empty Descending | 空递减堆栈 | ↓ | |
| FA | Full Ascending | 满递增堆栈 | ↑ | |
| EA | Empty Ascending | 空递增堆栈 | ↑ |
在堆栈操作中,满(Full) 和 空(Empty) 的区别在于:
- 满堆栈(Full):堆栈指针
SP指向最后压入堆栈的有效数据项。 - 空堆栈(Empty):堆栈指针
SP指向下一个待压入数据的空位置。
ARM 架构通常使用 满递减堆栈(FD),因此 STMFD 和 LDMFD 是指令集中常见的堆栈操作指令,它们也常常可以简写为 PUSH 和 POP。
⚠️ 重要注意事项
- 寄存器顺序与地址:无论你在
<registers>列表中如何书写寄存器,编号最小的寄存器总是被存储到最低的内存地址,编号最大的寄存器被存储到最高的内存地址。例如,STMIA R0!, {R5, R1, R3}会将 R1(编号最小)存入[R0],R3 存入[R0+4],R5(编号最大)存入[R0+8]。 - 写回机制
!:使用!写回后缀非常方便,可以自动更新基址寄存器,使其指向下一个内存区域,便于后续操作。但在某些情况下需要注意,确保覆盖Rn的原始值是你所期望的。 - 地址对齐:为了最佳性能,内存地址最好保持 4 字节对齐(即地址值是 4 的倍数)。非对齐访问在某些配置下可能导致性能下降或甚至产生对齐异常。
- 权限:执行存储操作时,当前处理器模式(如 EL1, EL2, EL3)必须有权限写入目标内存地址,否则会产生权限错误异常。
🖥️ STM 指令示例
假设寄存器 R1 的初始值是 0x8000,R2 = 0x11111111,R3 = 0x22222222,R4 = 0x33333333。
1. 使用 IA 模式存储数据块(不带写回)
STMIA R1, {R2, R3, R4} ; 将 R2 存入 [0x8000]
; 将 R3 存入 [0x8004]
; 将 R4 存入 [0x8008]
; 执行后 R1 的值不变,仍为 0x8000
2. 使用 IA 模式存储数据块(带写回)
STMIA R1!, {R2, R3, R4} ; 将 R2 存入 [0x8000]
; 将 R3 存入 [0x8004]
; 将 R4 存入 [0x8008]
; 执行后 R1 更新为 0x800C (0x8000 + 3*4)
3. 使用 DB 模式存储数据块(带写回)
STMDB R1!, {R2, R3, R4} ; 先减少 R1: R1 = R1 - 12 = 0x7FF4
; 将 R2 存入 [0x7FF4]
; 将 R3 存入 [0x7FF8]
; 将 R4 存入 [0x7FFC]
; 执行后 R1 为 0x7FF4
4. 使用 FD 模式进行压栈操作(最常见)
STMFD SP!, {R0, R4-R7, LR} ; 将 R0, R4, R5, R6, R7 和 LR(链接寄存器)压入堆栈。
; SP 会递减,并将最终地址写回 SP。
; 这是进入函数时保存寄存器的典型做法。
🔄 相关指令
- LDM:与
STM对应的批量加载指令,用于从内存加载多个寄存器的值。 - STR:存储单个寄存器的指令。
- PUSH:在许多 ARM 汇编器中,这是
STMFD SP!, <reg_list>的别名,更简洁的压栈写法。
💎 总结
ARMv8-R 中的 STM 指令是一条强大的批量存储指令,它能高效地将多个寄存器的值存入连续的内存地址。通过搭配不同的寻址模式(如 IA, IB, DA, DB)和写回选项 !,它可以灵活适应数据块存储和堆栈操作(推荐使用 FD 模式处理堆栈)等多种场景。
掌握 STM 的关键在于理解其寻址模式如何决定地址变化方式,以及写回机制和寄存器列表的处理规则。这使得它在系统编程,特别是上下文保存和恢复(如异常处理、任务切换)中不可或缺。
希望这些解释和示例能帮助你更好地理解和应用 STM 指令。
浙公网安备 33010602011771号