Practical ARM64 (selections and loops)

+Ch0pin🕷️
5 min readAug 16, 2022

So far we went trough the most important instructions of the AArch64 instruction set and it is time to move to something more practical. In these series of posts we are going to talk about structured programming in arm64. For better understanding, we are going to use C statements and try to “translate” them to their arm64 corresponding ones.

Selections

  • Simple if — then C statements, can be easily implemented in ARM by combining compare and branch instructions. The following examples are pretty straightforward:

Similarly, an if x ≥ 10 statement can be written as follows:

  • An if-then-else C statement can be achieved by adding an additional branch instruction in order to jump to the else branch, in case the if fails:

As being said, the compare and branch instruction can be combined to effectively simulate any if-then-else statement:

Let’s see an example:

ARM to C equivalent

Few things about the example above:

  • Starting in line 15, we load the nums address to the x0 register.
  • In line 16, the integers 2,1 will be stored in w1 and w2 respectively and x0 will be advanced by 8 bytes in order to point to the next integer 3. At this point w1=2, w2=1, w3 = 3.
  • The statement in line 20 compares w1 < w2 and if it succeeds it redirects the execution to the elseif label. If it fails, which means that w1≥ w2, the statement at line 22 will be executed and w1 will be compared with w2.
  • The statement in line 23 completes the if(a≥b && a≥ c) then max = a, since the csel statement will maintain the value of w1 if w1 is greater or equal to w3, or it will set w1 = w3 if w3 is grater than w1.
  • Line 24 uses a branch statement to jump to the else label and prepare the call to printf after storing the address of the first parameter “%d\n” to x0 and the second parameter (the max value) to x1(remember R0 to R7 store argument values passed to and results returned from a subroutine).
  • Line 26, 27 covers the if(b≥c) then max = b while
  • Arriving at line 29 we make sure that the max number is stored in x1, thus the call to printf will yield the number 3.

You can compile the above with the following oneliner:

Although that we are going to discus about the prologue and epilogue of a function in a dedicated post, line 14 saves the frame pointer and link register (which stores the return address to the _start function) to the stack, while line 31 restores these values, thus the execution returns to the calling function.

Loops

Depending upon the position of a control statement in a program, loop statements in ARM are classified into two types:

  • pre-test (for and while)
  • post-test (do-while )

Let’s see some examples:

Example 1: Reverse String

Reverse string, pre-test loop

Few things to notice about the above: in lines 17–18 we call the strlen which will return the length of the string inserted by the user to the w0 register. Assume, for example that the user entered “example” as an input, then w0 will be equal to 7. As we want to print the last char which is equal to inpt[6] we subtract #1 from w0 and we store the result to the stack (line 19) and subsequently we store this value back to w1 (this is because we need w0 to be available for our call to putchar in line 27).

The startloop label literally implements our while loop: the comparison in line 23 will test the value of w1 and will end the loop (line 23), if w1 is les than 0. The body of the loop is implemented in lines 24–29. More specifically we store the address of inpt to x2 register and load the value of x2+x1 to x0. As x1 is subtracted in each loop, x0 will store the values [x2+6], [x2+5],…,[x2+0], in each loop, thus the putchar will print the values “e”, “l”, “p”,…,“e” respectively.

Compile and run the program with

$as revstr.s -o revstr.o && ld revstr.o -o revstr -lc

Example 2: Print decimal to binary

In this program we are going to ask the user to enter an unsigned integer value of which we are going to print its binary form by performing short division by two with remainder. Let decimal number is N then divide this number from 2 because base of binary number system is 2. Note down the value of remainder, which will be either 0 or 1. Again divide remaining decimal number till it became 0 and note every remainder of every step. Then write remainders from bottom to up (or in reverse order), which will be equivalent binary number of given decimal number.

Compile with $as printbin.s -o printbin.o && ld printbin.o -o printbin -lc

--

--