How to make a Linux system call

How to make a system call by hand on Linux, and how to get the most out of this site along the way.

Making a syscall

It works the same way on every architecture. Only the registers and the instruction change.

  1. Look up the syscall number for your architecture. It's not the same everywhere: write is 1 on x64 and 64 on arm64.
  2. Put the number in the NR register and the arguments in ARG0, ARG1 and so on, in the order the signature lists them.
  3. Run the trap instruction. The kernel takes over and comes back when it's done.
  4. Read the result from the RETURN register.

The calling conventions table on the home page has the registers and the instruction for all 27 architectures, and each architecture page repeats them above its table.

When a call fails

On most architectures a failed call returns a negative errno, so anything from -4095 to -1 means something went wrong. A return of -2 is -ENOENT, for example.

A few architectures do it differently and return the positive errno with a separate flag instead. MIPS and Alpha set a3 to 1, PowerPC sets the summary overflow bit in cr0 when you use sc, and SPARC sets the carry flag.

Hello world

Here's write(1, msg, len) followed by exit(0) on three architectures. msg points at your text and len is its length.

x64, in NASM syntax:

mov eax, 1          ; write
mov edi, 1          ; fd 1 is stdout
lea rsi, [rel msg]  ; buffer
mov edx, len        ; count
syscall

mov eax, 60         ; exit
xor edi, edi        ; status 0
syscall

arm64:

mov x8, #64         // write
mov x0, #1          // fd 1 is stdout
adr x1, msg         // buffer
mov x2, #len        // count
svc #0

mov x8, #93         // exit
mov x0, #0          // status 0
svc #0

riscv64:

li a7, 64           # write
li a0, 1            # fd 1 is stdout
la a1, msg          # buffer
li a2, len          # count
ecall

li a7, 93           # exit
li a0, 0            # status 0
ecall

arm64 and riscv64 share their numbers because both use the generic syscall table, while x64 has its own. On x64 the syscall instruction also overwrites rcx and r11, so don't keep anything you need there.

If your program has threads, call exit_group instead of exit so every thread stops. That's 231 on x64 and 94 on arm64 and riscv64.

Getting around the site

Good to know

The data

Everything here comes from the kernel source. For each architecture, the generator reads the syscall table and the SYSCALL_DEFINE macros of every mainline release it scans, so these are the numbers the kernel actually dispatches on. The short descriptions come from the Linux man pages project, and the io_uring ones from liburing.

Want it in your own tools? Every architecture's full table is a JSON file, like /x64/syscalls.json, with the release each syscall showed up in (since) and the one that dropped it (removed_in). There's also an API:

curl https://api.syscall.sh/v2/syscalls/x64/read
curl https://api.syscall.sh/v2/diff/6.1/7.3?arch=x64

The second one lists what changed between two releases. The API docs have the rest. The site is rebuilt whenever the data changes.