Assembling a Loop

Written by Hunter Jansen on October 15, 2014

Continuing on with the previous entry in which we looked at a simple hello world program in assembler - this time I’m going to (try) to have a program loop through and print out 1-9 in each architecture.

The program itself is pretty simple, but as you’ll see - it takes a whole bunch more work in assembler to make even the simplest stuff to work.

What’re we doing?

The link for this week’s lab can be found here - the latter half.

Essentially what we’re going to do by hand in assembly matches up to the following in C

#include <stdio.h>

int main() {
    int i = 0;
    for(i = 0; i < 10; i++){
    	printf("Loop: %d\n", i);    
    }
}

Super simple program that takes a whopping 8 lines in C, but as we’ll see, it takes a significant effort to even cover the simple basis.

We’ll start off with x86, just because my brain seems to prefer it. This is what I came up with, but I’m fairly certain it could be optimized and improved upon in a couple places

.text
.globl  _start

start = 0                       /* starting value for the loop index */
max = 10                      /* loop exits when the index hits this number (loop condition is i<max) */

_start:
  mov     $start,%r15         /* loop index */

  loop:
    /* ... body of the loop ... do something useful here ... */
        movq    $len,%rdx                       /* message length */
        movq    $msg,%rsi                       /* message location */
        movq    $1,%rdi                         /* file descriptor stdout */
        movq    $1,%rax                         /* syscall sys_write */
        syscall

        movq    %r15, %r10                      /* move index to r10  */
        add     $0x30, %r10                     /* make index ascii to print  */
        movq     $msg2, %r9                     /* put msg2 as r9  */
        mov     %r10d, (%r9)                    /* put index as ascii into msg2  */

        movq    $0x4, %rdx                      /* move 4 bytes forward */
        movq    %r9, %rsi                       /* set r9/msg2 to write  */
        movq    $1,%rdi                         /* file descriptor stdout */
        movq    $1,%rax                         /* syscall sys_write */
        syscall

        movq     $0x4, %rdx
        movq     %r14, %rsi
        movq    $1,%rdi                         /* file descriptor stdout */
        movq    $1,%rax                         /* syscall sys_write */
        syscall

        movq    $0x1,%rdx                       /* message length */
        movq    $newLn,%rsi                     /* message location */
        movq    $1,%rdi                         /* file descriptor stdout */
        movq    $1,%rax                         /* syscall sys_write */
        syscall

        inc     %r15                /* increment index */
        cmp     $max,%r15           /* see if we're done */
        jne     loop                /* loop if we're not */
        movq    $0,%rdi                         /* exit status */
        movq    $60,%rax                        /* syscall sys_exit */
        syscall

.section .data

msg:    .ascii      "Loop: "
        len = . - msg
msg2:   .long 0
newLn:  .ascii "\n"

   

Instead of explaining everything after the fact, I figured it would probably be easier to comment everything heavily to explain what each line is doing. Running the output of this gives us:

Loop: 0
Loop: 1
Loop: 2
Loop: 3
Loop: 4
Loop: 5
Loop: 6
Loop: 7
Loop: 8
Loop: 9


As expected. GOSH that’s a lot of work for a simple loop - a lot of confusing work!

And THEN we’ve got to do the same thing in aarch, which I personally found a bit more confusing for some reason:

.text
.globl _start

start = 0
max = 10                 /* loop exits when the index hits this number (loop condition is i<max) */

_start:
  mov     x15, start

  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     x10, x5, 0x30   /* x5 is loop counter, change to ascii and put in x10 */
        adr     x1, msg2        /* put msg2 into x1 */
        str     w10, [x1]       /* change msg2 value to ascii loop value */

        mov     x2, 0x4         /* jump forward 4 bytes */
        adr     x1, msg2        /* message location */ 
        mov     x0, 1           /* file descriptor: 1 is stdout */
        mov     x8, 64          /* write is syscall #64 */
        svc     0               /* invoke syscall */

        mov     x0, 1           /* file descriptor: 1 is stdout */
        adr     x1, newLn         /* message location (memory address) */
        mov     x2, 0x1         /* message length (bytes) */
        mov     x8, 64          /* write is syscall #64 */
        svc     0               /* invoke syscall */

        add  x5, x5, 0x1        /* add one to loop index */
        mov  x6, x5             /* put new index into x6 */
        subs x6, x6, max        /* check if loop is finished */
        b.ne loop

        mov     x0, 0           /* status -> 0 */
        mov     x8, 93          /* exit is syscall #93 */
        svc     0               /* invoke syscall */
        
.data
msg:    .ascii      "Loop: "
len=    . - msg
msg2:   .long 0
newLn:   .ascii "\n"

And there we have it - both 0-9 loops.

A little weird, and it definitely takes some time to get used to…

Next time we’ll be….Actually, I’m not sure! Stay tuned!

Until Next time -Hunter