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 常用算术指令指令 | 示例 | 含义 |
---|---|---|
ADD | ADD X0, X1, X2 | X0 = X1 + X2 |
SUB | SUB X0, X1, X2 | X0 = X1 - X2 |
MUL | MUL X0, X0, X8 | X0 = X0 * X8 |
SDIV | SDIV X0, X0, X1 | X0 = X0 / X1(有符号除法运算) |
UDIV | SDIV X0, X0, X1 | X0 = X0 / X1(无符号除法运算) |
CMP | CMP X28, X0 | X28与 X0 相减,不存储结果,只更新 CPSR 中的条件标志位 |
CMN | CMP X28, X0 | X28与 X0 相加,根据结果更新, CPSR 中的条件标志位 |
ADDS/SUBS | ADDS X0, X1, X2 | 带 S 的指令运算结果会影响 CPSR 中的条件标志位,后面出现 |
的其他指令也同理。 |
#
2.2 常用跳转指令#
2.2.1 条件跳转指令指令 | 示例 | 含义 |
---|---|---|
B.cond | B.cond label | 若 cond 为真,则跳转到 label |
CBNZ | CBNZ Xn,label | 若 Xn != 0,则跳转到 label |
CBZ | CBZ Xn,label | 若 Xn == 0,则跳转到 label |
TBNZ | TBNZ Xn, #uimm6, label | 若 Xn[unimm6] != 0,则跳转到 label。 |
简单的说如果xn寄存器的第uimm6位不等于0,则跳转到label | ||
TBZ | TBZ Xn, #uimm6, label | 若 Xn[unimm6] == 0,则跳转到 label。 |
简单的说如果xn寄存器的第uimm6位等于0,则跳转到label |
#
2.2.2 无条件跳转指令指令 | 示例 | 含义 |
---|---|---|
B | B label | 无条件跳转到 label |
BL | BL label | 无条件跳转到 label,返回地址保存到 X30(LR)寄存器 |
BLR | BLR Xn | 无条件跳转到 Xn 寄存器的地址,返回地址保存到 X30(LR)寄存器 |
BR | BR Xn | 无条件跳转到 Xn 寄存器的地址 |
RET | RET {Xn} | 子程序返回指令,返回地址默认保存到 X30(LR)寄存器 |
将 LR(X30)寄存器的值复制给 PC 寄存器 |
#
2.2.3 常用逻辑指令指令 | 示例 | 含义 |
---|---|---|
AND | ADD X0, X1, X2 | X0 = X1 & X2 |
EOR | EOR X0, X1, X2 | X0 = X1 ^ X2 |
ORR | ORR X0, X1, X2 | X0 = X1 |
TST | TST W0, #0X40 | 测试 W0[3]是否为 1 |
#
2.2.4 常用数据传输指令指令 | 示例 | 含义 |
---|---|---|
MOV | MOV X19, X1 | X19 = X1 |
MOVZ | MOVZ Xn, #uimm16{, LSL#pos} | Xn = LSL(uimm16, pos) |
MOVN | MOVN Xn, #uimm16{, LSL#pos} | Xn = NOT(LSL(uimm16, pos)) |
MOVK | MOVK Xn, #uimm16{, LSL#pos} | Xn < pos+15:pos>= uimm16 |
#
2.2.5 常用地址偏移指令指令 | 示例 | 含义 |
---|---|---|
ADR | ADR Xn, label | Xn = PC + label |
ADRP | ADRP Xn, label | base = PC[11:0]=ZERO(12); Xd = base + label; |
#
2.2.6 常用移位运算指令指令 | 示例 | 含义 |
---|---|---|
ASR | ASR Xd, Xn, #uimm | 算术右移,结果带符号 |
LSL | LSL Xd, Xn, #uimm | 逻辑左移,移位后寄存器空出的低位补 0 |
LSR | LSR Xd, Xn, #uimm | 逻辑右移,移位后寄存器空出的低位补 0 |
ROR | ROR Xd, Xn, #uimm | 循环右移,从右端移除的位将被插入左端空出的位,可理解为”首位相连“ |
#
2.2.7 常用加载/存储指令指令 | 示例 | 含义 |
---|---|---|
LDR | LDR Xn/Wn, addr | 从内存地址 addre 读取 8/4个字节内容到 Xn/Wn中 |
STR | STR Xn/Wn, addr | 将 Xn/Wn 写入内存地址 addr中 |
LDUR | LDUR Xn/Wn, [basem #simm9] | 从 base + #simm9 地址中读取数据到 Xn/Wn 中,U 表示不需要按照字节对齐,取多少就是多少 |
STUR | STUR Xn/Wn, [basem #simm9] | 将 Xn/Wn 写入 base + #simm9 的内存地址 |
STP | STP Xn1,Xn2,addr | 将 Xn1 和 Xn2 写入内存地址 addr 中。P 表示一对,即同事操作两个寄存器。 |
LDP | LDP 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 中的条件码的状态和指令条件码域的设置有条件的执行。当指令执行条件满足时,指令被执行,否则被忽略。指令条件码机器助记符后缀如下。
条件码 | 助记符后缀 | 标志 | 含义 |
---|---|---|---|
0000 | EQ | Z 置位 | 相等 |
0001 | NE | z 置位 | 不相等 |
0010 | CS | C 置位 | 无符号数大于或者等于 |
0011 | CC | C 置位 | 无符号小于 |
0100 | MI | N 置位 | 负数 |
0101 | PL | N 清零 | 证书或零 |
0110 | VS | V 置位 | 溢出 |
0111 | VC | V 清零 | 未溢出 |
1000 | HI | C 置位、Z 清零 | 无符号大于 |
1001 | LS | C 清零、Z 置位 | 无符号小于或等于 |
1010 | GE | N 等于 V | 带符号大于或等于 |
1011 | LT | N 不等于 V | 带符号小于 |
1100 | GT | Z 清零且 N == V | 带符号大于 |
1101 | LE | Z 置位或 N != V | 带符号小于或等于 |
1110 | AL | 忽略 | 无条件执行 |
#
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加上页内偏移