In my last post we went through writing a program for printing a message with a 2 digit incrementing value in AArch64 assembly. This time, we're going to tackle the same thing on an x86_64 architecture system.
Starting with the original code we're given:
.text
.globl _start
min = 0 /* starting value for the loop index; note that this is a symbol (constant), not a variable */
max = 10 /* loop exits when the index hits this number (loop condition is i<max) */
_start:
mov $min,%r15 /* loop index */
loop:
/* ... body of the loop ... do something useful here ... */
inc %r15 /* increment index */
cmp $max,%r15 /* see if we're done */
jne loop /* loop if we're not */
mov $0,%rdi /* exit status */
mov $60,%rax /* syscall sys_exit */
syscall
First we need to add logic to print a message, like so:
.text
.globl _start
min = 0 /* starting value for the loop index; note that this is a symbol (constant), not a variable */
max = 10 /* loop exits when the index hits this number (loop condition is i<max) */
_start:
mov $min,%r15 /* loop index */
loop:
mov $len,%rdx /* message length */
mov $msg,%rsi /* message location */
mov $1,%rdi /* file descriptor stdout */
mov $1,%rax /* syscall sys_write */
syscall
inc %r15 /* increment index */
cmp $max,%r15 /* see if we're done */
jne loop /* loop if we're not */
mov $0,%rdi /* exit status */
mov $60,%rax /* syscall sys_exit */
syscall
.section .data
msg: .ascii "Loop\n"
len = . - msg
By adding the provided text output code as above we get the following output:
Loop
Loop
Loop
Loop
Loop
Loop
Loop
Loop
Loop
Loop
Now by adding logic to iterate an index and add it to the msg
string, we get the following:
.text
.globl _start
min = 0 /* starting value for the loop index; note that this is a symbol (constant), not a variable */
max = 10 /* loop exits when the index hits this number (loop condition is i<max) */
_start:
mov $min,%r15 /* loop index */
loop:
mov %r15,%r14
add $'0',%r14
movb %r14b,msg+6
mov $len,%rdx /* message length */
mov $msg,%rsi /* message location */
mov $1,%rdi /* file descriptor stdout */
mov $1,%rax /* syscall sys_write */
syscall
inc %r15 /* increment index */
cmp $max,%r15 /* see if we're done */
jne loop /* loop if we're not */
mov $0,%rdi /* exit status */
mov $60,%rax /* syscall sys_exit */
syscall
.section .data
msg: .ascii "Loop: #\n"
With the resulting output:
Loop: 0
Loop: 1
Loop: 2
Loop: 3
Loop: 4
Loop: 5
Loop: 6
Loop: 7
Loop: 8
Loop: 9
Finally, we'll move to printing a 2 digit index along with our loop by making the following changes:
.text
.globl _start
min = 0 /* starting value for the loop index; note that this is a symbol (constant), not a variable */
max = 15 /* loop exits when the index hits this number (loop condition is i<max) */
_start:
mov $min,%r15
mov $10,%r13
loop:
mov %r15,%rax
mov $0,%rdx
div %r13
cmp $0,%rax
je secondDigit
add $'0',%rax
mov %rax,%r12
movb %r12b,msg+6
secondDigit:
add $'0',%rdx
mov %rdx,%r12
movb %r12b,msg+7
mov $len,%rdx /* message length */
mov $msg,%rsi /* message location */
mov $1,%rdi /* file descriptor stdout */
mov $1,%rax /* syscall sys_write */
syscall
inc %r15 /* increment index */
cmp $max,%r15 /* see if we're done */
jne loop /* loop if we're not */
mov $0,%rdi /* exit status */
mov $60,%rax /* syscall sys_exit */
syscall
.section .data
msg: .ascii "Loop: #\n"
len = . - msg
Which gets us the appropriate output:
Loop: #0
Loop: #1
Loop: #2
Loop: #3
Loop: #4
Loop: #5
Loop: #6
Loop: #7
Loop: #8
Loop: #9
Loop: #10
Loop: #11
Loop: #12
Loop: #13
Loop: #14
Loop: #15
Loop: #16
Loop: #17
Loop: #18
Loop: #19
Loop: #20
Loop: #21
Loop: #22
Loop: #23
Loop: #24
Loop: #25
Loop: #26
Loop: #27
Loop: #28
Loop: #29
Loop: #30
Again this looks like the output we're looking for, so I'll break down how we got here and leave some parting thoughts on writing assembly programs for x86_64 vs my experience writing for AArch64.
_start:
mov $min,%r15
mov $10,%r13
Starting off with the start section, we move the value of min to r15
and set r13
to 10, which we'll use to divide and split our 2 digits. Remember in this syntax the destination register is placed on the right, contrary to how it was arranged in the AArch64 program.
loop:
mov %r15,%rax
mov $0,%rdx
div %r13
cmp $0,%rax
je secondDigit
Next we place the value to be divided into rax
and clear rdx
to accept the remainder, before using the div
instruction to divide what's in the rax
register. We compare the value placed in rax
, our "tens" column, to zero and branch to the secondDigit section if there's no tens column.
add $'0',%rax
mov %rax,%r12
movb %r12b,msg+6
In the first line here we add an ascii 0 to the result of the division, after which we move that to r12
and finally move a byte of that with movb
to the address of the pound sign in msg
.
secondDigit:
add $'0',%rdx
mov %rdx,%r12
movb %r12b,msg+7
Again here we add an ascii 0, this time to the remainder from the previous division, and then move that to r12
to have a byte moved to the pound sign address in msg
. Like the last program, I'll end my breakdown here as the rest is pretty self evident and discuss my experience with x86_64.
This was pretty similar to assembly in AArch64 in a lot of ways, there were some minor syntactical differences like the precedence of operands listed above, and $ and % symbols being used to denote immediate values and registers, respectively. I'd be hard pressed to pick one I prefer, but if I had to I'd lean toward the AArch64 for its syntax, which I find slightly more readable. The difference is pretty negligible though. I also like the philosophy of improvement and not being weighed down by legacy features and nomenclature that comes with x86_64, but that hasn't affected my coding on either to any great extent thus far.
Overall this was a good challenge and I'm looking forward to diving deeper into these architectures.
Top comments (0)