Week 6 - 64 Bit Assembler Lab - Lab 5

Hamza TeliHamza Teli
29 min read

Hello again everyone! In this article I will be completing the 64-bit assembly language lab. I will also be covering what we did in class. Let’s get right into it!

Steps covered in class

There were a total of 4 steps that we went over in class and I will also cover these as well and explain it from my understanding what we are accomplishing in each step.

Before I get into it, please get your terminals ready and create a directory named hellp and add a c file that prints Hello World. There are more files in this example but for those, you may follow along what was covered.

Step 0. Unpack the archive

In this step, login to both of the SPO600 servers and unpack the archive example that Professor Chris Tyler mentioned using the commands below:

  • cd ~

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

The commands above are doing two things. First you are heading to your home directory and then extracting the .tgz archive there.

After performing the commands above, you will see the structure listed below. Please note this structure was taken from Professor Chris Tyler’s wiki website.

spo600
 └── examples
     └── hello                     # "hello world" example programs
         ├── assembler
         │   ├── aarch64           # aarch64 gas assembly language version
         │   │   ├── hello.s
         │   │   └── Makefile
         │   ├── Makefile
         │   └── x86_64            # x86_64 assembly language versions
         │       ├── hello-gas.s   # ... gas syntax
         │       ├── hello-nasm.s  # ... nasm syntax
         │       └── Makefile
         └── c                     # Portable C versions
             ├── hello2.c          # ... using write()
             ├── hello3.c          # ... using syscall()
             ├── hello.c           # ... using printf()
             └── Makefile

Below is an image of what it should look like in your end:

Step 1. Build & Run the C program

Now to run the C program, first navigate using the command: cd spo600/examples/hello/c and then execute the make command. This is going to build the the three programs (hello.c, hello2.c, and hello3.c).

As you see in the picture above, new files were created, this can be seen by using the ls command again and you’ll see a total of 5 files created on both machines:

  • hello

  • hello2

  • hello0

  • hello-opt

  • hello-static

You can also use the ls -l command to get more metadata of each file such as the size.

Look at the static files, these have the largest size. Why is that? Because static files contain all the library code so the program can run without any external libraries.

Now let’s look at the difference between each code and execute each as well:

hello.c:

  •       #include <stdio.h>
    
          int main() {
              printf("Hello World!\n");
          }
    

hello2.c:

  •       #include <unistd.h>
    
          int main() {
              write(1,"Hello World!\n",13);
          }
    

hello3.c:

  •       #include <sys/syscall.h>
          #include <unistd.h>
    
          int main() {
              syscall(__NR_write,1,"Hello World!\n",13);
          }
    

When executing each file, we get the exact output:

Now lets disassemble the code.

Step 2. Disassemble the Code

We will now use a command we used in the last weeks article to print the object code and disassemble it into assembler.

Please perform the following command on each of the files in both servers.

  • objdump -d ./hello

  • objdump -d ./hello2

  • objdump -d ./hello3

x86 objdump -d outputs for each file

For ./hello there are a total of 125 lines of code:

./hello:     file format elf64-x86-64

0000000000401126 <main>:
  401126:    55                       push   %rbp
  401127:    48 89 e5                 mov    %rsp,%rbp
  40112a:    bf 10 20 40 00           mov    $0x402010,%edi
  40112f:    b8 00 00 00 00           mov    $0x0,%eax
  401134:    e8 f7 fe ff ff           call   401030 <printf@plt>
  401139:    b8 00 00 00 00           mov    $0x0,%eax
  40113e:    5d                       pop    %rbp
  40113f:    c3                       ret

For hello2.c, there are a total of 126 lines of code:

./hello2:     file format elf64-x86-64

0000000000401126 <main>:
  401126:    55                       push   %rbp
  401127:    48 89 e5                 mov    %rsp,%rbp
  40112a:    ba 0d 00 00 00           mov    $0xd,%edx
  40112f:    be 10 20 40 00           mov    $0x402010,%esi
  401134:    bf 01 00 00 00           mov    $0x1,%edi
  401139:    e8 f2 fe ff ff           call   401030 <write@plt>
  40113e:    b8 00 00 00 00           mov    $0x0,%eax
  401143:    5d                       pop    %rbp
  401144:    c3                       ret

For ./hello3, there are a total of 128 lines of code:

./hello3:     file format elf64-x86-64

0000000000401126 <main>:
  401126:    55                       push   %rbp
  401127:    48 89 e5                 mov    %rsp,%rbp
  40112a:    b9 0d 00 00 00           mov    $0xd,%ecx
  40112f:    ba 10 20 40 00           mov    $0x402010,%edx
  401134:    be 01 00 00 00           mov    $0x1,%esi
  401139:    bf 01 00 00 00           mov    $0x1,%edi
  40113e:    b8 00 00 00 00           mov    $0x0,%eax
  401143:    e8 e8 fe ff ff           call   401030 <syscall@plt>
  401148:    b8 00 00 00 00           mov    $0x0,%eax
  40114d:    5d                       pop    %rbp
  40114e:    c3                       ret

Now when you compare the main section of each of these you see the following patterns:

  • ./hello → main has 8 lines of code

  • ./hello2 → main has 9 lines of code

  • ./hello3 → main has 11 lines of code

  • NOTE: However, all of them start with the instruction push and end with ret

