Mathieu GAILLARD

Calling printf in NASM assembly on Linux

Recently I was trying to print a number in x86_64 assembly on Linux with NASM, and it took me quite some time to figure out how to do it correctly. In this article I will share my solution with you, together with some explanations.

Printing Hello World!

This is the file main.asm, which contains the main function of our program.

BITS 64;

        global    main                  ; the standard gcc entry point
        extern printf                   ; declare a C function to be called

        section   .text
main:   
        push        rbp                 ; set up stack frame, must be alligned
        mov         rdi, message        ; first argument for printf
        xor         rax, rax            ; rax must be 0 (see explanations below)
        call        printf              ; call the printf function
        pop	        rbp                 ; restore stack
        mov         rax, 0              ; normal, no error, return value
        ret                             ; return

        section   .data
message:  db "Hello, World!", 10, 0     ; note the newline (10) and null (0) at the end

This Makefile will compile and link our NASM program.

all: main

main: main.o
	gcc -m64 -o main main.o -lc -no-pie

main.o: main.asm
	nasm -f elf64 -o main.o main.asm

clean:
	rm -f main.o

To build and call the program:

$ make
$ ./main
# Hello World!

Explanations

The first line: BITS 64; tells NASM that we are running on a 64 bits architecture. It prevents a number of syntax highlight errors in VS Code.

Because we are linking with gcc, and not ld, the entry point is not _start but main like the int main() function in C. Therefore, we declare our main function with global main, we setup the stack frame with push rbp and pop rbp, and we return zero with mov rax, 0 and ret.

Calling printf() is done like in C. The first argument is a pointer to the null terminated string "Hello, World!\n", so we set it with mov rdi, message. According to the calling convention on Linux for functions with variable arguments, the register RAX contains the number of vector register arguments. Since we do not pass any floating-point numbers, RAX must be equal to zero. We reset it with xor rax, rax.

When linking with gcc, the option -no-pie is used in order to prevent a linking error about position independent executables. And the option -lc tells gcc to link with the C library.

Printing a number

If we wanted to print a number like this printf("Number = %d", 42) in C, we would use the following code.

mov rdi, message
mov rsi, 42
xor rax, rax
call printf

With the message string:

message: db 'Number = %d', 10, 0

Reference