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 */
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
Result
Loop
Loop
Loop
Loop
Loop
Loop
Loop
Loop
Loop
Loop
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
Result
Loop: 0
Loop: 1
Loop: 2
Loop: 3
Loop: 4
Loop: 5
Loop: 6
Loop: 7
Loop: 8
Loop: 9
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
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
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)