Week 6 - 64 Bit Assembler Lab - Lab 5

Table of contents
- Steps covered in class
- Step 0. Unpack the archive
- Step 1. Build & Run the C program
- Step 2. Disassemble the Code
- x86 objdump -d outputs for each file
- AArch64 objdump -d outputs for each file
- Step 3. Compare output with assembly language output
- Step 4A. Review, build, & run assembly language on AArch64
- Step 4B - Review, build, & run assembly language on x86_64
- Lab 5 - 64-Bit Assembly Language Lab
- Reflection
- References

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 withret
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 aret
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 aMakefile
).
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.
Convert the hello.s assembly code to print a loop
Modify the loop to print the index along with the String printed
Change the range of the loop to 00-32 (Ensure two digits are printed)
Change the loop to not print the leading 0 (Print 0-32)
Change the code output in hexadecimal (0-20) instead of (0-32)
AArch64 Assembly Language Lab
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 executingcat 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 :)
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
Change the range of the loop to 00-32 (Ensure two digits are printed)
This question involved the use of two main commands
udiv
andmsub
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
.
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!
Modify the loop to print the index along with the String printed
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
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 of0
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
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
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
Cover Image was taken from https://samsclass.info/127/proj/ED220.htm
Boilerplate code and files were provided by Professor Chris Tyler
Subscribe to my newsletter
Read articles from Hamza Teli directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
