SPO Lab 3 - Assembly

In this lab, I am going to dive into basics of assembly language for both x86_64 and ARM architectures. Simple loop structure will be used to print a message with incremented integer value.

Inserting digit into a string

The first task is to add zero integers to the loop message:

    Loop: 0  
    Loop: 0  
    Loop: 0  
    ..

Lets start by storing ‘0’ as ASCII code in a register %r10 :

        mov    $0x30, %r10    /* init to 0 in ASCII */

We can add ’0’ to the “Loop : “ message by substituting ‘space’ character with our zero digit. I defined position of that space character in .data section as :

      .set fd  , msg + 6  /* Position of our first digit ("Loop: <digit>") */

We need to move only one byte of our zero code into $msg at position fd by using b postfix with %r0 register (If you move the whole thing, \n will be overwritten as well):

      mov    %r10b, fd 

Full Code : part1_x86_64

Incrementing Digit

Now, lets increment the zero digit by adding loop counter to it. I defined loop counter as register %r15 at the beginning :

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

Which gets incremented in the loop body:

      inc     %r15                /* increment index */
      cmp     $max, %r15          /* see if we're done */
      jne     loop                /* loop if we're not */

We can add the counter to our zero ASCII digit and display it using logic described in previous section :

      add    %r15, %r10
      mov    %r10b, fd 

This should result in :

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

Full Code: part2_x86_64

2-digit Number:

The goal is to split counter into two digits so we can insert them separately into the message string using 2 registers. One way to do this is to divide counter by 10 and store remainder as our second digit and result of the division as our first digit:

e.g : 13 / 10 should yield 1 as quotient and 3 as remainder.

Take a look at division instruction in x86_64:

    div %r10  

Unlike add or sub instructions, div takes only one argument. It divides %rax by given register (%r10 in this case) and places quotient (result of division operation) into %rax and remainder into %rdx (which should be initiated to zero prior to division).

Let’s translate the logic into assembly code:

  1. initiate our %rdx and %rax registers as well as setting the divisor to 10 :
        mov    $0   , %rdx /* init remainder  */
        mov    %r15 , %rax /* place counter (divident) */
        mov    $10  , %r14 /* place divisor */
  1. Dividing counter by 10 and adding the results (quotient & remainder) to the first and second digit :
        div           %r14 
        /* Add result of division and remainder */
        add    %rax, %r10 
        add    %rdx, %r11
    
        /* Add numbers to our string output */
        mov    %r10b, fd
        mov    %r11b, sd

Where sd is the position of our second digit:

    .set sd  , fd + 1   /* Position of our second digit = pos of 1st digit + 1 */

This will, however, print zeroes in front of 1-digit number :

    Loop: 00 
    Loop: 01 
    Loop: 02 
    Loop: 03 
    Loop: 04 
    Loop: 05 
    Loop: 06 
    Loop: 07 
    Loop: 08 
    Loop: 09 
    Loop: 10 
    Loop: 11 
    Loop: 12 
    ...
    ...

In order to get rid of zeros, additional logic needs to be added: If counter is less than 10, remainder will we be stored in the first digit and the second digit will be space ($0x20 hex stands for space char in ASCII).

        cmp    $10  , %r15
        jl     shift
        /* Add result of division and remainder */
        add    %rax, %r10
        add    %rdx, %r11
    
        jmp    printl
    
    shift:
        add    %rdx,  %r10
        mov    $0x20, %r11
    printl:
        /* Add number to our string:  */
        mov    %r10b, fd
        mov    %r11b, sd
    
        /*  Print Message to the stdout  */
        mov    $len, %rdx                       /* message length */
        mov    $msg, %rsi
        ...
        ... 

Full Code (with zeros) : part3_x86_64

Full Code (final) : part4_x86_64

Aarch 64 Version:

I found Aarch code to be very similar to x86_64 version. There are couple of differences related to register names, order of registers in instructions and even instructions themselves.

For example, getting remainder and quotient in x86_64 took only one line, while aarch required 2 operations :

	/* Getting quotient */	
	udiv	w0, w15, w2
	/* Getting remainder */
	msub	w3, w0, w2, w15

Overall, translating one code to another seems to be pretty intuitive.

Links to code snippets in aarch:

Print Zeros : part1

Print 0-10 : part2

Print 0-30 (with zeros) : part3

Print 0-30 (final) : part4

Final Words:

Debugging in assembly can be quite hard; most of the time I’d get ambiguous seg-faults which required commenting code out (I wonder if something like GDB can be used with it). Unlike other languages, assembly requires you to spell every instruction out to it. I found x86_64 a little bit less intuitive, although it might be just because it was the first one I attempted to tackle. Overall, I can’t say one assembler version excites me more than another.

Written on September 27, 2017