AArch64 objdump -d outputs for each file

For hello.c, there are a total of 160 lines of code:

./hello:     file format elf64-littleaarch64

00000000004005d8 <main>:
  4005d8:    a9bf7bfd     stp    x29, x30, [sp, #-16]!
  4005dc:    910003fd     mov    x29, sp
  4005e0:    90000000     adrp    x0, 400000 <__abi_tag-0x278>
  4005e4:    9118c000     add    x0, x0, #0x630
  4005e8:    97ffffb2     bl    4004b0 <printf@plt>
  4005ec:    52800000     mov    w0, #0x0                       // #0
  4005f0:    a8c17bfd     ldp    x29, x30, [sp], #16
  4005f4:    d65f03c0     ret

For ./hello2.c, there are 162 lines of code:

./hello2:     file format elf64-littleaarch64

00000000004005d8 <main>:
  4005d8:    a9bf7bfd     stp    x29, x30, [sp, #-16]!
  4005dc:    910003fd     mov    x29, sp
  4005e0:    d28001a2     mov    x2, #0xd                       // #13
  4005e4:    90000000     adrp    x0, 400000 <__abi_tag-0x278>
  4005e8:    9118e001     add    x1, x0, #0x638
  4005ec:    52800020     mov    w0, #0x1                       // #1
  4005f0:    97ffffa8     bl    400490 <write@plt>
  4005f4:    52800000     mov    w0, #0x0                       // #0
  4005f8:    a8c17bfd     ldp    x29, x30, [sp], #16
  4005fc:    d65f03c0     ret

For ./hello3 there are 163 lines of code:

./hello3:     file format elf64-littleaarch64

00000000004005d8 <main>:
  4005d8:    a9bf7bfd     stp    x29, x30, [sp, #-16]!
  4005dc:    910003fd     mov    x29, sp
  4005e0:    528001a3     mov    w3, #0xd                       // #13
  4005e4:    90000000     adrp    x0, 400000 <__abi_tag-0x278>
  4005e8:    91190002     add    x2, x0, #0x640
  4005ec:    52800021     mov    w1, #0x1                       // #1
  4005f0:    d2800800     mov    x0, #0x40                      // #64
  4005f4:    97ffffaf     bl    4004b0 <syscall@plt>
  4005f8:    52800000     mov    w0, #0x0                       // #0
  4005fc:    a8c17bfd     ldp    x29, x30, [sp], #16
  400600:    d65f03c0     ret

Lets breakdown the main section for each file below:

  • ./hello → main has 8 lines of code

  • ./hello2 → main has 10 lines of code

  • ./hello3 → main has 11 lines of code

  • NOTE: For all three files, I observed that the main starts with stp & mov instructions and ends with a ret instruction.

Now we will compare the output above with the assembly language output of the C compiler

Step 3. Compare output with assembly language output

Now we will use an additional command that will output the assembly language output of the C compiler. This is done using the command below:

  • gcc -S hello.c

  • NOTE: Please run this command for all .c files

After executing the commands above, it produces a file with a .s extension. Lets take a look into these for each below

x86 Assembly Language Outputs

For hello.s:

    .file    "hello.c"
    .text
    .section    .rodata
.LC0:
    .string    "Hello World!"
    .text
    .globl    main
    .type    main, @function
main:
.LFB0:
    .cfi_startproc
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $.LC0, %edi
    call    puts
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (GNU) 14.2.1 20240912 (Red Hat 14.2.1-3)"
    .section    .note.GNU-stack,"",@progbits

For hello2.s:

    .file    "hello2.c"
    .text
    .section    .rodata
.LC0:
    .string    "Hello World!\n"
    .text
    .globl    main
    .type    main, @function
main:
.LFB0:
    .cfi_startproc
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $13, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    call    write
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (GNU) 14.2.1 20240912 (Red Hat 14.2.1-3)"
    .section    .note.GNU-stack,"",@progbits

For hello3.s:

    .file    "hello3.c"
    .text
    .section    .rodata
.LC0:
    .string    "Hello World!\n"
    .text
    .globl    main
    .type    main, @function
main:
.LFB0:
    .cfi_startproc
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $13, %ecx
    movl    $.LC0, %edx
    movl    $1, %esi
    movl    $1, %edi
    movl    $0, %eax
    call    syscall
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (GNU) 14.2.1 20240912 (Red Hat 14.2.1-3)"
    .section    .note.GNU-stack,"",@progbits

AArch64 Assembly Language outputs

For hello.s:

    .arch armv8-a
    .file    "hello.c"
    .text
    .section    .rodata
    .align    3
.LC0:
    .string    "Hello World!"
    .text
    .align    2
    .global    main
    .type    main, %function
main:
.LFB0:
    .cfi_startproc
    stp    x29, x30, [sp, -16]!
    .cfi_def_cfa_offset 16
    .cfi_offset 29, -16
    .cfi_offset 30, -8
    mov    x29, sp
    adrp    x0, .LC0
    add    x0, x0, :lo12:.LC0
    bl    puts
    mov    w0, 0
    ldp    x29, x30, [sp], 16
    .cfi_restore 30
    .cfi_restore 29
    .cfi_def_cfa_offset 0
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (GNU) 11.3.1 20220421 (Red Hat 11.3.1-3)"
    .section    .note.GNU-stack,"",@progbits

For hello2.s:

    .arch armv8-a
    .file    "hello2.c"
    .text
    .section    .rodata
    .align    3
.LC0:
    .string    "Hello World!\n"
    .text
    .align    2
    .global    main
    .type    main, %function
main:
.LFB0:
    .cfi_startproc
    stp    x29, x30, [sp, -16]!
    .cfi_def_cfa_offset 16
    .cfi_offset 29, -16
    .cfi_offset 30, -8
    mov    x29, sp
    mov    x2, 13
    adrp    x0, .LC0
    add    x1, x0, :lo12:.LC0
    mov    w0, 1
    bl    write
    mov    w0, 0
    ldp    x29, x30, [sp], 16
    .cfi_restore 30
    .cfi_restore 29
    .cfi_def_cfa_offset 0
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (GNU) 11.3.1 20220421 (Red Hat 11.3.1-3)"
    .section    .note.GNU-stack,"",@progbits

For hello3.s:

    .arch armv8-a
    .file    "hello3.c"
    .text
    .section    .rodata
    .align    3
.LC0:
    .string    "Hello World!\n"
    .text
    .align    2
    .global    main
    .type    main, %function
main:
.LFB0:
    .cfi_startproc
    stp    x29, x30, [sp, -16]!
    .cfi_def_cfa_offset 16
    .cfi_offset 29, -16
    .cfi_offset 30, -8
    mov    x29, sp
    mov    w3, 13
    adrp    x0, .LC0
    add    x2, x0, :lo12:.LC0
    mov    w1, 1
    mov    x0, 64
    bl    syscall
    mov    w0, 0
    ldp    x29, x30, [sp], 16
    .cfi_restore 30
    .cfi_restore 29
    .cfi_def_cfa_offset 0
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (GNU) 11.3.1 20220421 (Red Hat 11.3.1-3)"
    .section    .note.GNU-stack,"",@progbits

NOTE: When you compare each .s file with the disassembled output and look at the main section of both, you can see the same code in both sections. This tells us that the assembler code is more so human readable whereas the objdump output shows the machine instructions with memory addresses.

We are almost done our optional in class steps where we only have two steps. In these last 2 steps, we will build, run, and disassemble the assembly program on each server. We will do this on AArch64 first.

Step 4A. Review, build, & run assembly language on AArch64

To get started please ensure you navigate to the right directory by executing the following command:

  • cd ~/spo600/examples/hello/assembler/aarch64

  • NOTE: When you ls this directory you will see only two files (hello.s and a Makefile).

Now run the make command to build the program inside.

  • The make command will create a hello and hello.o file

Lets now compare the hello.s with the hello file. Run the two commands and capture each input

  • cat hello.s

  • objdump -d hello

You will see the following input:

FOR hello:
hello:     file format elf64-littleaarch64


Disassembly of section .text:

00000000004000b0 <_start>:
  4000b0:    d2800020     mov    x0, #0x1                       // #1
  4000b4:    100800e1     adr    x1, 4100d0 <msg>
  4000b8:    d28001c2     mov    x2, #0xe                       // #14
  4000bc:    d2800808     mov    x8, #0x40                      // #64
  4000c0:    d4000001     svc    #0x0
  4000c4:    d2800000     mov    x0, #0x0                       // #0
  4000c8:    d2800ba8     mov    x8, #0x5d                      // #93
  4000cc:    d4000001     svc    #0x0

For hello.s:
.text
.globl _start
_start:

    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 */

    mov     x0, 0         /* status -> 0 */
    mov     x8, 93        /* exit is syscall #93 */
    svc     0              /* invoke syscall */

.data
msg:     .ascii      "Hello, world!\n"
len=     . - msg

Step 4B - Review, build, & run assembly language on x86_64

Perform the same steps as above and navigate to the x86_64 direcrtory.

  • cd ~/spo600/examples/hello/assembler/x86_64

  • Note: ls command tells you there are 5 files

Now run the make command which will output the hello-gas and hello-nasm executable files.

We will compare these with the .s files again using the commands below:

  • cat hello-gas.s

  • objdump -d hello-gas

  • cat hello-nasm.s

  • objdump -d hello-nasm

For hello-gas.s:
/* 
   This is a 'hello world' program in x86_64 assembler using the 
   GNU assembler (gas) syntax. Note that this program runs in 64-bit
   mode.

   CTyler, Seneca College, 2014-01-20
   Licensed under GNU GPL v2+
*/

.text
.globl    _start

_start:

    movq    $len,%rdx            /* message length */
    movq     $msg,%rsi            /* message location */
    movq    $1,%rdi                /* file descriptor stdout */
    movq    $1,%rax                /* syscall sys_write */
    syscall

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

.section .data

msg:    .ascii      "Hello, world!\n"
    len = . - msg

----------------------------------------------
For hello-gas:
hello-gas:     file format elf64-x86-64


Disassembly of section .text:

0000000000401000 <_start>:
  401000:    48 c7 c2 0e 00 00 00     mov    $0xe,%rdx
  401007:    48 c7 c6 00 20 40 00     mov    $0x402000,%rsi
  40100e:    48 c7 c7 01 00 00 00     mov    $0x1,%rdi
  401015:    48 c7 c0 01 00 00 00     mov    $0x1,%rax
  40101c:    0f 05                    syscall
  40101e:    48 c7 c7 00 00 00 00     mov    $0x0,%rdi
  401025:    48 c7 c0 3c 00 00 00     mov    $0x3c,%rax
  40102c:    0f 05                    syscall

-----------------------------------------------
For hello-nasm.s:
section    .text
global    _start

_start:
    mov    rdx,len            ; message length
    mov    rcx,msg            ; message location
    mov    rbx,1            ; file descriptor stdout
    mov    rax,4            ; syscall sys_write
    int    0x80

    mov    rax,1            ; syscall sys_exit
    int    0x80

section    .data

msg    db    'Hello, world!',0xa
len    equ    $ - msg

---------------------------------------------
For hello-nasm:
hello-nasm:     file format elf64-x86-64


Disassembly of section .text:

0000000000401000 <_start>:
  401000:    ba 0e 00 00 00           mov    $0xe,%edx
  401005:    48 b9 00 20 40 00 00     movabs $0x402000,%rcx
  40100c:    00 00 00 
  40100f:    bb 01 00 00 00           mov    $0x1,%ebx
  401014:    b8 04 00 00 00           mov    $0x4,%eax
  401019:    cd 80                    int    $0x80
  40101b:    b8 01 00 00 00           mov    $0x1,%eax
  401020:    cd 80                    int    $0x80

NOTE: Just like in the AArch64 server, we see the same thing happening here in x86 where when you compare each .s file with the disassembled output and look at the main section of both, you can see the same code in both sections. This again tells us that the assembler code is more so human readable whereas the objdump output shows the machine instructions with memory addresses.

Now that we have completed the steps performed in class. Lets head into the lab itself

Lab 5 - 64-Bit Assembly Language Lab

I will now complete lab 5 as per the wiki instructions listed here. The lab will complete the tasks listed below on each SPO600 server. Please note for each section, I will discuss my findings and try to explain it in a way for you all to understand easily.

  1. Convert the hello.s assembly code to print a loop

  2. Modify the loop to print the index along with the String printed

  3. Change the range of the loop to 00-32 (Ensure two digits are printed)

  4. Change the loop to not print the leading 0 (Print 0-32)

  5. Change the code output in hexadecimal (0-20) instead of (0-32)

AArch64 Assembly Language Lab

  1. Convert the hello.s assembly code to print a loop:

    • This portion of the lab isn’t necessarily hard as Professor Chris Tyler provided some boilerplate code that loops, however the body is empty. Heres the code he provided:

        .text
         .globl _start
         min = 0                          /* starting value for the loop index; **note that this is a symbol (constant)**, not a variable */
         max = 6                         /* loop exits when the index hits this number (loop condition is i<max) */
         _start:
             mov     x19, min
         loop:
      
             /* ... body of the loop ... do something useful here ... */
      
             add     x19, x19, 1     /* increment the loop counter */
             cmp     x19, max        /* see if we've hit the max */
             b.ne    loop            /* if not, then continue the loop */
      
             mov     x0, 0           /* set exit status to 0 */
             mov     x8, 93          /* exit is syscall #93 */
             svc     0               /* invoke syscall */
      

      Now we know our task is to take our current hello.s which simply prints Hello World but do it multiple times. Lets first start off by looking at the hello.s code by executing cat hello.s

        .text
        .globl _start
        _start:
      
            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 */
      
            mov     x0, 0         /* status -> 0 */
            mov     x8, 93        /* exit is syscall #93 */
            svc     0              /* invoke syscall */
      
        .data
        msg:     .ascii      "Hello, world!\n"
        len=     . - msg
      

      All you have to do now is simply take this program and paste it into the body of the boilerplate provided by Professor Chris Tyler right? Not necessarily, this is where I got it wrong at first. Look at both programs, you can see the section from mov x0, 0 to svc 0. These seem like they are areas where the program terminats. Now we cant have two of those can we? If we do, the loop will terminate after the first loop. So lets start off by removing that and take everything else and paste it into the boilerplate code. Your code should look like below:

      ```plaintext .text .globl _start min = 0 / starting value for the loop index; note that this is a symbol (constant), not a variable / max = 6 / loop exits when the index hits this number (loop condition is i<max) / _start: mov x19, min loop:

      / ... body of the loop ... do something useful here ... / 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 x19, x19, 1 / increment the loop counter / cmp x19, max / see if we've hit the max / b.ne loop / if not, then continue the loop /

mov x0, 0 / set exit status to 0 / mov x8, 93 / exit is syscall #93 / svc 0 / invoke syscall /

.data msg: .ascii "Hamza!\n" len = . - msg


        If you build this and run the executable, it will print the following:

        ```plaintext
        Hamza!
        Hamza!
        Hamza!
        Hamza!
        Hamza!
        Hamza!

I changed the output a bit my modifying the msg at the bottom of the code, just to personalize it a bit more :)

  1. Modify the loop to print the index along with the String printed

    • This part took me some time to complete and was quite the experience. This section simply has an addition to the loop whereby we print the index with the String specified in msg. I will paste the code below and explain whats happening.

      ```plaintext .text .globl _start min = 0 / starting value for the loop index; note that this is a symbol (constant), not a variable / max = 6 / loop exits when the index hits this number (loop condition is i<max) / _start: mov x19, min loop:

      / ... body of the loop / / Here we will write Hamza! / mov x0, 1 / file descriptor: 1 is stdout / adr x1, msg mov x2, len

/ We call this syscall to write Hamza! / mov x8, 64 / write is syscall #64 / svc 0 / invoke syscall /

/ We have to convert the counter to a string / add x20, x19, #48 // Here we add 48 to the value at x19 and store it inside register x20 adr x1, number // Loads the address of digit in register x1 strb w20, [x1] // Here we store teh ASCII digit into buffer

// Now were doing the same as above for the initial String mov x0, 1 // adr x1, number mov x2, number_len

mov x8, 64 svc 0

add x19, x19, 1 / increment the loop counter / cmp x19, max / see if we've hit the max / b.ne loop / if not, then continue the loop /

mov x0, 0 / set exit status to 0 / mov x8, 93 / exit is syscall #93 / svc 0 / invoke syscall /

.data msg: .ascii "Hamza! " len = . - msg number: .ascii "0\n" number_len = . - number


        Before you freak out, lets take a deep breath first. Its not as complicated as you think. This is how I break it down. When we are printing to stdout, it always involves the code below:

        * ```plaintext
              mov     x0, 1           /* file descriptor: 1 is stdout */
              adr     x1, the_string
              mov     x2, the_length_of_string


              /* We call this syscall to write the String!  */
              mov     x8, 64          /* write is syscall #64 */
              svc     0               /* invoke syscall */

So now that you know this. The rest is quite simple. Note: before we can print the counter, we need to convert it into ASCII which we do by using the add instruction: add x20, x19, #48 This takes 48 and adds it into x19 which holds our index and places it inside register x20. Then we load address of number into x1 and then use strb to store it in the buffer. Then the rest of the code is essentially the same for printing!

  • Lets move on to the next question now
  1. Change the range of the loop to 00-32 (Ensure two digits are printed)

    1. This question involved the use of two main commands udiv and msub to complete the two digit function. Please take a look at the code below:

      ```plaintext .text .globl _start min = 0 / starting value for the loop index; note that this is a symbol (constant), not a variable / max = 33 / loop exits when the index hits this number (loop condition is i<max) / _start: mov x19, min mov x22, #10 // Set the divisor here
      loop:

/ Here we will write Hamza! / mov x0, 1 / file descriptor: 1 is stdout / adr x1, msg mov x2, len / We call this syscall to write Hamza! / mov x8, 64 / write is syscall #64 / svc 0 / invoke syscall /

// Get the quiotient and remainder udiv x23, x19, x22 // quotient = x19 (min) / x22 (10) msub x24, x23, x22, x19 // remainder

// Convert each like how we did before add x25, x23, #48 add x26, x24, #48

// Store these numbers adr x27, number strb w25, [x27] add x27, x27, 1 strb w26, [x27]

// Write the number using same logic mov x0, 1 adr x1, number
mov x2, number_len mov x8, 64 svc 0

add x19, x19, 1 / increment the loop counter / cmp x19, max / see if we've hit the max / b.ne loop / if not, then continue the loop /

mov x0, 0 / set exit status to 0 / mov x8, 93 / exit is syscall #93 / svc 0 / invoke syscall /

.data msg: .ascii "Hamza! " len = . - msg number: .ascii "00\n" number_len = . - number


        The `udiv` command calculated the quotient into register `x23` and the remainder was calculated with `msub` into register `x24`. Each of these were converted using `add` again. And then the `strb` command simply stored the lower byte into number for each of the digits.

        The rest of the logic is the same.

4. **Change the loop to not print the leading 0 (Print 0-32)**

    * This part wasn’t necessarily as hard as the quotient and remainder step as it was quite similar to the labs we completed for 6502. Take a look at my code below where I will explain the logic:

        ```plaintext
        .text
         .globl _start
         min = 0                          /* starting value for the loop index; **note that this is a symbol (constant)**, not a variable */
         max = 33                         /* loop exits when the index hits this number (loop condition is i<max) */
         _start:
             mov     x19, min
             mov x22, #10 // Set the divisor here    
         loop:


            /* Here we will write Hamza! */
            mov     x0, 1           /* file descriptor: 1 is stdout */
            adr     x1, msg
            mov     x2, len
            /* We call this syscall to write Hamza!  */
            mov     x8, 64          /* write is syscall #64 */
            svc     0               /* invoke syscall */

            // Get the quiotient and remainder
            udiv    x23, x19, x22 // quotient = x19 (min) / x22 (10)
            msub    x24, x23, x22, x19 // remainder 

            // Convert each like how we did before
            add     x25, x23, #48
            add     x26, x24, #48

            // Create a comparison
            cmp     x23, #0 // Easier to compare quotient with 0, basically if quotient is 0 we know its less than 10
            b.eq    print_only_one_digit


            // Store these numbers
            adr x27, number
            strb w25, [x27]
            add x27, x27, 1
            strb w26, [x27]

            // Write the number using same logic
            mov x0, 1
            adr x1, number    
            mov x2, number_len
            mov x8, 64 
            svc 0
            b continue_loop


        // Basically the sub routines we created in the previous lab for 6502
        print_only_one_digit:

            adr x27, number
            strb w26, [x27]
            add x27, x27, #1
            mov x28, #10
            strb w28, [x27]
            mov x2, #2

            mov     x0, 1
            adr x1, number
            mov x2, #2
            mov x8, 64
            svc 0
            b continue_loop

        continue_loop:
            add     x19, x19, 1     /* increment the loop counter */
            cmp     x19, max        /* see if we've hit the max */
            b.ne    loop            /* if not, then continue the loop */

            mov     x0, 0           /* set exit status to 0 */
            mov     x8, 93          /* exit is syscall #93 */
            svc     0               /* invoke syscall */

        .data
        msg:    .ascii "Hamza! "
        len =   . - msg
        number: .ascii "00\n"
        number_len = . - number

All thats happening here is I added a cmp or check that checks if the quotient is 0. If the quotient is 0, we know that the number is less than 10 or lesser than 2 digits. Therefore, I used this type of condition to then branch off to another sub routine called print_only_one_digit.

  1. Change the code output in hexadecimal (0-20) instead of (0-32)

    • This step was a bit tricky for me. Please view my code below and the explanation that follows. Note: I will cover only the changes I made to accomplish this:

      ```plaintext .text .globl _start min = 0 / starting value for the loop index; note that this is a symbol (constant), not a variable / max = 33 / loop exits when the index hits this number (loop condition is i<max) / _start: mov x19, min mov x22, #16 // Set the divisor here loop:

/ Here we will write Hamza! / mov x0, 1 / file descriptor: 1 is stdout / adr x1, msg mov x2, len / We call this syscall to write Hamza! / mov x8, 64 / write is syscall #64 / svc 0 / invoke syscall /

// Get the quiotient and remainder udiv x23, x19, x22 // quotient = x19 (min) / x22 (10) msub x24, x23, x22, x19 // remainder

// If quotient is less than 10 cmp x23, #10 blt convert_the_quotient_to_decimal //else add x25, x23, #55 // To convert we add 55 to get A - F b convert_the_remainder

convert_the_quotient_to_decimal: add x25, x23, #48

convert_the_remainder: cmp x24, #10 blt convert_remainder_to_decimal add x26, x24, #55 b store_and_print

convert_remainder_to_decimal: add x26, x24, #48

store_and_print:

// Store these numbers adr x27, number strb w25, [x27] add x27, x27, 1 strb w26, [x27]

// Write the number using same logic mov x0, 1 adr x1, number mov x2, number_len mov x8, 64 svc 0

add x19, x19, 1 / increment the loop counter / cmp x19, max / see if we've hit the max / b.ne loop / if not, then continue the loop /

mov x0, 0 / set exit status to 0 / mov x8, 93 / exit is syscall #93 / svc 0 / invoke syscall /

.data msg: .ascii "Hamza! " len = . - msg number: .ascii "00\n" number_len = . - number


        I created a few subroutines that do similar things. I first start off by comparing the quotient with the value `10`, if its less than `10`, it goes to `convert_the_quotient_to_decimal` which all it does is adds `48` to it. However, if its greater than `9`, then it adds `55` because adding `55` converts `10` to `65` which is `A` and so forth.

        Next, I then focus on the remainder part which has a similar approach. I check again if the remainder is less than or equal to `9`, if so I call the routine `convert_remainder_to_decimal`, which adds 48 to it. And if its 10 or greater it adds 55 as I’ve done in the quotient one.

        Then I simply call the store\_and\_print routine that does what was happening before but I did get rid of the continue\_loop. You can say I just renamed this subroutine to that.


This concludes the lab for AArch64, I will now proceed with the x86\_64 server.

## x86\_64 Assembly Language Lab

For this one, I will not go into as much detail as the AArch64 as the logic will be very similar

1. **Convert the hello.s assembly code to print a loop:**

    ```plaintext
    .text
    .globl    _start
    min = 0                         /* starting value for the loop index; **note that this is a symbol (constant)**, not a variable */
    max = 5                        /* loop exits when the index hits this number (loop condition is i<max) */

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

    loop:
        // Print the msg
        movq    $len,%rdx                       /* message length */
        movq    $msg,%rsi                       /* message location */
        movq    $1,%rdi                         /* file descriptor stdout */
        movq    $1,%rax                         /* syscall sys_write */                                                                                                  
        syscall    

        inc     %r15                /* increment the loop index */
        cmp     $max,%r15           /* see if we've hit the max */
        jne     loop                /* if not, then continue the loop */

        mov     $0,%rdi             /* set exit status to 0 */
        mov     $60,%rax            /* exit is syscall #60  */
        syscall                     /* invoke syscall */


    msg:    .ascii      "Hamza!\n"
    len = . - msg

This code is pretty straightforward, I used the same approach as last time where I took pieces from the original hello-gas.s and combined it with Professor Chris Tylers boiler plate code. Note: Running this code will print my name 5 times (Not Loop!) (I wanted to personalize it).

    Hamza!
    Hamza!
    Hamza!
    Hamza!
    Hamza!
  1. Modify the loop to print the index along with the String printed

    1. I’d be lying if I said I don’t like the GAS syntax. But after some tinkering, I figured it out. Heres the code:

       .text
       .globl    _start
       min = 0                         /* starting value for the loop index; **note that this is a symbol (constant)**, not a variable */
       max = 5                        /* loop exits when the index hits this number (loop condition is i<max) */
      
       _start:
           mov     $min,%r15           /* loop index */
      
       loop:
           // Print the msg
           movq    $len,%rdx                       /* message length */
           movq    $msg,%rsi                       /* message location */
           movq    $1,%rdi                         /* file descriptor stdout */
           movq    $1,%rax                         /* syscall sys_write */                                                                                                  
           syscall    
      
           // Convert the index to string again
           movb %r15b, %r8b
           addb $48, %r8b
           movb %r8b, number
      
           // Print the number
           movq $number_len, %rdx
           movq $number, %rsi
           movq $1, %rdi
           movq $1, %rax
           syscall
      
           inc     %r15                /* increment the loop index */
           cmp     $max,%r15           /* see if we've hit the max */
           jne     loop                /* if not, then continue the loop */
      
           mov     $0,%rdi             /* set exit status to 0 */
           mov     $60,%rax            /* exit is syscall #60  */
           syscall                     /* invoke syscall */
      
       .data  
       msg:    .ascii      "Hamza!: "
       len = . - msg
       number: .ascii "0\n"
       number_len = . - number
      

      This prints the following:

       Hamza!: 0
       Hamza!: 1
       Hamza!: 2
       Hamza!: 3
       Hamza!: 4
      
  2. Change the range of the loop to 00-32 (Ensure two digits are printed)

    • When I worked on this step, I ran into a few errors at first. Firstly, I tried to directly move the value from %rax into %r8b and %rbx into %r9b. But that didnt work as I needed to access the lower registers. So I used %al, and %dl. Secondly, I had to reset the %rdx register each time so I moved the value of 0 into it before moving $10 back into it. Heres the code below:

      ```plaintext .text .globl _start min = 0 / starting value for the loop index; note that this is a symbol (constant), not a variable / max = 33 / loop exits when the index hits this number (loop condition is i<max) /

      _start: mov $min,%r15 / loop index /

      loop: // Print the msg movq $len,%rdx / message length / movq $msg,%rsi / message location / movq $1,%rdi / file descriptor stdout / movq $1,%rax / syscall sys_write / syscall

      // Get quotient and remainder again mov %r15, %rax mov $0, %rdx mov $10, %rbx div %rbx

      // Move quotient and remainders mov %al, %r8b # Quotient mov %dl, %r9b # Remainder

      // Convert quotient addb $48, %r8b mov %r8b, number

      // Convert remainder addb $48, %r9b mov %r9b, number+1

// Print the number movq $number_len, %rdx movq $number, %rsi movq $1, %rdi movq $1, %rax syscall

inc %r15 / increment the loop index / cmp $max,%r15 / see if we've hit the max / jne loop / if not, then continue the loop /

mov $0,%rdi / set exit status to 0 / mov $60,%rax / exit is syscall #60 / syscall / invoke syscall /

.data
msg: .ascii "Hamza!: " len = . - msg number: .ascii "00\n" number_len = . - number


        This code prints the following:

        ```plaintext
        Hamza!: 00
        Hamza!: 01
        Hamza!: 02
        Hamza!: 03
        Hamza!: 04
        Hamza!: 05
        Hamza!: 06
        Hamza!: 07
        Hamza!: 08
        Hamza!: 09
        Hamza!: 10
        Hamza!: 11
        Hamza!: 12
        Hamza!: 13
        Hamza!: 14
        Hamza!: 15
        Hamza!: 16
        Hamza!: 17
        Hamza!: 18
        Hamza!: 19
        Hamza!: 20
        Hamza!: 21
        Hamza!: 22
        Hamza!: 23
        Hamza!: 24
        Hamza!: 25
        Hamza!: 26
        Hamza!: 27
        Hamza!: 28
        Hamza!: 29
        Hamza!: 30
        Hamza!: 31
        Hamza!: 32
  1. Change the loop to not print the leading 0 (Print 0-32)

    • For this one, I took a different approach, I ended up creating another label that holds the value for a single number called single_number. Its quite simple when broken down, the code is below:

      ```plaintext .text .globl _start min = 0 / starting value for the loop index; note that this is a symbol (constant), not a variable / max = 33 / loop exits when the index hits this number (loop condition is i<max) /

      _start: mov $min,%r15 / loop index /

      loop: // Print the msg movq $len,%rdx / message length / movq $msg,%rsi / message location / movq $1,%rdi / file descriptor stdout / movq $1,%rax / syscall sys_write / syscall

      // Get quotient and remainder again mov %r15, %rax // Check if index is less than 10 cmp $10, %r15 jb print_one_digit_only // Else mov $0, %rdx mov $10, %rbx div %rbx

      // Move quotient and remainders mov %al, %r8b # Quotient mov %dl, %r9b # Remainder

      // Convert quotient addb $48, %r8b mov %r8b, number

      // Convert remainder addb $48, %r9b mov %r9b, number+1

      // Print the number movq $number_len, %rdx movq $number, %rsi movq $1, %rdi movq $1, %rax syscall

      jmp continue_loop

      print_one_digit_only: movb %r15b, %r8b addb $48, %r8b movb %r8b, single_number

      // Print the single number movq $single_number_len, %rdx movq $single_number, %rsi movq $1, %rdi movq $1, %rax syscall

      jmp continue_loop

continue_loop:

inc %r15 / increment the loop index / cmp $max,%r15 / see if we've hit the max / jne loop / if not, then continue the loop /

mov $0,%rdi / set exit status to 0 / mov $60,%rax / exit is syscall #60 / syscall / invoke syscall /

.data
msg: .ascii "Hamza!: " len = . - msg number: .ascii "00\n" number_len = . - number single_number: .ascii "0\n" single_number_len = . - single_number


        This code outputs the following:

        ```plaintext
        Hamza!: 0
        Hamza!: 1
        Hamza!: 2
        Hamza!: 3
        Hamza!: 4
        Hamza!: 5
        Hamza!: 6
        Hamza!: 7
        Hamza!: 8
        Hamza!: 9
        Hamza!: 10
        Hamza!: 11
        Hamza!: 12
        Hamza!: 13
        Hamza!: 14
        Hamza!: 15
        Hamza!: 16
        Hamza!: 17
        Hamza!: 18
        Hamza!: 19
        Hamza!: 20
        Hamza!: 21
        Hamza!: 22
        Hamza!: 23
        Hamza!: 24
        Hamza!: 25
        Hamza!: 26
        Hamza!: 27
        Hamza!: 28
        Hamza!: 29
        Hamza!: 30
        Hamza!: 31
        Hamza!: 32
  1. Change the code output in hexadecimal (0-20) instead of (0-32)

    • For this last step, I used the same logic from the AArch64 step but utilized the corresponding instructions from x86_64. Heres the code:

      ```plaintext .text .globl _start min = 0 / starting value for the loop index; note that this is a symbol (constant), not a variable / max = 33 / loop exits when the index hits this number (loop condition is i<max) /

      _start: mov $min,%r15 / loop index /

      loop: // Print the msg movq $len,%rdx / message length / movq $msg,%rsi / message location / movq $1,%rdi / file descriptor stdout / movq $1,%rax / syscall sys_write / syscall

      // Get quotient and remainder again mov %r15, %rax mov $0, %rdx mov $16, %rbx div %rbx

      // Move quotient and remainders mov %al, %r8b # Quotient mov %dl, %r9b # Remainder

      // If quotient is less than 10 cmp $10, %r8b jl convert_the_quotient_to_decimal // else addb $55, %r8b jmp convert_the_remainder

convert_the_quotient_to_decimal: add $48, %r8b

convert_the_remainder: cmp $10, %r9b jl convert_remainder_to_decimal addb $55, %r9b jmp store_and_print

convert_remainder_to_decimal: add $48, %r9b

store_and_print: mov %r8b, number mov %r9b, number+1

// Print the number movq $number_len, %rdx movq $number, %rsi movq $1, %rdi movq $1, %rax syscall

inc %r15 / increment the loop index / cmp $max,%r15 / see if we've hit the max / jne loop / if not, then continue the loop /

mov $0,%rdi / set exit status to 0 / mov $60,%rax / exit is syscall #60 / syscall / invoke syscall /

.data
msg: .ascii "Hamza!: " len = . - msg number: .ascii "00\n" number_len = . - number


        The output will be as follows:

        ```plaintext
        Hamza!: 00
        Hamza!: 01
        Hamza!: 02
        Hamza!: 03
        Hamza!: 04
        Hamza!: 05
        Hamza!: 06
        Hamza!: 07
        Hamza!: 08
        Hamza!: 09
        Hamza!: 0A
        Hamza!: 0B
        Hamza!: 0C
        Hamza!: 0D
        Hamza!: 0E
        Hamza!: 0F
        Hamza!: 10
        Hamza!: 11
        Hamza!: 12
        Hamza!: 13
        Hamza!: 14
        Hamza!: 15
        Hamza!: 16
        Hamza!: 17
        Hamza!: 18
        Hamza!: 19
        Hamza!: 1A
        Hamza!: 1B
        Hamza!: 1C
        Hamza!: 1D
        Hamza!: 1E
        Hamza!: 1F
        Hamza!: 20

This concludes my lab for both servers. Please feel free to read the reflection section below to get my thoughts on it.

Reflection

Honestly, this lab was tough. But again, I learned a lot for both different types of architecture. One thing I can say is that logic is logic, if you can do it in one system, as long as you change the instructions and follow the same flow, it achieves the same result. In this lab, I got more comfortable with several things. Firstly, I have a better understanding of .s and .o files. Secondly, I became more familiar with AArch64 syntax as well as x86 GAS syntax. I did not learn nasm syntax, but looking at it briefly it looks less complicated than GAS as it looks just like AArch64 syntax. Lastly, I learned how to make conditionals and how to call sub routines. I can definitely say that the previous labs did provide a foundation to help me complete this lab.

References

0
Subscribe to my newsletter

Read articles from Hamza Teli directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Hamza Teli
Hamza Teli