3
\$\begingroup\$

I've written a short script to print a number to a string. This actually was very difficult for me to do -- I'm not sure if this is actually a tricky task or just because I'm so new to asm I was having a hard time. Here it is:

SYS_EXIT    = 60
SYS_WRITE   = 1
SYS_STDOUT  = 1

.section .rodata
number: .long   774728

.text
.globl _start
_start:

    
    # set up stack, align on 16 for syscalls
    push %rbp
    mov %rsp, %rbp
    push number
    sub $16, %rsp

    # r12 will store the size of the string to print
    xor %r12d, %r12d

    # on the stack we will store:
    # rbp-16 (properly sorted number to print)
    # rbp-8  (number that remains)
    # rbp-4  (current right-most digit, ie,remainder)

  loop:

    # if the number is zero, jump to print it
    cmpl $0, -8(%rbp)
    je print
    inc %r12d # increment the size of the string to print

    # Divide by ten
    # - %rdx will give us the remainder (right-most digit)
    # - %rax will give us the new number (for our next iteration of the loop
    xor %edx,       %edx
    movl -8(%rbp),  %eax
    mov $10,        %ebx
    div %ebx

    movl %eax,  -8(%rbp)

    # (a) add 48, because ASCII is number + 48, for example ord('7') ==> 55
    addl $48,   %edx
    # (b) move the asci number to rbp-8-len (to print in reverse)
    # store offset (8+len) in %r13
    mov $-8,        %r13
    sub %r12,       %r13
    movb %dl,       (%rbp, %r13)

    jmp loop

    
  print:
    # print(%edi:stdout(int), %esi:mem_of_string(* void), %edx:len_of_string(int))
    # (b) How to print the digit to asci (probably fixed offset for 0-9)
    # Our output has length %r12 and starts at rbp-8-len
    # Which, conveniently enough, is (%rbp, %r13)!
    mov $SYS_STDOUT,    %edi
    lea (%rbp, %r13),   %rsi    # remember, this is an 8-byte MEMORY ADDRESS (pointer), not a number
    mov %r12d,          %edx
    mov $SYS_WRITE,     %eax
    syscall
    
  exit:
    add $8, %rsp    # <-- can also do an empty pop, such as pop %rcx
    mov             %rbp, %rsp
    pop             %rbp
    mov $0,         %rdi
    mov $SYS_EXIT,  %eax
    syscall

And running it:

13_strings$ as file.s -o file.o && ld file.o -o file && ./file
# 774728

Thank you for taking the time to review. Any feedback would be greatly appreciated!

\$\endgroup\$
  • 1
    \$\begingroup\$ There's no need to use any call-preserved registers for this (R12..R15, or RBX or RBP). Printing an integer as a string with AT&T syntax, with Linux system calls instead of printf / How do I print an integer in Assembly Level Programming without printf from the c library? are my AT&T and NASM examples of doing this nicely. \$\endgroup\$ – Peter Cordes Sep 19 at 5:29
  • \$\begingroup\$ Did you copy that push number from What to do with an empty pop?? Like I commented there, that's a qword (8-byte) load from a 4-byte .long. The high bytes could hold random garbage depending what gets linked next to .rodata. (However, you later only look at the low 4 bytes). Also, IDK why you're using stack space at all; x86-64 has lots of registers. \$\endgroup\$ – Peter Cordes Sep 19 at 5:37
  • \$\begingroup\$ @PeterCordes thanks for your tips and feedback. How were you able to use/import the mov $__NR_write, %eax ? \$\endgroup\$ – samuelbrody1249 Sep 19 at 5:38
  • 1
    \$\begingroup\$ From #include <asm/unistd_64.h>, and assemble with gcc foo.S so it preprocesses your file with CPP before assembling. \$\endgroup\$ – Peter Cordes Sep 19 at 5:39
  • \$\begingroup\$ @PeterCordes registers: I think when I planned writing it I was going to call functions so I didn't want to use any call-clobbered registers, but then I did it all in _start. Oh I see about the high byte, it could hold an invalid/garbage value so needs to be accounted for. \$\endgroup\$ – samuelbrody1249 Sep 19 at 5:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.