Search code examples
assemblyx86llvm-gcc

Printing integer in x86 assembly


I am trying to print an integer in 32 bit x86 assembly on macOS High Sierra using this code:

.cstring
STR_D:
.asciz "%d"
.globl _main

_main:
    movl $53110, %edi     #moves constant int into register
    sub $8, %esp          #make space on stack
    movl %edi, 4(%esp)   #move int to stack
    movl $STR_D, 0(%esp) #move "%d" to stack
    call _printf
    add $8, %esp          #restore stack pointer
    ret

I am compiling using the command

 gcc -m32 -Wl,-no_pie -o foo foo.s

and then executing the executable foo. I then get an error saying "Illegal instruction: 4". I am pretty sure the error lies in the instruction moving the string constant to the stack. If I remove that line and the following function call, everything works. What am I doing wrong?

Further information/question: I am doing this as part of a project to write a compiler. When I execute these instructions on Linux (while of course changing platform-specific stuff such as main instead of _main, .data instead of .cstring, etc), it works. Why does macOS not work? Does this have to do with stack alignment?

My compiler version (obtained by gcc -version) is Apple LLVM 9.0.0 (clang-900.0.39.2).


Solution

  • Okay, I found out what the problem was. It is twofold:

    First, I discovered that I am missing a .text segment. This is apparently needed, even if it is empty. That was the reason for the "Illegal instruction: 4" error. However, if you correct this, you get a SEGFAULT.

    As I suspected in the question, it has to do with stack alignment. macOS has different requirements than Linux: the stack needs to be 16-bit aligned on macOS. This means that before doing a function call (which pushes the return address onto the stack), the stack needs to be 12-aligned (meaning the stack pointer needs to look like this: 0xnnnnnnnC). To ensure this, one needs to make sure to either

    1) push n times, where n is a multiple of 3 (push 3 times, 6 times, 9 times, etc)

    2) if you modify the stack pointer yourself (like I did), make sure to comply with the 12-bit requirement

    So all in all, code that works for me looks like this:

    .cstring
    STR_D:
    .asciz "%d"
    .text                   #need this, even if it's empty
    .globl _main
    
    _main:
      movl $53110, %edi     #moves constant int into register
      sub $12, %esp         #make space on stack, respecting alignment
      movl %edi, 4(%esp)    #move int to stack
      movl $STR_D, 0(%esp)  #move "%d" to stack
      call _printf
      add $12, %esp         #restore stack pointer
      ret
    

    Edit: if someone is interested in stack alignment, I found the explanation on this website very useful!