Search code examples
macosassemblyx8632-bitlibc

Segmentation Fault 11 linking os x 32-bit assembler


UPDATE: Sure enough, it was a bug in the latest version of nasm. I "downgraded" and after fixing my code as shown in the answer I accepted, everything is working properly. Thanks, everyone!

I'm having problems with what should be a very simple program in 32-bit assembler on OS X.

First, the code:

section .data
hello   db  "Hello, world", 0x0a, 0x00

section .text
default rel

global _main
extern _printf, _exit

_main:
    sub esp, 12     ; 16-byte align stack
    push hello
    call _printf

    push 0
    call _exit

It assembles and links, but when I run the executable it crashes with a segmentation fault: 11.

The command lines to assemble and link are:

nasm -f macho32 hello32x.asm -o hello32x.o

I know the -o there is not 100 percent necessary

Linking:

ld -lc -arch i386 hello32x.o -o hello32x

When I run it into lldb to debug it, everything is fine until it enters into the call to _printf, where it crashes as shown below:

  (lldb) s
  Process 1029 stopped
  * thread #1: tid = 0x97a4, 0x00001fac hello32x`main + 8, queue = 'com.apple.main-thread', stop reason = instruction step into
      frame #0: 0x00001fac hello32x`main + 8
  hello32x`main:
  ->  0x1fac <+8>:  calll  0xffffffff991e381e
      0x1fb1 <+13>: pushl  $0x0
      0x1fb3 <+15>: calll  0xffffffff991fec84
      0x1fb8:       addl   %eax, (%eax)
  (lldb) s
  Process 1029 stopped
  * thread #1: tid = 0x97a4, 0x991e381e libsystem_c.dylib`vfprintf + 49, queue = 'com.apple.main-thread', stop reason = instruction step into
      frame #0: 0x991e381e libsystem_c.dylib`vfprintf + 49
  libsystem_c.dylib`vfprintf:
  ->  0x991e381e <+49>: xchgb  %ah, -0x76f58008
      0x991e3824 <+55>: popl   %esp
      0x991e3825 <+56>: andb   $0x14, %al
      0x991e3827 <+58>: movl   0xc(%ebp), %ecx
  (lldb) s
  Process 1029 stopped
  * thread #1: tid = 0x97a4, 0x991e381e libsystem_c.dylib`vfprintf + 49, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x890a7ff8)
      frame #0: 0x991e381e libsystem_c.dylib`vfprintf + 49
  libsystem_c.dylib`vfprintf:
  ->  0x991e381e <+49>: xchgb  %ah, -0x76f58008
      0x991e3824 <+55>: popl   %esp
      0x991e3825 <+56>: andb   $0x14, %al
      0x991e3827 <+58>: movl   0xc(%ebp), %ecx

As you can see toward the bottom, it stops due to a bad access error.


Solution

  • 16-byte Stack Alignment

    One serious issue with your code is stack alignment. 32-bit OS/X code requires 16-byte stack alignment at the point you make a CALL. The Apple IA-32 Calling Convention says this:

    The function calling conventions used in the IA-32 environment are the same as those used in the System V IA-32 ABI, with the following exceptions:

    • Different rules for returning structures
    • The stack is 16-byte aligned at the point of function calls
    • Large data types (larger than 4 bytes) are kept at their natural alignment
    • Most floating-point operations are carried out using the SSE unit instead of the x87 FPU, except when operating on long double values. (The IA-32 environment defaults to 64-bit internal precision for the x87 FPU.)

    You subtract 12 from ESP to align the stack to a 16 byte boundary (4 bytes for return address + 12 = 16). The problem is that when you make a CALL to a function the stack MUST be 16 bytes aligned just prior to the CALL itself. Unfortunately you push 4 bytes before the call to printf and exit. This misaligns the stack by 4, when it should be aligned to 16 bytes. You'll have to rework the code with proper alignment. As well you must clean up the stack after you make a call. If you use PUSH to put parameters on the stack you need to adjust ESP after your CALL to restore the stack to its previous state.

    One naive way (not my recommendation) to fix the code would be to do this:

    section .data
    hello   db  "Hello, world", 0x0a, 0x00
    
    section .text
    default rel
    
    global _main
    extern _printf, _exit
    
    _main:
        sub esp, 8     
        push hello     ; 4(return address)+ 8 + 4 = 16 bytes stack aligned
        call _printf
        add esp, 4     ; Remove arguments
    
        push 0         ; 4 + 8 + 4 = 16 byte alignment again
        call _exit     ; This will not return so no need to remove parameters after
    

    The code above works because we can take advantage of the fact that both functions (exit and printf) require exactly one DWORD being placed on the stack for parameters. 4 bytes for main's return address, 8 for the stack adjustment we made, 4 for the DWORD parameter = 16 byte alignment.


    A better way to do this is to compute the amount of stack space you will need for all your stack based local variables (in this case 0) in your main function, plus the maximum number of bytes you will need for any parameters to function calls made by main and then make sure you pad enough bytes to make the value evenly divisible by 12. In our case the maximum number of bytes needed to be pushed for any one given function call is 4 bytes. We then add 8 to 4 (8+4=12) to become evenly divisible by 12. We then subtract 12 from ESP at the start of our function.

    Instead of using PUSH to put parameters on the stack you can now move the parameters directly onto the stack into the space we have reserved. Because we don't PUSH the stack doesn't get misaligned. Since we didn't use PUSH we don't need to fix ESP after our function calls. The code could then look something like:

    section .data
    hello   db  "Hello, world", 0x0a, 0x00
    
    section .text
    default rel
    
    global _main
    extern _printf, _exit
    
    _main:
        sub esp, 12           ; 16-byte align stack + room for parameters passed
                              ; to functions we call
        mov [esp],dword hello ; First parameter at esp+0
        call _printf
    
        mov [esp], dword 0    ; First parameter at esp+0
        call _exit
    

    If you wanted to pass multiple parameters you place them manually on the stack as we did with a single parameter. If we wanted to print an integer 42 as part of our call to printf we could do it this way:

    section .data
    hello   db  "Hello, world %d", 0x0a, 0x00
    
    section .text
    default rel
    
    global _main
    extern _printf, _exit
    
    _main:
        sub esp, 12           ; 16-byte align stack + room for parameters passed
                              ; to functions we call
    
        mov [esp+4], dword 42 ; Second parameter at esp+4
        mov [esp],dword hello ; First parameter at esp+0
        call _printf
    
        mov [esp], dword 0    ; First parameter at esp+0
        call _exit
    

    When run we should get:

    Hello, world 42


    16-byte Stack Alignment and a Stack Frame

    If you are looking to create a function with a typical stack frame then the code in the previous section has to be adjusted. Upon entry to a function in a 32-bit application the stack is misaligned by 4 bytes because the return address was placed on the stack. A typical stack frame prologue looks like:

    push ebp
    mov  ebp, esp
    

    Pushing EBP into the stack after entry to your function still results in a misaligned stack, but it is misaligned now by 8 bytes (4 + 4).

    Because of that the code must subtract 8 from ESP rather than 12. As well when determining the space needed to hold parameters, local stack variables, and pad bytes for alignment the stack allocation size will have to be evenly divisible by 8, not by 12. Code with a stack frame could look like:

    section .data
    hello   db  "Hello, world %d", 0x0a, 0x00
    
    section .text
    default rel
    
    global _main
    extern _printf, _exit
    
    _main:
        push ebp
        mov ebp, esp          ; Set up stack frame
        sub esp, 8            ; 16-byte align stack + room for parameters passed
                              ; to functions we call
    
        mov [esp+4], dword 42 ; Second parameter at esp+4
        mov [esp],dword hello ; First parameter at esp+0
        call _printf
    
        xor eax, eax          ; Return value = 0
        mov esp, ebp
        pop ebp               ; Remove stack frame
        ret                   ; We linked with C library that calls _main
                              ; after initialization. We can do a RET to
                              ; return back to the C runtime code that will
                              ; exit the program and return the value in EAX
                              ; We can do this instead of calling _exit
    

    Because you link with the C library on OS/X it will provide an entry point and do initialization before calling _main. You can call _exit but you can also do a RET instruction with the program's return value in EAX.


    Yet Another Potential NASM Bug?

    I discovered that NASM v2.12 installed via MacPorts on El Capitan seems to generate incorrect relocation entries for _printf and _exit, and when linked to a final executable the code doesn't work as expected. I observed almost the identical errors you did with your original code.

    The first part of my answer still applies about stack alignment, however it appears you will need to work around the NASM issue as well. One way to do this install the NASM that comes with the latest XCode command line tools. This version is much older and only supports Macho-32, and doesn't support the default directive. Using my previous stack aligned code this should work:

    section .data
    hello   db  "Hello, world %d", 0x0a, 0x00
    
    section .text
    ;default rel              ; This directive isn't supported in older versions of NASM
    
    global _main
    extern _printf, _exit
    
    _main:
        sub esp, 12           ; 16-byte align stack
        mov [esp+4], dword 42 ; Second parameter at esp+4
        mov [esp],dword hello ; First parameter at esp+0
        call _printf
    
        mov [esp], dword 0    ; First parameter at esp+0
        call _exit
    

    To assemble with NASM and link with LD you could use:

    /usr/bin/nasm -f macho hello32x.asm -o hello32x.o
    ld -macosx_version_min 10.8 -no_pie -arch i386 -o hello32x hello32x.o -lc 
    

    Alternatively you could link with GCC:

    /usr/bin/nasm -f macho hello32x.asm -o hello32x.o
    gcc -m32 -Wl,-no_pie -o hello32x hello32x.o
    

    /usr/bin/nasm is the location of the XCode command line tools version of NASM that Apple distributes. The version I have on El Capitan with latest XCode command line tools is:

    NASM version 0.98.40 (Apple Computer, Inc. build 11) compiled on Jan 14 2016

    I don't recommend NASM version 2.11.08 because it has a serious bug related to macho64 format. I recommend 2.11.09rc2. I have tested that version here and it does seem to work properly with the code above.