arm v8r中汇编指令ldp的详细用法
好的,我们来详细解析 ARMv8-R(AArch32 架构)中的 LDP 指令。它与之前讨论的 STP 指令是一对互补的操作,是 ARMv8 架构中优化内存访问的核心指令之一。
1. LDP 是什么?
LDP 是 Load Pair 的缩写,意为加载一对寄存器。它的核心功能是从两个连续的内存地址中取出数据,并将其分别加载到两个通用寄存器中。
- 优势:与
STP类似,它将两个单独的LDR(Load Register)指令合并为一条指令,从而:- 减少代码尺寸:一条指令代替两条。
- 提高性能:减少指令 fetch 和解码的开销,并且内存系统通常能更高效地处理连续的加载操作。
在 ARMv8-R(运行 AArch32 状态)中,LDP 和 STP 一样,是 ARMv8 架构引入的指令,用于提升实时系统的数据处理效率。
2. 语法格式 (AArch32)
ARMv8-AArch32 状态下的 LDP 指令语法与 STP 高度对称:
LDP{<c>}{<q>} <Rt1>, <Rt2>, [<Rn>{, #<imm>}]
LDP{<c>}{<q>} <Rt1>, <Rt2>, [<Rn>], #<imm> ; Post-indexed
LDP{<c>}{<q>} <Rt1>, <Rt2>, [<Rn>, #<imm>]! ; Pre-indexed
{<c>}:条件码(可选),如EQ,NE,GT等。在 ARMv8 中通常省略。{<q>}:指令宽度限定符(可选),在 Thumb 模式下使用。<Rt1>,<Rt2>:目标寄存器对,用于接收从内存加载的数据。- 关键限制:与
STP相同,<Rt1>和<Rt2>必须是两个相邻的寄存器,并且<Rt1>必须是偶数编号的寄存器。 - 合法示例:
R0和R1,R2和R3,R8和R9。 - 非法示例:
R1和R2(起始不是偶数),R4和R6(不相邻)。
- 关键限制:与
<Rn>:基址寄存器,包含要加载的内存地址。#<imm>:有符号的立即数偏移量。该立即数必须是 4 的倍数,范围通常较小(例如 -1024 到 +1020)。[]:寻址模式(与STP含义相同,但数据流向相反):[<Rn>{, #<imm>}]:偏移模式。内存地址是Rn + imm。Rn寄存器本身的值不会改变。[<Rn>], #<imm>:后变址模式。内存地址是Rn的原始值。加载操作完成后,Rn的值会被更新为Rn + imm。这是LDP最常用的模式,尤其用于弹栈。[<Rn>, #<imm>]!:前变址模式。内存地址是Rn + imm。在加载操作之前,Rn的值会先被更新为Rn + imm。
3. 作用与用途详解
作用:从内存地址 [address] 加载数据到寄存器 Rt1,从内存地址 [address] + 4 加载数据到寄存器 Rt2。
主要用途:
-
函数收场 (Function Epilogue):在函数结束时,从栈中恢复之前保存的寄存器对(如帧指针和返回地址),这是
LDP最经典的用法,与STP压栈操作配对使用。ldp r11, lr, [sp], #8 ; 从栈中弹出(恢复)R11和LR,SP后加8 bx lr ; 返回 -
恢复局部变量:将之前保存在栈上的临时寄存器值恢复回来。
LDP R4, R5, [SP], #8 ; 从栈顶恢复 R4 和 R5 的值,然后 SP 增加 8 -
高效数据搬运:从内存中连续位置批量加载数据到寄存器,用于后续处理。这在处理数组、结构体等数据时非常高效。
4. 详细示例
假设栈指针 SP 当前指向 0x8000_0FF8,并且该地址处的内存内容如下:
[0x8000_0FF8]=0x11223344(之前保存的R11值)[0x8000_0FFC]=0x55667788(之前保存的LR值)
示例 1:后变址模式 (Post-indexed) - 最常用于弹栈
LDP R11, LR, [SP], #8 ; 从SP指向的地址加载数据到R11和LR,然后SP加8
- 操作:
- 从
[SP]([0x8000_0FF8]) 加载数据到R11->R11 = 0x11223344 - 从
[SP + 4]([0x8000_0FFC]) 加载数据到LR->LR = 0x55667788 - 更新
SP寄存器:New SP = SP + 8 = 0x8000_1000
- 从
- 结果:
R11和LR被成功恢复,SP指向了新的栈顶0x8000_1000。这是模拟POP {R11, LR}的现代方式。
示例 2:偏移模式 (Offset)
LDP R6, R7, [SP, #16] ; 将 SP+16 和 SP+20 地址处的数据加载到 R6 和 R7
- 操作:
- 计算内存地址:
Effective Address = SP + 16 = 0x8000_1008 - 从
[0x8000_1008]加载数据到R6 - 从
[0x8000_1008 + 4] = [0x8000_100C]加载数据到R7
- 计算内存地址:
- 结果:
R6和R7被加载了新值,SP的值保持不变(0x8000_0FF8)。这常用于访问栈帧内的特定变量。
示例 3:前变址模式 (Pre-indexed)
LDP R8, R9, [SP, #8]! ; 先将SP加8,然后从新SP指向的地址加载数据到R8/R9
- 操作:
- 计算新地址:
New SP = SP + 8 = 0x8000_1000 - 从
[New SP]([0x8000_1000]) 加载数据到R8 - 从
[New SP + 4]([0x8000_1004]) 加载数据到R9 SP寄存器被更新为New SP (0x8000_1000)
- 计算新地址:
- 结果:
R8和R9被加载,同时SP也指向了新的位置。这种模式不如后变址模式常用。
5. 与 STP (Store Pair) 的完美配合
LDP 几乎总是与 STP 成对出现,实现数据的保存和恢复,尤其是在函数调用中。
一个完整的函数示例:
my_function:
//// 函数开场 (Prologue) - 使用 STP 压栈 ////
STP R11, LR, [SP, #-8]! // Push {R11, LR}:压入帧指针和返回地址
STP R4, R5, [SP, #-8]! // Push {R4, R5}:压入需要保留的寄存器
MOV R11, SP // 可选:设置帧指针
... // 函数主体,使用R4, R5等寄存器
//// 函数收场 (Epilogue) - 使用 LDP 弹栈 ////
LDP R4, R5, [SP], #8 // Pop {R4, R5}:恢复寄存器
LDP R11, LR, [SP], #8 // Pop {R11, LR}:恢复帧指针和返回地址
BX LR // 函数返回
总结
| 特性 | 说明 |
|---|---|
| 指令 | LDP (Load Pair of Registers) |
| 功能 | 从两个连续的内存地址加载数据到两个相邻的通用寄存器中。 |
| 主要优势 | 代码密度高,执行效率高。 |
| 关键约束 | Rt1 和 Rt2 必须是相邻寄存器对,且 Rt1 必须是偶数编号。 |
| 常用寻址模式 | 后变址 (], #imm):用于函数收场的弹栈操作 (POP 等效),最为常见。偏移模式:用于从栈帧或内存块的固定偏移处加载数据。 前变址 ( !):较少使用。 |
| 典型应用 | 函数epilogue、恢复寄存器上下文、从内存中高效加载连续数据。 |
掌握 LDP 指令,特别是其与 STP 的配合使用,是编写高效、紧凑的 ARMv8-R AArch32 汇编代码的基石。它极大地优化了基于栈的寄存器保存/恢复例程。
浙公网安备 33010602011771号