Skip to main content

02 | 指令集

2、指令集#

指令的基本格式如下: <opcode>{<cond>}{S} <Rd>, <Rn> {, <shift_op2>} 其中,尖括号是必须的,花括号可选。

标识符含义
opcode操作码。也就是指令编码助记符,说明指令需要执行的操作类型。
cond条件码域。指令允许执行的条件编码
S条件码设置域。这是一个可选项,当在指令中设置该域时,指令执行的结果将会印象程序状态寄存器 CPSR 中相应的标志
Rd/Xt目标寄存器。ARM64 指令可以选择 X0 - X30 或 W0 - W30
Rn/Xn第一个操作数的寄存器,和 Rd 一样,不同指令有不同要求
shift_op2第二个操作数,可以是立即数或寄存器位移方式

2.1 常用算术指令#

指令示例含义
ADDADD X0, X1, X2X0 = X1 + X2
SUBSUB X0, X1, X2X0 = X1 - X2
MULMUL X0, X0, X8X0 = X0 * X8
SDIVSDIV X0, X0, X1X0 = X0 / X1(有符号除法运算)
UDIVSDIV X0, X0, X1X0 = X0 / X1(无符号除法运算)
CMPCMP X28, X0X28与 X0 相减,不存储结果,只更新 CPSR 中的条件标志位
CMNCMP X28, X0X28与 X0 相加,根据结果更新, CPSR 中的条件标志位
ADDS/SUBSADDS X0, X1, X2带 S 的指令运算结果会影响 CPSR 中的条件标志位,后面出现
的其他指令也同理。

2.2 常用跳转指令#

2.2.1 条件跳转指令#

指令示例含义
B.condB.cond label若 cond 为真,则跳转到 label
CBNZCBNZ Xn,label若 Xn != 0,则跳转到 label
CBZCBZ Xn,label若 Xn == 0,则跳转到 label
TBNZTBNZ Xn, #uimm6, label若 Xn[unimm6] != 0,则跳转到 label。
简单的说如果xn寄存器的第uimm6位不等于0,则跳转到label
TBZTBZ Xn, #uimm6, label若 Xn[unimm6] == 0,则跳转到 label。
简单的说如果xn寄存器的第uimm6位等于0,则跳转到label

2.2.2 无条件跳转指令#

指令示例含义
BB label无条件跳转到 label
BLBL label无条件跳转到 label,返回地址保存到 X30(LR)寄存器
BLRBLR Xn无条件跳转到 Xn 寄存器的地址,返回地址保存到 X30(LR)寄存器
BRBR Xn无条件跳转到 Xn 寄存器的地址
RETRET {Xn}子程序返回指令,返回地址默认保存到 X30(LR)寄存器
将 LR(X30)寄存器的值复制给 PC 寄存器

2.2.3 常用逻辑指令#

指令示例含义
ANDADD X0, X1, X2X0 = X1 & X2
EOREOR X0, X1, X2X0 = X1 ^ X2
ORRORR X0, X1, X2X0 = X1
TSTTST W0, #0X40测试 W0[3]是否为 1

2.2.4 常用数据传输指令#

指令示例含义
MOVMOV X19, X1X19 = X1
MOVZMOVZ Xn, #uimm16{, LSL#pos}Xn = LSL(uimm16, pos)
MOVNMOVN Xn, #uimm16{, LSL#pos}Xn = NOT(LSL(uimm16, pos))
MOVKMOVK Xn, #uimm16{, LSL#pos}Xn < pos+15:pos>= uimm16

2.2.5 常用地址偏移指令#

指令示例含义
ADRADR Xn, labelXn = PC + label
ADRPADRP Xn, labelbase = PC[11:0]=ZERO(12); Xd = base + label;

2.2.6 常用移位运算指令#

指令示例含义
ASRASR Xd, Xn, #uimm算术右移,结果带符号
LSLLSL Xd, Xn, #uimm逻辑左移,移位后寄存器空出的低位补 0
LSRLSR Xd, Xn, #uimm逻辑右移,移位后寄存器空出的低位补 0
RORROR Xd, Xn, #uimm循环右移,从右端移除的位将被插入左端空出的位,可理解为”首位相连“

2.2.7 常用加载/存储指令#

