How to Print a Number in Arm Assembly

How to print a number in ARM assembly?

The syscall write takes on the second argument (r1) as a pointer to the string you want to print. You are passing it a pointer to an integer, which is why it's not printing anything, because there are no ASCII characters on the memory region you are passing to it.

Below you'll find a "Hello World" program using the syscall write.

.text
.global main
main:
push {r7, lr}

mov r0, #1
ldr r1, =string
mov r2, #12
mov r7, #4
svc #0

pop {r7, pc}

.data
string: .asciz "Hello World\n"

If you want to print a number you can use the printf function from the C library. Like this:

.text
.global main
.extern printf
main:
push {ip, lr}

ldr r0, =string
mov r1, #1024
bl printf

pop {ip, pc}

.data
string: .asciz "The number is: %d\n"

Finally, if you want to print the number with the syscall write you can also implement a itoa function (one that converts an integer to a string).

Add and print two numbers in ARM assembly

Your fix works because all architectures follow a certain calling convention, where registers are mapped to function parameters and return value. In this case, scanf takes two parameters so according to the ARM EABI calling convention the first parameter would be in r0 and the second in r1.

How can I print Numbers with aarch64, without using C

As Jester points out, the reason you can't print is that the write system call expects a pointer to the data to be written in x1, not the data itself. You need to store your character to some appropriate address in memory (strb) and pass that address in x1.

One approach would be to use the stack; just subtract from the stack pointer (in multiples of 16, for alignment) to allocate some memory for yourself, and remember to put it back when you're done. Here's an example that should work; XXX are the lines I added or changed.

printNumberEntry:                      /* XXX */
sub sp, sp, #16 /* XXX allocate 16 bytes of stack space */
printNumber:
mov x16, #10 /* apparently need this for udiv/msub */
udiv x14, x12, x16 /* x12 is the number I defined above, bc idk what registers are save to use (e.g. when syscall 64, print, happens, 0-8 are used) */
msub x13, x14, x16, x12 /* XXX fix unrelated bug */
sub x12, x12, x13 /* x13 is what above is digit, x12 is number */
udiv x12, x12, x16
add x13, x13, #48 /* digit to string, possible error source 1 */

strb w13, [sp] /* XXX Store the low byte of x13/w13 in memory at address sp */

mov x0, #1 /* the print part */
mov x1, sp /* XXX x1 points to the byte to be written */
mov x2, #1
mov w8, #64
svc #0

cmp x12, #0 /* the loop part */
beq exit /* genereric exit method I left out */
b printNumber

exit: /* XXX */
add sp, sp, #16 /* XXX restore stack before returning */
ret /* XXX */

I also fixed an unrelated bug: in your msub, x17 should be x14, since that's where the quotient is.

Another approach would be to reserve a byte in static memory:

        .bss
dataToWrite:
.resb 1
.text
printNumberEntry: /* XXX */
adr x1, dataToWrite /* XXX keep address in x1 throughout */
printNumber:
mov x16, #10 /* apparently need this for udiv/msub */
udiv x14, x12, x16 /* x12 is the number I defined above, bc idk what registers are save to use (e.g. when syscall 64, print, happens, 0-8 are used) */
msub x13, x14, x16, x12 /* XXX fix unrelated bug */
sub x12, x12, x13 /* x13 is what above is digit, x12 is number */
udiv x12, x12, x16
add x13, x13, #48 /* digit to string, possible error source 1 */

strb w13, [x1] /* XXX Store the low byte of x13/w13 at dataToWrite */

mov x0, #1 /* the print part */
/* x1 already contains the proper address */
mov x2, #1
mov w8, #64
svc #0

cmp x12, #0 /* the loop part */
beq exit /* genereric exit method I left out */
b printNumber

exit: /* XXX */
ret /* XXX */
/* your code */

The downside is this will make your function unusable for signal handlers, multiple threads, etc, as they will all try to use the same byte.

Other notes:

  • The digits print in reverse order, of course. That's something I guess you will work on later.

  • The sub and second udiv are unnecessary, because the first udiv already produces the rounded-down quotient (like Python's x14 = x12 // 10). So you could replace those two instructions with just mov x12, x14.

  • Several of your registers hold constant values throughout the whole function (x16, x1, x2, x8), so you could initialize them outside the loop instead of redundantly redoing it on every iteration. (Note x0 is overwritten with the system call's return value, so you do need to reinitialize it each time.)

  • You really want to find a way to be able to use a debugger. Otherwise, get ready for a lot more instances where you spend half an hour writing a StackOverflow question and waiting many more hours for an answer, for a bug that you could have found yourself in three minutes with a debugger. If you can't install gdb on your Android device, then consider setting up another Linux AArch64 box (say a Raspberry Pi 4, or a cloud server, or even a qemu emulator) where you can.

ARM printing hexadecimal number from register (not using C)

I suspect that SWI_WriteC takes the ASCII code of a character to print and then prints it to the console. So, if r0 is 2 on entry, it will print the character with code 2.

However, the character with ASCII code 2 is not the digit 2. It is a non-printing character. The ASCII code for the digit 2 is 50, or 0x32. In fact, the codes for digits 0 to 9 are 48 to 57, or 0x30 to 0x39, with the letters A to F at 65 to 70 (0x41 to 0x46) and lower-case letters a to f at 97 to 102 (0x61 to 0x66).

So, what you want is a routine that takes a number in the range 0 to 15, converts it into the ASCII code for the relevant digit or letter and then passes that ASCII code to SWI_WriteC.

That will work, assuming you don't have any more than 15 vowels. If you have 16 or more, your hex number will have two or more digits, and this is where things get a little trickier.



Related Topics



Leave a reply



Submit