6

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).

Sam
  • 7,252
  • 16
  • 46
  • 65
jszaday
  • 322
  • 1
  • 4
  • 12
  • 1
    That C# code is horrendous. In general (for all high level languages) there's nice easy to use abstractions (like `stack.push()`) that exist to prevent people from realising how bad the generated code actually is. Note: I dare you to disassemble the code generated by that C#.. ;-) – Brendan Nov 23 '12 at 05:57
  • I agree, I just threw it together in 5 minutes or so to demonstrate what I hope to achieve using assembler. – jszaday Nov 23 '12 at 14:08

4 Answers4

7

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.

Brendan
  • 35,656
  • 2
  • 39
  • 66
  • Thank you for your reply, I am a little bit confused about how the printNumber function in your first example works. Firstly, why are you doing a 'xor' before the division? Also, shouldn't the 'test eax,eax je .l1' be a jz (testing for zero). Also, is the character to print stored in eax? – jszaday Nov 23 '12 at 14:31
  • `xor edx,edx` just sets EDX to zero (which is necessary for the division). The `je` instruction (jump if equal) and the `jz` instruction (jump if zero) are synonyms (they are exactly the same opcode/instruction). The character to print would be in EAX. – Brendan Nov 24 '12 at 04:47
  • The other standard way to print in MSD-first printing order is to store digits into a buffer (on the stack), as in [How do I print an integer in Assembly Level Programming without printf from the c library?](https://stackoverflow.com/a/46301894). Especially when printing a whole string is about as cheap as printing a character (system call or even just stdio function call overhead.) – Peter Cordes Jun 17 '21 at 23:56
1

I think that maybe implementing a stack is not the best way to do this (and I really think you could figure out how to do that, saying as how pop is just a mov and a decrement of sp, so you can really set up a stack anywhere you like by just allocating memory for it and setting one of your registers as your new 'stack pointer'). I think this code could be made clearer and more modular if you actually allocated memory for a c-style null delimited string, then create a function to convert the int to string, by the same algorithm you use, then pass the result to another function capable of printing those strings. It will avoid some of the spaghetti code syndrome you are suffering from, and fix your problem to boot. If you want me to demonstrate, just ask, but if you wrote the thing above, I think you can figure out how with the more split up process.

deftfyodor
  • 294
  • 1
  • 9
  • Thank you for your advice, it is very valuable! I will try and write a function like the one you have described, and if I need more help than I will ask! – jszaday Nov 23 '12 at 14:15
  • I got a version working [here](http://dl.dropbox.com/u/18474025/Code/Assembler/ideone_Ncqsd0.asm), and I think that I now know how to use strings to acheive this task, I created [this program](http://dl.dropbox.com/u/18474025/Code/Assembler/ideone_1LXoVN.asm) for learning purposes. However, my question is how would I reverse a string? – jszaday Nov 23 '12 at 22:14
  • While there are several ways to reverse a string, I will suggest the following algorithm as an example: 1) Count characters in the string K (call that number n) ;2) Let i=0, j=n-1; 3) While j-i>0; 3.1) Swap K[i] and K[j] ; 3.2) Increment i and decrement j ; 4) Return K; – deftfyodor Nov 23 '12 at 22:42
  • You could also use a stack, and that would require you to: 1) Allocate memory for the stack; 2) push the string onto the stack; 3) pop the characters off again;. That would require using extra memory, and the algorithm itself isn't terribly nice, but it's pretty easy to do, basically in the same way you handled the working version of your integer-printer. – deftfyodor Nov 23 '12 at 22:47
1
; Input
; EAX = pointer to the int to convert
; EDI = address of the result
; Output:
; None
int_to_string:
    xor   ebx, ebx        ; clear the ebx, I will use as counter for stack pushes
.push_chars:
    xor edx, edx          ; clear edx
    mov ecx, 10           ; ecx is divisor, devide by 10
    div ecx               ; devide edx by ecx, result in eax remainder in edx
    add edx, 0x30         ; add 0x30 to edx convert int => ascii
    push edx              ; push result to stack
    inc ebx               ; increment my stack push counter
    test eax, eax         ; is eax 0?
    jnz .push_chars       ; if eax not 0 repeat

.pop_chars:
    pop eax               ; pop result from stack into eax
    stosb                 ; store contents of eax in at the address of num which is in EDI
    dec ebx               ; decrement my stack push counter
    cmp ebx, 0            ; check if stack push counter is 0
    jg .pop_chars         ; not 0 repeat
    mov eax, 0x0a
    stosb                 ; add line feed
    ret                   ; return to main
0
; eax = number to stringify/output
; edi = location of buffer

intToString:
    push  edx
    push  ecx
    push  edi
    push  ebp
    mov   ebp, esp
    mov   ecx, 10

 .pushDigits:
    xor   edx, edx        ; zero-extend eax
    div   ecx             ; divide by 10; now edx = next digit
    add   edx, 30h        ; decimal value + 30h => ascii digit
    push  edx             ; push the whole dword, cause that's how x86 rolls
    test  eax, eax        ; leading zeros suck
    jnz   .pushDigits

 .popDigits:
    pop   eax
    stosb                 ; don't write the whole dword, just the low byte
    cmp   esp, ebp        ; if esp==ebp, we've popped all the digits
    jne   .popDigits

    xor   eax, eax        ; add trailing nul
    stosb

    mov   eax, edi
    pop   ebp
    pop   edi
    pop   ecx
    pop   edx
    sub   eax, edi        ; return number of bytes written
    ret
cHao
  • 84,970
  • 20
  • 145
  • 172
  • 1
    This code will not work unless the string happens to be UTF-32. For 32-bit code, "push" stores 32-bit values and not 8-bit values, so the value 123 will end up being something like "1\0\0\02\0\0\03\0\0\0". When displayed as an ASCII or UTF-8 string, those unwanted zeros are string terminators that would mean only 1 digit is displayed. – Brendan Nov 23 '12 at 08:18
  • i am clueless in this subject, should I write `mov eax, number` `mov edi, [buffer]` before calling the method? – İsmet Alkan Jul 02 '14 at 06:45
  • 1
    @IsmetAlkan: Depends on what `buffer` is. If it's the actual block of memory, then you'd want to say `mov edi, buffer` so that EDI contains the address `buffer` rather than the first dword of it. – cHao Jul 02 '14 at 07:40
  • 1
    @IsmetAlkan: Likewise, if `number` is the label for some memory containing a number, then you'd say `mov eax, [buffer]` so that EAX contains the contents of that memory. If it's just an `equ`, on the other hand, what you have is fine. – cHao Jul 02 '14 at 08:01