DEV Community

gus
gus

Posted on

x86_64 Assembly Language

In my last post we went through writing a program for printing a message with a 2 digit incrementing value in AArch64 assembly. This time, we're going to tackle the same thing on an x86_64 architecture system.

Starting with the original code we're given:

.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     $min,%r15           /* loop index */

loop:
    /* ... body of the loop ... do something useful here ... */

    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
Enter fullscreen mode Exit fullscreen mode

First we need to add logic to print a message, like so:

.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     $min,%r15           /* loop index */

loop:

    mov     $len,%rdx           /* message length */
    mov     $msg,%rsi           /* message location */
    mov     $1,%rdi             /* file descriptor stdout */
    mov     $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 */

    mov     $0,%rdi             /* exit status */
    mov     $60,%rax            /* syscall sys_exit */
    syscall

.section .data

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

By adding the provided text output code as above we get the following output:

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

Now by adding logic to iterate an index and add it to the msg string, we get the following:

.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     $min,%r15           /* loop index */

loop:

    mov     %r15,%r14           
    add     $'0',%r14     
    movb    %r14b,msg+6        

    mov     $len,%rdx           /* message length */
    mov     $msg,%rsi           /* message location */
    mov     $1,%rdi             /* file descriptor stdout */
    mov     $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 */

    mov     $0,%rdi             /* exit status */
    mov     $60,%rax            /* syscall sys_exit */
    syscall

.section .data

msg:    .ascii      "Loop: #\n"
Enter fullscreen mode Exit fullscreen mode

With the resulting output:

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

Finally, we'll move to printing a 2 digit index along with our loop by making the following changes:

.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     $min,%r15        
    mov     $10,%r13          

loop:

    mov     %r15,%rax         
    mov     $0,%rdx          
    div     %r13          
    cmp     $0,%rax
    je     secondDigit

    add     $'0',%rax          
    mov     %rax,%r12
    movb    %r12b,msg+6       

secondDigit:

    add     $'0',%rdx          
    mov     %rdx,%r12
    movb    %r12b,msg+7        

    mov     $len,%rdx           /* message length */
    mov     $msg,%rsi           /* message location */
    mov     $1,%rdi             /* file descriptor stdout */
    mov     $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 */

    mov     $0,%rdi             /* exit status */
    mov     $60,%rax            /* syscall sys_exit */
    syscall

.section .data

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

Which gets us the appropriate output:

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
Loop: #15
Loop: #16
Loop: #17
Loop: #18
Loop: #19
Loop: #20
Loop: #21
Loop: #22
Loop: #23
Loop: #24
Loop: #25
Loop: #26
Loop: #27
Loop: #28
Loop: #29
Loop: #30
Enter fullscreen mode Exit fullscreen mode

Again this looks like the output we're looking for, so I'll break down how we got here and leave some parting thoughts on writing assembly programs for x86_64 vs my experience writing for AArch64.

_start:
    mov     $min,%r15        
    mov     $10,%r13       
Enter fullscreen mode Exit fullscreen mode

Starting off with the start section, we move the value of min to r15 and set r13 to 10, which we'll use to divide and split our 2 digits. Remember in this syntax the destination register is placed on the right, contrary to how it was arranged in the AArch64 program.

loop:

    mov     %r15,%rax         
    mov     $0,%rdx          
    div     %r13          
    cmp     $0,%rax
    je     secondDigit
Enter fullscreen mode Exit fullscreen mode

Next we place the value to be divided into rax and clear rdx to accept the remainder, before using the div instruction to divide what's in the rax register. We compare the value placed in rax, our "tens" column, to zero and branch to the secondDigit section if there's no tens column.

    add     $'0',%rax          
    mov     %rax,%r12
    movb    %r12b,msg+6   
Enter fullscreen mode Exit fullscreen mode

In the first line here we add an ascii 0 to the result of the division, after which we move that to r12 and finally move a byte of that with movb to the address of the pound sign in msg.

secondDigit:

    add     $'0',%rdx          
    mov     %rdx,%r12
    movb    %r12b,msg+7 
Enter fullscreen mode Exit fullscreen mode

Again here we add an ascii 0, this time to the remainder from the previous division, and then move that to r12 to have a byte moved to the pound sign address in msg. Like the last program, I'll end my breakdown here as the rest is pretty self evident and discuss my experience with x86_64.

This was pretty similar to assembly in AArch64 in a lot of ways, there were some minor syntactical differences like the precedence of operands listed above, and $ and % symbols being used to denote immediate values and registers, respectively. I'd be hard pressed to pick one I prefer, but if I had to I'd lean toward the AArch64 for its syntax, which I find slightly more readable. The difference is pretty negligible though. I also like the philosophy of improvement and not being weighed down by legacy features and nomenclature that comes with x86_64, but that hasn't affected my coding on either to any great extent thus far.

Overall this was a good challenge and I'm looking forward to diving deeper into these architectures.

Top comments (0)