Search code examples
stringassemblyintnasm

Printing an Int (or Int to String)


I am looking for a way to print an integer in assembler (the compiler I am using is NASM on Linux), however, after doing some research, I have not been able to find a truly viable solution. I was able to find a description for a basic algorithm to serve this purpose, and based on that I developed this code:

global _start

section .bss
digit: resb 16
count: resb 16
i: resb 16

section .data

section .text

_start:
mov             dword[i], 108eh         ; i = 4238
mov             dword[count], 1
L01:
mov             eax, dword[i]
cdq
mov             ecx, 0Ah
div             ecx  
mov             dword[digit], edx

add             dword[digit], 30h       ; add 48 to digit to make it an ASCII char
call            write_digit

inc             dword[count]

mov             eax, dword[i]
cdq
mov             ecx, 0Ah
div             ecx  
mov             dword[i], eax 
cmp             dword[i], 0Ah  
jg              L01

add             dword[i], 48            ; add 48 to i to make it an ASCII char
mov             eax, 4                  ; system call #4 = sys_write
mov             ebx, 1                  ; file descriptor 1 = stdout
mov             ecx, i                  ; store *address* of i into ecx
mov             edx, 16                 ; byte size of 16
int             80h

jmp             exit

exit:
mov             eax, 01h                ; exit()
xor             ebx, ebx                ; errno
int             80h

write_digit:
mov             eax, 4                  ; system call #4 = sys_write
mov             ebx, 1                  ; file descriptor 1 = stdout
mov             ecx, digit              ; store *address* of digit into ecx
mov             edx, 16                 ; byte size of 16
int             80h
ret

C# version of what I want to achieve (for clarity):

static string int2string(int i)
{
    Stack<char> stack = new Stack<char>();
    string s = "";

    do
    {
        stack.Push((char)((i % 10) + 48));
        i = i / 10;
    } while (i > 10);

    stack.Push((char)(i + 48));

    foreach (char c in stack)
    {
        s += c;
    }

    return s;
}

The issue is that it outputs the characters in reverse, so for 4238, the output is 8324. At first, I thought that I could use the x86 stack to solve this problem, push the digits in, and pop them out and print them at the end, however when I tried implementing that feature, it flopped and I could no longer get an output.

As a result, I am a little bit perplexed about how I can implement a stack in to this algorithm in order to accomplish my goal, aka printing an integer. I would also be interested in a simpler/better solution if one is available (as it's one of my first assembler programs).


Solution

  • One approach is to use recursion. In this case you divide the number by 10 (getting a quotient and a remainder) and then call yourself with the quotient as the number to display; and then display the digit corresponding to the remainder.

    An example of this would be:

    ;Input
    ; eax = number to display
    
        section .data
    const10:    dd 10
        section .text
    
    printNumber:
        push eax
        push edx
        xor edx,edx          ;edx:eax = number
        div dword [const10]  ;eax = quotient, edx = remainder
        test eax,eax         ;Is quotient zero?
        je .l1               ; yes, don't display it
        call printNumber     ;Display the quotient
    .l1:
        lea eax,[edx+'0']
        call printCharacter  ;Display the remainder
        pop edx
        pop eax
        ret
    

    Another approach is to avoid recursion by changing the divisor. An example of this would be:

    ;Input
    ; eax = number to display
    
        section .data
    divisorTable:
        dd 1000000000
        dd 100000000
        dd 10000000
        dd 1000000
        dd 100000
        dd 10000
        dd 1000
        dd 100
        dd 10
        dd 1
        dd 0
        section .text
    
    printNumber:
        push eax
        push ebx
        push edx
        mov ebx,divisorTable
    .nextDigit:
        xor edx,edx          ;edx:eax = number
        div dword [ebx]      ;eax = quotient, edx = remainder
        add eax,'0'
        call printCharacter  ;Display the quotient
        mov eax,edx          ;eax = remainder
        add ebx,4            ;ebx = address of next divisor
        cmp dword [ebx],0    ;Have all divisors been done?
        jne .nextDigit
        pop edx
        pop ebx
        pop eax
        ret
    

    This example doesn't suppress leading zeros, but that would be easy to add.