How to make a Linux system call
Making a syscall
It works the same way on every architecture. Only the registers and the instruction change.
- Look up the syscall number for your architecture. It's not the same everywhere:
writeis 1 on x64 and 64 on arm64. - Put the number in the NR register and the arguments in ARG0, ARG1 and so on, in the order the signature lists them.
- Run the trap instruction. The kernel takes over and comes back when it's done.
- 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
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
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
- Search. The box on the home page finds a syscall by name or by words from its description, and shows its number on every architecture that has it.
- Filter. Each architecture page has a filter for the name, the arguments, the description, the family and the kconfig option. A plain number like
59or0x3bfinds that exact syscall. - Keyboard. Press / to jump to the search box on the home page, or to the filter on an architecture page.
- Older kernels. The kernel selector shows a table as it was in an earlier release. Pick "all, with removed" to bring back the syscalls that were dropped along the way, each marked with the release that removed it.
- Syscall pages. Every syscall has a page with its signature, the register for each argument, the man page, its source in the kernel and the same syscall on other architectures. The signature and the number have copy buttons, and the family links to the rest of the family in the table.
- Compare. The compare page puts two architectures side by side, with each number and what's missing on either side. Handy when porting code.
- Releases. The releases page lists what each kernel release added or removed.
- Sharing. The filter and the kernel end up in the address, so a link like /x64/?kernel=5.10&q=open opens exactly that view.
- Emacs. syscall.el has the same tables inside Emacs, and they work offline. It can also insert the assembly for a call and show signatures as you type.
Good to know
- The oldest release scanned is Linux 3.3, and some architectures start later. A syscall that was already there in its architecture's first scanned release shows that release as "or earlier", because it may be much older.
- Syscall numbers have gaps, because some ranges are reserved and syscalls added since Linux 5.1 start at 424 so they line up across architectures. A few keep their own offset on top (MIPS adds 4000, 5000 or 6000 depending on the ABI, and Alpha adds 110). That's why arm64 has a little over 300 syscalls while its numbers go past 470.
- A few names show up more than once, like setsockopt on x32. The page lists each entry with its own number.
- Every number in the x32 table already includes the x32 bit, 0x40000000.
- Since Linux 5.14, x64 only reads the syscall number from
eax. Older kernels fail withENOSYSif the upper half ofraxisn't zero, andmov eax, Nclears it, so that works everywhere.
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.