目录
1 比较与测试指令详解
1.1 CMP指令
1.1.1 概述
1.1.2 指令编码分析
1.2 CMN指令
1.2.1 概述
1.2.2 指令编码分析
1.3 TST指令
1.3.1 概述
1.3.2 指令编码分析
1.4 比较与测试指令实验
1.4.1 实验要求
1.4.2 实验代码
1.4.3 调试分析
2 条件执行指令综述
2.1 A64指令集中的条件执行
2.2 A64指令集中会设置标志位的指令
2.2.1 算术运算指令
2.2.2 逻辑运算指令
2.2.3 比较与测试指令
2.3 条件操作后缀
2.4 AArch64条件执行指令分类
2.4.1 带符号位的算术运算
2.4.2 条件选择指令(Conditional select)
2.4.3 条件比较指令(Conditional comparison)
2.4.4 条件跳转指令(Conditonal branch)
3 条件选择指令详解
3.1 CSEL指令编码分析
3.2 CSEL指令编码验证
3.3 CSINC指令编码分析
3.4 CSINC指令编码验证
3.5 CSINV指令编码分析
3.6 CSINV指令编码验证
3.7 CSNEG指令编码分析
3.8 CSNEG指令编码验证
3.9 条件选择指令实验
3.9.1 实验代码
3.9.2 调试分析
3.10 条件选择指令使用场景实例
4 条件选择衍生指令详解
4.1 CSET指令编码分析
4.2 CSET指令编码验证
4.3 CSETM指令编码分析
4.4 CSETM指令编码验证
4.5 CINC指令编码分析
4.6 CINC指令编码验证
4.7 CINV指令编码分析
4.8 CINV指令编码验证
4.9 CNEG指令编码分析
4.10 CNEG指令编码验证
4.11 条件选择衍生指令实验
4.11.1 实验代码
4.11.2 调试分析
5 条件比较指令
5.1 CCMP指令
5.1.1 CCMP(immediate)指令编码分析
5.1.2 CCMP(immediate)指令编码验证
5.1.3 CCMP(register)指令编码分析
5.1.4 CCMP(register)指令编码验证
5.2 CCMN指令
5.2.1 概述
5.2.2 指令编码分析
5.3 条件比较指令实验
5.3.1 实验代码
5.3.2 调试分析
6 基本跳转指令详解
6.1 相对跳转指令
6.1.1 B指令编码分析
6.1.2 B指令编码验证
6.1.3 BL指令编码分析
6.1.4 BL指令编码验证
6.2 绝对跳转指令
6.2.1 BR指令编码分析
6.2.2 BR指令编码验证
6.2.3 BLR指令编码分析
6.2.4 BLR指令编码验证
6.3 跳转返回指令
6.3.1 RET指令编码分析
6.3.2 RET指令编码验证
6.3.3 ERET指令概述
7 条件跳转指令详解
7.1 B.Cond指令编码分析
7.2 B.Cond指令编码验证
7.3 CBZ指令编码分析
7.4 CBZ指令编码验证
7.5 CBNZ指令概述
7.6 TBZ指令编码分析
7.7 TBZ指令编码验证
7.8 TBNZ指令概述
1. CMP(Compare)指令通过SUBS指令实现,与SUBS指令的差别在于,CMP指令仅根据减法计算的结果设置标志位,但是不保存减法运算结果
2. 根据不同的第二操作数构成方法,CMP指令有extended register / immediate / shifted register三种编码方式,与SUBS指令是编码方式是对应的
1.1.2.1 CMP(extended register)
1. CMP(extended register)指令将SUBS(extended register)指令中的目的操作数寄存器指定为XZR,从而丢弃减法运算的结果。计算过程可表示为如下形式,
// 计算值并设置标志位
Rn - LSL(extend(Rm), amount)
2. CMP(extended register)指令可以将SP寄存器作为第一源操作数
1.1.2.2 CMP(immediate)
1. CMP(immediate)指令将SUBS(immediate)指令中的目的操作数寄存器指定为XZR,从而丢弃减法运算的结果。计算过程可表示为如下形式,
// 计算值并设置标志位
Rn - shift(imm)
2. CMP(immediate)指令也可以将SP寄存器作为第一源操作数
1.1.2.3 CMP(shifted register)
CMP(shifted register)指令将SUBS(shifted register)指令中的目的操作数寄存器指定为XZR,从而丢弃减法运算的结果。计算过程可表示为如下形式,
// 计算值并设置标志位
Rn - shift(Rm,amount)
说明:本文中介绍的指令很多是基于基础的算术和移位指令实现的(e.g. 此处的SUBS指令),关于这些基础指令的详细内容,可参考ARMv8体系结构基础04:算术和移位指令
1. CMN(Compare Negative)指令实现负数比较,也就是将第一源操作数减去第二源操作数的负值(相当于加上第二源操作数)并根据计算结果设置标志位
2. CMN指令通过ADDS指令实现,与ADDS指令的差别在于,CMN指令仅根据加法计算的结果设置标志位,但是不保存加法运算结果
3. 根据不同的第二操作数构成方法,CMN指令有extended register / immediate / shifted register三种编码方式,与ADDS指令是编码方式是对应的
1.2.2.1 CMN(extended register)
1. CMN(extended register)指令将ADDS(extended register)指令中的目的操作数寄存器指定为XZR,从而丢弃加法运算的结果。计算过程可表示为如下形式,
// 计算值并设置标志位
Rn + LSL(extend(Rm), amount)
2. CMN(extended register)指令可以将SP寄存器作为第一源操作数
1.2.2.2 CMN(immediate)
1. CMN(immediate)指令将ADDS(immediate)指令中的目的操作数寄存器指定为XZR,从而丢弃加法运算的结果。计算过程可表示为如下形式,
// 计算值并设置标志位
Rn + shift(imm)
2. CMN(immediate)指令也可以将SP寄存器作为第一源操作数
1.2.2.3 CMN(shifted register)
CMN(shifted register)指令将ADDS(shifted register)指令中的目的操作数寄存器指定为XZR,从而丢弃加法运算的结果。计算过程可表示为如下形式,
// 计算值并设置标志位
Rn + shift(Rm, amount)
1. TST(Test)指令通过ANDS指令实现,与ANDS指令的差别在于,TST指令仅根据减法计算的结果设置标志位,但是不保存逻辑与运算结果
2. 根据不同的第二操作数构成方法,TST指令有immediate / shifted register两种种编码方式,与ANDS指令是编码方式是对应的
1.3.2.1 TST(immediate)
TST(immediate)指令将ANDS(immediate)指令中的目的操作数寄存器指定为XZR,从而丢弃逻辑与运算的结果。计算过程可表示为如下形式,
// 计算值并设置标志位
Rn AND imm
1.3.2.2 TST(shifted register)
TST(shifted register)指令将ANDS(shifted register)指令中的目的操作数寄存器指定为XZR,从而丢弃逻辑与运算的结果。计算过程可表示为如下形式,
// 计算值并设置标志位
Rn AND shift(Rm, amount)
使用汇编语言实现如下C语言函数,
unsigned long compare_and_return(unsigned long a, unsigned long b)
{
if (a >= b)
return 0;
else // a < b
return 0xffffffffffffffff;
}
1. 根据AArch64的函数传参规则,第一个参数使用x0传递,第二个参数使用x1传递
2. 首先使用cmp指令对两个参数进行比较,并根据计算结果设置标志位,其中,
① 当x0 < x1时,会发生算术上的借位;但是条件标志位上没有溢出,C = 0
② 当x0 >= x1时,不会发生算术上的借位;但是条件标志位溢出,C = 1
3. 在使用sbc指令携带C标志位进行计算时,
① 当x0 < x1时,x0 = 0 - 0 - 1 + C = -1
② 当x0 >= x1时,x0 = 0 - 0 - 1 + C = 0
1. 首先以参数10和9调用compare_and_return函数,即
compare_and_return(10, 9);
① sbc指令调用前
② sbc指令调用后,
2. 第二次以参数9和10调用compare_and_return函数,即
compare_and_return(9, 10);
① sbc指令调用前
② sbc指令调用后
说明:Linux内核中的array_index_mask_nospec函数(arch/arm64/include/asm/barrier.h)函数就是使用上述指令实现的,只不过使用的是内嵌汇编的语法
1. 在ARMv7体系结构中,绝大部分指令都可以条件执行。但是在A64指令集中,绝大多数指令都不支持条件执行
这是因为将执行条件编码到指令中需要占用4 bit空间,但是在现代处理器中的收益却不大
2. 在AArch64中有一部分指令可以依据条件标志位工作,他们将条件标志位作为指令输入的一部分参与运算
通过这些指令,可以替换原先ARMv7体系结构中对条件执行指令的使用
3. 上文介绍的比较和测试指令,则是条件执行指令的基础。一般都是先调用比较与测试指令设置标志位,之后条件执行指令根据标志位执行
1. ADDS(Add,Setting flags)
2. ADCS(Add with Carry,Setting flags)
3. SUBS(Subtract,Setting flags)
4. SBCS(Subtract with Carry,Setting flags)
5. SUBPS(Subtract Pointer,Setting flags,ARMv8.5引入)
6. NEGS(Negate,Setting flags,通过SUBS指令实现)
7. NEGCS(Negate with Carry,Setting flags,通过SBCS指令实现)
1. ANDS(Bitwise AND,Setting flags)
2. BICS(Bitwise Bit Clear,Setting flags)
1. CMP(Compare,通过SUBS指令实现)
2. CMN(Compare Negative,通过ADDS指令实现)
3. CMPP(Compare with Tag,ARMv8.5引入,通过SUBPS指令实现)
4. TST(Test bit,通过ANDS指令实现)
要进行条件执行,就需要在指令中包含条件操作后缀。根据不同的标志位,A64指令集中共支持如下18种条件操作后缀(注意其中有编码相同的后缀)
可见实际的条件操作编码共16种,编码到指令中需要4 bit空间
说明1:条件操作后缀说明
条件后缀 |
含义 |
标志位 |
一般场景 |
EQ:Equal |
相等 |
Z = 1 |
减法运算结果为0 |
NE:Not Equal |
不相等 |
Z = 0 |
减法运算结果不为0 |
CS:Carry Set HS:Higher or Same |
无符号数大于或等于 |
C = 1 |
减法运算,判断无符号数大小关系 本质是C位置位,在减法运算中,当没有发生算术上的借位时,C = 1 |
CC:Carry Clear LO:Lower |
无符号数小于 |
C = 0 |
减法运算,判断无符号数大小关系 本质是C位清零,在减法运算中,当发生算术上的借位时,C = 0 |
MI:Minus |
负数 |
N = 1 |
以有符号数角度判断计算结果是否为负数 |
PL:Positive or zero |
整数或零 |
N = 0 |
以有符号数角度判断计算结果是否为0或正数 |
VS:Overflow Set |
有符号溢出 |
V = 1 |
以有符号数角度判断计算结果是否溢出 |
VC:Overflow Clear |
有符号未溢出 |
V = 0 |
以有符号数角度判断计算结果是否溢出 |
HI:Higher |
无符号数大于 |
(C = 1) && (Z = 0) |
减法运算,判断无符号数大小关系 C = 1表示没有发生算术上的借位(大于等于) Z = 0表示减法运算结果不为0(不等于) |
LS:Lower or Same |
无符号数小于或等于 |
(C = 0) || (Z = 1) |
减法运算,判断无符号数大小关系 C = 0表示发生发生算术上的借位(小于) Z = 1表示减法运算结果为0(等于) |
GE:Greater or Equal |
有符号数大于或等于 |
N = V |
减法运算,判断有符号数大小关系 |
LT:Little |
有符号数小于 |
N != V |
减法运算,判断有符号数大小关系 |
GT:Greater |
有符号数大于 |
(Z = 0) && (N = V) |
减法运算,判断有符号数大小关系 |
LE:Litter or Equal |
有符号数小于或等于 |
(Z = 1) || (N != V) |
减法运算,判断有符号数大小关系 |
AL:Always execute |
无条件执行 |
NA |
NA |
NV:Always execute |
无条件执行 |
NA |
NA |
说明2:有符号数大小关系判断分析
有符号数的大小判断主要依据标志位中的N位和V位,列表分析如下,这里理解的关键是,N位只是在逻辑上表示计算结果的正负,并不代表实际结果的正负,因为运算可能溢出
N |
V |
说明 |
0 |
0 |
N = 0:逻辑上计算结果为正数或者0 V = 0:没有发生溢出 所以实际结果与逻辑结果一致,计算结果为正数或者0,即有符号数大于或等于 |
1 |
1 |
N = 1:逻辑上计算结果为负数 V = 0:发生溢出 所以实际结果与逻辑结果相反,计算结果为正数或者0,即有符号数大于或等于 |
0 |
1 |
N = 0:逻辑上计算结果为正数或者0 V = 1:发生溢出 所以实际结果与逻辑结果相反,计算结果为负数,即有符号数小于 |
1 |
0 |
N = 1:逻辑上计算结果为负数 V = 0:没有发生溢出 所以实际结果与逻辑结果一致,计算结果为负数,即有符号数小于 |
包括ADDS和SUBS指令,这里的条件执行,是指他们会将条件标志位作为指令输入的一部分参与运算
1. 条件选择指令根据当前条件标志位的值,在第一源操作数寄存器和第二源操作数寄存器之间进行选择,
① 如果条件为true,则将第一源操作数寄存器中的值拷贝到目的寄存器
② 如果条件为false,则将第二源操作数寄存器中的值拷贝到目的寄存器
2. 对条件选择指令可以进行扩展,可以先对第二源操作数寄存器中的值进行修改,之后再写入目的寄存器
可进行的修改包括按位取反、取负值、值加1
3. 对条件选择指令还可以进行衍生,利用上面的条件选择指令可以实现条件设置、条件取反等指令
4. A64指令集中的条件选择指令及其衍生指令如下,
说明:条件选择指令有浮点数版本,即FCSEL指令
1. 条件比较指令根据当前条件标志位的值进行比较,
① 如果条件为true,则比较两个操作数,并根据比较结果设置标志位
② 如果条件为false,则使用指令中给出的立即数设置标志位
2. A64指令集中的条件比较指令如下,
说明:条件比较指令有浮点数版本,即FCCMP指令
1. 条件跳转根据当前条件标志位的值或者通过与指令中通用寄存器值的比较结果来修改执行流,也就是进行跳转
2. A64指令集中的条件跳转指令如下,
1. CSEL(Conditioanl Select)指令根据当前标志位的值判断指令中的
① 如果成立,则将第一源操作数寄存器的值拷贝到目的寄存器
② 否则,则将第二源操作数寄存器的值拷贝到目的寄存器
计算过程可表示为如下形式,
Rd = if cond then Rn else Rm
2. 指令中的
编译如下指令,
csel x0, x1, x2, ne
对应机器码如下,
CSINC(Conditioanl Select Increment)指令根据当前标志位的值判断指令中的
① 如果成立,则将第一源操作数寄存器的值拷贝到目的寄存器
② 否则,则将第二源操作数寄存器的值加1后拷贝到目的寄存器
计算过程可表示为如下形式,
Rd = if cond then Rn else (Rm + 1)
编译如下指令,
csinc x0, x1, x2, hs
对应机器码如下,
说明:在汇编中使用的条件码标识符为hs,反汇编后的标识符为cs,可见指令集本身更倾向于使用cs标识符
CSINV(Conditioanl Select Invert)指令根据当前标志位的值判断指令中的
① 如果成立,则将第一源操作数寄存器的值拷贝到目的寄存器
② 否则,则将第二源操作数寄存器的值按位取反后拷贝到目的寄存器
计算过程可表示为如下形式,
Rd = if cond then Rn else NOT (Rm)
编译如下指令,
csinv x0, x1, x2, gt
对应机器码如下,
CSEL(Conditioanl Select Negation)指令根据当前标志位的值判断指令中的
① 如果成立,则将第一源操作数寄存器的值拷贝到目的寄存器
② 否则,则将第二源操作数寄存器的值的负数后拷贝到目的寄存器
计算过程可表示为如下形式,
Rd = if cond then Rn else -Rm
编译如下指令,
csneg x0, x1, x2, lt
对应机器码如下,
首先通过cmp指令比较寄存器x0和x1的值,结果肯定是不相等的,之后验证条件选择指令对第二源操作数的处理
1. csel指令执行后,第二源操作数寄存器x1的值被拷贝到目的寄存器x2
2. csinc指令执行后,将第二源操作数寄存器x1的值加1后再拷贝到目的寄存器x2
3. csinv指令执行后,将第二源操作数寄存器x1的值按位取反后再拷贝到目的寄存器x2
4. csneg指令执行后,将第二源操作数寄存器x1值的负数拷贝到目的寄存器x2,此处就是-1的补码
A64指令集中加入条件选择指令,为减少使用ARMv7体系结构的条件执行指令提供了方案。假设要实现如下C代码,
if (i == 0)
r = r + 2;
else
r = r - 1;
使用条件选择指令实现如下,
cmp x0, #0 // if (i == 0)
sub x2, x1, #1 // r = r - 1
add x1, x1, #2 // r = r + 2
csel x1, x1, x2, eq // select between the two result
1. CSET(Conditional Set)指令通过CSINC指令实现,只是将2个源操作数寄存器均设置为XZR
2. CSET指令根据当前标志位的值判断指令中的
① 如果成立,则将目的寄存器值置为1
② 否则,则将目的寄存器值设置为0
计算过程可表示为如下形式,
Rd = if cond then 1 else 0
3. 指令中的
① 条件码不能是AL和NV,也就是对应的编码值不能是0b1110和0b1111
② 编码条件码时,LSB要取反
说明:关于条件编码LSB取反
① 在编码时将条件码的LSB取反
② 在指令的对应关系中,CSET指令条件码为
这里其实表示的是对条件取反,也就是CSET指令中的
③ 因此,当CSET指令中的
当CSET指令中的
④ 在编码上的LSB取反要求,就是实现上面的语义。因为将条件码的LSB取反后,对应的就是取反的条件码
由此也可以看出为什么CSET指令中的条件码不能是AL和NV,因为这两个条件码的编码只有LSB为取反关系,但是表达的含义却是一样的
编译如下指令,
cset x0, eq // eq条件码的编码值为0b0000
对应机器码如下,
说明:合法的条件码
编译如下指令,会报出条件码非法错误
cset x0, al
1. CSETM(Conditional Set Mask)指令通过CSINV指令实现,只是将2个源操作数寄存器均设置为XZR
2. CSETM指令根据当前标志位的值判断指令中的
① 如果成立,则将目的寄存器值所有位置位1
② 否则,则将目的寄存器值设为0
计算过程可表示为如下形式,
Rd = if cond then -1 else 0
3. 指令中的
① 当CSETM指令中的
② 当CSETM指令中的
编译如下指令,
csetm x0, eq // eq条件码的编码值为0b0000
对应机器码如下,
1. CINC(Conditional Increment)指令通过CSINC指令实现,只是将2个源操作数寄存器均设置为CINC指令中的源操作数寄存器
2. CINC指令根据当前标志位的值判断指令中的
① 如果成立,则将源操作数寄存器的值加1后拷贝到目的寄存器
② 否则,则将源操作数寄存器的值拷贝到目的寄存器
计算过程可表示为如下形式,
Rd = if cond then Rn+1 else Rn
3. 指令中的
① 当CINC指令中的
② 当CINC指令中的
编译如下指令,
cinc x0, x1, eq // eq条件码的编码值为0b0000
对应机器码如下,
1. CINV(Conditional Invert)指令通过CSINV指令实现,只是将2个源操作数寄存器均设置为CINV指令中的源操作数寄存器
2. CINV指令根据当前标志位的值判断指令中的
① 如果成立,则将源操作数寄存器的值按位取反后拷贝到目的寄存器
② 否则,则将源操作数寄存器的值拷贝到目的寄存器
计算过程可表示为如下形式,
Rd = if cond then NOT(Rn) else Rn
3. 指令中的
① 当CINV指令中的
② 当CINV指令中的
编译如下指令,
cinv x0, x1, eq // eq条件码的编码值为0b0000
对应机器码如下,
1. CNEG(Conditional Negate)指令通过CSNEG指令实现,只是将2个源操作数寄存器均设置为CNEG指令中的源操作数寄存器
2. CNEG指令根据当前标志位的值判断指令中的
① 如果成立,则将源操作数寄存器值的负数拷贝到目的寄存器
② 否则,则将源操作数寄存器的值拷贝到目的寄存器
计算过程可表示为如下形式,
Rd = if cond then -Rn else Rn
3. 指令中的
① 当CNEG指令中的
② 当CNEG指令中的
编译如下指令,
cneg x0, x1, eq // eq条件码的编码值为0b0000
对应机器码如下,
首先通过cmp指令比较寄存器x0和x1的值,结果肯定是相等的,之后验证条件衍生选择指令对源操作数的处理
1. cset指令执行后,由于条件成立,目的寄存器x0的值被置为1
2. csetm指令执行后,由于条件成立,目的寄存器x0的值被置为全F
3. cinc指令执行后,将源操作数寄存器x1的值加1后再拷贝到目的寄存器x0
4. cinv指令执行后,将源操作数寄存器x1的值按位取反后再拷贝到目的寄存器x0
5. cneg指令执行后,将源操作数寄存器x1值的负数拷贝到目的寄存器x0
1. CCMP(immediate)指令根据当前标志位的值判断指令中的
① 如果成立,则对2个源操作数进行CMP比较运算,并根据运算结果设置标志位
② 否则,使用指令中的#
计算过程可表示为如下形式,
flags = if cond then compare(Rn, #imm) else #nzcv
2. 指令中的
3. 指令中的#
4. 指令中的#
编译如下指令,
ccmp x0, #31, #0b1111, eq // eq条件码的编码值为0b0000
对应机器码如下,
说明:合法的立即数
编译如下指令,会报出立即数越界错误
ccmp x0, #32, #0b1111, eq // eq条件码的编码值为0b0000
同样地,对于#
ccmp x0, #31, #16, eq // eq条件码的编码值为0b0000
CCMP(register)指令相较于CCMP(immediate)指令,将第二操作数从立即数替换为寄存器,计算过程可表示为如下形式,
flags = if cond then compare(Rn, Rm)
编译如下指令,
ccmp x0, x1, #0b1111, eq // eq条件码的编码值为0b0000
对应机器码如下,
CCMN指令的编码方式和CCMP类似,只是将比较方法从CMP替换为CMN。根据不同的第二操作数构成方式,CCMN指令也有immediate / register两种编码方式,与CCMP指令的编码方式是对应的
5.2.2.1 CCMN(immediate)
CCMN(immediate)指令根据当前标志位的值判断指令中的
① 如果成立,则对2个源操作数进行CMN比较运算,并根据运算结果设置标志位
② 否则,使用指令中的#
计算过程可表示为如下形式,
flags = if cond then compare(Rn, #-imm) else #nzcv
5.2.2.2 CCMN(register)
CCMN(register)指令相较于CCMN(immediate)指令,将第二操作数从立即数替换为寄存器,计算过程可表示为如下形式,
flags = if cond then compare(Rn, -Rm) else #nzcv
第1组代码验证在条件成立的情况下条件比较指令的行为
第2组代码验证在条件不成立的情况下条件比较指令的行为
1. 第1组代码执行后,由于条件满足,实际进行的是0和10的CMP运算,所以标志位中只有N位被置1
2. 第2组代码执行后,由于条件不满足,所以使用指令中的0b1111设置标志位
1. B(Branch)指令执行的是无条件的相对跳转,且跳转后不返回(因为没有保存返回地址)
2. B指令跳转的目标为程序中的标号
3. B指令的跳转范围为±128MB,以偏移量除以4的方式被编码在imm26字段
4. B指令提示CPU的分支预测逻辑这不是一个函数调用
说明:imm26可表示的数值范围为64MB(2^26),但是由于A64指令集中每条指令长度为4B,因此偏移量编码时以4B为单位,所以可以表示(64 * 4 = 256MB)的地址范围
其实取指时对PC寄存器有对齐检查,要求4B对齐,即取指地址的bit [1:0]均为0,非对齐取指会产生异常
6.1.2.1 label在指令之前
编译如下代码,
label:
mov x0, x0
b label
对应机器码如下,可见跳转的目的地址为(PC - 4),越过mov指令
6.1.2.2 label在指令之后
编译如下代码,
b label
label:
mov x0, x0
对应机器码如下,可见跳转的目的地址为(PC + 4),越过b指令
1. BL(Branch with Link)指令的跳转及偏移量编码方式和B指令是相同的,差别在于BL指令在跳转时会将跳转的返回地址,也就是PC + 4,设置到X30寄存器,从而可实现跳转返回
2. BL指令提示CPU的分支预测逻辑这是一个函数调用
说明:BL指令在跳转时,将返回地址保存在X30寄存器中,这个寄存器也被称作Link Regiser(LR)
在ARMv7体系结构中,是将r14寄存器作为Link Register使用,而且r14寄存器是banked register,下面附上ARMv7体系结构的寄存器
编译如下代码,
label:
mov x0, x0
bl label
对应机器码如下,可见跳转的目的地址为(PC - 4),越过mov指令
说明:非叶子函数对函数返回地址X30的保存与恢复
① 由于BL指令都是将函数返回地址写入X30寄存器,因此,对于非叶子函数,在再次进行函数调用之前需要保存本级函数返回地址,也就是当前X30寄存器的值。否则将导致X30寄存器中的值被覆盖,从而使得本级函数无法返回
② 在AArch64中,一般通过栈来保存和恢复X30寄存器。编译如下C程序,
C
int func2(int a)
{
return a + 1;
}
// 非叶子函数
int func1(int a)
{
return func2(a) + 1;
}
int main(void)
{
func1(1);
return 0;
}
对应的汇编程序如下,
1. func1是非叶子函数,在其中还要调用func2函数。因此在进入func1函数后,将X29(FP)和X30(LR)压栈保存
2. func2是叶子函数,只要在函数中不修改X30的值,就可以不压栈保存
1. BR(Branch to Register)指令执行的是无条件的绝对跳转,且跳转后不返回(因为没有保存返回地址)
2. BR指令跳转的目标地址存储在
3. BR指令提示CPU的分支预测逻辑这不是一个函数调用
编译如下代码,
br x1
对应机器码如下,
1. BLR(Branch with Link to Register)指令的跳转及目的地址的编码方式和BR指令是相同的,差别在于BLR指令在跳转时会将跳转的返回地址,也就是PC + 4,设置到X30寄存器,从而可实现跳转返回
2. BLR指令提示CPU的分支预测逻辑这是一个函数调用
编译如下代码,
blr x1
对应机器码如下,
1. RET(Return)指令用于进行跳转返回,返回地址存储在
2. 如果省略命令中的
3. RET指令提示CPU的分支预测逻辑这是一个函数返回
编译如下代码,
ret
对应机器码如下,
1. 与RTE指令相关,还有一个ERET(Exception Return)指令,该指令用于实现异常返回
2. 当发生异常时,PE会将当前的PSTATE保存到target exception level的SPSR_ELn寄存器中,将异常返回地址保存到target exception level的ELR_ELn寄存器中
当调用ERET指令进行异常返回时,PE会使用SPSR_ELn恢复PSTATE,并使用ELR_ELn恢复PC
3. ERET指令可以实现PE异常等级的切换,比如从EL1切换到EL0,这里的原理就是构造异常返回的场景,详情见后续异常处理相关笔记
1. B.Cond(Branch Conditionally)指令执行的是有条件的相对跳转,且跳转后不返回(因为没有保存返回地址)
2. B.Cond指令跳转的目标为程序中的标号
3. B.Cond指令的跳转范围为±1MB,以偏移量除以4的方式被编码在imm19字段
4. B.Cond指令提示CPU的分支预测逻辑这不是一个函数调用
说明:imm19可表示的数值范围为512KB(2^19),但是由于A64指令集中每条指令长度为4B,因此偏移量编码时以4B为单位,所以可以表示(512KB * 4 = 2MB)的地址范围
编译如下代码,
label:
mov x0, x0
b.cc label // cc = carry clear,无符号数小于
对应机器码如下,
1. CBZ(Compare and Branch on Zero)指令执行的是有条件的相对跳转,且跳转后不返回(因为没有保存返回地址)
2. CBZ指令的跳转条件,是将指令中的
3. CBZ指令跳转的目标为程序中的标号
4. CBZ指令的跳转范围为±1MB,以偏移量除以4的方式被编码在imm19字段
5. CBZ指令提示CPU的分支预测逻辑这不是一个函数调用
6. CBZ指令虽然将
说明:imm19可表示的数值范围为512KB(2^19),但是由于A64指令集中每条指令长度为4B,因此偏移量编码时以4B为单位,所以可以表示(512KB * 4 = 2MB)的地址范围
编译如下代码,
label:
mov x0, x0
cbz x1, label
对应机器码如下,
CBNZ(Compare and Branch on Nozero)指令与CBZ指令编码方式类似,差别在于CBNZ指令是在
1. TBZ(Test bit and Branch if Zero)指令执行的是有条件的相对跳转,且跳转后不返回(因为没有保存返回地址)
2. TBZ指令的跳转条件,是将指令中的
3. 参与比较的
①
②
二者结合,可指定的寄存器为0 ~ 30的通用寄存器,以及零寄存器(XZR / WZR)
4. 指令中的#
① 如果参与比较的寄存器是32位(Wn),则b5为0,那么参与TST运算的立即数就是[0:b40],取值范围为0 ~ 31
② ① 如果参与比较的寄存器是64位(Xn),则b5为1,那么参与TST运算的立即数就是[1:b40],取值范围为32 ~ 63
5. TBZ指令跳转的目标为程序中的标号
6. TBZ指令的跳转范围为±32KB,以偏移量除以4的方式被编码在imm14字段
7. TBZ指令提示CPU的分支预测逻辑这不是一个函数调用
8. TBZ指令虽然将寄存器与#
说明:imm14可表示的数值范围为16KB(2^14),但是由于A64指令集中每条指令长度为4B,因此偏移量编码时以4B为单位,所以可以表示(16KB * 4 = 64KB)的地址范围
编译如下代码,
label:
mov x0, x0
tbz x0, #0b1100, label
对应机器码如下,可见指令中x0被编译为了w0,这是因为参与比较的立即数编码b5为0,此时只能使用32位寄存器Wn
如果修改上面的指令,将参与TST运算的立即数的最高位设置为1,则参与TST运算的就是64位寄存器X0
label:
mov x0, x0
tbz x0, #0b101100, label
由此可见,TBZ指令对于32位寄存器Wn只能测试低32位,对于64位寄存器Xn只能测试高32位
TBNZ(Test bit and Branch if Nozero)指令与TBZ指令编码方式类似,差别在于TBNZ指令是在