ARMv8-A 指令学习

本文对应 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;三种编码之后,手册再统一给出它们共用的汇编符号、公共解码和执行过程。

字段名中的 immimmediate (立即数)的缩写,后面的数字表示字段宽度。例如,imm9 是 9 位立即数字段,imm12 是 12 位立即数字段。字段宽度不等于字节偏移;字段的符号、单位和换算方式由 DecodeAssembler Symbols 共同定义。

三种编码的 Decode 都会由 size 生成 scalescale 不用于区分三种寻址形式,其与访问宽度的关系将在第 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,并生成 wbackpostindexscaleoffset。这些变量将在后续公共解码和执行过程中共同决定访问宽度、有效地址与基址写回行为。

4. 三种编码的公共部分

手册随后进入三种编码共用的部分,依次给出 Assembler SymbolsShared Decode for all encodingsOperation for all encodings。这些内容不再从属于某一种具体编码。

其中,Assembler Symbols 位于汇编端,规定源码操作数与机器码字段的对应关系;Shared Decode for all encodingsOperation 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 表示 signedpimm 中的 p 源于 positive,但该操作数允许取 0,因此实际范围是非负数。两者的数值都以字节为单位。

汇编操作数、机器码字段和解码变量分属不同阶段:

所处阶段 名称 含义
汇编语法 <simm><pimm> 程序员书写的字节偏移
机器编码 imm9imm12 指令中保存立即数信息的位字段
解码结果 offset 供地址计算使用的 64 位字节偏移

对于合法指令,汇编操作数与最终 offset 表示相同的字节偏移,但中间的编码方式不同:simm 以 9 位有符号数直接保存在 imm9 中;pimm 则按访问宽度折算后保存在 imm12 中。

4.2 Shared Decode for all encodings

三种编码各自的 Decode 都会产生 wbackpostindexscaleoffsetShared 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 同时读取数据并更新基址;如果 RnRt 指向同一通用寄存器,结果可能受到架构约束。Unsigned offset 的 wbackFALSE,不涉及这种写回重叠。

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 保持不变

datasizeregsize 以位为单位,而 Mem 的长度参数以字节为单位,所以读取长度写作 datasize DIV 8。写入 Wt 时,对应 Xt 的高 32 位清零;写入 Xt 时,完整写入 64 位。

上述栏目共同构成从汇编操作数到架构行为的完整定义:各编码的 Decode 产生相应的控制变量,公共解码补充访问宽度和寄存器信息,公共执行过程依据这些变量确定最终结果。

5. 关于 Unsigned offset 的缩放编码

Unsigned offset 使用缩放立即数:imm12 不直接表示字节偏移,而是以访问宽度为单位编码。本节说明该编码的换算关系、范围及其设计作用。

5.1 imm12scale

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 = FALSEX1 仍为 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 WtLDR Xt 分别沿 '00''01' 分支执行,此时 memop 保持为 MemOp_LOADsigned 保持为 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)
基准地址 XnSP 当前指令地址 PC[]
偏移字段 imm9imm12 imm19
典型用途 数组、结构体、栈帧中的数据 代码附近的常量池
是否存在显式基址寄存器

7. 指令别名与伪指令

汇编源码与机器指令并不总是一一对应。架构手册主要定义处理器能够识别的机器编码及其架构行为;汇编器还可以接受别名、伪指令、指示符和宏。因此,某种写法没有出现在架构手册中,并不一定表示语法错误。

常见形式可以分为:

形式 是否具有独立机器编码 定义来源 示例
架构指令 架构手册 LDR X0, [X1, #8]
指令别名 否,固定映射到另一条指令编码 架构手册 MOV X0, X1CMP 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 确认真实机器指令

判断一条汇编写法时,可以依次检查:

  1. 在架构手册中检索助记符。若条目标明 alias of,继续查看对应的基础指令。
  2. 若架构手册中不存在该写法,查阅当前汇编器的参考文档。GNU assembler、Arm assembler 和 LLVM 集成汇编器支持的伪指令并不完全相同。
  3. 汇编源文件并查看目标文件的反汇编结果,确认汇编器生成的机器指令、重定位项和常量池数据。

以 GNU 工具链为例:

text 复制代码
aarch64-linux-gnu-as demo.s -o demo.o
aarch64-linux-gnu-objdump -dr demo.o

目标文件的反汇编结果可以显示汇编器生成的指令序列;链接器随后还可能解析重定位或调整部分编码。对于别名,反汇编器仍可能显示别名助记符;对于伪指令,一行源码则可能对应多条指令或额外的常量池数据。

8. A64 指令条目的阅读顺序

同一方法适用于其他 A64 指令,阅读顺序如下:

  1. 确认条目范围: 区分 A64/AArch32、通用寄存器/SIMD&FP,以及标题括号中的编码形式。
  2. 阅读 Description: 明确数据来源、结果位置和主要架构效果。
  3. 区分 Encoding: 找出固定比特、操作数字段和各编码变体的适用条件。
  4. 分析 Decode: 将位字段转换为有明确含义的内部变量,确认符号扩展、零扩展和缩放规则。
  5. 核对 Assembler Symbols: 确认汇编操作数的范围、单位及其与编码字段的对应关系。
  6. 继续 Shared Decode: 确认访问宽度、寄存器编号、特性要求和公共约束。
  7. 代入 Operation: 沿伪代码确定有效地址、数据宽度、寄存器结果和写回行为。
  8. 阅读 Operational information: 补充异常、时序属性和架构扩展相关信息。

一条指令的完整定义并不局限于编码图或某段伪代码,而是由各栏目共同构成。明确区分汇编操作数、机器码字段和解码后的内部变量,即可沿着固定层级解析结构复杂的指令条目。

参考资料

相关推荐
_张一凡1 小时前
Ubuntu 完美安装 FT_SCServo_Debug_Qt 飞特SCS_STS舵机调试工具
linux·ubuntu·舵机调试
南清的coding日记1 小时前
7.31 RAG八股+小程序学习+公众号阅读
学习
星恒随风2 小时前
Linux 基础指令(三):grep、压缩打包、系统信息与 Shell 运行原理
linux·笔记·学习
Tyfrank2 小时前
Linux内核收包路径及中断
linux·运维·单片机
库玛西2 小时前
快速幂算法全景总结图:从“愚公移山”到“细胞分裂”
c语言·c++·笔记·算法
男孩李2 小时前
浅谈Linux的last命令
java·linux·服务器
yunqi2 小时前
ELK日志平台架构详解:从Filebeat采集到Elasticsearch检索的企业级日志方案实践
linux
是江迪呀2 小时前
关于记笔记的批评和自我批评
笔记·aigc·ai编程
MartinYeung52 小时前
[论文学习]Skill-MAS:面向自动多智能体系统的元技能进化
大数据·人工智能·学习