指令示例含义
LDRLDR Xn/Wn, addr从内存地址 addre 读取 8/4个字节内容到 Xn/Wn中
STRSTR Xn/Wn, addr将 Xn/Wn 写入内存地址 addr中
LDURLDUR Xn/Wn, [basem #simm9]从 base + #simm9 地址中读取数据到 Xn/Wn 中,U 表示不需要按照字节对齐,取多少就是多少
STURSTUR Xn/Wn, [basem #simm9]将 Xn/Wn 写入 base + #simm9 的内存地址
STPSTP Xn1,Xn2,addr将 Xn1 和 Xn2 写入内存地址 addr 中。P 表示一对,即同事操作两个寄存器。
LDPLDP Xn1,Xn2,addr从内存地址 addr 中读取数据到 Xn1 和 Xn2 中。

加载/存储指令都是成对出现,有时也会遇到这些指令的一些扩展,比如 LDRB、LDRSB 等,他们的含义如下

指令含义
B无符号 8 bit
SB有符号 8 bit
H无符号 16 bit
SH有符号 16 bit
W无符号 32 bit
SW有符号 32 bit

ARM指令的一个重要特点就是可以条件执行,每条 ARM 指令的条件码域包含 4 位条件吗,共 16 种。几乎所有的指令均根据 CPSR 中的条件码的状态和指令条件码域的设置有条件的执行。当指令执行条件满足时,指令被执行,否则被忽略。指令条件码机器助记符后缀如下。

条件码助记符后缀标志含义
0000EQZ 置位相等
0001NEz 置位不相等
0010CSC 置位无符号数大于或者等于
0011CCC 置位无符号小于
0100MIN 置位负数
0101PLN 清零证书或零
0110VSV 置位溢出
0111VCV 清零未溢出
1000HIC 置位、Z 清零无符号大于
1001LSC 清零、Z 置位无符号小于或等于
1010GEN 等于 V带符号大于或等于
1011LTN 不等于 V带符号小于
1100GTZ 清零且 N == V带符号大于
1101LEZ 置位或 N != V带符号小于或等于
1110AL忽略无条件执行

2.3 ADR、ADRP常用地址偏移指令额外说明#

ADRP + ADD表示取值,常用于取常量和全局变量的值,但是这里取的是地址值。 ADRP + ADD表示取值,常用于取常量和全局变量的值,但是这里取的是地址值。

2.3.1 ADR#

小范围的地址读取指令。ADR 指令将基于 PC 的相对偏移的地址读取到寄存器中。 格式:adr Xn , exper. 编译源程序时,汇编器首先计算当前 PC 值(当前指令位置)到 exper的距离,然后用一条 ADD 或者 SUB 指令替换这条伪指令。 例如: ADD Xn, PC, #offset_to_exper。 注意:标号 exper 与指令必须在同一个代码段中。 比如:adr x0,_start;将指定地址赋值到 x0 中。 ...... _start: b _start x0 的值为标号_start 与此指令的距离差+PC 值 简单的来说就是将一个立即值 与 PC 值相加,并将结果写入到目标寄存器。

2.3.2 ADRP#

这是一条中等范围的地址读取伪指令,它将基于 PC 的相对偏移的地址值读取到目标寄存器中。 格式如下:ADRP Xn, exper 编译源程序时,汇编器会用两条合适的指令替换这条伪指令。比如: ADRP Xn,PC,offset1 ADD Xn,Xn1,offset2 ​

与 ADR 相比,它能读取更大返回的地址。注意标号 exper 与指令必须在同一代码段。 adrp 用来定位数据段中的数据使用,因为 aslr 会导致代码及数据的地址随机化,用 adrp来根据 pc 值做辅助定位。 ADRP 指令是以页为单位的大范围的地址读取指令,P为page, pageoff为page offset,这里填写的地址是最终地址的页基址,汇编会自动把opcode转换成相对于pc页基址的地址。 符号扩展一个21位的offset(immhi+immlo)。向左移动12位,将PC的值的低12位清零,然后把这两者相加,结果写入到Xd寄存器,用来得到一块含有label的4KB对齐内存区域的base地址(也就是说label所在的地址,一定落在这个4KB的内存区域里,指令助记符里Page也就是这个意思), 可用来寻址 +/- 4GB的范围(2^33次幂)。

2.3.3 计算规则#

adrp   x10, x                    x10, x10, #0x230  
  • 将 x 的值,左移 12 位
    • 例如 x = 1,x << 12 = 1 0000 0000 0000 == 0x0001000
  • 获取当前 PC 计数器的值,将其低 12 位清零
    • 例如 pc = 0x000000010018e154,低 12 位清零后 值为0x000000010018e000
  • 将 x 左移后的值 + PC 计数器低 12 位清零的值 等于 x10 的值。
    • x10 = 0x0001000 + 0x000000010018e000
    • x10 = 0x000000010018f000

2.3.4 示例#

int counter = 1;int getCount() {    return counter;}

汇编的完整结果如下:

    .section    __TEXT,__text,regular,pure_instructions    .globl  _getCount               ; -- Begin function getCount    .p2align    2_getCount:                              ; @getCount    adrp    x8, _counter@PAGE    add x8, x8, _counter@PAGEOFF    ldr w0, [x8]    ret                                        ; -- End function    .section    __DATA,__data    .globl  _counter                ; @counter    .p2align    2_counter:    .long   1                       ; 0x1
  • adrp x8, _counter@PAGE:使用 adrp 计算出_counter label 基于 PC 的偏移量的高 21 位,并存储到 x8 寄存器中。@PAGE 代表页偏移的高 21 位。
  • add x8, x8, _counter@PAGEOFF:使用 add 命令将余下的 12 位补齐,通过@PAGEOFF 代表页偏低的 12 位。
  • ldr w0, [x8] :此时 x8 中即为 counter 变量的实际地址了,通过 ldr 命令将寄存器的值读取到 w0,返回番薯返回值。

示例 2:

ADRP            X8, #_g@PAGEADD             X8, X8, #_g@PAGEOFF
  • 得到一个大小为4KB的页的基址,而且在该页中有全局变量g的地址;ADRP就是讲该页的基址存到寄存器X8中;
  • ADD指令会算出g的地址,X8+#_g@PAGEOFF,#_g@PAGEOFF是一个偏移量;这样就得到了g的地址X8;

2.3.5 @PAGE和@PAGEOFF 补充#

adrp @page 找到目标页地址 add @pageoff加上页内偏移