For the next couple tasks I want to do in assembly, I need to be able to inspect an array of numbers. This is useful for debugging searching and sorting algorithms. Since my last attempt to convert binary to ASCII was so ugly, I figured I would clean that up at the same time.
It turns out I can use the reverse code as well.
Unlike my last couple of posts where I show each of the interim steps, I have made this one work, and will just go through the final product. There was a lot of trial and error getting this to work, and I can see the need for getting organized in Assembly. The scale of the programs will quickly get beyond the scope of what I can keep in my head.
One thing this code required me to learn was how to nest function calls. I have two functions, one which calls the other. Thus, the return location for the first needs to get pushed on the stack before it calls the second, and needs to get popped off the stack at the end.
Here is the program main body. It is a function call, and a system call
_start: bl display_array /* syscall exit (int status) */ mov x0, #0 /* status := 0 */ mov w8, #93 /* exit is syscall #1 */ svc #0 |
I can now see how the C programming language evolved from assembler macros.
Here is the logic to convert binary to ASCII decimal. It puts into the provided buffer in reverse order, from least to most significant digit. This little endian approach means we do not have to pre-calculate the size of the buffer.
display_array: str LR, [SP, #16]! /*read the first value out of the array. In the final implementation, I need to be able to advance this pointer value by bvalue */ mov x0, #0 ldr x6, =Array loop1: /* this is the pointer to location where we write the latest value. At the end of the loop, it points to the end of the text written. */ ldr x8, =msg /* buf := msg */ ldr x7, =msg /*x2 has the result of the msub that we want to use as the basis on the second and additional iterations. by loading the initial value into x2, we keep that logic at the start of the loop. In doing so, we allow x2 to stay unchanged at the end of the loop, where we us it for the comparison. */ ldr w2, [x6] mov x1, #10 loop2: /* caluculate modulus of the working binary number */ mov x0, x2 udiv x2, x0, x1 msub x3, x2, x1, x0 /* convert binary to ascii digit */ add x3, x3, #48 strb w3, [X8] add x8, x8 ,#1 cmp x2, #0 B.NE loop2 |
It does mean we have to reverse the string at the end.
/* reverse the string */ ldr x1, =msg /* buf := msg */ sub x2, X8, X7 /* count = len */ add x2, x2, #1 bl reverse |
Append a space and null terminate the string (Probably not needed) and print out the buffer.
mov x3, #32 /*space */ strb w3, [X8] add x8, x8, #1 mov x3, #0 /*null terminate the string */ strb w3, [X8] /* calculate the string length of the number we just wrote*/ sub x2, x8, x7 /*syscall write(int fd, const void *buf, size_t count) */ mov x0, #1 /* fd := STDOUT_FILENO */ ldr x1, =msg /* buf := msg */ mov w8, #64 /* write is syscall #64 */ svc #0 /*invoke syscall */ |
Advance the pointer to the initial buffer by 4 bytes, which is the size of a word. Make sure we don’t go off the end when we loop to the beginning.
add x6, x6, #4 ldr x1, =Array ldr x2, =ArrayLen /* count = len */ add x2, x1, x2 cmp x6, x2 B.LT loop1 |
After we print the buffer, print a final newline so we can compare it with the next line.
mov x0, #1 /* fd := STDOUT_FILENO */ ldr x1, =newline /* buf := msg */ ldr x2, =len_newline /* buf := msg */ mov w8, #64 /* write is syscall #64 */ svc #0 /*invoke syscall */ |
Restore the stack and return to the calling location.
LDR LR, [SP], #16 ret |
In AARCH64 assembly, we have 16 General purpose registers. In order to keep from pushing and popping registers onto and off of the stack, I use a separate number of registers for the reverse code, allowing me to leave the values in the registers that are used for the numeric conversions. Thus, my reveser function now looks like this.
reverse: add X14, X1, 0 add X16, x1, x2 sub X16, X16, #1 loop: sub X16, X16, #1 ldrb w15, [x14] ldrb w17, [x16] strb w15, [X16] strb w17, [X14] add X14, X14, #1 cmp X14, X16 b.lt loop ret |
I just added 10 to the register values in the first version.
This function does not call another function, so it does not need to save and restore the return value in LR.
Here is the output
503 87 61 908 170 897 709 901 271 528 834 154 231 303 435 707 424 861 |