8. Performing system calls¶
This chapter describes how to perform system calls using inline assembly.
A system call is the programmatic way for a user program to request a service from the operating system (OS). The mechanism for performing a system call is defined by the OS.
The two most common approaches of performing a system call are using a C function or using assembly instructions.
To check which approach applies to your program, consult your OS manual. Some OSes provide a C API as the only stable way to perform system calls.
To perform a system call through a C library, refer to Building mixed-language programs.
We are going to perform the WRITE
system call on a 64-bit ARM machine
running Linux. On these targets, system calls are performed using the SVC
(SuperVisor Call) instruction.
The arguments to a system call are passed via registers, where the OS defines
which registers to use and what they mean to the system call. In the case of
64-bit ARM Linux, the system call number is passed in register X8
. The
first argument to the system call is passed in register X0
, the second in
X1
, etc. The return value of the system call is written to register X0
.
In the case of a WRITE
system call, the system call number is 64
, the
first argument is the file descriptor to write to, the second argument is the
pointer to the data that will be written, the third argument is the size of the
input data, and the return value of the system call is the number of bytes that
were written, or an error if the value is negative.
The execution of a system call can modify registers other than the return value
register as a side effect. These registers must be “clobbered” in the asm!
invocation using out
registers. In the case of 64-bit ARM Linux, no
additional register needs to be clobbered.
A safe wrapper around the WRITE
system call would look like this:
use core::ffi::c_int;
pub fn write(fd: c_int, buf: &[u8]) -> Result<usize, isize> {
const SYSCALL_NUMBER: usize = 64;
let buf_pointer = buf.as_ptr();
let buf_size = buf.len();
let retval: isize;
unsafe {
core::arch::asm!(
"SVC 0",
in("x8") SYSCALL_NUMBER,
inout("x0") fd as usize => retval,
in("x1") buf_pointer,
in("x2") buf_size,
options(nostack),
);
}
if retval >= 0 {
Ok(retval as usize)
} else {
Err(retval)
}
}