在探讨CPSR时我们已经接触了条件状态。我们通过跳转(分支)或者一些只有满足特定条件才执行的指令来控制程序在运行时的执行流。通过CPSR寄存器中的特定bit位来表示条件状态。这些位根据指令每次执行的结果而不断变化。例如,比较运算时如果两个数相等,那么就置CPSR中的Zero位(Z=1),实际上是因为:a - b = 0,这种情况下就是相等状态。如果第一个数大,那么就是大于状态。如果第二个数大,就是小于状态。除此之外,还有小于等于、大于等于等等。
下面的表格列出了可用的条件状态码,描述和标志位:
在下面代码片段中看一下执行条件加法时的实际用法L:
.global main
main:
mov r0, #2 /* 初始化变量 */
cmp r0, #3 /* 将R0中的值与3比较,负数位置1 */
addlt r0, r0, #1 /* 如果上一条比较结果是小于(查看CPSR),则将R0加1 */
cmp r0, #3 /* 将R0中的值再与3比较, 零位置1,同时负数位重置为0 */
addlt r0, r0, #1 /* 如果上一条比较结果是小于(查看CPSR),则将R0加1 */
bx lr
第一条cmp
指令结果导致CPSR
中的负数位置1(2- 3 = -1
)意思是R0
小于R3
。因为满足小于条件(CPSR
中的溢出位不等于负数位V != N
)所以接下来的ADDLT
指令执行。在执行下一条cmp
指令时,R0 = 3
。所以清除负数位(3 - 3 = 0
,负数位清零),零位置位(Z = 1
)。现在溢出位是0,负数位是0,不满足小于条件。所以最后一条ADDLT
指令不执行,R0
值保持3不变。
Thumb模式下的条件执行
我们在介绍指令集的章节讨论了Thumb状态下的不同。具体而言是Thumb-2版本支持条件执行。某些 ARM 处理器版本支持”IT”指令,允许在 Thumb 状态下支持多达4个条件执行指令。参考:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/BABIJDIC.html。
语法:IT{x{y{z}}} cond
- cond 指定 IT 块的第一个指令的条件。
- x 指定 IT 块中第二个指令的条件开关。
- y 指定 IT 块中第三个指令的条件开关。
- z 指定 IT 块中第四个指令的条件开关。
其实IT指令的结构就是“IF-Then-(Else)”,语法都是由字母“T”和“E”构成:
- IT:If-Then(下一条指令是条件的);
- ITT:If-Then-Then(后两条指令是条件的);
- ITE:If-Then-Else(后两条指令是条件的);
- ITTE:If-Then-Then-Else(后三条指令是条件的);
- ITTEE:If-Then-Then-Else-Else(后四条指令是条件的);
IT块中的每条指令必须指定相同或逻辑相反的条件后缀。意思是,如果使用ITE,那么前两个指令必须有相同的后缀,而第三个必须是逻辑相反的后缀。下面是 ARM 参考手册中的一些示例,说明了这些逻辑:
ITTE NE ; 接下来的3条指令都是有条件的。
ANDNE R0, R0, R1 ; ANDNE不更新条件标志。
ADDSNE R2, R2, #1 ; ADDSNE更新条件标志。
MOVEQ R2, R3 ; 有条件的移动
ITE GT ; 接下来的2条指令都是有条件的。
ADDGT R1, R0, #55 ; 条件满足大于时进行相加。
ADDLE R1, R0, #48 ; 条件不满足大于时进行相加。
ITTEE EQ ; 接下来的4条指令都是有条件的。
MOVEQ R0, R1 ; 有条件的MOV
ADDEQ R2, R2, #10 ; 有条件的ADD
ANDNE R3, R3, #1 ; 有条件的AND
BNE.W dloop ; 分支指令只能在IT块的最后一个指令中使用。
错误示例:
IT NE ; 下一条指令是条件的。
ADD R0, R0, R1 ; 语法错误,不是有条件的指令。
下面是条件代码和相反代码:
现在使用以下代码来测试:
.syntax unified @ 非常重要!
.text
.global _start
_start:
.code 32
add r3, pc, #1 @ PC的值加1并存储到R3。
bx r3 @ 跳转到R3中的地址处,并切换运行模式 ->切换到Thumb模式,因为R3最低有效位(LSB) = 1。
.code 16 @ Thumb模式
cmp r0, #10
ite eq @ 如果R0等于10...
addeq r1, #2 @ ... 那么 R1 = R1 + 2
addne r1, #3 @ ... 否则 R1 = R1 + 3
bkpt
.code 32
示例中的代码开始在ARM模式下,第一条指令将PC中的地址值加1并存储到R3
,然后bx指令跳转到R3
中的地址位置,并且模式切换成Thumb
模式,因为R3
中的值最低有效位为1(0不切换)。为此使用bx
(分支+交换)非常重要。
.code 16
在Thumb
模式下,首先比较R0
和10
,结果将负数位N置位(0 - 10 = -10
)。之后使用If-Then-Else
块,因为零位Z(Zero)没有被置位所以ADDEQ
指令被跳过,然后因为结果不相等所以执行ADDNE
指令。
在 GDB
中单步执行此代码会干扰结果,因为你要在 ITE
块中执行这两个指令。 但是,在 GDB
中运行代码而不设置断点并单步执行每个指令将生成正确的结果设置 R1
= 3。
分支
分支(跳转)允许我们跳转到另一个代码段。当你需要跳过(或者重复)某块代码或者跳转到指定的函数的时候,分支很有用。此类情形中最佳的示例是IF和循环。先来看看IF案例。
.global main
main:
mov r1, #2 /* 设置初始变量a */
mov r2, #3 /* 设置初始变量b */
cmp r1, r2 /* 比较两个变量值看哪个更大 */
blt r1_lower /* 因为R2更大(N==1),跳转到r1_lower */
mov r0, r1 /* 如果没有跳转, 例如R1的值更大(或者相等),则将R1的值存储到R0 */
b end /* 结束 */
r1_lower:
mov r0, r2 /* R1小于R2时跳转到此处, 将R2的值存储到R0 */
b end /* 结束 */
end:
bx lr /* THE END */
上面代码是比较两个初始值并返回最大值,C语言伪代码:
int main() {
int max = 0;
int a = 2;
int b = 3;
if(a < b) {
max = b;
}
else {
max = a;
}
return max;
}
现在再看一下怎么使用条件分支实现循环:
.global main
main:
mov r0, #0 /* 设置初始变量a */
loop:
cmp r0, #4 /* 比较a==4 */
beq end /* 如果a==4,结束 */
add r0, r0, #1 /* 否则将R0中的值递增1 */
b loop /* 跳转到loop开始位置 */
end:
bx lr /* THE END */
C语言伪代码:
int main() {
int a = 0;
while(a < 4) {
a= a+1;
}
return a;
}
B、BX、BLX指令
有三种类型的分支指令:
普通分支(B)
简单的跳转到一个函数。
带链接的跳转(BL)
将PC+4的值保存到LR寄存器,然后跳转。
带状态切换的跳转(BX)和带状态切换及链接的跳转(BLX)
与B和BL一致,只是添加了工作状态的切换(ARM模式-Thumb模式)。
需要寄存器作为第一个操作数。
BX、BLX用来切换ARM模式到Thumb模式。
.text
.global _start
_start:
.code 32 @ ARM mode
add r2, pc, #1 @ put PC+1 into R2
bx r2 @ branch + exchange to R2
.code 16 @ Thumb mode
mov r0, #1
这里的技巧是获得当前PC的值,加1然后保存到一个寄存器,然后跳转(并且切换状态模式)到这个寄存器内的地址。可以看到加指令(add r2, pc, #1
)获取到有效的PC地址值(当前PC内的值+8=0x805C
)然后加1(0x805C + 1 = 0x805D
)。接下来,我们跳转的地址( 0x805D = 10000000 01011101
)最低有效位为1,那么意味着地址不是4字节(32bit
)对齐的。跳转到这样的地址不会导致非对齐问题。在GDB
中运行的样子(含GEF
):
注意上面的gif
图片是在低版本的GEF
下创建的,所以你的显示界面可能不一样,但是逻辑是一样的。
条件分支
分支也可以有条件地执行,用于在满足特定条件时跳转到函数。我们看一个使用BEQ
应用条件分支的例子,这是一段没太有用的汇编代码,只不过是在寄存器等于特定值时将一个值移动到寄存器并跳转到另一个函数的过程。
.text
.global _start
_start:
mov r0, #2
mov r1, #2
add r0, r0, r1
cmp r0, #4
beq func1
add r1, #5
b func2
func1:
mov r1, r0
bx lr
func2:
mov r0, r1
bx lr