ARM常用指令(3)
比较跳转指令
比较指令
- cmn指令
与cmp指令类似,不同的是cmn指令是将一个数与另一个数的相反数进行比较,并且在汇编过程中等同于adds指令
1 | cmn x1, x2 |
- csel指令
CSEL Wd, Wn, Wm, cond ; 32-bit
CSEL Xd, Xn, Xm, cond ; 64-bit
判断cond是否为真,为真返回Xn/Wn,为假返回Xm/Wm,返回值存放于Xd/Wd寄存器
1 | mov x1, #3 |
- cset指令
CSET Wd, cond ; 32-bit
CSET Xd, cond ; 64-bit
判断cond是否为真,为真设置Xd/Wd为1,否则设置为0
- csinc指令
类似于csel,不过当cond为假是,返回Xm/Wm + 1
1 | mov x1, #3 |
跳转指令
普通跳转指令
B B label 当前PC偏移量±128MB范围 B.cond B.cond label 当前PC偏移量±1MB范围 BL BL label 将返回值保存至X30寄存器,保存的值等于调用BL指令的当前PC值加4 BR BR Xn 跳转到寄存器指定地址 BLR BLR Xn 跳转到寄存器指定地址,并将返回地址保存在X30寄存器 比较跳转指令
CBZ cbz Xt, label Xt == 0,跳转至label,跳转范围是当前PC地址±1MB CBNZ cbnz Xt, label Xt != 0,跳转至label,跳转范围是当前PC地址±1MB TBZ tbz R , #imm, label Rt寄存器第imm位为0,跳转至label,跳转范围是当前PC地址±32KB TBNZ tbnz R , #imm, label Rt寄存器第imm位不为0,跳转至label,跳转范围是当前PC地址±32KB X29和X30寄存器的使用
调用子函数时,需要将X29和X30寄存器入栈,在主函数返回时,将X29和X30出栈,否则会出现主函数无法退出的情况。
错误案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// main.cpp
extern "C" void test2();
int main() {
test2();
return 0;
}
// main.s
.arch armv8-a
.text
.align 3
.global csel_test
csel_test:
cmp x0, 0
sub x2, x1, 1
add x3, x1, 2
csel x0, x3, x2, eq
ret
.global test2
test2:
mov x0, 1
mov x2, 3
bl csel_test
retLinux主机编译支持debug的arm跨平台可执行文件方法,参照另一篇文章《Android Toolchain编译与GDB调试》
1
2
3
4
5
6
7
8
9./gdb # 进入gdb程序
b main # 加断点
n # next 下一步,不进入函数 执行至test2();
s # step 下一步,进入test2函数内部
n
n
info r # info register 打印寄存器,可以查看lr寄存器为0x4030f0,pc寄存器为0x4031ac
s # step 下一步,进入csel_test函数内部
info r # info register 打印寄存器,可以查看lr寄存器为0x4031b0,pc寄存器为0x403190以上过程可以看出,lr寄存器的值,在csel_test子函数执行时lr寄存器的值为上一步pc寄存器值+4,即0x4031ac + 4 = 0x4031b0
当csel_test函数返回值test2时,lr寄存器的值为0x4031ac,此时test2函数只有按在lr寄存器的值为0x4030f0时才能正确返回,因此函数会卡住
正确案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.arch armv8-a
.text
.align 3
.global csel_test
csel_test:
cmp x0, 0
sub x2, x1, 1
add x3, x1, 2
csel x0, x3, x2, eq
ret
.global main
main:
stp x29, x30, [sp, -16]!
mov x0, 1
mov x2, 3
ldp x29, x30, [sp], #16
bl csel_test
ret重复上述gdb步骤,查看lr,pc寄存器的值
test2,lr寄存器的值为0x4030f0,pc寄存器的值为0x4031b0
csel_test,lr寄存器的值为0x4031b4,pc寄存器的值为0x4031a0
返回test2函数后,lr寄存器的值为0x4030f0,pc寄存器的值为0x4031b8
test2调用子函数前后lr寄存器的值保持不变,test2函数正常结束
PC相对地址加载指令
- ADR指令
加载当前PC值±1MB范围内的label地址到目的寄存器
- ADRP指令
加载当前PC值±4GB范围内的label 4KB对齐的地址到目的寄存器
1 | .arch armv8-a |
可以通过gdb -tui进行界面模式,然后通过layout reg切换至寄存器显示模式
通过GDB查看,x0地址为0x403190,x2地址为0x403000,前者为my_data1的地址,否则为my_data1地址按照4KB对齐后的地址,因此x1寄存器的值为0x8a,与定义相同,x3寄存器的值错误。
1 | adrp x2, my_data1 |
“#:lo12:”,表示4KB大小的偏移量,再通过GDB查看,结果正确。
- adr和ldr伪指令的区别
- ldr伪指令加载的是绝对地址,adr加载的是当前pc的相对地址
- 当运行地址和链接地址相同时,adr和ldr伪指令等效
- 当运行地址和链接地址不同时,ldr伪指令加载label的链接地址,adr加载label物理地址
系统寄存器访问指令
- mrs指令
读取系统寄存器的值到通用寄存器
1 | mrs x0, nzcv |
- msr指令
1 | mrs x0, nzcv |
内存屏障指令
DMB | 数据存储屏障,Data Memory Barrier,确保在执行新的存储器访问前所有的存储器访问都已经完成 |
---|---|
DSB | 数据同步屏障,Data Synchronization Barrier,确保在下一个指令执行前所有的存储器访问都已经完成 |
ISB | 指令同步屏障,Instruction Synchronization Barrier,清空流水线,确保在执行新的指令前,之前所有的指令都已经完成 |
LDAR | 加载-获取指令,Load-acquire,LDAR指令后面的读写内存指令必须在LDAR指令之后才能完成 |
STLR | 存储-释放指令,Store-release,所有加载和存储指令必须在STLR指令之前完成 |
伪指令
对齐伪指令
1
2.align 2 # 四字节对齐
.align 5, 0, 100 # 32字节对齐,最多跳过字节数为100,填充0数据定义伪指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14.byte 将8位数作为数据插入汇编代码
.hword/.short 将16位数作为数据插入汇编代码
.long/.int 将32位数作为数据插入汇编代码
.word 将32位数作为数据插入汇编代码
.quad 将64位数作为数据插入汇编代码
.float 将浮点数作为数据插入汇编代码
.ascii/.string 将字符串作为数据插入汇编代码
.asciz 将字符串作为数据插入汇编代码,自动插入结尾字符”\0”
.rept/.endr 重复执行操作
.rept 3
.long 0
.endr
.equ 符号赋值操作
.equ my_data, 100函数相关伪指令
1
2
3
4
5
6.global 全局函数符号,或者全局变量符号
.include 引用头文件
.if .else .endif 条件判断
.ifdef/.ifndef symbol 判断符号是否定义
.ifc/.ifeqs 判断两字符串是否相等
.ifeq/.ifge/.ifle/.ifne 判断值是否为0/大于等于/小于等于0/不等于0宏伪指令
1
2
3.macro add p1 p2
add x0, \p1, \p2
.endm