Introduction and ObjDumps
We are finally stepping into the realm where we start doing stuff with assembly. For lab 4, we compared assembly's version to c's version of a simple hello world programme.
We used to makefile to make a executable of a c programme. When we used objdump -d hello
in both aarch64 and x86. For your reference here is the x86's version of the dump which only includes the main part.
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
Now we did the same for the assembly's version and the main part can be seen below. NOTE: this dump is from x86 and from GNU as assembler.
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
Assembly code in Aarch64
We were given an incomplete. code below that prints the word loop 10 times. Here is what the completed loop looks like.
.text
.globl _start
min = 0 /* starting value for the loop index; **note that this is a symbol (constant)**, not a variable */
max = 10 /* loop exits when the index hits this number (loop condition is i<max) */
_start:
mov x19, min
loop:
mov x0, 1``
adr x1, msg
mov x2, 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 "Loop:\n"
len= . - msg
We were also asked to print numbers at the end of the text representing the time of times the loops was ran, below is the code for that
.text
.globl _start
min = 0 /* starting value of the loop idx. this is a symbol */
max = 10 /* ending value of the loop idx. this is a symbol */
prefix = 0
_start:
mov x19, min /* store min to x19, which will be our idx register */
loop:
mov x0, 1 /* file descriptor: 1 is stdout */
adr x1, msg /* message location (memory address) */
mov x2, len /* message length (bytes) */
mov x4, x19 /* copy idx(x19) to x4 */
add x4, x4, 48 /* add 48 to make it ASCII */
strb w4, [x1, 6] /* store 1 byte into the memory loc of msg + 6 */
mov x8, 64 /* write is syscall #64 */
svc 0 /* invoke syscall */
add x19, x19, 1 /* add 1 to idx(x19) */
cmp x19, max /* compare x19 to max */
b.ne loop /* branch if not equal to loop */
mov x0, 0 /* status -> 0 */
mov x8, 93 /* exit is syscall #93 */
svc 0 /* invoke syscall */
.data
msg: .ascii "Loop: 0\n"
len= . - msg
Now the hardest part, we were asked to implement the same logic, but run the loop 30 times. This is challenging because we are now supposed keep a track of 2 variables that is one for ones place and one for second place. Below is the code that I came up with
.text
.globl _start
min = 0 /* starting value of the loop idx. this is a symbol */
max = 3 /* ending value of the loop idx. this is a symbol */
prefix = 0
default = 0
_start:
mov x19, min /* store min to x19, which will be our idx register */
mov x20, prefix
loop:
mov x0, 1 /* file descriptor: 1 is stdout */
adr x1, msg /* message location (memory address) */
mov x2, len /* message length (bytes) */
mov x4, x19 /* copy idx(x19) to x4 */
add x4, x4, 48 /* add 48 to make it ASCII */
cmp x4, 58
b.ne is_not_over
mov x19, default
add x20, x20, 1 /* add 1 to idx(x19) */
is_not_over:
mov x4, x20 /* copy idx(x19) to x4 */
add x4, x4, 48 /* add 48 to make it ASCII */
strb w4, [x1, 6] /* store 1 byte into the memory loc of msg + 6 */
mov x4, x19 /* copy idx(x19) to x4 */
add x4, x4, 48 /* add 48 to make it ASCII */
strb w4, [x1, 7] /* store 1 byte into the memory loc of msg + 7 */
b continue
continue:
mov x8, 64 /* write is syscall #64 */
svc 0 /* invoke syscall */
add x19, x19, 1 /* add 1 to idx(x19) */
cmp x20, max /* compare x19 to max */
b.ne loop /* branch if not equal to loop */
mov x0, 0 /* status -> 0 */
mov x8, 93 /* exit is syscall #93 */
svc 0 /* invoke syscall */
.data
msg: .ascii "Loop: 00\n"
len= . - msg
Lastly, we were supposed to remove the prefix 0 so that the output is more cleaner, this was actually easy, I just added an extra branch
default = 0
_start:
mov x19, min /* store min to x19, which will be our idx register */
mov x20, prefix
loop:
mov x0, 1 /* file descriptor: 1 is stdout */
adr x1, msg /* message location (memory address) */
mov x2, len /* message length (bytes) */
mov x4, x19 /* copy idx(x19) to x4 */
add x4, x4, 48 /* add 48 to make it ASCII */
cmp x4, 58
b.ne is_not_over
mov x19, default
add x20, x20, 1
is_not_over:
mov x4, x20 /* copy idx(x19) to x4 */
cmp x4, 0
b.eq print_ones
add x4, x4, 48 /* add 48 to make it ASCII */
strb w4, [x1, 6] /* store 1 byte into the memory loc of msg + 6 */
print_ones:
mov x4, x19 /* copy idx(x19) to x4 */
add x4, x4, 48 /* add 48 to make it ASCII */
strb w4, [x1, 7] /* store 1 byte into the memory loc of msg + 7 */
b continue
continue:
mov x8, 64 /* write is syscall #64 */
svc 0 /* invoke syscall */
add x19, x19, 1 /* add 1 to idx(x19) */
cmp x20, max /* compare x19 to max */
b.ne loop /* branch if not equal to loop */
mov x0, 0 /* status -> 0 */
mov x8, 93 /* exit is syscall #93 */
svc 0 /* invoke syscall */
.data
msg: .ascii "Loop: 0\n"
len= . - msg
Assembly code in x86
For this part I was supposed to do everything I did for arch in x86, So save some time I will plug the final code here.
.globl _start
min = 0 /* starting value for the loop index; **note that this is a symbol (constant)**, not a variable */
max = 3 /* loop exits when the index hits this number (loop condition is i<max) */
prefix = 0
default = 0
_start:
mov $min,%r15 /* loop index */
mov $prefix,%r12
loop:
movq $len,%rdx /* message length */
movq $msg,%rsi /* message location */
mov %r15, %r14
add $48, %r14
movq %r15, %r14
add $48, %r14
cmp $58, %r14 /* Compare r14 with ASCII value of '9' (which is 57) */
jne not_overflow
inc %r12
movq $default,%r15
not_overflow:
movq %r15, %r14
add $48, %r14
add $7, %rsi /* Move to the 5th index of the message */
movb %r14b, (%rsi)
sub $7, %rsi
movq %r12, %r14
add $48, %r14
cmp $48, %r14 /* Compare r14 with ASCII value of '9' (which is 57) */
je continue
add $6, %rsi /* Move to the 5th index of the message */
movb %r14b, (%rsi)
sub $6, %rsi
continue:
movq $1,%rdi /* file descriptor stdout */
movq $1,%rax /* syscall sys_write */
syscall
inc %r15 /* increment the loop index */
cmp $max,%r12 /* 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
.section .data
msg: .ascii "Loop: 0\n"
len = . - msg
Conclusion
This lab was an engaging exploration of assembly language programming across both AArch64 and x86 architectures. The stark differences in syntax and structure between the two made for a fascinating challenge, allowing me to appreciate the unique characteristics of each assembly language. I found the AArch64 assembly to be more intuitive, which made debugging feel somewhat more manageable compared to x86.
Completing the task of implementing loops with varying output formats sharpened my understanding of low-level programming concepts, particularly in relation to system calls and data handling.
Top comments (1)
This lab was a great introduction to assembly! It's fascinating to see how simple tasks like printing "Hello, World!" are handled at such a low level. I especially enjoyed the AArch64 assembly - it felt more intuitive than x86 for debugging.