10

This needs to be done in pure assembly (ie. no libraries or calls to C).

I understand the essence of the problem: one needs to divide the integer by 10, convert the one-digit remainder to ASCII, output that and then repeat the process with the quotient.

But for some reason, it's just not working. I'm using NASM on x86.

Here's what I have up to now (doesn't output anything, but doesn't throw any assembler errors either):

; integer to output is stored in eax
mov ecx, 10   ; for base 10

loop:
div ecx  ;EAX contains the quotient, EDX the remainder

; Do something to EDX to convert it to ASCII, not sure if this is correct
add edx, '0'

push eax    ;We'll be playing with EAX to output EDX, save EAX to the stack

mov eax, 4              ; sys_write
mov ebx, 1              ; to STDOUT
mov ecx, edx
mov edx, 1
int 0x80

pop eax  ;restore EAX

cmp eax, 0   ;If EAX is 0, our job is done
jnz loop

There are a number of questions similar to this one (namely, this and this), but I'm lost in the implementation. This question (for DOS) was also helpful, but I'm still confused.

I must be missing something here. Thoughts?

Community
  • 1
  • 1
David Chouinard
  • 6,466
  • 8
  • 43
  • 61
  • I don't see the constant `10` anywhere in this code, which would be necessary for printing out the decimal digits. (You need to repeatedly `div` and `mod` with `10`, store the digits, reverse them, and output them. Or go from the 'big end' first, and divide by `100,000,000`, then by `10,000,000`, etc. -- but that might not be any less complicated than the store-and-reverse method.) – sarnold Nov 07 '11 at 01:30
  • @sarnold Forgot to include a line at the top. Edited my question to add it. – David Chouinard Nov 07 '11 at 01:33
  • possible duplicate of [NASM Linux Assembly Printing Integers](http://stackoverflow.com/questions/6903435/nasm-linux-assembly-printing-integers) – Ciro Santilli OurBigBook.com May 25 '15 at 06:46
  • Related: [How do I print an integer in Assembly Level Programming without printf from the c library?](//stackoverflow.com/a/46301894) has a working implementation for x86-64 Linux, trivial to port to 32-bit `int 0x80` system calls. – Peter Cordes Apr 02 '19 at 03:50
  • Basically a duplicate of [How to print a character in Linux x86 NASM?](https://stackoverflow.com/q/21429355), or that's a duplicate of this. Either way, same problem of passing an ASCII char value instead of a pointer to ASCII chars in memory. – Peter Cordes Sep 20 '21 at 17:59

2 Answers2

5

There are at least two more problems. beyond the corruption of ecx that @sarnold mentioned:

  1. div ecx divides the 64-bit value edx:eax by ecx, so you need to ensure that you set edx to 0 before the division.

  2. The second argument to the write system call (in ecx) should be a pointer to a buffer containing the character you want to print, not the character itself.

One way to solve the second problem is to push the register containing the character you want to print on the stack, and then assign the stack pointer esp to ecx (the stack pointer points at the most recently pushed item, and x86 stores values little-endian, so the first byte is the low 8 bits). e.g.

push edx         ; save value on stack
mov  eax, 4      ; sys_write
mov  ebx, 1      ; to STDOUT
mov  ecx, esp    ; first byte on stack
mov  edx, 1      ; length = one byte
int  0x80
pop  edx         ; remove what we pushed (or "add esp, 4" would do just as well here;
                 ;                        we don't need the actual value again)

That should be enough to get some output...

(But at that point, you might notice a "feature" of your algorithm, and want to re-think how you store the digits that are produced by the division!)

Matthew Slattery
  • 45,290
  • 8
  • 103
  • 119
  • This is a great explanation, thanks for clarifying it. As you hint to, I understand why this algorithm prints the integers in reverse. Any thoughts on fixing that? – David Chouinard Nov 08 '11 at 15:26
  • 2
    Reserve some space for the digits (the largest unsigned 32-bit integer is 4294967295, so 10 bytes is sufficient), either in a data section (e.g. using the `resb` directive) or on the stack (just subtract from `esp`, but make sure you subtract a multiple of 4 to keep the stack properly aligned). Then either store the digits into the buffer going forwards, then loop over the buffer in reverse printing each character; or (better?) store the digits into the buffer starting from the last byte of the buffer and working backwards, then you can write all the digits with a single `write` syscall. – Matthew Slattery Nov 08 '11 at 21:29
  • @DavidChouinard: [How do I print an integer in Assembly Level Programming without printf from the c library?](https://stackoverflow.com/a/46301894) puts digits in a buffer (starting from the end), and makes *one* print system call when it's done. Much more efficient. – Peter Cordes Nov 09 '20 at 23:36
2

You properly set ecx to 10 at the top of your routine, but overwrite ecx later:

mov eax, 4              ; sys_write
mov ebx, 1              ; to STDOUT
mov ecx, edx ;;; oops -- lost the 10
mov edx, 1
int 0x80

Try moving the loop up one line, so ecx is re-initialized to 10 each time through the loop.

sarnold
  • 102,305
  • 22
  • 181
  • 238
  • Good catch, fixed it. I'm still not getting any output, my guess is that `add edx, '0'` is not correctly converting the single digit to decimal. – David Chouinard Nov 07 '11 at 01:41