This is the fourth post about x86 assembly. In this post I will show an example how to call the printf function from the C standard library in assembly code.
section .text default rel extern printf global main main: ; Create a stack-frame, re-aligning the stack to 16-byte alignment before calls push rbp mov rdi, fmt mov rsi, message mov rax, 0 ; Call printf call printf wrt ..plt pop rbp ; Pop stack mov rax,0 ; Exit code 0 ret ; Return section .data message: db "Hello, World", 10, 0 fmt: db "%s", 10, 0
$ nasm printf.asm -f elf64 -o printf.o $ gcc printf.o $ ./a.out # Hello world
The first part
default rel extern printf global main
default rel is a nasm assembly directive. It tells nasm to use rip relative adressing. In short it tells the assembler to rewrite the references in instructions that uses our
message constants relative to the instruction pointer. This is needed because default for the linker in 64-bit linux is to use position-independent code.
extern printf part tells the assembler that this symbol exists outside of this file and needs to be referenced at a later stage.
Last row here is
global main which is needed for gcc and it’s the entrypoint for libc.
First we need to align the stack because the x86_64 ABI requires the stackpointer to always be 16-byte aligned so therefor we push a value to it.
Then we prepare our registers for the function call to
printf and then we call
printf wrt ..plt. What happens here is that we load printf from the libc shared library and this is a little bit complicated. But very briefly it says
call printf with relation to procedure linkage table. The PCL will then the first time
printf is called resolve where
printf is in memory with the help of the dynamic link loader in linux. It then stores that adress for future calls. We could link
printf statically which would copy the code of
printf into the executable.
Lastly we need to set return value of
main to zero and do a
call and ret
When you do a
call operation two things happen. First the instruction on the next instruction is pushed onto the stack. In our example above it will be the memory adress of the instruction
pop rbp. Secondly it will jump to the memory adress where
printf starts. When
printf is done it will do a
ret instruction which will pop our memory adress from the stack and jump back to our main function.