我们在讨论消费品安全标准登记册时已经简要地谈到了条件的主题。我们在程序运行时使用条件来控制程序流,通常是通过跳转(分支)或仅在满足条件时执行某些指令。该条件被描述为CPSR寄存器中特定位的状态。这些比特根据一些指令的结果不时地变化。例如,当我们比较两个数字,结果它们相等时,我们触发零位(Z=1),因为在引擎盖下会发生以下情况:a–b=0。在这种情况下,我们有等式条件。如果第一个数字更大,我们将有一个大于条件,在相反的情况下——小于。还有更多的条件,如更低或相等(LE)、更大或相等(GE)等等。
下表列出了可用的条件代码、它们的含义以及测试标志的状态。
我们可以使用下面的代码来研究我们执行条件加法的条件的实际用例。
.global main
main:
mov r0, #2 /* setting up initial variable */
cmp r0, #3 /* comparing r0 to number 3. Negative bit get's set to 1 */
addlt r0, r0, #1 /* increasing r0 IF it was determined that it is smaller (lower than) number 3 */
cmp r0, #3 /* comparing r0 to number 3 again. Zero bit gets set to 1. Negative bit is set to 0 */
addlt r0, r0, #1 /* increasing r0 IF it was determined that it is smaller (lower than) number 3 */
bx lr
上面代码中的第一条CMP指令触发设置负位(2–3=-1),指示r0中的值低于数字3。随后,执行ADDLT指令,因为当V!=N(CPSR中的溢出位和负位的值不同)。在执行第二个CMP之前,我们的r0=3。这就是为什么第二个CMP清除负位(因为3–3=0,不需要设置负标志)并设置零标志(Z=1)。现在我们有V=0和N=0,这导致LT条件失败。结果,第二ADDLT不被执行,并且r0保持未被修改。程序退出,结果为3。
分支(又名跳转)允许我们跳转到另一个代码段。当我们需要跳过(或重复)代码块或跳转到特定函数时,这很有用。此类用例的最佳示例是IF和Loops。因此,让我们先来看看IF案例。
.global main
main:
mov r1, #2 /* setting up initial variable a */
mov r2, #3 /* setting up initial variable b */
cmp r1, r2 /* comparing variables to determine which is bigger */
blt r1_lower /* jump to r1_lower in case r2 is bigger (N==1) */
mov r0, r1 /* if branching/jumping did not occur, r1 is bigger (or the same) so store r1 into r0 */
b end /* proceed to the end */
r1_lower:
mov r0, r2 /* We ended up here because r1 was smaller than r2, so move r2 into r0 */
b end /* proceed to the 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 /* setting up initial variable a */
loop:
cmp r0, #4 /* checking if a==4 */
beq end /* proceeding to the end if a==4 */
add r0, r0, #1 /* increasing a by 1 if the jump to the end did not occur */
b loop /* repeating the loop */
end:
bx lr /* THE END */
类似C的伪代码如下所示:
int main() {
int a = 0;
while(a < 4) {
a= a+1;
}
return a;
}
有三种类型的分支指令:
与B/BL+交换指令集相同(ARM<->Thumb) 需要一个寄存器作为第一个操作数:BX/BLX reg
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,将结果存储到寄存器中,然后分支(+exchange)到该寄存器。我们看到,加法(加r2,pc,#1)只需取有效的pc地址(即当前pc寄存器的值+8->0x805C),然后加1(0x805C+1=0x805D)。然后,如果我们分支到的地址的最低有效位(LSB)为1(这种情况下,因为0x805D=10000000 01011101),则发生交换,这意味着地址不是4字节对齐的。分支到这样的地址不会导致任何错位问题。这就是GDB(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