VOL. 1 / NO. 1 x86-64 · LINUX · NASM FOLIO 001

Hello, World &
nothing else.

Twelve instructions, two system calls, no C runtime. Below is the smallest useful x86-64 program you can write on Linux — and a tour of why every line is there.

ARCH x86-64  ·  OS Linux  ·  SYNTAX NASM  ·  ABI System V AMD64

The program

 1section .data
 2    msg db  "Hello, World!", 10      ; bytes + LF
 3    len equ $ - msg              ; length, computed at assembly
 4
 5section .text
 6    global _start
 7
 8_start:
 9    mov     rax, 1             ; sys_write
10    mov     rdi, 1             ; fd = stdout
11    mov     rsi, msg           ; buf
12    mov     rdx, len           ; count
13    syscall
14
15    mov     rax, 60            ; sys_exit
16    xor     rdi, rdi           ; status = 0
17    syscall

Line by line

  1. section .data

    Marks the start of initialised data. Bytes here live in the binary and are loaded into a read-write page at runtime.

  2. msg db "Hello, World!", 10

    db means define byte. The string's ASCII bytes are emitted in order, followed by 10 — the line-feed character. msg is the address of the first byte.

  3. len equ $ - msg

    $ is the assembler's "current address." Subtracting msg yields the byte length. equ binds it as a compile-time constant — no memory, no instruction, just a number the assembler substitutes.

  4. section .text

    Switches to the code segment, which the linker will mark read-execute.

  5. global _start

    Exposes the symbol so the linker can find the entry point. With no C runtime in the picture, the kernel jumps straight here when the process starts.

  6. mov rax, 1

    Loads syscall number 1sys_write — into rax. On syscall the kernel reads rax to dispatch.

  7. mov rdi, 1

    First argument: file descriptor. 1 is stdout, inherited from the shell.

  8. mov rsi, msg

    Second argument: pointer to the buffer to write.

  9. mov rdx, len

    Third argument: byte count.

  10. syscall

    Traps into the kernel. The kernel performs the write and returns; rax now holds the result — bytes written, or a negative errno.

  11. mov rax, 60

    Syscall 60 is sys_exit. Without this, the CPU would fall through into whatever bytes follow _start in memory and the process would crash. There is no main to return to.

  12. xor rdi, rdi

    Idiomatic zero. xor reg, reg assembles to a shorter encoding than mov reg, 0 and breaks the register's dependency chain — the canonical way to clear a register on x86. Sets exit status to 0.

  13. syscall

    Kernel terminates the process. Control never returns.

Syscall ABI

The kernel reads arguments from a fixed set of registers — different from, but related to, the function-call ABI used between user-space functions.

SlotRegRole
rax syscall number in · return value out
arg 1 rdi first argument
arg 2 rsi second argument
arg 3 rdx third argument
arg 4 r10 fourth argument
arg 5 r8 fifth argument
arg 6 r9 sixth argument

The function-call ABI uses rcx as the fourth argument; the syscall ABI uses r10 instead because the syscall instruction itself clobbers rcx and r11 — the CPU stashes rip and rflags there on entry.

Build & run

$nasm -f elf64 hello.asm -o hello.o
$ld hello.o -o hello
$./hello
Hello, World!

The resulting binary is around 8 KB on a current toolchain — almost all of it ELF headers and alignment padding. The actual machine code is roughly 40 bytes.

Footnotes

macOS. Same architecture, different kernel ABI. Syscalls are numbered differently and live in the 0x2000000-prefixed range; the entry symbol is _main with a leading underscore, and you'd typically link against libSystem rather than calling the kernel directly.

GAS syntax. The GNU assembler reverses operand order — movq $1, %rax instead of mov rax, 1 — and prefixes registers with % and immediates with $. Same instructions, different spelling.

Why no ret? Because there is nowhere to return to. The kernel jumps to _start with a stack laid out for argc, argv, envp, and an auxiliary vector — but no return address. Falling off the end is undefined behaviour. sys_exit is mandatory.

Set in Iowan Old Style and SF Mono Self-contained, single file No dependencies