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

.section .rodata
number: .long   774728

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


    # 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(%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
    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

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!

  • 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\$ Sep 19, 2020 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\$ Sep 19, 2020 at 5:37
  • \$\begingroup\$ @PeterCordes thanks for your tips and feedback. How were you able to use/import the mov $__NR_write, %eax ? \$\endgroup\$ Sep 19, 2020 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\$ Sep 19, 2020 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\$ Sep 19, 2020 at 5:42


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.