Lab05 - x86_64

Objective: 

In this lab, I worked with 64-bit assembly language on x86_64 platform to learn about CPU architecture and assembly instructions.

Getting Started with Code Example 

The first step is to retrieve the code example from the server. The file is compressed, so we'll extract it into the desired location (in my case, the home directory):

cd ~

tar xvf /public/spo600-assembler-lab-examples.tgz

Once extracted, you’ll see a folder named spo600, which contains all the necessary files.

x86_64:


Investigating the C Code

To explore the C version of the program, navigate to the folder containing the hello.c file and compile it using make:

make

x86_64:


 Disassemble the binary to view the assembly code:

objdump -d hello

x86_64:

This will show us the assembly instructions, where we can observe that different CPU architectures (x86_64 vs Aarch64) generate different instructions, even for the same program.

Exploring the x86_64 Platform

Now, let’s dive into the Aarch64-specific example:

  • Navigate to /spo600/examples/hello/assembler and compile it using make.
  • After compilation, you’ll have an executable file named hello.
  • Running it will print "Hello, World!" to the screen.

Fixing a Loop in x86_64

We were provided with a loop in x86_64, but it wasn’t doing anything. The task was to modify it to print something each time it loops.

Here's a simple version of the loop that prints "Loop" each time it runs:

.text

.globl    _start

min = 0                         /* Initial value for the loop index (constant, not a variable) */

max = 5                         /* Loop terminates when the index reaches this value (i < max) */

_start:

    mov     $min,%r15           /* Initialize the loop counter with the starting value (min) */


loop:

    mov     $len,%rdx           /* Set the message length */

    mov     $msg,%rsi           /* Set the message location (address of msg) */

    mov     $1,%rdi             /* Set file descriptor for stdout (1) */

    mov     $1,%rax             /* Set the syscall number for sys_write (1) */

    syscall                     /* Perform the syscall to print the message */

    inc     %r15                /* Increment the loop counter by 1 */

    cmp     $max,%r15           /* Compare the loop counter with the max value */

    jne     loop                /* If the counter is less than max, continue the loop */


    mov     $0,%rdi             /* Set the exit status to 0 (successful execution) */

    mov     $60,%rax            /* Set the syscall number for sys_exit (60) */

    syscall                     /* Perform the syscall to exit the program */


.section .data


msg:  .ascii  "Loop\n"           /* The message to be printed each time */

len=  .-msg                      /* Calculate the length of the message */

Result:

Loop with Loop Number

We can further enhance the loop to print the loop number alongside "Loop". Here’s an updated version:

.text

.globl    _start

min = 0                         /* Initial value for the loop index (constant, not a variable) */

max = 6                         /* Loop terminates when the index reaches this value (i < max) */

_start:

    mov     $min,%r15           /* Initialize the loop counter with the starting value (min) */


loop:

    mov     $len,%rdx           /* Set the message length */

    mov     $msg,%rsi           /* Set the message location (address of msg) */

    mov     $1,%rdi             /* Set file descriptor for stdout (1) */

    mov     $1,%rax             /* Set the syscall number for sys_write (1) */

    syscall                     /* Perform the syscall to print the message */


    lea     buffer(%rip),%r9    /* Load the address of buffer into r9 */


    mov     %r15,%r8            /* Copy the loop counter (r15) into r8 */

    add     $48,%r8             /* Convert the loop counter to ASCII (48 is '0') */

    movb    %r8b,(%r9)          /* Store the ASCII character in buffer */

    add     $1,%r9              /* Move to the next byte in buffer */

    movb    $10,(%r9)           /* Store newline character (ASCII 10) into buffer */


    mov     $2,%rdx             /* Set the message length (2 bytes: number + newline) */

    mov     $buffer,%rsi        /* Set the message location (buffer address) */

    mov     $1,%rdi             /* Set file descriptor for stdout (1) */

    mov     $1,%rax             /* Set the syscall number for sys_write (1) */

    syscall                     /* Perform the syscall to print the loop counter and newline */

    inc     %r15                /* Increment the loop counter by 1 */

    cmp     $max,%r15           /* Compare the loop counter with the max value */

    jne     loop                /* If the counter is less than max, continue the loop */


    mov     $0,%rdi             /* Set the exit status to 0 (successful execution) */

    mov     $60,%rax            /* Set the syscall number for sys_exit (60) */

    syscall                     /* Perform the syscall to exit the program */


.section .data


msg:    .ascii  "Loop: "         /* The message to be printed before each loop count */

len=    .-msg                    /* Calculate the length of the "Loop: " message */

buffer: .space 2                 /* Allocate space for the loop count character and newline */

Result:
    

Loop with 2-digit Numbers (Up to 32)

text

.globl    _start


