本文对应 Architecture Reference Manual Armv8-A (下文简称"架构手册")Part C、6.2 Alphabetical list of A64 base instructions 中的
LDR条目。不同修订版的章节编号和补充说明可能变化,阅读时应以指令条目名称为检索依据。
架构手册不是循序渐进的教材,而是一份精确定义架构行为的规范。同一页中往往同时出现机器编码、汇编语法、解码伪代码和执行约束。初次阅读的难点通常不在公式本身,而在于辨明各栏目的职责及其联系。
本文以常用的 LDR 为例,说明一条 A64 指令从汇编源码到架构行为的完整定义过程。重点不在记忆某个编码值,而在建立一套可以迁移到其他指令的阅读方法。
正文重点分析 LDR (immediate) 和 LDR (literal)。前者涵盖基址加偏移、立即数编码和基址写回,后者用于说明 PC 相对寻址。掌握这两种形式的定义方式后,LDR (register) 可以沿用相同方法分析,因而只作概要介绍。
讨论范围限于以 W/X 通用寄存器为目的寄存器的 LDR;SIMD&FP 寄存器使用独立的同名条目。
1. LDR 指令形式
ARMv8-A 在 AArch64 执行状态下使用 A64 指令集。A64 指令均为 32 位,但一个助记符可以对应多种机器编码。LDR 按地址生成方式分为以下形式:
| 指令条目 | 地址来源 | 典型语法 |
|---|---|---|
LDR (immediate) |
基址寄存器加立即数 | LDR X0, [X1, #24] |
LDR (register) |
基址寄存器加索引寄存器 | LDR X0, [X1, X2] |
LDR (literal) |
当前指令地址加立即数 | LDR X0, label |
名称中的 immediate 描述地址的生成方式,并不表示将该立即数直接写入目的寄存器。三种形式最终都执行内存读取,区别在于访问地址的生成方式。
目的寄存器决定读取宽度:
asm
LDR W0, [...] // 读取 4 字节,X0 的高 32 位清零
LDR X0, [...] // 读取 8 字节
访问宽度不仅决定读取多少数据,还会影响部分立即数的编码方式。
2. A64 指令条目的组成
阅读具体指令之前,先明确架构手册中各栏目的职责:
| 栏目 | 作用 | 主要内容 |
|---|---|---|
Description |
概述指令功能 | 指令读取什么、写入什么、地址如何产生 |
Encoding |
给出 32 位机器码布局 | 哪些位固定,哪些位保存寄存器或立即数 |
Encoding for ... variant |
定义具体变体及汇编语法 | 当前编码对应 32 位还是 64 位操作 |
Decode for all variants of this encoding |
将位字段转换为内部变量 | 立即数是否带符号、是否缩放、是否写回基址 |
Assembler Symbols |
解释汇编模板中的占位符 | 操作数范围、单位及其与位字段的关系 |
Shared Decode for all encodings |
处理多个编码共用的解码逻辑 | 访问宽度、寄存器编号和公共约束是什么 |
Operation for all encodings |
定义架构可见行为 | 最终访问哪个地址,哪些寄存器发生变化 |
Operational information |
补充执行属性 | 是否涉及扩展特性、时序属性或其他约束 |
这些栏目涉及汇编和执行两条相互衔接的路径:
text
汇编端:汇编语法 -- Assembler Symbols --> 机器码字段
处理器:机器码字段 -- Decode --> Shared Decode --> Operation
前一条路径说明汇编操作数如何写入机器码字段,后一条路径说明处理器如何解释这些字段并产生架构可见结果。后文将沿这一层次分析 LDR。
3. LDR (immediate) 的三种形式
Description:
Load Register (immediate) loads a word or doubleword from memory and writes it to a register. The address that is
used for the load is calculated from a base register and an immediate offset.
LDR (immediate) 以基址寄存器和立即数生成地址。手册按 Post-index、Pre-index、Unsigned offset 的顺序给出三种编码。每种编码都有独立的编码图、汇编变体和 Decode for all variants of this encoding;三种编码之后,手册再统一给出它们共用的汇编符号、公共解码和执行过程。
字段名中的 imm 是 immediate (立即数)的缩写,后面的数字表示字段宽度。例如,imm9 是 9 位立即数字段,imm12 是 12 位立即数字段。字段宽度不等于字节偏移;字段的符号、单位和换算方式由 Decode 与 Assembler Symbols 共同定义。
三种编码的 Decode 都会由 size 生成 scale。scale 不用于区分三种寻址形式,其与访问宽度的关系将在第 4.2 节统一说明。
Pre 和 Post 均以内存访问为参照:
- Pre-index:先用偏移量调整基址,再访问内存。
- Post-index:先用原始基址访问内存,再用偏移量调整基址。
index 在这一术语中表示"使用偏移量调整基址"。
3.1 Post-index

Post-index 先使用原始基址访问内存,再把偏移加入基址并写回:
asm
LDR <Wt>, [<Xn|SP>], #<simm> // size == 10 时适用
LDR <Xt>, [<Xn|SP>], #<simm> // size == 11 时适用
假设 X1 = 0x1000:
asm
LDR X0, [X1], #8
执行结果为:
text
访问地址 = 0x1000
X1 = 0x1008
该形式的 Decode 设置:
text
boolean wback = TRUE;
boolean postindex = TRUE;
integer scale = UInt(size);
bits(64) offset = SignExtend(imm9, 64);
wback = TRUE:将调整后的地址写回基址寄存器;postindex = TRUE:本次内存访问使用原始基址;offset = SignExtend(imm9, 64):将 9 位有符号立即数扩展为 64 位字节偏移。
符号扩展 SignExtend
imm9只有 9 位,而地址计算使用 64 位基址。有符号扩展在保持数值正负不变的前提下将imm9扩展为 64 位,使-16扩展后仍表示-16,而不会被解释为正数496。
3.2 Pre-index

Pre-index 先把偏移加入基址,以结果地址访问内存,并把该地址写回基址:
asm
LDR <Wt>, [<Xn|SP>, #<simm>]! // size == 10 时适用
LDR <Xt>, [<Xn|SP>, #<simm>]! // size == 11 时适用
末尾的 ! 表示基址写回,同时构成 Pre-index 与 Unsigned offset 的语法区别。假设 X1 = 0x1000:
asm
LDR X0, [X1, #-16]!
执行结果为:
text
访问地址 = 0x0ff0
X1 = 0x0ff0
该形式的 Decode 设置:
text
boolean wback = TRUE;
boolean postindex = FALSE;
integer scale = UInt(size);
bits(64) offset = SignExtend(imm9, 64);
wback = TRUE:将调整后的地址写回基址寄存器;postindex = FALSE:本次内存访问使用base + offset;offset = SignExtend(imm9, 64):将 9 位有符号立即数扩展为 64 位字节偏移。
3.3 Unsigned offset

Unsigned offset 将非负偏移加入基址,不修改基址寄存器:
asm
LDR <Wt>, [<Xn|SP> {, #<pimm>}] // size == 10 时适用
LDR <Xt>, [<Xn|SP> {, #<pimm>}] // size == 11 时适用
花括号表示偏移可以省略,省略时默认为 0。假设 X1 = 0x1000:
asm
LDR X0, [X1, #24]
执行结果为:
text
访问地址 = 0x1018
X1 = 0x1000
该形式的 Decode 设置:
text
wback = FALSE
postindex = FALSE
scale = UInt(size)
offset = LSL(ZeroExtend(imm12, 64), scale)
wback = FALSE:不写回基址寄存器;postindex = FALSE:本次内存访问使用base + offset;scale = UInt(size):在当前编码中作为imm12的左移位数;offset = LSL(ZeroExtend(imm12, 64), scale):将imm12转换为 64 位字节偏移。
三种编码至此分别完成自身的 Decode,并生成 wback、postindex、scale 和 offset。这些变量将在后续公共解码和执行过程中共同决定访问宽度、有效地址与基址写回行为。
4. 三种编码的公共部分
手册随后进入三种编码共用的部分,依次给出 Assembler Symbols、Shared Decode for all encodings 和 Operation for all encodings。这些内容不再从属于某一种具体编码。
其中,Assembler Symbols 位于汇编端,规定源码操作数与机器码字段的对应关系;Shared Decode for all encodings 和 Operation for all encodings 位于处理器端,将各编码产生的变量归并为统一的架构行为。
4.1 Assembler Symbols
汇编模板中的 <Wt>、<simm> 和 <pimm> 不是机器码字段,而是对汇编操作数的抽象表示。Assembler Symbols 负责说明这些符号允许写什么,以及它们如何编码:
| 汇编符号 | 含义 | 编码关系 |
|---|---|---|
<Wt> |
32 位目的通用寄存器 | 寄存器编号编码在 Rt 中 |
<Xt> |
64 位目的通用寄存器 | 寄存器编号编码在 Rt 中 |
| `<Xn | SP>` | 64 位基址通用寄存器或 SP |
<simm> |
-256~255 字节的有符号偏移 | 直接编码在 imm9 中 |
<pimm>,32 位形式 |
0~16380 字节,且为 4 的整数倍 | imm12 = pimm / 4 |
<pimm>,64 位形式 |
0~32760 字节,且为 8 的整数倍 | imm12 = pimm / 8 |
其中,Post-index 和 Pre-index 使用 <simm>,Unsigned offset 使用 <pimm>。simm 中的 s 表示 signed ;pimm 中的 p 源于 positive,但该操作数允许取 0,因此实际范围是非负数。两者的数值都以字节为单位。
汇编操作数、机器码字段和解码变量分属不同阶段:
| 所处阶段 | 名称 | 含义 |
|---|---|---|
| 汇编语法 | <simm>、<pimm> |
程序员书写的字节偏移 |
| 机器编码 | imm9、imm12 |
指令中保存立即数信息的位字段 |
| 解码结果 | offset |
供地址计算使用的 64 位字节偏移 |
对于合法指令,汇编操作数与最终 offset 表示相同的字节偏移,但中间的编码方式不同:simm 以 9 位有符号数直接保存在 imm9 中;pimm 则按访问宽度折算后保存在 imm12 中。
4.2 Shared Decode for all encodings
三种编码各自的 Decode 都会产生 wback、postindex、scale 和 offset。Shared Decode for all encodings 接收这些结果,并完成三种编码共用的寄存器解析、访问宽度计算和约束检查。
text
integer n = UInt(Rn);
integer t = UInt(Rt);
integer regsize;
regsize = if size == '11' then 64 else 32;
integer datasize = 8 << scale;
boolean tag_checked = wback || n != 31;
boolean wb_unknown = FALSE;
if wback && n == t && n != 31 then
c = ConstrainUnpredictable();
assert c IN {Constraint_WBSUPPRESS, Constraint_UNKNOWN, Constraint_UNDEF, Constraint_NOP};
case c of
when Constraint_WBSUPPRESS wback = FALSE; // writeback is suppressed
when Constraint_UNKNOWN wb_unknown = TRUE; // writeback is UNKNOWN
when Constraint_UNDEF UNDEFINED;
when Constraint_NOP EndOfInstruction();
UInt(Rt) 和 UInt(Rn) 将机器码中的寄存器字段解释为无符号整数,得到目的寄存器编号 t 和基址寄存器编号 n。
scale 本身没有字节单位,它是由 size 字段得到的整数。公共解码用它计算访问宽度:
| 指令变体 | size |
scale |
datasize = 8 << scale |
|---|---|---|---|
LDR Wt, ... |
10 |
2 | 32 位 |
LDR Xt, ... |
11 |
3 | 64 位 |
datasize 以位为单位,因此 32 位和 64 位分别对应 4 字节和 8 字节内存访问。regsize 确定写入目的寄存器时采用的寄存器宽度。
公共解码还检查写回基址与目的寄存器重叠等约束。例如,Post-index 或 Pre-index 同时读取数据并更新基址;如果 Rn 与 Rt 指向同一通用寄存器,结果可能受到架构约束。Unsigned offset 的 wback 为 FALSE,不涉及这种写回重叠。
4.3 Operation for all encodings
Operation for all encodings 面向三种编码使用同一组公共变量,依次完成基址读取、有效地址计算、内存访问和基址写回:
text
if HaveMTE2Ext() then
SetTagCheckedInstruction(tag_checked);
bits(64) address;
bits(datasize) data;
if n == 31 then
CheckSPAlignment();
address = SP[];
else
address = X[n];
if !postindex then
address = address + offset;
data = Mem[address, datasize DIV 8, AccType_NORMAL];
X[t] = ZeroExtend(data, regsize);
if wback then
if wb_unknown then
address = bits(64) UNKNOWN;
elsif postindex then
address = address + offset;
if n == 31 then
SP[] = address;
else
X[n] = address;
postindex 决定本次访存使用原始基址还是调整后的地址,wback 决定是否把调整后的地址写回基址寄存器。二者组合后得到三种编码的行为:
| 编码 | postindex |
wback |
本次访问地址 | 执行后基址 |
|---|---|---|---|---|
| Post-index | TRUE |
TRUE |
base |
base + offset |
| Pre-index | FALSE |
TRUE |
base + offset |
base + offset |
| Unsigned offset | FALSE |
FALSE |
base + offset |
保持不变 |
datasize 和 regsize 以位为单位,而 Mem 的长度参数以字节为单位,所以读取长度写作 datasize DIV 8。写入 Wt 时,对应 Xt 的高 32 位清零;写入 Xt 时,完整写入 64 位。
上述栏目共同构成从汇编操作数到架构行为的完整定义:各编码的 Decode 产生相应的控制变量,公共解码补充访问宽度和寄存器信息,公共执行过程依据这些变量确定最终结果。
5. 关于 Unsigned offset 的缩放编码
Unsigned offset 使用缩放立即数:imm12 不直接表示字节偏移,而是以访问宽度为单位编码。本节说明该编码的换算关系、范围及其设计作用。
5.1 imm12 与 scale
Unsigned offset 的局部解码为:
text
scale = UInt(size)
offset = LSL(ZeroExtend(imm12, 64), scale)
ZeroExtend(imm12, 64) 在高位补 0,将 12 位无符号数扩展为 64 位,数值保持不变。LSL(value, scale) 再将该数左移 scale 位:
text
LDR Wt:scale = 2,offset = imm12 << 2 = imm12 * 4
LDR Xt:scale = 3,offset = imm12 << 3 = imm12 * 8
scale 不是访问宽度,也不以字节为单位。它是访问宽度的指数,同时也是该表达式中的左移位数;实际访问宽度为 2^scale 字节。
5.2 缩放机制与编码范围
A64 指令固定为 32 位。Unsigned offset 的编码布局还包含操作类型、访问宽度、基址寄存器和目的寄存器;在这一布局中,立即数信息由 12 位 imm12 字段保存。如果该字段直接表示字节,最大只能表示 4095 字节。
在数组、结构体等常见数据布局中,32 位或 64 位数据的偏移通常是 4 或 8 的整数倍,相应偏移的低 2 位或低 3 位为零。Unsigned offset 不在机器码中保存这些可以由访问宽度推导出的零,而是在解码时通过左移补回:
text
32 位形式:imm12 后隐含 2 个低位 0
64 位形式:imm12 后隐含 3 个低位 0
同一个 12 位字段因而获得更大的字节范围:
| 形式 | 直接按字节解释 | 按访问宽度缩放 |
|---|---|---|
LDR Wt |
0~4095 字节 | 0~16380 字节,4 的整数倍 |
LDR Xt |
0~4095 字节 | 0~32760 字节,8 的整数倍 |
这种编码以粒度换取范围。它只保证偏移量是访问宽度的整数倍:32 位形式的偏移量是 4 的整数倍,64 位形式的偏移量是 8 的整数倍。
有效地址由 base + offset 计算,其对齐方式仍取决于基址。基址按相同宽度对齐时,有效地址保持对齐;基址未对齐时,缩放后的偏移量也不会使有效地址自动对齐。缩放编码的作用是扩大可编码范围,而不是强制地址对齐。
5.3 完整解码示例
以 LDR X0, [X1, #24] 为例,假设 X1 = 0x1000。
目的寄存器为 X0,因此汇编器选择 64 位变体:
text
size = 0b11
scale = UInt(size) = 3
Assembler Symbols 规定,64 位变体的 <pimm> 按 8 字节单位编码:
text
pimm = 24 字节
imm12 = pimm / 8
= 3
= 0b000000000011
处理器识别 Unsigned offset 编码后,执行该编码自己的 Decode:
text
wback = FALSE
postindex = FALSE
offset = LSL(ZeroExtend(imm12, 64), scale)
= 3 << 3
= 24 字节
随后,公共解码得到 datasize = 64,公共执行过程计算:
text
address = X1 + offset
= 0x1000 + 24
= 0x1018
处理器从 0x1018 读取 8 字节并写入 X0。由于 wback = FALSE,X1 仍为 0x1000。
整条链路可以归纳为:
text
源码 #24
-> pimm = 24
-> imm12 = 3
-> offset = 24
-> address = X1 + 24
-> 从内存读取 8 字节到 X0
5.4 与 LDUR 的编码比较
LDR 的 Unsigned offset 与 LDUR 都以 base + offset 访问内存,也都不写回基址。二者的区别在于立即数编码:
| 指令 | 立即数字段 | 符号与缩放 | 64 位形式的范围 |
|---|---|---|---|
LDR Xt, [Xn, #pimm] |
imm12 |
无符号,乘以 8 | 0~32760,8 的整数倍 |
LDUR Xt, [Xn, #simm] |
imm9 |
有符号,不缩放 | -256~255,任意整数 |
asm
LDR X0, [X1, #16] // 可编码:imm12 = 16 / 8 = 2
LDUR X0, [X1, #16] // 可编码:imm9 = 16
LDUR X0, [X1, #20] // 可编码:imm9 = 20
// #20 无法用 64 位 Unsigned offset 编码
LDR X0, [X1, #800] // 可编码:imm12 = 800 / 8 = 100
// #800 超出 LDUR 的 imm9 范围
两类编码互为补充:Unsigned offset 提供较大的非负缩放范围,LDUR 提供较小的有符号逐字节范围。
6. LDR (literal)
Description:
Load Register (literal) calculates an address from the PC value and an immediate offset, loads a word from memory, and writes it to a register.

LDR (literal) 是独立于 LDR (immediate) 的编码形式。Post-index、Pre-index 和 Unsigned offset 只属于以基址寄存器和立即数生成地址的 LDR (immediate),并不是所有 LDR 条目共有的编码分类。
LDR (literal) 不使用显式基址寄存器,而是固定以当前指令地址加上 PC 相对偏移定位数据。由于不存在需要更新的基址寄存器,该形式没有基址写回,也没有 Post-index、Pre-index 或 Unsigned offset 等寻址形式。
本文关注的两种汇编形式为:
asm
LDR <Wt>, <label> // 读取 4 字节
LDR <Xt>, <label> // 读取 8 字节
literal 通常指放在代码附近的常量池数据。该指令不是把立即数字段本身写入寄存器,而是用立即数形成地址,再从该地址读取内存。
6.1 Decode
手册的解码伪代码为:
text
integer t = UInt(Rt); // Rt 对应的寄存器编号
MemOp memop = MemOp_LOAD; // 内存操作类型,默认为加载
boolean signed = FALSE; // 是否对读取结果进行符号扩展
integer size; // 内存访问的字节数
bits(64) offset; // 64 位 PC 相对字节偏移
case opc of
when '00'
size = 4; // LDR Wt:读取 4 字节
when '01'
size = 8; // LDR Xt:读取 8 字节
when '10'
size = 4;
signed = TRUE; // 读取后进行符号扩展
when '11'
memop = MemOp_PREFETCH; // 进入预取路径
offset = SignExtend(imm19 : '00', 64); // 生成 PC 相对字节偏移
opc 是编码图中的 2 位操作选择字段。MemOp 是手册用于区分内存操作类型的枚举:MemOp_LOAD 表示加载,MemOp_PREFETCH 表示预取。本文讨论的 LDR Wt 和 LDR Xt 分别沿 '00' 和 '01' 分支执行,此时 memop 保持为 MemOp_LOAD,signed 保持为 FALSE。
每种汇编形式旁的 Applies when 条件用于确定 opc。'10' 和 '11' 不属于当前两个 LDR 形式,只需了解其伪代码行为。
最后一行是 literal 寻址的核心解码。imm19 : '00' 表示在 imm19 低位拼接两个 0,等价于左移 2 位。拼接后的 21 位数再进行符号扩展,因此 literal 偏移以 4 字节为单位,可以向前或向后寻址。其范围为 -1,048,576 到 +1,048,572 字节,通常概括为 ±1 MiB。
6.2 Operation
手册的执行伪代码为:
text
bits(64) address = PC[] + offset;
bits(size * 8) data;
if HaveMTE2Ext() then
SetTagCheckedInstruction(FALSE);
case memop of
when MemOp_LOAD
data = Mem[address, size, AccType_NORMAL];
if signed then
X[t] = SignExtend(data, 64);
else
X[t] = data;
when MemOp_PREFETCH
Prefetch(address, t<4:0>);
第一行给出 literal 寻址的核心关系:
text
address = PC[] + offset
PC[] 表示当前这条指令的地址,不包含旧式流水线模型中的附加偏移。size 已由 opc 确定,因此 bits(size * 8) 表示 32 位或 64 位数据。AccType_NORMAL 表示普通内存访问类型,不改变地址计算结果。
对于 opc == '00' 和 opc == '01',执行路径均进入 MemOp_LOAD,并且 signed = FALSE:
text
data = Mem[address, size, AccType_NORMAL]
X[t] = data
opc == '00' 时读取 4 字节并写入 Wt,对应 Xt 的高 32 位清零;opc == '01' 时读取 8 字节并写入 Xt。
对于另外两个 opc 取值,伪代码已经给出相应行为:'10' 将 signed 设为 TRUE,使读取的数据符号扩展为 64 位;'11' 进入 MemOp_PREFETCH 分支,只发起预取,不向通用寄存器写入数据。它们不是本条目列出的 LDR Wt/Xt 汇编形式,因此本文不再展开。HaveMTE2Ext() 对应内存标签扩展相关处理,不改变 PC[] + offset 的地址计算关系。
6.3 地址计算示例
以下示例假设 LDR 位于 0x00400000,常量位于 0x00400008:
asm
// 0x00400000
LDR X0, constant
RET
// 0x00400008
constant:
.xword 0x1122334455667788
标签相对 LDR 的距离为 8 字节:
text
imm19 = 8 / 4 = 2
offset = SignExtend(2 << 2, 64) = 8
address = 0x00400000 + 8 = 0x00400008
因此,处理器从 0x00400008 读取 8 字节并写入 X0。
6.4 与 LDR (immediate) 的比较
LDR (immediate) 与 LDR (literal) 的核心区别为:
| 项目 | LDR (immediate) |
LDR (literal) |
|---|---|---|
| 基准地址 | Xn 或 SP |
当前指令地址 PC[] |
| 偏移字段 | imm9 或 imm12 |
imm19 |
| 典型用途 | 数组、结构体、栈帧中的数据 | 代码附近的常量池 |
| 是否存在显式基址寄存器 | 是 | 否 |
7. 指令别名与伪指令
汇编源码与机器指令并不总是一一对应。架构手册主要定义处理器能够识别的机器编码及其架构行为;汇编器还可以接受别名、伪指令、指示符和宏。因此,某种写法没有出现在架构手册中,并不一定表示语法错误。
常见形式可以分为:
| 形式 | 是否具有独立机器编码 | 定义来源 | 示例 |
|---|---|---|---|
| 架构指令 | 是 | 架构手册 | LDR X0, [X1, #8] |
| 指令别名 | 否,固定映射到另一条指令编码 | 架构手册 | MOV X0, X1、CMP X0, X1 |
| 伪指令 | 不一定,可能展开为一条或多条指令,也可能生成数据 | 汇编器文档 | LDR X0, =value |
| 汇编器指示符 | 不是指令 | 汇编器文档 | .xword、.align、.global |
| 宏 | 不是固定指令,由汇编阶段展开源码 | 汇编器或源文件 | .macro、.endm |
7.1 指令别名
指令别名是同一机器编码的另一种汇编写法。别名本身没有独立编码,汇编器会将它转换为对应的基础指令。例如:
asm
MOV X0, X1 // 对应 ORR X0, XZR, X1
CMP X0, X1 // 对应 SUBS XZR, X0, X1
TST X0, X1 // 对应 ANDS XZR, X0, X1
架构手册通常会为常用别名单独建立条目,并标明 alias of 及其首选反汇编条件。由于别名和基础指令具有相同编码,一条机器指令可能根据反汇编器的显示规则呈现为不同助记符。
7.2 LDR X0, =value
部分汇编器接受以下写法:
asm
LDR X0, =0x1122334455667788
这是汇编器提供的伪指令,不是架构手册中 LDR (literal) 的规范语法。等号表示"把这个值装入寄存器"的汇编意图,具体实现由汇编器选择。
一种实现是把常量放入附近的常量池,并生成真正的 LDR (literal):
asm
LDR X0, .Lliteral
...
.Lliteral:
.xword 0x1122334455667788
另一种可能的实现是使用 MOVZ/MOVK 构造常量:
asm
MOVZ X0, #0x7788
MOVK X0, #0x5566, LSL #16
MOVK X0, #0x3344, LSL #32
MOVK X0, #0x1122, LSL #48
两种容易混淆的写法具有不同含义:
asm
LDR X0, label // 架构指令:从 label 指向的内存地址读取数据
LDR X0, =value // 伪指令:请求汇编器将 value 装入 X0
对于符号操作数,LDR X0, =symbol 表示装入符号表达式的值;对于普通的代码或数据标签,该值通常是符号地址。具体重定位和展开方式由汇编器及目标文件格式决定。
7.3 指示符与宏
.xword、.align 和 .global 等关键字是汇编器指示符,而不是处理器指令。例如,前文示例中的:
asm
.xword 0x1122334455667788
.xword 要求汇编器在输出中放置一个 8 字节数据值。.align 控制对齐,.global 声明全局符号;它们都不会作为指令交给处理器执行。
以 . 开头并不足以判断一项内容是否为指示符。例如,.Lliteral: 末尾带有冒号,表示一个局部标签;.xword 后跟数据操作数,表示汇编器指示符。具体类型应由完整语法判断。
.macro 与 .endm 用于定义汇编宏。宏在汇编阶段进行源码展开,展开结果可能包含多条架构指令、伪指令或指示符。
7.4 确认真实机器指令
判断一条汇编写法时,可以依次检查:
- 在架构手册中检索助记符。若条目标明 alias of,继续查看对应的基础指令。
- 若架构手册中不存在该写法,查阅当前汇编器的参考文档。GNU assembler、Arm assembler 和 LLVM 集成汇编器支持的伪指令并不完全相同。
- 汇编源文件并查看目标文件的反汇编结果,确认汇编器生成的机器指令、重定位项和常量池数据。
以 GNU 工具链为例:
text
aarch64-linux-gnu-as demo.s -o demo.o
aarch64-linux-gnu-objdump -dr demo.o
目标文件的反汇编结果可以显示汇编器生成的指令序列;链接器随后还可能解析重定位或调整部分编码。对于别名,反汇编器仍可能显示别名助记符;对于伪指令,一行源码则可能对应多条指令或额外的常量池数据。
8. A64 指令条目的阅读顺序
同一方法适用于其他 A64 指令,阅读顺序如下:
- 确认条目范围: 区分 A64/AArch32、通用寄存器/SIMD&FP,以及标题括号中的编码形式。
- 阅读 Description: 明确数据来源、结果位置和主要架构效果。
- 区分 Encoding: 找出固定比特、操作数字段和各编码变体的适用条件。
- 分析 Decode: 将位字段转换为有明确含义的内部变量,确认符号扩展、零扩展和缩放规则。
- 核对 Assembler Symbols: 确认汇编操作数的范围、单位及其与编码字段的对应关系。
- 继续 Shared Decode: 确认访问宽度、寄存器编号、特性要求和公共约束。
- 代入 Operation: 沿伪代码确定有效地址、数据宽度、寄存器结果和写回行为。
- 阅读 Operational information: 补充异常、时序属性和架构扩展相关信息。
一条指令的完整定义并不局限于编码图或某段伪代码,而是由各栏目共同构成。明确区分汇编操作数、机器码字段和解码后的内部变量,即可沿着固定层级解析结构复杂的指令条目。
参考资料
- Arm, Architecture Reference Manual Armv8-A ,本地文件:
D:\resource\datasheet\ARM\arm_datesheet\armV8\Architecture Reference Manual armv8-a.pdf,Part C, section 6.2。 - Arm Developer, LDR (immediate), Load register (immediate)。
- Arm Community, The A64 ISA and compilers。