Search code examples
cmacosassemblyx86-64mach

Mac assembly: segfault with libc exit


I am diving into x86_64 assembly on macOS 10.12.

I am trying to call libc exit() function:

.section __TEXT,__text
.globl _main
_main:
    pushq %rbp
    movq %rsp, %rbp
    movl $5, %edi
    callq _exit

And compile it with:

as exit2.s -o exit2.o
ld exit2.o -e _main -lc -o exit

The result is:

Segmentation fault: 11

Why is it so?

Edit: the issue is in linking libc and calling conventions.


Solution

  • @fuz is almost certainly correct: you crash because you didn't initialize libc. There's probably a NULL pointer somewhere in the data structures that exit(3) checks before actually exiting. e.g. it flushes stdout if needed, and it runs any functions registered with atexit(3).

    If you don't want it to do all that work, then either make the sys_exit system call directly with a syscall instruction, or call the thin _exit(2) libc wrapper function for it. (The basics of the situation will be the same as on Linux, because exit(3) vs. _exit(2) are standardized by POSIX: see Syscall implementation of exit().


    I think the tutorial you're following mostly looks good, but perhaps some older version of OS X allowed libc functions (including printf?!?) to be used without calling any libc init functions. Or else they didn't test their code after an edit to the build commands. (Assuming they tested at all, maybe it was with dynamic linking, which would work.)


    OS X prefixes symbol names in assembly with an _, so use call __exit (two underscores) to call _exit(). (e.g. call _printf calls the C printf function).

    _exit(2) probably won't crash if you call it without initializing libc, but it's still a bad idea to call any libc functions without having called libc init functions first. Better to make the system call directly (see later in the tutorial), or even better, build it with gcc hello_asm.S -o hello_asm to make sure libc is initialized. Then you can follow the rest of the tutorial, including the printf.


    Don't call your Mach-O entry point _main or main in a static executable. CRT startup code hasn't run yet. The usual convention is to call it _start for the process entry point.

    (Note that OS X puts the CRT start code in the dynamic linker, so the "entry point" in a dynamically-linked executable is the C main function, unlike in Linux where dynamic executables can avoid the CRT startup code.

    libc would be initialized for you if you linked with gcc exit2.o -o exit instead of ld, which you're using to do the equivalent of gcc -static -nostartfiles.)