【ARMv8 编程】A64 流控制指令

news/2024/9/2 14:07:28 标签: armv8, 汇编, B, BL, A64

A64 指令集提供了许多不同种类的分支指令。对于简单的相对分支,即那些从当前地址偏移的分支,使用 B 指令。无条件简单相对分支可以从当前程序计数器位置向后或向前分支最多 128MB。 有条件的简单相对分支,其中条件代码附加到 B,具有 ±1MB 的较小范围。

调用子程序时,需要将返回地址存储在链接寄存器(X30)中,使用 BL 指令。这没有条件版本。BL 的行为类似于 B 指令,具有将返回地址存储在寄存器 X30 中的附加作用,返回地址是 BL 之后指令的地址。

除了这些 PC 相关指令外,A64 指令集还包括两个绝对分支。 BR Xn 指令对 Xn 中的地址执行绝对分支,而 BLR Xn 具有相同的效果,但还将返回地址存储在 X30(链接寄存器)中。 RET 指令的行为类似于 BR Xn,但它向分支预测逻辑提示它是一个函数返回。RET 默认分支到 X30 中的地址,但可以指定其他寄存器。

A64 指令集包括一些特殊的条件分支。在某些情况下,这些允许提高代码密度,因为不需要显式比较。

CBZ Rt, label // 如果为零则比较并分支
CBNZ Rt, label // 如果不为零则比较并分支

这些指令将 32 位或 64 位源寄存器与零进行比较,然后有条件地执行分支。分支偏移量的范围为 ± 1MB。这些指令不读取或写入条件代码标志(NZCV)。

有两个类似的测试和分支指令

TBZ Rt, bit, label // 如果 Rt<bit> 为零,则测试并分支
TBNZ Rt, bit, label // 如果 Rt<bit> 不为零,则测试并分支

这些指令在立即和条件分支指定的位位置测试源寄存器中的位,具体取决于该位是设置还是清除。分支偏移量的范围为 ±32kB。与 CBZ/CBNZ 一样,这些指令不读取或写入条件代码标志(NZCV)。

一、分支指令

B_26">1.1 B

分支指令导致无条件分支到 PC 相对偏移处的标签,并提示这不是子例程调用或返回。

在这里插入图片描述

B <label>

<label> 是要无条件跳转到的程序标签。 它与该指令地址的偏移量在 +/-128MB 范围内,编码为“imm26”乘以 4。

