blob: fe6aa34824a700b8f313f3cb831e048c78f60867 [file]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
//! Main syscall dispatch routine. For every syscall in the system stamp out a separate
//! `wrapper_<name of syscall>` routine using the [`do_syscall`] inline function instantiated
//! by Zither-generated wrapper functions.
//!
//! The end result is a `wrapper_<syscall>` that does per syscall argument validation and
//! argument marshalling to an inner routine called `sys_<syscall>`.
use super::syscall_numbers::SYSCALL_NAME_REFS;
use crate::kernel::{stats, thread};
use crate::ktrace_rs::{InternedString, declare_interned_string};
use crate::object::ProcessDispatcher;
use crate::userabi::valid_syscall_pc as ValidSyscallPC;
use ::debug::{ltracef, ltracef_level};
use zr::{static_assert, static_assert_size_and_align};
use zx_status::Status;
use zx_types::ZX_EXCP_POLICY_CODE_BAD_SYSCALL;
const LOCAL_TRACE: u32 = 0;
declare_interned_string!(OUT_OF_RANGE_REF, "[out of range]");
/// Result returned from the syscall dispatcher to the assembly entry point.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SyscallResult {
/// The assembler relies on the fact that the ABI will return this in
/// `x0`,`x1` (arm) or `rax`,`rdx` (x86) so we use plain types here to ensure this.
pub status: u64,
/// Non-zero if thread was signaled.
pub is_signaled: u64,
}
static_assert_size_and_align!(SyscallResult, 16, 8);
static_assert!(core::mem::offset_of!(SyscallResult, status) == 0);
static_assert!(core::mem::offset_of!(SyscallResult, is_signaled) == 8);
/// Conversion of a syscall's return value into the raw 64-bit value handed back to user mode.
trait SyscallReturn {
/// Returns the value as the 64-bit integer the assembly entry point returns to user mode.
fn into_raw_return(self) -> u64;
}
impl SyscallReturn for Status {
#[inline(always)]
fn into_raw_return(self) -> u64 {
// A `zx_status_t` is sign-extended into the 64-bit return register.
i64::from(self.into_raw()) as u64
}
}
impl SyscallReturn for Result<(), Status> {
#[inline(always)]
fn into_raw_return(self) -> u64 {
i64::from(Status::result_into_raw(self)) as u64
}
}
impl SyscallReturn for i64 {
#[inline(always)]
fn into_raw_return(self) -> u64 {
self as u64
}
}
impl SyscallReturn for u64 {
#[inline(always)]
fn into_raw_return(self) -> u64 {
self
}
}
impl SyscallReturn for u32 {
#[inline(always)]
fn into_raw_return(self) -> u64 {
u64::from(self)
}
}
impl SyscallReturn for i32 {
#[inline(always)]
fn into_raw_return(self) -> u64 {
i64::from(self) as u64
}
}
impl SyscallReturn for usize {
#[inline(always)]
fn into_raw_return(self) -> u64 {
self as u64
}
}
/// Returns the interned name of syscall `syscall_num` for `kernel:syscall` trace events.
#[inline]
fn syscall_name_ref(syscall_num: u64) -> &'static InternedString {
if let Some(&name_ref) = SYSCALL_NAME_REFS.get(syscall_num as usize) {
return name_ref;
}
core::hint::cold_path();
OUT_OF_RANGE_REF
}
/// Handles a syscall whose number is out of range or whose PC did not originate from the vDSO.
///
/// Signals a `ZX_EXCP_POLICY_CODE_BAD_SYSCALL` policy exception on the current thread and returns
/// `Status::BAD_SYSCALL` as the syscall status.
#[cold]
#[inline(never)]
fn sys_invalid_syscall(num: u64, pc: u64, vdso_code_address: usize) -> Status {
ltracef!("invalid syscall {} from PC {:#x} vDSO code {:#x}\n", num, pc, vdso_code_address);
thread::signal_policy_exception(ZX_EXCP_POLICY_CODE_BAD_SYSCALL, num as u32);
Status::BAD_SYSCALL
}
/// Records the start of a `kernel:syscall` trace duration for `syscall_num`.
#[cold]
#[inline(never)]
fn ktrace_syscall_begin(syscall_num: u64) {
crate::ktrace_rs::duration_begin!("kernel:syscall", (syscall_name_ref(syscall_num)));
}
/// Records the end of a `kernel:syscall` trace duration for `syscall_num`.
#[cold]
#[inline(never)]
fn ktrace_syscall_end(syscall_num: u64) {
crate::ktrace_rs::duration_end!("kernel:syscall", (syscall_name_ref(syscall_num)));
}
/// Shared syscall preamble; returns the address of the vDSO code in the current process.
///
/// Try to do as much as possible in the shared preamble code to maximize code reuse
/// between syscalls.
///
/// N.B. Interrupts must be disabled on entry and they will be enabled on exit.
/// The reason is the calls to `curr_cpu_num` in the ktrace and per-CPU stats calls: we
/// don't want the CPU changing during the call.
///
/// # Safety
///
/// Must be called on the syscall path of the current user thread with interrupts disabled.
#[inline(never)]
unsafe fn do_syscall_pre(syscall_num: u64) -> usize {
if crate::ktrace_rs::category_enabled!("kernel:syscall") {
ktrace_syscall_begin(syscall_num);
}
stats::inc_syscalls();
// Re-enable interrupts to maintain kernel preemptiveness.
// This must be done after the above `ktrace_syscall_begin` call, and after the
// above `inc_syscalls` call as it also calls `curr_cpu_num`.
crate::arch_rs::enable_ints();
ProcessDispatcher::with_current(|up| up.vdso_code_address())
}
/// Shared syscall postamble that disables interrupts, ends the ktrace duration, and checks for
/// pending thread signals.
///
/// # Safety
///
/// Must be called on the syscall path of the current user thread with interrupts enabled; it
/// returns with interrupts disabled.
#[inline(never)]
unsafe fn do_syscall_post(ret: u64, syscall_num: u64) -> SyscallResult {
ltracef_level!(2, "t {:p} ret {:#x}\n", thread::current_get(), ret);
// Disable interrupts on the way out before checking thread signals.
//
// To avoid a situation where we fail to process a thread signal (the "lost wakeup" problem),
// it's critical that once we've checked for thread signals, we do not enable interrupts until
// we have either processed all pending signals or we have returned to user mode.
//
// Also, disable interrupts before the `ktrace_syscall_end` call below.
crate::arch_rs::disable_ints();
if crate::ktrace_rs::category_enabled!("kernel:syscall") {
ktrace_syscall_end(syscall_num);
}
// The assembler caller will re-disable interrupts at the appropriate time.
SyscallResult { status: ret, is_signaled: u64::from(thread::current_is_signaled()) }
}
/// Stamped out syscall veneer routine for every syscall. Try to maximize shared code by forcing
/// most of the setup and teardown code into non-inlined preamble and postamble code.
///
/// N.B. Interrupts must be disabled on entry and they will be disabled on exit.
/// The reason is the two calls to `curr_cpu_num` in the ktrace calls: we
/// don't want the CPU changing during the call.
///
/// `valid_pc` tests whether the offset of `pc` from the start of the vDSO code is one at which
/// the vDSO issues syscall `syscall_num`; `make_call` marshals the arguments and calls the
/// syscall's implementation.
///
/// # Safety
///
/// Must be called on the syscall path of the current user thread with interrupts disabled; it
/// returns with interrupts disabled.
#[inline(always)]
unsafe fn do_syscall(
syscall_num: u64,
pc: u64,
valid_pc: impl FnOnce(usize) -> bool,
make_call: impl FnOnce() -> u64,
) -> SyscallResult {
// Call the shared preamble code.
// SAFETY: The caller guarantees this is the current user thread's syscall path with
// interrupts disabled, which is what `do_syscall_pre` requires.
let vdso_code_address = unsafe { do_syscall_pre(syscall_num) };
ltracef_level!(
2,
"t {:p} syscall {} ip/pc {:#x}\n",
thread::current_get(),
syscall_name_ref(syscall_num).as_c_str().to_str().unwrap_or("?"),
pc
);
// Validate the user space program counter originated from the vDSO at the proper location,
// otherwise call through to the invalid syscall handler.
let ret = if !valid_pc((pc as usize).wrapping_sub(vdso_code_address)) {
core::hint::cold_path();
sys_invalid_syscall(syscall_num, pc, vdso_code_address).into_raw_return()
} else {
// Per syscall inlined routine to marshall args appropriately.
make_call()
};
// Call through to the shared postamble code.
// SAFETY: `do_syscall_pre` enabled interrupts and nothing since has disabled them, which is
// what `do_syscall_post` requires.
unsafe { do_syscall_post(ret, syscall_num) }
}
/// Called when an out of bounds syscall number is passed from user space.
///
/// # Safety
///
/// Must only be called by the architecture syscall dispatcher on the syscall path of the current
/// user thread, with interrupts disabled and `pc` the user program counter that issued the
/// syscall.
#[unsafe(no_mangle)]
unsafe extern "C" fn unknown_syscall(syscall_num: u64, pc: u64) -> SyscallResult {
// SAFETY: The caller upholds the `do_syscall` contract: this is the syscall path of the
// current user thread and interrupts are disabled.
unsafe { do_syscall(syscall_num, pc, |_| false, || unreachable!()) }
}
/// Autogenerated per-syscall wrapper functions.
mod kernel_wrappers {
include!(rustenv_path::envpath!("KERNEL_WRAPPERS_PATH"));
}
/// Tests for the syscall name table used by `kernel:syscall` trace events.
#[cfg(ktest)]
#[unittest::suite(name = "syscalls_rust")]
mod tests {
use super::super::syscall_numbers::{SYSCALL_NAME_REFS, ZX_SYS_COUNT};
use super::{OUT_OF_RANGE_REF, syscall_name_ref};
use unittest::{expect_eq, expect_true};
/// Tests that every valid syscall number maps to its own entry of the generated table.
#[test]
fn test_syscall_name_ref_in_range() {
expect_eq!(SYSCALL_NAME_REFS.len(), ZX_SYS_COUNT);
expect_true!(ZX_SYS_COUNT > 0);
for (num, name) in SYSCALL_NAME_REFS.iter().enumerate() {
expect_true!(core::ptr::eq(syscall_name_ref(num as u64), *name));
expect_true!(!name.as_c_str().is_empty());
}
// The table interns the bare syscall name, without the `zx_` prefix, so that trace
// labels read e.g. `channel_write`.
expect_true!(SYSCALL_NAME_REFS.iter().any(|name| name.as_c_str() == c"channel_write"));
}
/// Tests that out-of-range syscall numbers map to the `[out of range]` label.
#[test]
fn test_syscall_name_ref_out_of_range() {
expect_true!(core::ptr::eq(syscall_name_ref(ZX_SYS_COUNT as u64), OUT_OF_RANGE_REF));
expect_true!(core::ptr::eq(syscall_name_ref(u64::MAX), OUT_OF_RANGE_REF));
expect_true!(syscall_name_ref(u64::MAX).as_c_str() == c"[out of range]");
}
}