Bulat M. Bulat M. - 1 year ago 65
Linux Question

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
lea rdi, [fmt] ; print simple string
xor eax, eax
call printf
mov eax, 60 ; exit successfully
xor edi, edi


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


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

How should one fix this?

Answer Source

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.

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

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.

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