Sebastian Karlsson
Sebastian Karlsson

Reputation: 745

x86 assembly, integer to string reversed

Having some problems where my string is in the wrong order.

I have written a function in assembly to convert an integer into a string:

.type itos, @function
itos:
 pushl %ebp
 movl %esp, %ebp
  
 movl 8(%esp), %eax           # number
 movl $12, %edi
 leal (%esp, %edi, 1), %ebx   # buffer
 movl $0, %edi                # counter

 pushl $0x0
 jmp itos_loop 


itos_loop:
  movl $0, %edx

  movl $10, %ecx
  div %ecx
  
  addl $48, %edx
  pushl %edx

  movb (%esp), %cl
  movb %cl, (%ebx, %edi, 1)

  test %eax, %eax
  je itos_end

  inc %edi

  jmp itos_loop

itos_end:
  movl %ebx, %eax
  movl %ebp, %esp
  popl %ebp
  ret

I call my function like this (converting the number 256 as an example):

subl $8, %esp  # making space for a buffer
pushl (%esp)   # pushing buffer as 2 argument to function
pushl $256     # pushing an integer as 1 argument to function
call itos
pushl %eax
call print

When I run this, I get the following output:

652

I understand why it is in reverse and I have some ideas on how to make it not in reverse. For example, instead of pushing (%esp) as the buffer, I could push 8(%esp) and then change my itos function to decrement instead of increment.

However I am not too keen on that solution.

What would be another efficient way of doing this?

And btw, my print that I am using in the code above looks like this:

print:
 pushl %ebp
 movl %esp, %ebp
 pushl 8(%esp)
 call strlen
 addl $4, %esp
 movl 8(%esp), %ecx
 movl %eax, %edx
 movl $4, %eax
 movl $1, %ebx
 movl %ebp, %esp
 popl %ebp
 int $0x80
 ret

Thank you!

Upvotes: 0

Views: 216

Answers (2)

Sebastian Karlsson
Sebastian Karlsson

Reputation: 745

Thanks to Peter Cordes, Here is my solution that works the way I want it to. It simply pops the digits in a separate loop:

.type itos, @function
itos:
 pushl %ebp
 movl %esp, %ebp
  
 movl 8(%esp), %eax           # number
 movl $12, %edi
 leal (%esp, %edi, 1), %ebx   # buffer
 movl $0, %edi                # counter
 movl $0, %esi

 pushl $0x0
 jmp itos_loop 


itos_loop:
  movl $0, %edx

  movl $10, %ecx
  div %ecx
  
  addl $48, %edx
  pushl %edx 

  test %eax, %eax
  je itos_buffer_loop

  inc %edi

  jmp itos_loop

itos_buffer_loop:
  popl %ecx
  movb %cl, (%ebx, %esi, 1)

  test %edi, %edi
  je itos_end

  dec %edi
  inc %esi

  jmp itos_buffer_loop

itos_end:
  movl %ebx, %eax
  movl %ebp, %esp
  popl %ebp
  ret

Upvotes: 0

Peter Cordes
Peter Cordes

Reputation: 364210

Some itoa functions push their digits in one loop, and pop them in another, so they can get them in MSD-first printing order after div generates LSD-first. But there is zero point in pushl %edx / movb (%esp), %cl - you're just reloading it right away and filling up the stack (before movl %ebp, %esp removes it). You might as well have just done movb %dl, (%ebx, %edi, 1).

The better way to handle this is to start at the end of a buffer and decrement a pointer, so your ASCII digits end up in memory in printing order, opposite of the order you generated, without any crappy push/pop loop.

See Printing an integer as a string with AT&T syntax, with Linux system calls instead of printf for a working version (using x86-64, but easy enough to port to 32-bit code.) How do I print an integer in Assembly Level Programming without printf from the c library? is a NASM version, also explaining the digit-ordering logic.


Also, it's silly not to return the length; you can easily do a pointer subtraction so the caller knows how many digits you stored in the buffer. That way they don't have to call strlen. (Or 0-terminate the buffer at all if they're just passing it to a write system call).

If you want to also return the pointer, you can do that too, in a different register. This is assembly language; you can easily return multiple separate things, not constrained by the inconvenience of C calling conventions.

Or if you're returning a pointer to the start of the string data after converting backwards, the caller can do the subtraction since they know what end-of-buffer pointer they passed to your itoa_end function in the first place.

Upvotes: 3

Related Questions