In this lab we were tasked with creating a loop for both the x86_64 and Aarch64 architectures in assembly. The output of the loop was to look like below:
Loop: 0 Loop: 1 Loop: 2 Loop: 3 Loop: 4 Loop: 5 Loop: 6 Loop: 7 Loop: 8 Loop: 9 ... Loop: 100
This seemingly simple task actually took far more lines of code than I initially anticipated. Below is the source code for x86_64 version which I will go through piece by piece.
.text .globl _start start = 0 /* starting value for the loop index; note that this is a symbol (constant), not a variable */ max = 100 /* loop exits when the index hits this number (loop condition is i<max) */ asci = 48 /* value for 0 */ _start: mov $start,%r15 /* loop index */ mov $10,%r13 /* put value 10 into register 13 */ loop: /* ... body of the loop ... do something useful here ... */ mov $asci,%r14 /* store 0 value in 14 */ mov %r15, %rax /* move data from register 15 into accumulator register */ mov $0, %rdx /* put value 0 into data register */ div %r13 /* divide rax by r13 put result in rax and remainder in rdx */ mov $asci,%r14 /* loop index */ add %rax, %r14 /* creates digit */ movb %r14b,msg+6 /* puts into msg */ mov $asci,%r14 /* loop index */ add %rdx, %r14 /* creates digit */ movb %r14b,msg+7 /* puts into msg */ movq $len,%rdx /* length of msg */ movq $msg,%rsi /* string */ movq $1,%rdi /* STDOUT */ movq $1,%rax /* write */ syscall inc %r15 /* increment index */ cmp $max,%r15 /* see if we're done */ jne loop /* loop if we're not */ mov $0,%rdi /* exit status */ mov $60,%rax /* syscall sys_exit */ syscall .data msg: .ascii "Loop: \n" len= . - msg
I will now try to explain each of the main sections of the code:
Initially we are are setting the start point of our loop, as well as the ASCII value for the number 0. When the program begins, we store values into registers. So the loop counter known as start will be stored in r15, where the loop end at 10 will be stored at r13.
In the body of the loop, the most important part is the idea of dividing the loop index by 10, and by doing so, using the remainder as the value for the two digits of the loop counter. We can perform this division by using the div command which will store quotient in rax, and the remainder into rdx. We then move the single byte into an element in the msg array.
Now that the message has been created, we can now proceed to printing the message to the screen using a syscall. We then increment the loop, and compare the loop index to the max value. If they are not equal, then the loop procedure continues to run. The .data portion contains the loop message as well as its length. And that’s pretty much it in terms of how we went about creating this loop. As you can see, the code is much more granular than if you writing in a higher level language.
We also completed the same loop with the Aarch64 architecture:
.text .globl _start start = 0 /* loop index */ max = 31 ascii = 48 /* starting ascii value for 0 */ _start: mov x3,start /* initialize loop iterator to start(0) */ loop: mov x4,10 /* store the value 10, for calculating quotient/remainder */ udiv x6,x3,x4 /* x6 = x3 / 10 - udiv will give you the quotient*/ msub x7,x4,x6,x3 /* x7 = x3 - (10 * x6) - msub will give you the remainder*/ adr x1, msg /* msg location memory address */ add x6,x6,ascii /* convert integer to ascii */ add x7,x7,ascii /* convert integer to ascii */ strb w6,[x1,6] /* store in msg + 6 bytes memory location */ strb w7,[x1,7] /* store in msg + 7 bytes memory location */ /* print */ mov x0,1 /* file descriptor: 1 is stdout */ mov x2,len mov x8,64 /* write is syscall #64 */ svc 0 /* invoke syscall */ add x3,x3,1 /* increment index */ cmp x3,max /* check for end of loop */ bne loop /* loop if compare returns false */ mov x0,0 /* status -> 0 */ mov x8,93 /* exit is syscall #93 */ svc 0 /* invoke syscall */ .data msg: .ascii "Loop: \n" len = . - msg
The logic is pretty much the same as you can see, but what I will briefly discuss are some of the differences between writing for each assembler. In x86_64, registers names always begin with a % symbol i.e %rdx. Furthermore, some registers such as %rdx have special meaning as evident in their name. In Aarch64 assembler however, there is % symbol used, and register names carry no special meaning. Furthermore, Aarch64 commands seem much more granular than x86_64. For example, division in Aarch64 requires two commands, udiv to find the quotient, and msub to find the remainder. In contrast, x86_64 simply uses one command called div and automatically stores the quotient result in %rax, and the remainder in %rdx.
Overall it was a very interesting experience coding in assembly, but I will say its definitely not my cup of tea.