min = 0                         /* Initial value for the loop index (constant, not a variable) */

max = 33                        /* Loop terminates when the index reaches this value (i < max) */


_start:


    mov     $min,%r15           /* Initialize the loop counter with the starting value (min) */


loop:

    mov     $len,%rdx           /* Set the message length */

    mov     $msg,%rsi           /* Set the message location (address of msg) */

    mov     $1,%rdi             /* Set file descriptor for stdout (1) */

    mov     $1,%rax             /* Set the syscall number for sys_write (1) */

    syscall                     /* Perform the syscall to print the message */


    lea     buffer(%rip),%r9    /* Load the address of buffer into r9 */


    mov     %r15,%rax           /* Copy the loop counter (r15) into rax */

    xor     %rdx,%rdx           /* Clear the rdx register */

    mov     $10,%r11            /* Store the divisor (10) into r11 */

    div     %r11                /* Perform unsigned division (rax = quotient, rdx = remainder) */

    mov     %rax,%r8            /* Store the quotient (tens place) in r8 */

    mov     %rdx,%r10           /* Store the remainder (ones place) in r10 */


    add     $48,%r8             /* Convert the quotient (tens place) to ASCII */

    movb    %r8b,(%r9)          /* Store the ASCII character of tens place in buffer */

    add     $1,%r9              /* Move to the next byte in buffer */


    add     $48,%r10            /* Convert the remainder (ones place) to ASCII */

    movb    %r10b,(%r9)         /* Store the ASCII character of ones place in buffer */

    add     $1,%r9              /* Move to the next byte in buffer */


    movb    $10,(%r9)           /* Store newline character (ASCII 10) into buffer */


    mov     $3,%rdx             /* Set the message length (3 bytes: tens digit + ones digit + newline) */

    mov     $buffer,%rsi        /* Set the message location (buffer address) */

    mov     $1,%rdi             /* Set file descriptor for stdout (1) */

    mov     $1,%rax             /* Set the syscall number for sys_write (1) */

    syscall                     /* Perform the syscall to print the loop counter and newline */


    inc     %r15                /* Increment the loop counter by 1 */

    cmp     $max,%r15           /* Compare the loop counter with the max value */

    jne     loop                /* If the counter is less than max, continue the loop */


    mov     $0,%rdi             /* Set the exit status to 0 (successful execution) */

    mov     $60,%rax            /* Set the syscall number for sys_exit (60) */

    syscall                     /* Perform the syscall to exit the program */


.section .data


msg:    .ascii  "Loop: "         /* The message to be printed before each loop count */

len=    .-msg                    /* Calculate the length of the "Loop: " message */

buffer: .space 3                 /* Allocate space for the two digits (tens and ones) and newline */


Result:

Loop with 2-digit Numbers (Up to 32) Suppressing the Leading 0s

.text

.globl    _start


min = 0                         /* Initial value for the loop index (constant, not a variable) */

max = 33                        /* Loop terminates when the index reaches this value (i < max) */


_start:


    mov     $min,%r15           /* Initialize the loop counter with the starting value (min) */


loop:

    mov     $len,%rdx           /* Set the message length */

    mov     $msg,%rsi           /* Set the message location (address of msg) */

    mov     $1,%rdi             /* Set file descriptor for stdout (1) */

    mov     $1,%rax             /* Set the syscall number for sys_write (1) */

    syscall                     /* Perform the syscall to print the message */


    lea     buffer(%rip),%r9    /* Load the address of buffer into r9 */


    mov     %r15,%rax           /* Copy the loop counter (r15) into rax */

    xor     %rdx,%rdx           /* Clear rdx value */

    mov     $10,%r11            /* Store divisor to r11 (for division by 10) */

    div     %r11                /* Perform unsigned division, rax = quotient (tens), rdx = remainder (ones) */

    

    mov     %rax,%r8            /* Store quotient (tens place) in r8 */

    mov     %rdx,%r10           /* Store remainder (ones place) in r10 */


    cmp     $0,%r8              /* Check if the tens place is zero */

    je      units_digit         /* If it is zero, skip storing the tens place and go directly to the ones place */

    

    add     $48,%r8             /* Convert tens place to ASCII */

    movb    %r8b,(%r9)          /* Store the tens place character into buffer */

    add     $1,%r9              /* Move to next byte in buffer */

    

