DEV Community

Seung Woo (Paul) Ji
Seung Woo (Paul) Ji

Posted on • Updated on

Exploring Assembler on the AArch64 Platform

Introduction

In this post, we are going to investigate a simple code snippet, that loops a few times, in AArch64 system.

Original Code

.text
.globl _start

min = 0                          /* starting value for the loop index; note that this is a symbol (constant), not a variable */
max = 30                         /* loop exits when the index hits this number (loop condition is i<max) */

_start:

    mov     x19, min

loop:

    add     x19, x19, 1
    cmp     x19, max
    b.ne    loop

    mov     x0, 0           /* status -> 0 */
    mov     x8, 93          /* exit is syscall #93 */
    svc     0               /* invoke syscall */
Enter fullscreen mode Exit fullscreen mode

The code does not really do anything special but just loop itself for given maximum number of times (max = 30).

Let's improve this code a little bit and make it to print out message for us.

Improved Code - Print Message

.text
.globl _start

min = 0                          /* starting value for the loop index; note that this is a symbol (constant), not a variable */
max = 10                         /* loop exits when the index hits this number (loop condition is i<max) */

_start:

    mov     x19, min

loop:

    mov     x0, 1           /* file descriptor: 1 is stdout */
    adr     x1, msg         /* message location (memory address) */
    mov     x2, len         /* message length (bytes) */

    mov     x8, 64          /* write is syscall #64 */
    svc     0               /* invoke syscall */

    add     x19, x19, 1         /* increment by 1 */
    cmp     x19, max
    b.ne    loop

    mov     x0, 0           /* status -> 0 */
    mov     x8, 93          /* exit is syscall #93 */
    svc     0               /* invoke syscall */

.data
msg:    .ascii      "Loop\n"
len=    . - msg
Enter fullscreen mode Exit fullscreen mode

Result

Loop
Loop
Loop
Loop
Loop
Loop
Loop
Loop
Loop
Loop
Enter fullscreen mode Exit fullscreen mode

This is much better than the original code and prints out something in the console. But, the message is not really meaningful us. Why don't we make it in a way that it prints out the loop number instead?

Improved Code - Print Loop Number

.text
.globl _start

min = 0                          /* starting value for the loop index; note that this is a symbol (constant), not a variable */
max = 10                         /* loop exits when the index hits this number (loop condition is i<max) */

_start:

    mov     x19, min

loop:

// Inserting digit
    add    x18, x19, '0'        /* Create a digit character by adding a ascii value of '0' */
    adr    x17, msg+6           /* Pointer pointing to the pound sign in the msg */
    strb   w18, [x17]           /* Put the digit within the pound sign of the msg */

// Print message
    mov     x0, 1           /* file descriptor: 1 is stdout */
    adr     x1, msg         /* message location (memory address) */
    mov     x2, len         /* message length (bytes) */

    mov     x8, 64          /* write is syscall #64 */
    svc     0               /* invoke syscall */

// Proceed with loop
    add     x19, x19, 1         /* increment by 1 */
    cmp     x19, max
    b.ne    loop

    mov     x0, 0           /* status -> 0 */
    mov     x8, 93          /* exit is syscall #93 */
    svc     0               /* invoke syscall */

.data
msg:    .ascii      "Loop: #\n"
len=    . - msg
Enter fullscreen mode Exit fullscreen mode

Result

Loop: 0
Loop: 1
Loop: 2
Loop: 3
Loop: 4
Loop: 5
Loop: 6
Loop: 7
Loop: 8
Loop: 9
Enter fullscreen mode Exit fullscreen mode

The code finally prints out some meaningful messages to the console. Note that we use strb instruction instead of str because we only want to deal with a single character (1 byte) not a whole 64 bytes. As a result, we need to add w prefix for the register as it is required to use this instruction.

However, the code above only works for one digit number of loops. If the loop number is bigger than 10, the code would start printing out the non-numeric character because the numeric characters are defined between 48 and 57 in ASCII table. For this, we need to add additional lines of codes.

Improved Code - Print Two Digit Loop Number

.text
.globl _start

min = 0                          /* starting value for the loop index; note that this is a symbol (constant), not a variable */
max = 15                         /* loop exits when the index hits this number (loop condition is i<max) */

_start:

    mov     x19, min
    mov     x20, 10

loop:

// Finding the tens digit
    udiv    x21, x19, x20       /* Divide by 10 */
    cmp     x21, 0
    b.eq    oneDigit            /* Skip to inser the tens digit if the quotient is equal to zero */

// Inserting the tens digit
    add     x18, x21, '0'       /* Create a digit character by adding a ascii value of '0' */
    adr     x17, msg+6          /* Pointer pointing to the pound sign in the msg */
    strb    w18, [x17]          /* Put the digit within the pound sign of the msg */

oneDigit:
// Finding the ones digit
    msub    x22, x20, x21, x19  /* Load x22 with the value of r19 - (r20 * r21) */

// Inserting the ones digit
    add     x18, x22, '0'       /* Create a digit character by adding a ascii value of '0' */
    adr     x17, msg+7          /* Pointer pointing to the pound sign in the msg */
    strb    w18, [x17]          /* Put the digit within the pound sign of the msg */

// Print message
    mov     x0, 1           /* file descriptor: 1 is stdout */
    adr     x1, msg         /* message location (memory address) */
    mov     x2, len         /* message length (bytes) */

    mov     x8, 64          /* write is syscall #64 */
    svc     0               /* invoke syscall */

// Proceed with loop
    add     x19, x19, 1         /* increment by 1 */
    cmp     x19, max
    b.ne    loop

    mov     x0, 0           /* status -> 0 */
    mov     x8, 93          /* exit is syscall #93 */
    svc     0               /* invoke syscall */

.data
msg:    .ascii      "Loop:  #\n"
len=    . - msg
Enter fullscreen mode Exit fullscreen mode

Result

Loop:  0
Loop:  1
Loop:  2
Loop:  3
Loop:  4
Loop:  5
Loop:  6
Loop:  7
Loop:  8
Loop:  9
Loop: 10
Loop: 11
Loop: 12
Loop: 13
Loop: 14
Enter fullscreen mode Exit fullscreen mode

Let's walk through the code. First of all, we divide the given r19 value of the loop by 10. We use the quotient to fill out the tens digit. Since udiv instruction only gives the quotient value, we have to utilize another instruction to find the remainder and msub instruction is exactly what we need for it. With given quotient and remainder, we just need to print out the numeric character to the screen but one important step remains. That is, we have to remove the leading zero for the r19 value less than 10. For this, we use another label called oneDigit to skip inserting the tens digit if and only if the tens digit is equal to 0.

Conclusion

In this blog post, we learned how to make a small code snippet to print out the number of loops in the screen. It's interesting to see the way AArch64 assembly works is strikingly similar to the one in 6502 system. In the next post, we will further investigate the same code snippet but with another popular system in the modern days, x86_64.

Top comments (0)