下面是使用 B 指令的例子。

    long long int x = 0;
    long long int y = 0;
    long long int z = 0;
    LOGD("x=%llx y=%llx z=%llx", x, y, z);
    asm volatile(
        "MOV %x[x], #1\n"
        "B skipy\n"
        "MOV %x[y], #2\n"
        "skipy:\n"
        "MOV %x[z], #3\n"
    :[x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=%llx y=%llx z=%llx", x, y, z);

由于 B skipy 跳过了 MOV %x[y], #2 这条指令,因此 y 的值还是初始值。

运行结果如下:

2023-05-05 07:59:28.672 12642-12704/com.demo.myapplication D/NativeCore: x=0 y=0 z=0
2023-05-05 07:59:28.672 12642-12704/com.demo.myapplication D/NativeCore: -----------------------------
2023-05-05 07:59:28.672 12642-12704/com.demo.myapplication D/NativeCore: x=1 y=0 z=3

Bcond_70">1.2 B.cond

有条件地分支到 PC 相对偏移处的标签,并提示这不是子例程调用或返回。

在这里插入图片描述

B.<cond> <label>

<cond> 是标准条件之一,以标准方式编码在“cond”字段中。

<label> 是有条件跳转到的程序标签。它与该指令地址的偏移量在 +/-1MB 范围内,编码为“imm19”乘以 4。

下面是使用 B.cond 指令的例子。

    long long int x = 0;
    long long int y = 0;
    long long int z = 0;
    LOGD("x=%llx y=%llx z=%llx", x, y, z);
    asm volatile(
        "MOV X0, #2\n"
        "SUBS X0, X0, #1\n"
        "B.EQ skipy\n"
        "MOV %x[x], #1\n"
        "SUBS X0, X0, #1\n"
        "B.EQ skipy\n"
        "MOV %x[y], #2\n"
        "skipy:\n"
        "MOV %x[z], #3\n"
    :[x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=%llx y=%llx z=%llx", x, y, z);

由于第二条 B.EQ skipy 跳过了 MOV %x[y], #2 这条指令,因此 y 的值还是初始值。第一条 B.EQ skipy 指令因为 第一条 SUBS X0, X0, #1 指令并没有设置 Z 标志位,所以没有跳转,否则 x 的值最终也不会改变。

运行结果如下:

2023-05-05 08:08:27.579 16848-16909/com.demo.myapplication D/NativeCore: x=0 y=0 z=0
2023-05-05 08:08:27.579 16848-16909/com.demo.myapplication D/NativeCore: -----------------------------
2023-05-05 08:08:27.579 16848-16909/com.demo.myapplication D/NativeCore: x=1 y=0 z=3

BL_120">1.3 BL

BL(Branch with Link)指令分支到 PC 相对偏移,将寄存器 X30 设置为 PC + 4。它提供了这是一个子例程调用的提示。

在这里插入图片描述

BL <label>

<label> 是要无条件跳转到的程序标签。它与该指令地址的偏移量在 +/-128MB 范围内,编码为“imm26”乘以 4。

下面是使用 BL 指令的例子。

    long long int x = 0;
    long long int y = 0;
    long long int z = 0;
    LOGD("x=%llx y=%llx z=%llx", x, y, z);
    asm volatile(
        "BL mod_y\n"
        "MOV %x[x], #1\n"
        "B mod_z\n"
        "MOV %x[x], #4\n"
        "mod_y:\n"
        "MOV %x[y], #2\n"
        "BR LR\n"
        "mod_z:\n"
        "MOV %x[z], #3\n"
    :[x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=%llx y=%llx z=%llx", x, y, z);

在这里插入图片描述

运行结果如下:

2023-05-05 09:13:40.038 15433-15580/com.demo.myapplication D/NativeCore: x=0 y=0 z=0
2023-05-05 09:13:40.038 15433-15580/com.demo.myapplication D/NativeCore: -----------------------------
2023-05-05 09:13:40.038 15433-15580/com.demo.myapplication D/NativeCore: x=1 y=2 z=3

BR_169">1.4 BR

BR (Branch to Register)指令无条件地分支到寄存器中的地址,并提示这不是子程序返回。

在这里插入图片描述

BR <Xn>

<Xn> 是保存要分支到的地址的通用寄存器的 64 位名称,在“Rn”字段中编码。

BR 指令的例子参考 BL 指令的例子即可。

BLR_182">1.5 BLR

BLR(Branch with Link to Register)指令在寄存器中的地址调用子程序,将寄存器 X30 设置为 PC+4。

在这里插入图片描述

BLR <Xn>

<Xn> 是保存要分支到的地址的通用寄存器的 64 位名称,在“Rn”字段中编码。

下面是使用 BLR 指令的例子。

    long long int x = 0;
    long long int y = 0;
    long long int z = 0;
    LOGD("x=%llx y=%llx z=%llx", x, y, z);
    asm volatile(
        "BL mod_y\n"
        "MOV %x[x], #1\n"
        "BR LR\n"
        "MOV %x[x], #4\n"
        "mod_y:\n"
        "MOV %x[y], #2\n"
        "BLR LR\n"
        "MOV %x[y], #5\n"
        "mod_z:\n"
        "MOV %x[z], #3\n"
    :[x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=%llx y=%llx z=%llx", x, y, z);

在这里插入图片描述

BLR 指令执行会设置 LR 寄存器的值为 PC + 4,BR 指令则只分支!

运行结果如下:

2023-05-07 15:58:22.939 4243-4243/com.example.myapplication D/native-armv8a: x=0 y=0 z=0
2023-05-07 15:58:22.939 4243-4243/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-07 15:58:22.939 4243-4243/com.example.myapplication D/native-armv8a: x=1 y=5 z=3

1.6 RET

无条件地从子程序分支返回到寄存器中的地址,并提示这是子程序返回。

在这里插入图片描述

RET {<Xn>}

<Xn> 是保存要分支到的地址的通用寄存器的 64 位名称,在“Rn”字段中编码。 如果不存在则默认为 X30。

下面是使用 RET 指令的例子。将上面例子中 BR LR 改为 RET,x、y、z 最终的值和上面的例子保持一致。

    long long int x = 0;
    long long int y = 0;
    long long int z = 0;
    LOGD("x=%llx y=%llx z=%llx", x, y, z);
    asm volatile(
        "BL mod_y\n"
        "MOV %x[x], #1\n"
        "RET\n"
        "MOV %x[x], #4\n"
        "mod_y:\n"
        "MOV %x[y], #2\n"
        "BLR LR\n"
        "MOV %x[y], #5\n"
        "mod_z:\n"
        "MOV %x[z], #3\n"
    :[x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=%llx y=%llx z=%llx", x, y, z);

运行结果如下:

2023-05-07 16:21:41.276 19720-19720/com.example.myapplication D/native-armv8a: x=0 y=0 z=0
2023-05-07 16:21:41.276 19720-19720/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-07 16:21:41.276 19720-19720/com.example.myapplication D/native-armv8a: x=1 y=5 z=3

二、条件分支指令

BZ_283">2.1 CBZ

CBZ(Compare and Branch on Zero)指令将寄存器中的值与零进行比较,如果比较相等,则有条件地分支到 PC 相对偏移量处的标签。它提供了这不是子例程调用或返回的提示。该指令不影响条件标志。

在这里插入图片描述

32-bit (sf == 0)

CBZ <Wt>, <label>

64-bit (sf == 1)

CBZ <Xt>, <label>

<Wt> 是要测试的通用寄存器的 32 位名称,编码在“Rt”字段中。

<Xt> 是要测试的通用寄存器的 64 位名称,编码在“Rt”字段中。

<label> 是有条件跳转到的程序标签。它与该指令地址的偏移量在 +/-1MB 范围内,编码为“imm19”乘以 4。

下面是使用 CBZ 指令的例子。

    long long int x = 0;
    long long int y = 0;
    LOGD("x=%llx y=%llx", x, y);
    asm volatile(
        "MOV X0, #1\n"
        "CBZ X0, mod_y\n"
        "MOV %x[x], #1\n"
        "SUB X0, X0, #1\n"
        "CBZ X0, mod_y\n"
        "MOV %x[x], #3\n"
        "mod_y:\n"
        "MOV %x[y], #2\n"
    :[x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=%llx y=%llx", x, y);

第一次执行 CBZ X0, mod_y ,X0 的值不为 0,因此会继续向下执行到 MOV %x[x], #1,接着 SUB X0, X0, #1 对 X0 寄存器减一操作,X0 的值已经为 0,第二次执行 CBZ X0, mod_y 已经满足分支到 mod_y 标签处,所以直接跳到 mod_y 处执行(跳过了 MOV %x[x], #3)。也就是最终 x 的值为 1,y 的值为 2。

运行结果如下:

2023-05-07 16:29:49.401 22423-22423/com.example.myapplication D/native-armv8a: x=0 y=0
2023-05-07 16:29:49.401 22423-22423/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-07 16:29:49.401 22423-22423/com.example.myapplication D/native-armv8a: x=1 y=2

BNZ_338">2.2 CBNZ

CBNZ(Compare and Branch on Nonzero)指令将寄存器中的值与零进行比较,如果比较不相等,则有条件地分支到 PC 相对偏移处的标签。它提供了这不是子例程调用或返回的提示。该指令不影响条件标志。

在这里插入图片描述

32-bit (sf == 0)

CBNZ <Wt>, <label>

64-bit (sf == 1)

CBNZ <Xt>, <label>

<Wt> 是要测试的通用寄存器的 32 位名称,编码在“Rt”字段中。

<Xt> 是要测试的通用寄存器的 64 位名称,编码在“Rt”字段中。

<label> 是有条件跳转到的程序标签。它与该指令地址的偏移量在 +/-1MB 范围内,编码为“imm19”乘以 4。

下面是使用 CBNZ 指令的例子。修改 CBZ 指令例子中的第一条 CBZ 指令为 CBNZ 即可。

    long long int x = 0;
    long long int y = 0;
    LOGD("x=%llx y=%llx", x, y);
    asm volatile(
        "MOV X0, #1\n"
        "CBNZ X0, mod_y\n"
        "MOV %x[x], #1\n"
        "SUB X0, X0, #1\n"
        "CBZ X0, mod_y\n"
        "MOV %x[x], #3\n"
        "mod_y:\n"
        "MOV %x[y], #2\n"
    :[x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=%llx y=%llx", x, y);

第一次执行 CBNZ X0, mod_y ,X0 的值不为 0,因此直接分支到 mod_y 标签处,所以直接跳到 mod_y 处执行(跳过了 中间的其他指令)。也就是最终 x 的值还为 0,但 y 的值已经被修改为 2。

运行结果如下:

2023-05-07 16:36:26.884 31211-31211/com.example.myapplication D/native-armv8a: x=0 y=0
2023-05-07 16:36:26.884 31211-31211/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-07 16:36:26.884 31211-31211/com.example.myapplication D/native-armv8a: x=0 y=2

BZ_393">2.3 TBZ

TBZ(Test bit and Branch if Zero)指令将测试位的值与零进行比较,如果比较相等,则有条件地分支到 PC 相对偏移处的标签。它提供了这不是子例程调用或返回的提示。该指令不影响条件标志。

在这里插入图片描述

TBZ <R><t>, #<imm>, <label>

<R> 是宽度说明符,在“b5”字段中编码。它可以具有以下值:

Rb5
W0
X1

Bian.html" title=汇编>汇编程序源代码中,始终允许使用“X”说明符,但仅当位数小于 32 时才允许使用“W”说明符。

<t> 为待测通用寄存器的编号 [0-30] 或名称 ZR(31),编码在“Rt”字段中。

<imm> 为要测试的位数,取值范围为 0~63,编码为“b5:b40”。

<label> 是有条件跳转到的程序标签。它与该指令地址的偏移量在 +/-32KB 范围内,被编码为“imm14”乘以 4。

下面是使用 TBZ 指令的例子。

    long long int x = 0;
    long long int y = 0;
    LOGD("x=%llx y=%llx", x, y);
    asm volatile(
        "MOV X0, #0b100\n"
        "TBZ X0, 2, mod_y\n"
        "MOV %x[x], #1\n"
        "TBZ X0, 1, mod_y\n"
        "MOV %x[x], #3\n"
        "mod_y:\n"
        "MOV %x[y], #2\n"
    :[x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=%llx y=%llx", x, y);

执行 MOV X0, #0b100 后,X0 的值为 0b100(也就是从低到高第三位不为 0,从 0 开始计数的话即位的 index 为 2),TBZ X0, 2, mod_y 测试并比较位 index 为 2 处 X0 对应的位,此位为 1,所以不满足 TBZ 分支到 mod_y 处的情况,这会继续执行 MOV %x[x], #1 ,第二次执行 TBZ 指令 TBZ X0, 1, mod\_y,由于满足分支条件,直接跳到 mod_y 处执行(跳过了 MOV %x[x], #3),最终 x 的值为 1,但 y 的值已经被修改为 2。

运行结果如下:

2023-05-07 17:16:43.303 13323-13323/com.example.myapplication D/native-armv8a: x=0 y=0
2023-05-07 17:16:43.303 13323-13323/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-07 17:16:43.303 13323-13323/com.example.myapplication D/native-armv8a: x=1 y=2

BNZ_450">2.4 TBNZ

TBNZ(Test bit and Branch if Nonzero)指令将通用寄存器中的位值与零进行比较,如果比较不相等,则有条件地分支到 PC 相对偏移量处的标签。它提供了这不是子例程调用或返回的提示。该指令不影响条件标志。

在这里插入图片描述

TBNZ <R><t>, #<imm>, <label>

<R> 是宽度说明符,在“b5”字段中编码。它可以具有以下值:

Rb5
W0
X1

Bian.html" title=汇编>汇编程序源代码中,始终允许使用“X”说明符,但仅当位数小于 32 时才允许使用“W”说明符。

<t> 为待测通用寄存器的编号 [0-30] 或名称 ZR(31),编码在“Rt”字段中。

<imm> 为要测试的位数,取值范围为 0~63,编码为“b5:b40”。

<label> 是有条件跳转到的程序标签。它与该指令地址的偏移量在 +/-32KB 范围内,被编码为“imm14”乘以 4。

下面是使用 TBNZ 指令的例子。

    long long int x = 0;
    long long int y = 0;
    LOGD("x=%llx y=%llx", x, y);
    asm volatile(
        "MOV X0, #0b100\n"
        "TBNZ X0, 2, mod_y\n"
        "MOV %x[x], #1\n"
        "mod_y:\n"
        "MOV %x[y], #2\n"
    :[x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=%llx y=%llx", x, y);

执行 MOV X0, #0b100 后,X0 的值为 0b100(也就是从低到高第三位不为 0,从 0 开始计数的话即位的 index 为 2),TBNZ X0, 2, mod_y 测试并比较位 index 为 2 处 X0 对应的位,此位为 1,所以满足 TBNZ 分支到 mod_y 处的情况,这会跳过 MOV %x[x], #1 执行,最终 x 的值还为 0,但 y 的值已经被修改为 2。

运行结果如下:

2023-05-07 17:07:33.926 10241-10241/com.example.myapplication D/native-armv8a: x=0 y=0
2023-05-07 17:07:33.926 10241-10241/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-07 17:07:33.926 10241-10241/com.example.myapplication D/native-armv8a: x=0 y=2

参考资料

1.《ARMv8-A-Programmer-Guide》
2.《Arm® A64 Instruction Set Architecture Armv8, for Armv8-A architecture profile》


http://www.niftyadmin.cn/n/330522.html

相关文章

【K8s】安全认证与DashBoard

文章目录 一、概述1、客户端2、认证、鉴权与准入控制 二、认证管理1、认证方式2、HTTPS证书认证 三、授权管理1、授权与RBAC2、Role 与 ClusterRole3、RoleBinding 与 ClusterRoleBinding4、案例&#xff1a;创建一个只能管理dev空间下Pods资源的账号 四、准入控制五、DashBoar…

MADDPG-学习笔记(1)

文献链接&#xff1a;https://arxiv.org/abs/1706.02275 "Multi-Agent Actor-Critic for Mixed Cooperative-Competitive Environments"&#xff08;作者&#xff1a;Lowe, Ryan等人&#xff0c;2017年&#xff09; 环境搭建&#xff1a;https://zhuanlan.zhihu.co…

【数据库】存储过程

存储过程/函数:procedure/function 通俗说,存储过程是个模块化的程序段,完成一定功能,可被反复调用,可用于解决复杂的问题,减少程序冗余度。 这里的过程和函数就是C语言的方法/函数。过程一般不直接返回值,但可以通过实参和形参返回给主调程序计算结果;函数在SQLSERV…

红黑树封装map和set

文章目录 红黑树封装map和set1. 改良红黑树1.1 改良后的节点1.2 改良后的类分别添加仿函数代码 3. 封装map和set3.1 set3.2 map 3. 迭代器3.1 begin 和 end3.2 operator()和operator--()3.3 const迭代器set的迭代器map的迭代器 4. map的operator[]的重载5. 完整代码实现5.1 RBT…

IPWorks EDI 2022 .NET Edition 22.0.8 Crack

IPWorks EDI基于用于安全 EDI 通信&#xff08;AS2、SFTP、OFTP、RosettaNet、MLLP 等&#xff09;的领先 EDI-INT 协议&#xff0c;IPWorks EDI 库包含促进安全 EDI 消息传递以及 EDI 映射、翻译和验证&#xff08;X12、 EDIFACT、HL7、TRADACOMS、VDA、XML 和 JSON&#xff0…

opencv-python相机标定详解

文章目录 角点检测查看角点标定 opencv中内置了张正友的棋盘格标定法&#xff0c;通过一些姿态各异的棋盘格图像&#xff0c;就能标定相机的内外参数。 角点检测 第一步是角点检测&#xff0c;首先需要读取棋盘格图像 import numpy as np import cv2 import ospath imgs #…

“浅谈模块的加载机制“

模块的加载机制 ​ 模块在第一次加载后会被缓存。这意味着多次调用require()不会导致模块的代码被执行多次。 ​ 内置模块、用户自定义模块、第三方模块,它们都会优先从缓存加载,从而提高模块的加载效率。 1.内置模块的加载机制 ​ 内置模块的加载优先级最高。 例如: 即使…

Kali-linux控制Meterpreter

Meterpreter是Metasploit框架中的一个杀手锏&#xff0c;通常作为利用漏洞后的攻击载荷所使用&#xff0c;攻击载荷在触发漏洞后能够返回给用户一个控制通道。当使用Armitage、MSFCLI或MSFCONSOLE获取到目标系统上的一个Meterpreter连接时&#xff0c;用户必须使用Meterpreter传…