Printing a loop message output on both x86_64 and Aarch64 architectures.
Loop: 00 Loop: 01 Loop: 02 Loop: 03 Loop: 04 Loop: 05 Loop: 06 Loop: 07 Loop: 08 Loop: 09 Loop: 10 Loop: 11 ... Loop: 30
This is a reference guide to what our group followed in order to generate the output above on a x86_64 architecture.
.text /* or section.text - used for keeping the actual code */ .globl _start /* tells kernel where program execution begins */ start = 0 /* starting value for the loop index; note that this is a symbol (constant), not a variable */ max = 31 /* loop exits when the index hits this number (loop condition is i<max) */ zero = 48 /* start of decimal notation for character 0 */ _start: mov $start,%r15 /* loop index start is in register 15 */ mov $10,%r13 /* put value 10 into register 13 */ loop: mov $zero,%r14 /* loop index */ mov %r15,%rax /* move data from register 15 into accumulator register */ mov $0,%rdx /* put value 0 into data register */ div %r13 /* divide rax by register 13 and put quotient into rax and remainder into rdx*/ mov $zero,%r14 /* loop index */ add %rax,%r14 /* add quotient(in rax) to register 14 */ movb %r14b,msg+6 /* add 6 bits to register 14 */ mov $zero,%r14 /* loop index */ add %rdx,%r14 /* add remainder(in rdx) to register 14 */ movb %r14b,msg+7 /* add 7 bits to register 14*/ movq $len,%rdx movq $msg,%rsi movq $1,%rax 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 .data /* for SEGFAULT error, stores as read/write */ msg: .ascii "Loop: \n" len = . - msg
Our idea here was to leave some blank space in our msg string “Loop: \n”, and have the quotient and remainder of 10 divided by the loop index added to the address of the msg + 6 bytes for the first digit and msg + 7 bytes for the second digit to produce the 2-digit decimal number after “Loop: “.
gdb’s layout asm was a useful debugging tool to help me figure out exactly what was going on in the memory and registers.
So for example, on the source code above(“lab3.s”), you could do:
as -g -o lab3.o lab3.s
- -o: output filename
- -g: request that symbol information be attached to the binary for debugging purposes
Run the linker:
ld -o lab3 lab3.s
Run GNU debugger:
Some useful debugging commands:
- Set a breakpoint:
- Start program:
- Switch to asm layout:
- Step one line:
- Step over (don’t go into function call):
- Print value in register:
info register 14 or i r 14
- Print value in specific address:
print "%s\n", 0x30
- Add x number of bits to an address:
print "%s\n", 0x30+4
print "%s\n", 0x30+8
Here is a reference to replicate the same output on the AArch64 architecture.
I was able to replicate a similar solution to the one for the x86_64 architecture.
.text .globl _start start = 0 /* loop index */ max = 31 /* number to stop loop at */ num = 48 /* starting ascii value for 0 number */ /* index = %r10 - to store register 10 into index macro */ _start: mov x3,start loop: mov x4,10 /* for calculating quotient/remainder */ /* calculate quotient - store in x6 */ udiv x6,x3,x4 /* x6 = x3 / 10 */ /* calculate remainder - store in x7 */ msub x7,x4,x6,x3 /* x7 = x3 - (10 * x6) */ adr x1, msg /* msg location memory address */ add x6,x6,num /* atoi */ add x7,x7,num /* atoi */ strb w6,[x1,6] /* store in msg + 6 bytes memory location */ strb w7,[x1,7] /* store in msg + 7 bytes memory location */ /* print */ mov x0,1 /* file descriptor: 1 is stdout */ mov x2,len mov x8,64 /* write is syscall #64 */ svc 0 /* invoke syscall */ add x3,x3,1 /* increment index */ cmp x3,max /* check for end of loop */ bne loop /* loop if compare returns false */ mov x0,0 /* status -> 0 */ mov x8,93 /* exit is syscall #93 */ svc 0 /* invoke syscall */ .data msg: .ascii "Loop: \n" len = . - msg
Differences between the x86_64 and Aarch64 architectures syntax:
- Register names:
- x86_64: prefixed by %
- Aarch64: not prefixed
- x86_64: prefixed by $
- Aarch64: not prefixed
- Aarch64: two operator functions are required for this.
div %r13 – calculates the quotient and remainder. It takes one register argument (%r13), and divides value in rax from it, storing the quotient in rax, and remainder in rdx
udiv x6,x3,x4 – divides x3 by x4 and stores quotient into x6
msub x7,x4,x6,x3 – subtracts x3 by (x4 * x6) and stores remainder in x7
- x86_64: Data sources are given as first argument
- Aarch64: Destination is given as first argument
Wrapping my head around how the different registers work, memory operations and overall syntax was difficult, but once I had a look at the disassembly, I was able to get a better view of what was going on in memory after analyzing each executed line of code. After that, translating the syntax from one architecture to another was relatively simple, at least for what was required for generating this output.
More useful links:
ARM Instruction Set Overview