ARM assembly [4]: CONDITIONAL EXECUTION

        We touched upon the subject of conditions briefly when we discussed the Register of Consumer Product Safety Standards. We use conditions to control program flow while a program is running, usually by jumping (branching) or executing certain instructions only if the condition is met. The condition is described as the state of a specific bit in the CPSR register. These bits change from time to time depending on the results of some instructions. For example, when we compare two numbers and they are equal, we trigger the zero bit (Z=1), because the following happens under the hood: a – b = 0. In this case we have the equality condition. If the first number is greater, we will have a greater than condition, in the opposite case - less than. There are more conditions like lower or equal (LE), greater or equal (GE) and more.

  The following table lists the available condition codes, their meanings, and the status of the test flags.

 We can use the code below to investigate a practical use case of our conditional to perform conditional addition.

.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

      The first CMP instruction in the code above triggers setting the negative bit (2–3=-1), indicating that the value in r0 is lower than the number 3. Subsequently, the ADDLT instruction is executed, because when V! =N (the value of the overflow bit and the negative bit in the CPSR are different). Before performing the second CMP, we have r0=3. That's why the second CMP clears the negative bit (since 3–3=0, no need to set the negative flag) and sets the zero flag (Z=1). Now we have V=0 and N=0 which causes the LT condition to fail. As a result, the second ADDLT is not performed, and r0 remains unmodified. The program exits with result 3.

conditional branch

     Branches (aka jumps) allow us to jump to another piece of code. This is useful when we need to skip (or repeat) a block of code or jump to a specific function. The best examples of such use cases are IF and Loops. So let's look at the IF case first.

.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 */

 The code above just checks which initial number is greater and returns that as the exit code. C-like pseudocode looks like this:

int main() {
   int max = 0;
   int a = 2;
   int b = 3;
   if(a < b) {
    max = b;
   }
   else {
    max = a;
   }
   return max;
}

Here is how we use conditional and unconditional branches to create loops.

.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-like pseudocode looks like this:

int main() {
   int a = 0;
   while(a < 4) {
   a= a+1;
   }
   return a;
}

B / BX / BLX

   There are three types of branch instructions:

  • B simply jumps into a function
  • BL saves the address of (PC+4) to LR, and then jumps to the function
  • BX and BLX     

       Same as B/BL+ exchange instruction set (ARM<->Thumb) Requires a register as the first operand: BX/BLX reg

BX/BLX is used to swap the instruction set from ARM to 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

         The trick here is to take the current value of the actual PC, increment it by 1, store the result into a register, and then branch (+exchange) to that register. We see that the addition (add r2, pc, #1) only needs to take the effective pc address (that is, the value of the current pc register +8->0x805C), and then add 1 (0x805C+1=0x805D). Then, if the least significant bit (LSB) of the address we branched to is 1 (in this case, because 0x805D = 10000000 01011101), a swap occurs, which means the address is not 4-byte aligned. Branching to such addresses does not cause any misalignment problems. This is the case with GDB (GEF extension):

Conditional Branches

   Branches can also be executed conditionally and are used to branch to functions if certain conditions are met. Let's look at a very simple example of a conditional branch suing BEQ. This piece of assembly doesn't do much interesting other than move values ​​into registers and branch to another function when the register equals the specified value.

.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

    

 

Guess you like

Origin blog.csdn.net/wanglei_11/article/details/132466441
Recommended