• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

SOC/IP验证工程师

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

ARM V8R中汇编指令LDR的详细用法

好的,我们来全面、深入地解析 ARMv8-R 架构(通常运行在 AArch32 状态)中 LDR (Load Register) 指令的用法。这是 ARM 汇编中最为核心和重要的指令之一,负责将数据从内存加载到寄存器,是 CPU 与内存交互的关键入口。

🧠 核心功能与概述

LDR 指令的核心功能是:从内存中的一个指定地址读取数据,并将其加载到目标寄存器中。

  • 操作定义:Rt = Memory[Address]
  • 主要用途:
    1. 访问内存中的变量和数据。
    2. 读取硬件外设寄存器的值(通过对内存映射的寄存器进行读取)。
    3. 在函数调用中,从栈上恢复临时寄存器的值。
    4. 加载大立即数或地址值到寄存器(作为伪指令)。

为了让您快速概览 LDR 指令的各种形态,下表总结了其最常见的使用方式:

指令示例 名称/模式 功能描述 关键点
LDR Rt, [Rn] 基址寻址 从 Rn 指向的地址加载数据到 Rt 最基础的形式
LDR Rt, [Rn, #offset] 基址+偏移 从 Rn + offset 的地址加载数据 Rn 不变
LDR Rt, [Rn, #offset]! 前变基寻址 先从 Rn + offset 计算地址加载数据,然后更新 Rn Rn 会改变
LDR Rt, [Rn], #offset 后变基寻址 先从 Rn 的地址加载数据,然后更新 Rn = Rn + offset Rn 会改变
LDR Rt, =value 伪指令 将一个大立即数或地址加载到 Rt 汇编器自动转换
LDRB Rt, [Rn] 加载字节 从内存加载一个字节,零扩展到32位
LDRH Rt, [Rn] 加载半字 从内存加载一个半字,零扩展到32位
LDRSB Rt, [Rn] 加载有符号字节 从内存加载一个字节,符号扩展到32位
LDRSH Rt, [Rn] 加载有符号半字 从内存加载一个半字,符号扩展到32位

⚙️ 语法与操作数格式

LDR 指令的通用语法如下:

LDR{<size>}{<cond>} <Rt>, [<Rn>, <offset>]{!}
LDR{<size>}{<cond>} <Rt>, [<Rn>], <offset>
  • {<size>}:可选的数据大小指定符。
    • (空):默认,加载字 (Word, 32位)
    • B:加载字节 (Byte, 8位),零扩展。
    • H:加载半字 (Halfword, 16位),零扩展。
    • SB:加载有符号字节 (Signed Byte),符号扩展。
    • SH:加载有符号半字 (Signed Halfword),符号扩展。
  • {<cond>}:可选的条件码后缀(如 EQ, NE)。
  • <Rt>:目标寄存器,内存中的数据将被加载到这里。
  • <Rn>:基址寄存器,其值是内存访问的基地址。
  • <offset>:偏移量。可以是:
    • 一个立即数(如 #12)。
    • 一个寄存器(如 Rm)。
    • 一个移位操作的寄存器(如 Rm, LSL #2)。
  • {!}:写回后缀。如果使用 !,则操作完成后,计算后的新地址会被写回基址寄存器 Rn。

🛠️ 详细用法与示例

1. 基本加载:从变量或绝对地址读取数据

这是最直接的用法,从另一个寄存器所指向的内存位置读取数据。

LDR R0, =my_variable @ 将变量 'my_variable' 的地址加载到 R0
LDR R1, [R0]         @ 从 R0 所指向的内存地址读取数据,加载到 R1
                     @ 即:R1 = my_variable

2. 访问数组或结构体成员(基址+偏移)

通过基址加偏移的方式,可以高效地访问数组元素或结构体成员。

@ 假设 R0 是数组基地址,我们要读取 array[2] (假设每个元素占4字节)
LDR R1, [R0, #8]     @ 地址 = R0 + 8 (2 * 4 bytes)。R0 的值保持不变。
                     @ R1 = array[2]

@ 访问结构体 (假设 R0 是结构体指针,第二个成员在偏移量 #12 处)
LDR R2, [R0, #12]    @ R2 = 结构体的第二个成员

3. 后变基寻址:加载后自动移动指针

这种模式在连续读取内存块(如遍历数组、处理缓冲区)时极其高效。

MOV R0, =array_start @ R0 指向数组起始地址

LDR R1, [R0], #4     @ 步骤 1: 从 [R0] 加载数据到 R1 (array[0]), 然后将 R0 = R0 + 4 (指向array[1])
LDR R2, [R0], #4     @ 步骤 2: 从 [R0] 加载数据到 R2 (array[1]), 然后将 R0 = R0 + 4 (指向array[2])

@ 执行后:
@ R1 = array[0]
@ R2 = array[1]
@ R0 指向 array[2]

4. 前变基寻址:先移动指针,再加载

同样用于连续读取,但逻辑是“先准备好下一个地址,再读数据”。

MOV R0, =array_start

LDR R1, [R0, #4]!    @ 步骤 1: 先将 R0 更新为 R0 + 4 (指向array[1]),然后从 [R0] 加载数据到 R1 (array[1])
LDR R2, [R0, #4]!    @ 步骤 2: 先将 R0 更新为 R0 + 4 (指向array[2]),然后从 [R0] 加载数据到 R2 (array[2])

5. 加载不同大小和符号的数据

LDR  R1, [R0]    @ 从 [R0] 加载一个完整的 32 位字
LDRB R2, [R0]    @ 从 [R0] 加载一个字节(无符号),零扩展到 32 位。 R2 = 0x000000XX
LDRH R3, [R0]    @ 从 [R0] 加载一个半字(无符号),零扩展到 32 位。 R3 = 0x0000XXXX
LDRSB R4, [R0]   @ 从 [R0] 加载一个字节(有符号),符号扩展到 32 位。若字节为0xFE,则R4=0xFFFFFFFE
LDRSH R5, [R0]   @ 从 [R0] 加载一个半字(有符号),符号扩展到 32 位。

6. 读取硬件寄存器(内存映射I/O)

在嵌入式系统中,通过读取外设寄存器的内存映射地址来获取硬件状态。

@ 假设 UART 状态寄存器的地址是 0x40001000
LDR R0, =0x40001000  @ 将外设寄存器地址加载到 R0
LDR R1, [R0]         @ 读取状态寄存器的值到 R1
TST R1, #0x01        @ 测试“数据就绪”位
BNE data_ready       @ 如果就绪,则跳转处理

7. 伪指令:加载大立即数或地址(极其重要!)

这是 LDR 指令一个独特而强大的用法。当 MOV 指令无法加载一个大的立即数(如 32 位地址或常数)时,使用 LDR 伪指令。

MOV R0, #0x12345678  @ 错误!0x12345678 很可能是无效立即数,汇编失败

LDR R0, =0x12345678  @ 正确!汇编器会自动处理:
                     @ 1. 如果可能,转换为 MOV/MOVK 组合指令
                     @ 2. 否则,将该常数放入“文字池”,并生成一条 PC 相对的 LDR 指令来加载它

LDR R0, =my_variable @ 最常用的用法:直接将变量地址加载到寄存器
LDR R1, =0x55AA55AA  @ 加载任意 32 位常数

⚠️ 重要注意事项与原理

  1. 地址对齐:

    • LDR:要求地址是 4 字节对齐的。非对齐访问可能会触发异常。
    • LDRH:要求地址是 2 字节对齐的。
    • LDRB:可以对任何地址进行操作。
  2. 偏移量范围:

    • 立即数偏移量 offset 有其取值范围(例如 ±4095 字节)。
  3. 写回操作 (!):

    • 使用 ! 后缀会更新基址寄存器 Rn。这在遍历数组时非常有用,可以节省一条 ADD 指令。
  4. 与伪指令的区别:

    LDR R1, [R0]        @ 这是**指令**:从内存地址 [R0] 加载数据
    LDR R1, =0x1234     @ 这是**伪指令**:将立即数 0x1234 加载到 R1
    

    汇编器会根据上下文区分它们的用途。

  5. 访问权限:你只能从具有读权限的内存区域进行加载操作。

💎 总结

LDR 指令是 ARM 汇编中将数据从内存系统输入到CPU核心的基础工具,其重要性无可替代。

它的核心价值在于:

  • 数据访问:读取内存中的变量和数组元素。
  • 硬件交互:通过内存映射I/O读取外设状态,这是嵌入式编程的核心。
  • 高效数据处理:通过各种灵活的寻址模式,高效地遍历数据。
  • 常量加载:作为伪指令,解决了加载大立即数的核心难题。

简单决策指南:

  • 需要从内存加载32位数据 -> 使用 LDR
  • 需要加载8位/16位数据 -> 使用 LDRB/LDRH 或 LDRSB/LDRSH(注意符号扩展)
  • 需要连续读取(如数组处理)-> 使用前变基 [Rn, #offset]! 或后变基 [Rn], #offset 寻址模式
  • 需要加载一个大的立即数或地址 -> 使用 LDR Rt, =value 伪指令

掌握 LDR 指令是进行任何有意义的 ARM 汇编编程的前提,无论是应用开发还是底层系统编程,都离不开它。

posted on 2025-09-05 21:05  SOC验证工程师  阅读(96)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3