0

This is a simple project which creates a random digit from the system clock. It takes a number, transforms it into a digit, then adds 48 so it matches in the ASCII table. The problem I have is that it doesn't print anything. From what I can tell, it needs to be a string in order to even consider printing. Is there a way I can "cast" it? Like yeah, I know, it's an integer, but pretend that it's on the ascii table? Here's my code for reference. Everything works except "prnt_clock". Last thing, I cannot use any C code/functions so no, I cannot use printf.

.global rand

.equ    STDOUT, 1
.equ    WRITE, 4

rand:
    PUSH    {LR}
    BL      get_clock
    LDR     R1, =time
    ADD     R1, R1, #5
    LDRB    R0, [R1]
    BL      num_to_digit
    ADD     R0, R0, #48
    MOV     R1, R0
    BL      prnt_clock
    B       rand_leave

get_clock:
    MOV     R7, #78         @ Get time of day
    LDR     R0, =time       @ address of holder for time_t
    MOV     R1, #0          @ time zone not needed
    SWI     0
    MOV     PC, LR

// Assumes the number is in R0
num_to_digit:
    CMP     R0, #9          @ Compare num to 9
    SUBGT   R0, R0, #10     @ If greater than, substract 10
    BGT     num_to_digit    @ Repeat until its eq or lower than 9
    MOV     PC, LR          @ Comeback to main

prnt_clock:
    MOV     R0, #STDOUT     @ Print to terminal
    MOV     R2, #1          @ Print only the digit
    MOV     R7, #WRITE      @ Write Syscall
    @MOV        R3, #0          @ Create a counter
    SWI     0

rand_leave:
    POP     {LR}
    MOV     PC, LR

.data
time:   .space  9
BloodLord
  • 49
  • 5
  • You need to store your integer (ascii char) in memory and pass its address. See man 2 write. – Jester Mar 16 '21 at 20:23
  • So how do I even store it in memory? Do I make a label asciz filled with spaces and then store it inside? I have honestly no idea how I'm suppose t do this. I tried STR, and LDR. but I can't find a way to store the integer in memory/ – BloodLord Mar 16 '21 at 20:46
  • Yes, allocating a static buffer would work. It doesn't have to be zero terminated and if it's a single digit (character) you can use `STRB`. You can also push it to the stack and pass its address (but then don't forget to clean it up afterwards). – Jester Mar 16 '21 at 20:53
  • repeated-subtraction is a really slow algorithm for div/remainder, especially with a small divisor like 10. See [How do I print an integer in Assembly Level Programming without printf from the c library?](https://stackoverflow.com/a/46301894) for a C algorithm, and x86 Linux asm implementation. You could compile the C to get ARM code, and adapt the stack space buffer handling from my x86 asm to ARM. – Peter Cordes Mar 17 '21 at 03:21

1 Answers1

2

Normally, you use printf or other libraries to print an integer.

If you are looking for a standalone program, use a compiler to compile C for assembly output:

/*
    r3 = magic division number
    r2 = division
    r0 = integer to be converted
*/

.syntax unified
.equ division, 0x66666667 // 1717986919; (2 ^ 34 + 6) / 10

.section .text

    .global _start
    _start:
        push {ip}
        mov r1, #0x0a // 10; newline
        push {r1}
        mov r7, #0x04 // write syscall
        /*
            If your architecture is ARMv7 (or above).
            movw r3, #:lower16:division // 26215
            movt r3, #:upper16:division // 26214
        */
        ldr r3, =division
        mov r0, #(0x01 << 0x1e) // replace this

    _division:
        smmul r1, r0, r3
        asr r2, r1, #0x02

    _modulo:
        add r1, r2, r1, lsr #0x1f // 31
        add r1, r1, r1, lsl #0x02
        sub r0, r0, r1, lsl #0x01

    _string:
        add r0, #0x30 // 48; '0'
        push {r0}

    _shift:
        mov r0, r2
        cmp r0, #0x00
        bne _division

    _write:
        mov r1, sp
        ldr r3, [r1]
        cmp r3, #0x00
        beq _end

        mov r0, #0x01 // stdout file descriptor
        pop {ip}
        mov r2, #0x01 // length
        swi 0x00 // execute syscall

        bne _write


    _end:
        pop {ip}
        mov r7, #0x01 // exit syscall
        mov r0, #0x00 // exit status
        swi 0x00 // execute syscall

Github (More up-to-date): https://raw.githubusercontent.com/GrpeApple/Assembly/test/ARM/src/arm/int2str.S

If you want this to be your dependency: https://raw.githubusercontent.com/GrpeApple/Assembly/test/ARM/src/deps/int2str.S (uses registers r0-r2)

See Why does GCC use multiplication by a strange number in implementing integer division? for the smmul / asr trick to divide by 10 more efficiently than an actual divide instruction.

You use modulo and division to get the last integer and next-first-to-last integer respectively. You use division to check until zero.

It adds the ascii value of '0'. I have also included in the comments for ARMv7.

  • If you have code to share, especially if you wrote it just for this answer, *include it in your answer directly* (in a code block with triple backticks), not via a link. (Especially not off-site links; linking other SO Q&As is ok if the question itself shouldn't just be closed as a duplicate.) – Peter Cordes Mar 17 '21 at 14:37
  • Yeah, my thought was: "I am updating the github repo constantly, so I might link it." But I did not realize I could include both. –  Mar 17 '21 at 15:11
  • Oh, yeah you could still include a link, and a note that it might be more up-to-date than the answer, as long as you do include code here. – Peter Cordes Mar 17 '21 at 15:14
  • `ldr r1, =division` could be hoisted out of the loop if you used a different register, especially if you generate a string in a buffer and call `write` *once* for the whole thing, like the C in [How do I print an integer in Assembly Level Programming without printf from the c library?](https://stackoverflow.com/a/46301894). Then you don't need to put system-call args in regs until once at the end. – Peter Cordes Mar 17 '21 at 16:45