arm v8r中汇编指令stp的详细用法
好的,我们来深入探讨 ARMv8-R(AArch32 架构)中的 STP 指令。这是一条非常高效且重要的指令,尤其在现代编译器和系统编程中。
1. STP 是什么?
STP 是 Store Pair 的缩写,意为存储一对寄存器。它的核心功能是将两个通用寄存器的值,连续地存储到相邻的两块内存空间中(通常是栈空间)。
- 优势:它将两个单独的
STR(Store Register)指令合并为一条指令,从而:- 减少代码尺寸:一条指令代替两条。
- 提高性能:减少指令 fetch 和解码的开销,并且内存系统通常更高效地处理连续的存储操作。
在 ARMv8-R(运行 AArch32 状态)中,STP 是 ARMv8 架构引入的指令,在 ARMv7-R(如 Cortex-R 系列)中不可用。v8-R 通常用于高实时性、高可靠性的场景,STP 指令对其性能优化很有帮助。
2. 语法格式 (AArch32)
ARMv8-AArch32 状态下的 STP 指令语法如下:
STP{<c>}{<q>} <Rt1>, <Rt2>, [<Rn>{, #<imm>}]
STP{<c>}{<q>} <Rt1>, <Rt2>, [<Rn>], #<imm> ; Post-indexed
STP{<c>}{<q>} <Rt1>, <Rt2>, [<Rn>, #<imm>]! ; Pre-indexed
{<c>}:条件码(可选),如EQ,NE,GT等。例如STPEQ。在 ARMv8 中,大多数指令默认为无条件执行,通常省略。{<q>}:指令宽度限定符(可选),在 Thumb 模式下使用,如.W(Wide)来强制使用 32 位编码。<Rt1>,<Rt2>:要存储的源寄存器对。它们必须是通用寄存器(如R0-R12)。- 重要限制:在 AArch32 状态下,
<Rt1>和<Rt2>必须是两个相邻的寄存器,并且<Rt1>必须是偶数编号的寄存器。 - 合法示例:
R0和R1,R2和R3,R8和R9,R10和R11。 - 非法示例:
R1和R2(起始不是偶数),R4和R6(不相邻)。
- 重要限制:在 AArch32 状态下,
<Rn>:基址寄存器,包含要存储到的内存地址。#<imm>:有符号的立即数偏移量。该立即数必须是 4 的倍数,范围取决于指令的具体形式,但通常是一个较小的数字(例如 -1024 到 +1020)。[]:寻址模式:[<Rn>{, #<imm>}]:偏移模式。内存地址是Rn + imm。Rn寄存器本身的值不会改变。这是最常用的模式。[<Rn>], #<imm>:后变址模式。内存地址是Rn的原始值。存储操作完成后,Rn的值会被更新为Rn + imm。[<Rn>, #<imm>]!:前变址模式。内存地址是Rn + imm。在存储操作之前,Rn的值会先被更新为Rn + imm。
3. 作用与用途详解
作用:将寄存器 Rt1 的值存储到内存地址 [address],将寄存器 Rt2 的值存储到内存地址 [address] + 4。
主要用途:
-
函数开场 (Function Prologue):在函数开始时,将需要保留的链接寄存器(
LR/R14)和帧指针(FP/R11)或者其他需要保存的寄存器对压入栈中,以便函数返回时恢复。push {r11, lr} ; 传统的 PUSH 方式 ; 等价于 (概念上,并非完全一样): STP R11, LR, [SP, #-8]! ; 更现代、更高效的方式:预减栈指针并存储 -
保存局部变量:将函数中的临时寄存器值保存到栈上,以便腾出这些寄存器供后续计算使用。
STP R4, R5, [SP, #-8]! ; 保存 R4 和 R5 到栈上,SP 先减 8 ... ; 使用 R4 和 R5 进行计算 LDP R4, R5, [SP], #8 ; 函数返回前,从栈上恢复 R4 和 R5,SP 后加 8 -
批量内存初始化:高效地将一对寄存器值写入连续的内存位置。
4. 详细示例
假设栈指针 SP 当前指向 0x8000_1000。
示例 1:偏移模式 (Offset)
STP R6, R7, [SP, #16] ; 将 R6 和 R7 存储到 SP+16 的位置
- 操作:
- 计算内存地址:
Effective Address = SP + 16 = 0x8000_1010 - 将
R6的值存储到[0x8000_1010] - 将
R7的值存储到[0x8000_1010 + 4] = [0x8000_1014]
- 计算内存地址:
- 结果:
SP的值保持不变(0x8000_1000)。
示例 2:前变址模式 (Pre-indexed) - 最常用于压栈
STP R8, R9, [SP, #-8]! ; 先将 SP 减 8,然后将 R8/R9 存储到新 SP 指向的地址
- 操作:
- 计算新地址:
New SP = SP - 8 = 0x8000_0FF8 - 将
R8的值存储到[New SP] = [0x8000_0FF8] - 将
R9的值存储到[New SP + 4] = [0x8000_0FFC] SP寄存器被更新为New SP (0x8000_0FF8)
- 计算新地址:
- 结果:
SP指向0x8000_0FF8,R8和R9的值已成功压入栈顶。这是模拟PUSH {R8, R9}的现代方式。
示例 3:后变址模式 (Post-indexed)
STP R10, R11, [SP], #8 ; 先将 R10/R11 存储到 SP 当前指向的地址,然后 SP 加 8
- 操作:
- 将
R10的值存储到[SP] = [0x8000_1000] - 将
R11的值存储到[SP + 4] = [0x8000_1004] - 更新
SP寄存器:New SP = SP + 8 = 0x8000_1008
- 将
- 结果:数据存储在了
0x8000_1000和0x8000_1004,SP指向了新的位置0x8000_1008。这常用于从栈中弹出一个区域,但通常LDP与之配合更常见。
5. 与 LDP (Load Pair) 的配合
STP 几乎总是与它的逆指令 LDP (Load Pair) 配对使用,用于数据的保存和恢复。
my_function:
// 函数开场:保存帧指针和返回地址
STP R11, LR, [SP, #-8]! // Push {R11, LR}
MOV R11, SP // 设置帧指针
// 保存其他需要使用的寄存器
STP R4, R5, [SP, #-8]! // Push {R4, R5}
... // 函数主体
// 函数收场:恢复寄存器并返回
LDP R4, R5, [SP], #8 // Pop {R4, R5}
LDP R11, LR, [SP], #8 // Pop {R11, LR}
BX LR // 返回
总结
| 特性 | 说明 |
|---|---|
| 指令 | STP (Store Pair of Registers) |
| 功能 | 将两个相邻的通用寄存器的值存储到连续的内存地址中。 |
| 主要优势 | 代码密度高,执行效率高。 |
| 关键约束 | Rt1 和 Rt2 必须是相邻寄存器对,且 Rt1 必须是偶数编号。 |
| 常用寻址模式 | 前变址 (!):用于函数开场的压栈操作 (PUSH 等效)。偏移模式:用于存储到栈帧内的特定位置。 后变址:较少用于 STP,更多用于 LDP 弹栈。 |
| 典型应用 | 函数prologue/epilogue、保存/恢复寄存器上下文、高效内存写入。 |
掌握 STP/LDP 指令对是编写高效 ARMv8-R AArch32 汇编代码的关键,尤其是在进行栈操作和寄存器维护时。
浙公网安备 33010602011771号