arm v8r中汇编指令ldrd的详细用法
ARMv8-R 架构中的 LDRD 指令是一条非常实用的指令,用于从内存中一次性加载两个 32 位字(Word)到两个通用寄存器中。这相当于把两个 LDR 指令合并为一条,可以提高代码效率。由于 ARMv8-R 主要支持 AArch32 状态,这里的说明将聚焦于此。
以下是 LDRD 指令的要点,我用一个表格来汇总:
| 项目 | 说明 |
|---|---|
| 指令功能 | 从连续的内存地址加载两个 32 位字到两个寄存器中 |
| 语法格式 | LDRD{<c>} <Rt>, <Rt2>, [<Rn> {, #+/-<imm>}]{!} LDRD{<c>} <Rt>, <Rt2>, [<Rn>], #+/-<imm> |
| 典型应用场景 | 一次性加载64位数据、处理结构体或数组元素、优化代码效率和性能 |
| 重要约束 | 寄存器必须为通用寄存器,Rt 和 Rt2 不能是 PC,Rt 的编号必须小于 Rt2 的编号 |
| 偏移量限制 | 立即数偏移 imm 通常必须是 4 的倍数,范围有限(例如,±1020 或 ±255) |
| 地址对齐 | 地址最好64位对齐,非对齐访问可能影响性能或触发异常 |
🖋 语法格式详解
LDRD 指令的基本语法格式如下:
LDRD{<c>} <Rt>, <Rt2>, [<Rn> {, #+/-<imm>}]{!}
LDRD{<c>} <Rt>, <Rt2>, [<Rn>], #+/-<imm>
{<c>}:可选的条件码(如EQ,NE,GT等),用于条件执行。<Rt>, <Rt2>:目标寄存器对。LDRD会将内存中的第一个字(较低地址)加载到Rt,将第二个字(较高地址)加载到Rt2。- 约束:
Rt和Rt2必须是通用寄存器,且Rt的编号必须小于Rt2的编号(例如R1, R2有效,R2, R1无效)。它们不能是 PC (R15)。
- 约束:
<Rn>:基址寄存器,包含内存访问的基地址。#+/-<imm>:可选的有符号立即数偏移量。该偏移量通常必须是 4 的倍数(因为访问的是字),并且其取值范围有限(例如,在 ARMv7/AArch32 中,LDRD的偏移量范围通常为 ±1020)。!:用于前变基模式。如果使用!,则会在内存访问之后,将计算后的新地址(Rn + offset)写回基址寄存器Rn。[<Rn>], #+/-<imm>:用于后变基模式。这种格式下,先使用Rn的原始值作为内存地址进行加载操作,然后再将Rn的值更新为Rn + imm。
🔍 寻址模式
LDRD 支持三种主要的寻址模式,这与其他的加载存储指令类似。
-
偏移模式 (Offset)
内存地址是Rn的值加上或减去一个立即数偏移量。Rn寄存器本身的值不会改变。LDRD R1, R2, [R5, #16] ; 从地址 (R5 + 16) 加载一个字到 R1,从 (R5 + 20) 加载一个字到 R2。R5 不变。 -
前变基模式 (Pre-indexed)
内存地址是Rn + offset。在内存访问之前,Rn的值会先被更新为Rn + offset。LDRD R1, R2, [R5, #16]! ; 先将 R5 更新为 (R5 + 16),然后从新 R5 (即原R5+16) 加载到 R1,从 (新R5 + 4) 加载到 R2。 -
后变基模式 (Post-indexed)
内存地址是Rn的原始值。在内存访问之后,Rn的值会被更新为Rn + offset。LDRD R1, R2, [R5], #16 ; 先使用 R5 的原始值作为基地址:从 R5 加载到 R1,从 (R5+4) 加载到 R2。然后将 R5 更新为 (R5 + 16)。
⚠️ 重要约束和使用注意
使用 LDRD 时,需要注意以下几点:
- 寄存器对限制:
Rt和Rt2必须是通用寄存器,并且Rt的编号必须小于Rt2(例如R1, R2有效,R2, R1无效)。它们不能是 PC (R15)。 - 偏移量范围:立即数偏移
imm的值范围是有限的,具体限制取决于ARM架构版本和指令变种。例如,在 ARMv7-A/R 中,LDRD的立即数偏移通常必须是 4 的倍数,范围在 -1020 到 +1020 之间。请查阅具体的架构参考手册以获取精确值。 - 地址对齐:虽然
LDRD可以处理非对齐的地址访问(取决于系统配置),但为了获得最佳性能,建议确保内存地址是 64 位对齐的(即地址是 8 的倍数)。非对齐访问可能导致性能下降或触发对齐异常。
🛠 使用示例
假设我们有一段内存,起始地址为 0x8000,其中存储了多个 64 位数据(每个 64 位数据由两个连续的 32 位字构成)。我们想将这些数据加载到寄存器中进行处理。
.data
my_data:
.word 0x11223344 @ 64位数据的低32位 (地址 0x8000)
.word 0x55667788 @ 64位数据的高32位 (地址 0x8004)
.word 0xAABBCCDD @ 下一个64位数据的低32位 (地址 0x8008)
.word 0xEEFF0011 @ 下一个64位数据的高32位 (地址 0x800C)
.text
.global _start
_start:
MOV R5, #0x8000 @ 将数据区的起始地址加载到 R5
@ 示例 1: 使用偏移模式加载第一个64位数据到 R0 和 R1
LDRD R0, R1, [R5] @ 从 R5 (0x8000) 加载到 R0 (得到 0x11223344),
@ 从 R5+4 (0x8004) 加载到 R1 (得到 0x55667788)
@ 示例 2: 使用前变基模式加载第二个64位数据,并更新基址
@ 假设 R5 现在还是 0x8000
LDRD R2, R3, [R5, #8]! @ 先将 R5 更新为 R5+8 = 0x8008,
@ 然后从新 R5 (0x8008) 加载到 R2 (得到 0xAABBCCDD),
@ 从新 R5+4 (0x800C) 加载到 R3 (得到 0xEEFF0011)
@ 此时 R5 的值已经是 0x8008
@ 示例 3: 使用后变基模式加载数据(假设地址已重新设置),并自动递增基址
MOV R5, #0x8000 @ 重新设置 R5 为 0x8000
LDRD R4, R5, [R5], #8 @ (!注意: 这里用 R5 同时做基址寄存器和目标寄存器,需谨慎)
@ 先操作: 从 R5(0x8000) 加载到 R4 (得到 0x11223344),
@ 从 R5+4(0x8004) 加载到 R5 (得到 0x55667788) – 这会覆盖原基址值!
@ 然后操作: 将基址寄存器(已经是新值0x55667788)再加8 -> 结果不可预知,错误用法!
@ 更安全的後变基示例:使用不同的寄存器
MOV R6, #0x8000 @ 基址寄存器使用 R6
LDRD R4, R5, [R6], #8 @ 从 R6(0x8000) 加载到 R4 (0x11223344), 从 R6+4(0x8004) 加载到 R5 (0x55667788)
@ 然后将 R6 更新为 R6 + 8 = 0x8008
注意:在後变基模式示例中,试图使用 R5 同时作为基址寄存器和目标寄存器(Rt2)会导致不可预期的结果,因为基址寄存器在加载过程中被覆盖了。这是一个常见的错误,应避免让目标寄存器与基址寄存器相同(除非你非常清楚其后果)。
💡 进阶应用:与 STRD 配对使用
LDRD 通常与它的“搭档”指令 STRD(Store Register Dual,存储双寄存器)一起使用,后者用于将两个寄存器的值存储到连续的内存地址中。
@ 假设我们要将 R0 和 R1 中的值(构成一个64位数据)存储到内存中地址 0x9000 开始的地方
MOV R4, #0x9000
STRD R0, R1, [R4] @ 将 R0 的值存储到 [0x9000], 将 R1 的值存储到 [0x9004]
@ 同样,STRD 也支持偏移、前变基和后变基模式。
⚠️ 与 LDP 指令的区别
在 ARMv8-A AArch64 状态中,有 LDP(Load Pair)指令,其功能与 LDRD 类似,但更加强大和灵活(支持64位寄存器,偏移量范围更大)。然而,在 ARMv8-R(主要运行 AArch32 状态)中,LDRD/STRD 仍然是加载存储双字的主要方式。
希望这些解释和示例能帮助你更好地理解和使用 ARMv8-R 中的 LDRD 指令。
浙公网安备 33010602011771号