ARM常用指令(3)

比较跳转指令

比较指令

  • cmn指令

与cmp指令类似,不同的是cmn指令是将一个数与另一个数的相反数进行比较,并且在汇编过程中等同于adds指令

1
2
3
cmn x1, x2
=>
adds xzr, 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
2
3
4
5
mov x1, #3
mov x2, #4

cmp x1, x2
csel x0, x1, x2, ge // x0 = 4
  • cset指令

CSET Wd, cond ; 32-bit

CSET Xd, cond ; 64-bit

判断cond是否为真,为真设置Xd/Wd为1,否则设置为0

  • csinc指令

类似于csel,不过当cond为假是,返回Xm/Wm + 1

1
2
3
4
5
mov x1, #3
mov x2, #4

cmp x1, x2
csinc x0, x1, x2, ge // x0 = 5

跳转指令

  • 普通跳转指令

    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
    #include <iostream>

    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
    ret

    Linux主机编译支持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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.arch armv8-a
.text
.align 3

my_data1:
.dword 0x8a

.global test2
test2:
adr x0, my_data1
ldr x1, [x0]

adrp x2, my_data1
ldr x3, [x2]
ret

可以通过gdb -tui进行界面模式,然后通过layout reg切换至寄存器显示模式

通过GDB查看,x0地址为0x403190,x2地址为0x403000,前者为my_data1的地址,否则为my_data1地址按照4KB对齐后的地址,因此x1寄存器的值为0x8a,与定义相同,x3寄存器的值错误。

https://s2.loli.net/2022/05/23/4w1xtTYNIyQXb7J.png
1
2
3
adrp x2, my_data1
add x2, x2, #:lo12:my_data1
ldr x3, [x2]

“#:lo12:”,表示4KB大小的偏移量,再通过GDB查看,结果正确。

https://s2.loli.net/2022/05/23/CxqeDOmPaLQ4hgN.png
  • adr和ldr伪指令的区别
    1. ldr伪指令加载的是绝对地址,adr加载的是当前pc的相对地址
    2. 当运行地址和链接地址相同时,adr和ldr伪指令等效
    3. 当运行地址和链接地址不同时,ldr伪指令加载label的链接地址,adr加载label物理地址

系统寄存器访问指令

  • mrs指令

读取系统寄存器的值到通用寄存器

1
2
mrs x0, nzcv
// 读取NZCV寄存器,查看标志位是否为1
  • msr指令
1
2
mrs x0, nzcv
// 更新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/.short16位数作为数据插入汇编代码
    .long/.int32位数作为数据插入汇编代码
    .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