Search code examples
linuxassemblyx86-64linker-errorsld

Linker error when calling printf from _start


I tried to write simple program without main

    segment .data
fmt db "test", 0xa, 0

    segment .text
    global _start
    extern printf
_start:
    lea rdi, [fmt] ; print simple string
    xor eax, eax
    call printf
    mov eax, 60    ; exit successfully
    xor edi, edi
    syscall

Compile:

yasm -f elf64 main.s; ld -o main main.o

Got

main.o: In function `_start':
main.s:(.text+0xb): undefined reference to `printf'

How should one fix this?


Solution

  • Cause of Your Error

    The reason you get undefined reference to printf while linking is because you didn't link against the C library. As well, when using your own _start label as an entry point there are other issues that must be overcome as discussed below.


    Using LIBC without C Runtime Startup Code

    To make sure you are using the appropriate dynamic linker at runtime and link against the C library you can use GCC as a frontend for LD. To supply your own _start label and avoid the C runtime startup code use the -nostartfiles option. Although I don't recommend this method, you can link with:

    gcc -m64 -nostartfiles main.o -o main
    

    If you wish to use LD you can do it by using a specific dynamic linker and link against the C library with -lc. To link x86_64 code you can use:

    ld -melf_x86_64 --dynamic-linker=/lib64/ld-linux-x86-64.so.2 main.o -lc -o main
    

    If you had been compiling and linking for 32-bit, then the linking could have been done with:

    ld -melf_i386 --dynamic-linker=/lib/ld-linux.so.2 main.o -lc -o main
    

    Preferred Method Using C Runtime Startup Code

    Since you are using the C library you should consider linking against the C runtime. This method also works if you were to create a static executable. The best way to use the C library is to rename your _start label to main and then use GCC to link:

    gcc -m64 main.o -o main
    

    Doing it this way allows you to exit your program by returning (using RET) from main the same way a C program ends. This also has the advantage of flushing standard output when the program exits.


    Flushing output buffers / exit

    Using syscall with EAX=60 to exit won't flush the output buffers. Because of this you may find yourself having to add a newline character on your output to see output before exiting. This is still not a guarantee you will see what you output with printf. Rather than the sys_exit SYSCALL you might consider calling the C library function exit. the MAN PAGE for exit says:

    All open stdio(3) streams are flushed and closed. Files created by tmpfile(3) are removed.

    I'd recommend replacing the sys_exit SYSCALL with:

    extern exit
    
    xor edi, edi    ; In 64-bit code RDI is 1st argument = return value
    call exit