AsAssembly · Lesson 8 of 10

Calling C Functions from ASM

You don't have to do everything in raw assembly. Linking against libc gives you printf, malloc, file I/O, and the entire C standard library — while your hot paths stay in assembly.

x86-64 ASM
; Link with libc: nasm -f elf64 main.asm -o main.o && gcc main.o -o main -no-pie
; (gcc handles linking libc and the C runtime startup)
; Entry point becomes main instead of _start

extern printf
extern malloc
extern free
extern strlen
extern puts

section .data
    fmt_int  db "Value: %ld", 10, 0     ; printf format string (null-terminated)
    fmt_str  db "Hello, %s!", 10, 0
    mystr    db "World", 0

section .text
global main

main:
    push rbp
    mov  rbp, rsp
    sub  rsp, 16            ; align and reserve locals

    ; printf("Value: %ld\n", 42)
    lea  rdi, [rel fmt_int] ; format string (1st arg)
    mov  rsi, 42            ; integer (2nd arg)
    xor  eax, eax           ; rax = 0 (no vector regs used — required for variadic!)
    call printf

    ; printf("Hello, %s!\n", "World")
    lea  rdi, [rel fmt_str]
    lea  rsi, [rel mystr]
    xor  eax, eax
    call printf

    ; puts("simple string")
    lea  rdi, [rel mystr]
    call puts

    ; malloc(128) — allocate 128 bytes
    mov  rdi, 128
    call malloc             ; rax = pointer to 128 bytes (or NULL)
    mov  r12, rax           ; save pointer

    ; strlen(pointer)
    mov  rdi, r12
    call strlen             ; rax = length

    ; free(pointer)
    mov  rdi, r12
    call free

    ; return 0 from main
    xor  eax, eax
    leave                   ; equivalent to: mov rsp, rbp; pop rbp
    ret
Bash
# Assemble and link with libc via gcc
nasm -f elf64 main.asm -o main.o
gcc main.o -o main -no-pie
./main

# -no-pie: disable position-independent executable
# (PIE changes how globals/extern are addressed, complicates assembly)

# See what symbols your .o needs from external libs:
nm -u main.o

# See what libc functions a compiled binary calls:
objdump -d main | grep call
ltrace ./main   # trace library calls at runtime
strace ./main   # trace syscalls at runtime
◆ Note
For variadic C functions like `printf`, you must set `rax = number of XMM registers used for floating-point arguments`. If you're not passing floats, `xor eax, eax` is always correct. Forgetting this causes silent crashes or garbage output when printf tries to read FP arguments that don't exist.