units_digit:

    add     $48,%r10            /* Convert ones place to ASCII */

    movb    %r10b,(%r9)         /* Store the ones place character into buffer */

    add     $1,%r9              /* Move to next byte in buffer */


    movb    $10,(%r9)           /* Store newline character (ASCII 10) into buffer */


    mov     $3,%rdx             /* Set the message length (3 bytes: tens digit, ones digit, newline) */

    mov     $buffer,%rsi        /* Set the message location (buffer address) */

    mov     $1,%rdi             /* Set file descriptor for stdout (1) */

    mov     $1,%rax             /* Set the syscall number for sys_write (1) */

    syscall                     /* Perform the syscall to print the loop counter and newline */


    inc     %r15                /* Increment the loop counter by 1 */

    cmp     $max,%r15           /* Compare the loop counter with the max value */

    jne     loop                /* If the counter is less than max, continue the loop */


    mov     $0,%rdi             /* Set the exit status to 0 (successful execution) */

    mov     $60,%rax            /* Set the syscall number for sys_exit (60) */

    syscall                     /* Perform the syscall to exit the program */


.section .data


msg:    .ascii  "Loop: "         /* The message to be printed before each loop count */

len=    .-msg                    /* Calculate the length of the "Loop: " message */

buffer: .space 3                 /* Allocate space for the loop counter (2 digits max) and newline */

Result:


Output in hexadecimal (0-20)

.text

.globl    _start


/* Constants for loop index and maximum loop value */

min = 0

max = 33


_start:


    /* Initialize loop counter (min) */

    mov     $min, %r15


loop:


    /* Display "Loop: " before each count */

    mov     $len, %rdx           /* Message length */

    mov     $msg, %rsi           /* Message address */

    mov     $1, %rdi             /* File descriptor: stdout */

    mov     $1, %rax             /* Syscall number for sys_write */

    syscall                     /* Invoke syscall to print the message */


    /* Prepare buffer for storing the loop counter (tens and ones digits) */

    lea     buffer(%rip), %r9    /* Load address of buffer into r9 */


    /* Perform division to separate tens and ones places */

    mov     %r15, %rax           /* Copy loop counter (r15) to rax */

    xor     %rdx, %rdx           /* Clear rdx (remainder) */

    mov     $16, %r11            /* Divisor is 16 */

    div     %r11                 /* Perform unsigned division: quotient in rax, remainder in rdx */

    mov     %rax, %r8            /* Store quotient (tens place) in r8 */

    mov     %rdx, %r10           /* Store remainder (ones place) in r10 */


    /* Process tens place: */

    cmp     $0, %r8              /* Check if tens place is 0 */

    je      units_digit          /* If 0, skip storing tens digit and move to units place */


    add     $48, %r8             /* Convert tens place to ASCII */

    movb    %r8b, (%r9)          /* Store the tens place character in buffer */

    add     $1, %r9              /* Move to next byte in buffer */


units_digit:

    add     $48, %r10            /* Convert ones place to ASCII */

    cmp     $58, %r10            /* Check if ones place is less than or equal to 9 */

    jl      st_unit              /* If less than 58 ('9' in ASCII), store it */


    /* If greater than '9', adjust for ASCII letters A-E */

    add     $7, %r10             /* Convert numbers 10-15 to 'A'-'F' */

    

st_unit:

    movb    %r10b, (%r9)         /* Store ones place character in buffer */

    add     $1, %r9              /* Move to next byte in buffer */


    /* Add newline after the number */

    movb    $10, (%r9)           /* Store newline character (ASCII 10) in buffer */


    /* Display the number (two digits and newline) */

    mov     $3, %rdx             /* Message length: 3 bytes (tens, ones, newline) */

    mov     $buffer, %rsi        /* Address of buffer with number and newline */

    mov     $1, %rdi             /* File descriptor: stdout */

    mov     $1, %rax             /* Syscall number for sys_write */

    syscall                     /* Invoke syscall to print the buffer */


    /* Increment loop counter */

    inc     %r15


    /* Check if loop counter has reached max value */

    cmp     $max, %r15

    jne     loop                 /* If counter < max, continue the loop */


    /* Exit program */

    mov     $0, %rdi             /* Exit status: 0 */

    mov     $60, %rax            /* Syscall number for sys_exit */

    syscall                     /* Invoke syscall to exit */


.section .data


msg:    .ascii "Loop: "          /* "Loop: " message to be printed before number */

len=    .-msg                    /* Calculate length of "Loop: " */

buffer: .space 3                 /* Allocate space for two digits and newline character */

Result: 


Reflection:
Writing in assembler has been challenging but rewarding. Understanding the low-level operations, such as moving data between registers and managing memory, took time and effort. I found debugging especially difficult, as there’s no easy way to pinpoint errors. Each instruction must be carefully checked, and small mistakes can lead to big issues. The experience of working with different architectures—6502, x86_64, and aarch64—showed me the differences in complexity and ease of use. Overall, assembler forces me to think deeply about how computers work, but it’s a slow and precise process.

Comments

Popular posts from this blog

Project Stage 1

Lab 03 - 6502 Program Lab (Revised)