Skip to content

ABI Specification

This overview document presents the rou2exOS (aka r2) kernel interface for external applications. Applications should use one of the language libraries from the apps repository and link it statically: libcr2 for C, libc++r2 for C++, or libgor2 for Go. See the SDK Overview for the rules every program has to follow.

Privilege Levels

The privilege levels are to be specified and defined in the Global Descriptor Table (GDT) in early boot sequence procedures.

CPU Ring Common Interrupt Target purpose
0 * kernel space
~~1~~ ~~0x7d~~ kernel tasks, drivers, kernel services
~~2~~ ~~0x7e~~ privileged user space, services, privileged shell access
3 0x7f user space, user programs, common shell

*Kernel itself handles multiple software (CPU exceptions) and hardware interrupts (e.g. IRQs).

All common interrupts are callable from anywhere, but are handled only when called from the defined CPU ring, therefore are locked to such space.

Syscall Specification

The system call (syscall) is a procedure for requesting or modifying of kernel components, modules and drivers. Syscalls use the software interrupts (int 0x7f) under the hood to notify the CPU and kernel to take an action. Parameters of a syscall are passed using the CPU registers that are listed below.

All values passed into a syscall are 64-bit.

Register Usage Example value (64bit)
RAX syscall No. 0x01
RDI argument No. 1 0x01
RSI argument No. 2 0x123abc
RCX optional argument No. 3 frame length for 0x44 operation 3
RAX return value 0x00

Entry Stub

The SDK supplies syscall registers, int 0x7f enters ring 0, the stub dispatches to syscall_inner, and iretq returns with the result in RAX and R9 clobbered.

The ring-3 to ring-0 round trip. The dispatcher can be preempted after it re-enables interrupts; the caller resumes when the stub returns.

The interrupt gate for 0x7f is a naked stub (syscall_handler in src/abi/syscall.rs) that saves the general-purpose registers, calls the dispatcher syscall_inner(arg1, arg2, syscall_no, arg3) and returns with iretq:

cld                 ; Rust expects forward string operations
push rax ... r15     ; every GPR except R9
sub rsp, 8          ; align the stack for the Rust call
mov rdx, rax         ; syscall number becomes the 3rd C argument
                     ; RCX, untouched, is the 4th: the optional argument 3
call syscall_inner
mov r9, rax          ; keep the result across the pops
add rsp, 8
pop r15 ... rax
mov rax, r9          ; result into RAX
iretq

Consequences for callers:

  • The number is read from RAX. Setting RDX alone is not enough; libc++r2 sets both to be safe.
  • Two arguments, and a third in RCX for calls that require it. RDX is overwritten with the number before the call, so it never reaches a syscall. 0x1d reads RCX as a metadata pointer only when bit 63 of argument 2 is set, because older wrappers may leave anything there. 0x44 always reads it: pass the frame length for operation 3, and zero for every other operation. The RNTP bridge 0x47 takes the format buffer length in RCX for operation 5 and zero otherwise. Other syscalls use two arguments; calls that need more take a pointer to a request struct (e.g. 0x39, 0x3a).
  • R9 is clobbered by every syscall and must be declared as such in inline assembly. Every other register but RAX comes back as it went in, so a compiler may keep a value in RCX, RDX, RSI or RDI across the call.
  • Interrupts are re-enabled at the top of the dispatcher, so a long syscall can be preempted by the scheduler.

Pointer Arguments

Every syscall that takes a pointer checks the whole buffer it will touch, not just its first byte. The buffer must lie wholly inside one user region: the program image and the initial user stacks (0x400_000..0xA00_000: the stacks of slots 16–31, the image, then the stacks of slots 0–15; see Memory Overview), the userland heap (0xC00_000..0x1000_000, handed out by syscall 0x0a), or the heap's extension once the heap has grown into it (from 0xA000_000; see 0x0a). Otherwise the call fails with InvalidInput (or the call's own error value). A buffer may not straddle the gap between two regions. NUL-terminated strings are read up to the end of the region they start in.

Syscall Return Codes

Most syscalls return one of these codes. Syscalls that return a count, an address or a PID document their own return value; several of them use u64::MAX (-1 as int64_t) for an error.

Code (uint64) Meaning
0x00 Okay
0xf9 Unchanged — nothing new since the caller last asked; nothing was copied (0x1d with metadata)
0xfa Busy — lock contention or a resource/ownership conflict; consult the call before retrying
0xfb NotImplemented
0xfc InvalidInput
0xfd FilesystemError
0xfe FileNotFound (also used for "no such process")
0xff InvalidSyscall

Syscall Map

Range Group Page
0x00–0x0f, 0x3b–0x3c, 0x3e, 0x41–0x43 Exit, system information, pipes, time, heap, kill (0x3b), memory information (0x3c), power (0x3e), command lines (0x41), desktop relaunch (0x42), shared heap range check (0x43) System, Processes & Memory
0x10–0x1f, 0x3f Console, graphics, framebuffer capture (0x1c, 0x1d), audio (HD Audio PCM 0x3f) Video & Audio
0x20–0x2f, 0x39–0x3a, 0x40 Files, directories, VFS, program execution, task list, mount sizes (0x40) Filesystem
0x30–0x38, 0x3d, 0x44–0x47 I/O ports, serial, packets, IPC, network configuration (0x3d), userspace IPv4 tunnel and ICMP probes (0x44), NIC throughput counters (0x45), TCP port owners (0x46), RNTP block bridge (0x47) Ports & Networking

Related documentation: SDK Overview, Type Definitions, Kernel Internals.