blob: c76dd05d3cd3c03fda90cb823c6b6c94b53698ee [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
use super::dispatcher::Dispatcher;
use super::exception_dispatcher::ExceptionDispatcher;
use super::job_dispatcher::JobDispatcher;
use super::thread_dispatcher::{AutoBlocked, Blocked, ThreadDispatcher};
use crate::arch_rs::{
ArchExceptionContext, arch_dump_exception_context, arch_exception_context_user_synth_code,
arch_exception_context_user_synth_data,
};
use crate::kernel::thread::with_exception_context;
use core::ptr::NonNull;
use debug::ltracef;
use fbl::RefPtr;
use kprint::kprintln;
use zx_status::Status;
use zx_types::{
ZX_ERR_INTERNAL_INTR_KILLED, ZX_EXCP_FATAL_PAGE_FAULT, ZX_EXCP_GENERAL, ZX_EXCP_HW_BREAKPOINT,
ZX_EXCP_POLICY_ERROR, ZX_EXCP_PROCESS_STARTING, ZX_EXCP_SW_BREAKPOINT, ZX_EXCP_THREAD_EXITING,
ZX_EXCP_THREAD_STARTING, ZX_EXCP_UNALIGNED_ACCESS, ZX_EXCP_UNDEFINED_INSTRUCTION,
ZX_MAX_NAME_LEN, ZX_TASK_RETCODE_EXCEPTION_KILL, zx_status_t,
};
const LOCAL_TRACE: u32 = 0;
fn excp_type_to_string(type_: u32) -> &'static str {
match type_ {
ZX_EXCP_FATAL_PAGE_FAULT => "fatal page fault",
ZX_EXCP_UNDEFINED_INSTRUCTION => "undefined instruction",
ZX_EXCP_GENERAL => "general fault",
ZX_EXCP_SW_BREAKPOINT => "software breakpoint",
ZX_EXCP_HW_BREAKPOINT => "hardware breakpoint",
ZX_EXCP_UNALIGNED_ACCESS => "alignment fault",
ZX_EXCP_POLICY_ERROR => "policy error",
ZX_EXCP_PROCESS_STARTING => "process starting",
ZX_EXCP_THREAD_STARTING => "thread starting",
ZX_EXCP_THREAD_EXITING => "thread exiting",
_ => "unknown fault",
}
}
#[inline]
fn has_restricted_in_thread_handler() -> bool {
// SAFETY: `current_get()` returns the non-null `Thread*` of the currently executing thread.
unsafe { crate::kernel::thread::in_restricted(crate::kernel::thread::current_get()) }
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum ExceptionDeliveryMethod {
FirstChanceDebugChannel,
RestrictedModeVectoredException,
ThreadChannel,
ProcessChannel,
SecondChanceDebugChannel,
JobChannel,
}
/// Walks the ordered chain of exception handlers for `thread`.
///
/// This isn't an "iterator" in the pure Rust sense. We don't need all that
/// complexity. I just couldn't think of a better term.
///
/// Exception handlers are tried in the following order:
/// - debugger
/// - thread (or restricted mode in-thread vectored handler)
/// - process
/// - debugger (in dealing with a second-chance exception)
/// - job (first owning job, then its parent job, and so on up to root job)
struct ExceptionHandlerIterator<'a> {
thread: &'a ThreadDispatcher,
exception: RefPtr<ExceptionDispatcher>,
next_method: ExceptionDeliveryMethod,
next_job: Option<RefPtr<JobDispatcher>>,
}
impl<'a> ExceptionHandlerIterator<'a> {
fn new(thread: &'a ThreadDispatcher, exception: RefPtr<ExceptionDispatcher>) -> Self {
Self {
thread,
exception,
next_method: ExceptionDeliveryMethod::FirstChanceDebugChannel,
next_job: None,
}
}
/// Sends the exception to the next registered handler, starting with
/// `ZX_EXCEPTION_CHANNEL_TYPE_DEBUGGER` (the process debug channel) on the first call.
///
/// Returns `true` and fills `result` if the exception was sent to a handler, or returns
/// `false` if there are no more to try (retaining the last non-`NEXT` delivery error in
/// `result` if sending to a valid channel failed). Do not call this function again after it
/// returns `false`.
fn next(&mut self, result: &mut Result<(), Status>) -> bool {
loop {
let sent;
match self.next_method {
ExceptionDeliveryMethod::FirstChanceDebugChannel => {
(*result, sent) = self.thread.handle_exception(
self.thread.process().debug_exceptionate(),
&self.exception,
);
if has_restricted_in_thread_handler() {
self.next_method = ExceptionDeliveryMethod::RestrictedModeVectoredException;
} else {
self.next_method = ExceptionDeliveryMethod::ThreadChannel;
}
}
ExceptionDeliveryMethod::SecondChanceDebugChannel => {
(*result, sent) = self.thread.handle_exception(
self.thread.process().debug_exceptionate(),
&self.exception,
);
self.next_method = ExceptionDeliveryMethod::JobChannel;
self.next_job = Some(self.thread.process().job());
}
ExceptionDeliveryMethod::RestrictedModeVectoredException => {
let rs_ptr = crate::kernel::thread::current_restricted_state();
let mut rs_ptr = NonNull::new(rs_ptr)
.expect("restricted exception without bound RestrictedState");
// SAFETY: `rs_ptr` is owned exclusively by the current thread.
let rs = unsafe { rs_ptr.as_mut() };
let report =
self.exception.fill_report().expect("failed to fill exception report");
// SAFETY: `has_restricted_in_thread_handler()` verified that the current user
// thread is in restricted mode, `rs` is its bound `RestrictedState`, and
// `dispatch_user_exception` installed the thread's exception context for the
// duration of `exception_handler_worker`.
unsafe {
crate::kernel::restricted::redirect_restricted_exception_to_normal_mode(
rs, &report,
);
}
// Handle the exception on behalf of restricted mode.
//
// Note this implies that restricted exceptions can never propagate
// further and as such are ineligible for subsequent handlers.
*result = Ok(());
return true;
}
ExceptionDeliveryMethod::ThreadChannel => {
(*result, sent) =
self.thread.handle_exception(self.thread.exceptionate(), &self.exception);
self.next_method = ExceptionDeliveryMethod::ProcessChannel;
}
ExceptionDeliveryMethod::ProcessChannel => {
(*result, sent) = self
.thread
.handle_exception(self.thread.process().exceptionate(), &self.exception);
// This state may change during the handling of the debugger exception
// channel. Accordingly, we check its value dynamically rather than caching it
// when the iterator is constructed.
if self.exception.is_second_chance() {
self.next_method = ExceptionDeliveryMethod::SecondChanceDebugChannel;
} else {
self.next_method = ExceptionDeliveryMethod::JobChannel;
self.next_job = Some(self.thread.process().job());
}
}
ExceptionDeliveryMethod::JobChannel => {
let Some(job) = self.next_job.take() else {
// Reached the root job and there was no handler.
return false;
};
(*result, sent) =
self.thread.handle_exception(job.exceptionate(), &self.exception);
self.next_job = job.parent();
}
}
// Return to the caller once a handler was activated.
if sent {
return true;
}
}
}
}
/// Subroutine of [`dispatch_user_exception`] to simplify the code.
/// One useful thing this does is guarantee [`ExceptionHandlerIterator`] is properly destructed.
///
/// Returns `(result, processed)`, where `processed` indicates if at least one handler processed
/// the exception, and `result` is:
/// * `Ok(())` if the thread has been resumed.
/// * `Err(Status::NEXT)` if we ran out of handlers before the thread resumed.
/// * `Err(Status(ZX_ERR_INTERNAL_INTR_KILLED))` if the thread was killed.
/// * `Err(Status::NO_MEMORY)` on allocation failure
/// (TODO(https://fxbug.dev/42108776): remove this case).
///
/// # Safety
///
/// `context` must point to a valid, initialized `arch_exception_context_t` for the current thread.
unsafe fn exception_handler_worker(
exception_type: u32,
context: *const ArchExceptionContext,
thread: &ThreadDispatcher,
) -> (Result<(), Status>, bool) {
debug_assert!(!context.is_null());
let mut processed = false;
// SAFETY: Caller guarantees `context` points to a valid `arch_exception_context_t`.
let report = unsafe { ExceptionDispatcher::build_arch_report(exception_type, context) };
// SAFETY: `report` and `context` outlive `_exception_cleaner` below, which calls
// `exception.clear()` on all exit paths before `report` goes out of scope.
let Some(exception) = (unsafe {
ExceptionDispatcher::create(RefPtr::from_ref(thread), exception_type, &report, context)
}) else {
core::hint::cold_path();
// No memory to create the exception, we just have to drop it which
// will kill the process.
kprintln!(
"KERN: failed to allocate memory for {:s} exception in user thread {:u}.{:u}",
excp_type_to_string(exception_type),
thread.process().get_koid(),
thread.get_koid(),
);
return (Err(Status::NO_MEMORY), processed);
};
// Most of the time we'll be holding the last reference to the exception
// when this function exits, but if the task is killed we return HS_KILLED
// without waiting for the handler which means someone may still have a
// handle to the exception.
//
// For simplicity and to catch any unhandled status cases below, just clean
// out the exception before returning no matter what.
let _exception_cleaner = zr::defer(|| exception.clear());
let mut iter = ExceptionHandlerIterator::new(thread, exception.clone());
let mut result: Result<(), Status> = Err(Status::NEXT);
while iter.next(&mut result) {
ltracef!("handler returned {}\n", Status::result_into_raw(result));
// ZX_ERR_NEXT means the handler wants to pass it up to the next in the
// chain, keep looping but mark that at least one handler saw the exception.
if result == Err(Status::NEXT) {
processed = true;
continue;
}
// ZX_ERR_STOP means the handler wants to kill the thread.
if result == Err(Status::STOP) {
processed = true;
debug_assert!(thread.is_current());
ThreadDispatcher::kill_current();
return (Err(Status::err_from_raw(ZX_ERR_INTERNAL_INTR_KILLED)), processed);
}
// Anything other than ZX_ERR_NEXT means we're done.
return (result, processed);
}
if let Err(status) = result
&& status != Status::NEXT
{
core::hint::cold_path();
// If the iterator returned false, but status got changed from ZX_ERR_NEXT, then this means
// it attempted to send to a handler, but the sending process failed. We still have no way
// to handle the exception but we can at least make noise in the debuglog.
kprintln!(
concat!(
"KERN: Failed to deliver {:s} exception to exception handler ",
"in user thread {:u}.{:u} with status: {}",
),
excp_type_to_string(exception_type),
thread.process().get_koid(),
thread.get_koid(),
status.into_raw(),
);
}
// If we got here we ran out of handlers and nobody resumed the thread.
(Err(Status::NEXT), processed)
}
#[inline]
fn get_task_name(task: &Dispatcher) -> [u8; ZX_MAX_NAME_LEN] {
let mut name = [0u8; ZX_MAX_NAME_LEN];
let result = task.get_name(&mut name);
// get_name cannot fail for a process or for a thread. We are processing an exception on the
// current thread, so its state should remain RUNNING until we've decided to kill the thread
// below.
debug_assert!(result.is_ok());
name
}
#[inline]
fn c_str_bytes(buf: &[u8; ZX_MAX_NAME_LEN]) -> &[u8] {
let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
&buf[..len]
}
/// Logs the exception type, thread/process names and koids, and architecture-specific register
/// state to the kernel console.
///
/// # Safety
///
/// `arch_context` must point to a valid, initialized `arch_exception_context_t` for the current
/// thread.
unsafe fn dump_exception_context(
exception_type: u32,
thread: &ThreadDispatcher,
pname: &[u8; ZX_MAX_NAME_LEN],
arch_context: *const ArchExceptionContext,
) {
let tname = get_task_name(thread);
kprintln!(
"KERN: {:s} in user thread '{:s}' ({:u}) in process '{:s}'",
excp_type_to_string(exception_type),
c_str_bytes(&tname),
thread.get_koid(),
c_str_bytes(pname),
);
// SAFETY: Caller guarantees `arch_context` points to a valid `arch_exception_context_t`.
unsafe {
arch_dump_exception_context(arch_context);
}
}
/// Dispatches an exception to the appropriate handler. Called by arch code when it cannot handle
/// an exception.
///
/// If this returns `Ok(())`, the caller is expected to resume the thread "as if" nothing happened;
/// the handler is expected to have modified state such that resumption is possible.
///
/// If this returns `Err(Status::BAD_STATE)`, the current thread is not a user thread (i.e., not
/// associated with a `ThreadDispatcher`).
///
/// Otherwise, we cause the current thread to exit and do not return at all.
///
/// # Safety
///
/// * `arch_context` must point to a valid, initialized `arch_exception_context_t` for the current
/// thread that outlives this call.
/// * If `(*arch_context).frame` is non-null, it must point to a valid, initialized, writable
/// `iframe_t` that outlives this call without conflicting Rust references (since
/// [`with_exception_context`] exposes it to debugger reads and writes).
/// * No thread lock, dispatcher lock, spinlock, or active `ChainLockTransaction` may be held.
// TODO(dje): Support unwinding from this exception and introducing a different exception?
pub unsafe fn dispatch_user_exception(
exception_type: u32,
arch_context: *const ArchExceptionContext,
) -> Result<(), Status> {
ltracef!("type {}, context {:p}\n", exception_type, arch_context);
let Some(thread) = ThreadDispatcher::try_get_current() else {
core::hint::cold_path();
// The current thread is not a user thread; bail.
return Err(Status::BAD_STATE);
};
// From now until the exception is resolved the thread is in an exception.
let _by = AutoBlocked::new(Blocked::EXCEPTION);
// We're about to handle the exception. Install `arch_context` on the current thread to make
// its user register state available to debuggers and exception handlers while the thread is
// "in exception".
// SAFETY: `thread` was obtained from `ThreadDispatcher::try_get_current()` above so the
// current thread is a user thread, and the caller guarantees `arch_context` (and any non-null
// `(*arch_context).frame`) is valid for the current thread without conflicting references and
// that no thread lock, dispatcher lock, spinlock, or active `ChainLockTransaction` is held.
let (result, processed) = unsafe {
with_exception_context(arch_context, || {
exception_handler_worker(exception_type, arch_context, &thread)
})
};
let Err(status) = result else {
return Ok(());
};
// If the thread wasn't resumed or explicitly killed, kill the whole process.
// If the process is critical to the root job, any fatal exception merits
// logging all the details available whether the handler decided to kill the
// process, or no handler processed it at all.
let process = thread.process();
if status.into_raw() == ZX_ERR_INTERNAL_INTR_KILLED {
// An exception handler has decided the process should be terminated.
if process.critical_to_root_job() {
// Terminating a critical process will likely reboot the system so dump some extra
// diagnostics to assist in debugging.
kprintln!("KERN: fatal exception in process critical to root job!");
let pname = get_task_name(process);
// SAFETY: Caller guarantees `arch_context` is valid for the current thread.
unsafe {
dump_exception_context(exception_type, &thread, &pname, arch_context);
}
}
} else {
let pname = get_task_name(process);
if !processed {
// The exception was not handled. Normally, at least crashsvc would handle the
// exception and make a smarter decision about what to do with it, but in case it
// didn't, dump some info to the kernel logs.
kprintln!("KERN: exception_handler_worker returned {}", status.into_raw());
// SAFETY: Caller guarantees `arch_context` is valid for the current thread.
unsafe {
dump_exception_context(exception_type, &thread, &pname, arch_context);
}
}
// This log message is load-bearing server-side as it's used to identify the
// critical component responsible for the reboot.
// Please notify //src/developer/forensics/OWNERS upon changing.
kprintln!(
"KERN: terminating process '{:s}' ({:u})",
c_str_bytes(&pname),
process.get_koid(),
);
process.kill(ZX_TASK_RETCODE_EXCEPTION_KILL);
}
// Either the current thread or its whole process was killed, we can now stop
// it from running.
ThreadDispatcher::exit_current();
}
/// Prints the common architecture-independent fields in `arch_exception_context`.
///
/// # Safety
///
/// `context` must point to a valid, initialized `arch_exception_context_t`.
pub unsafe fn dump_common_exception_context(context: *const ArchExceptionContext) {
// SAFETY: Caller guarantees `context` points to a valid `arch_exception_context_t`.
let (synth_code, synth_data) = unsafe {
(
arch_exception_context_user_synth_code(context),
arch_exception_context_user_synth_data(context),
)
};
// Print the common fields in arch_exception_context.
//
// In case of a page fault exception, the error code is typecast from zx_status_t to uint32_t
// when populating user_synth_code. So also log the value typecast to zx_status_t to make
// debugging easier.
kprintln!(
"synth_code {:u} ({}), synth_data {:u}",
synth_code,
synth_code as zx_status_t,
synth_data,
);
}