blob: 7bf72a03eae450fc95552d32b4344d12d59b3639 [file] [log] [blame]
// 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
#include <arch.h>
#include <assert.h>
#include <inttypes.h>
#include <lib/fit/defer.h>
#include <stdio.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/syscalls/object.h>
#include <zircon/types.h>
#include <arch/exception.h>
#include <kernel/restricted.h>
#include <ktl/array.h>
#include <ktl/move.h>
#include <object/exception_dispatcher.h>
#include <object/job_dispatcher.h>
#include <object/process_dispatcher.h>
#include <object/thread_dispatcher.h>
#include <ktl/enforce.h>
#define LOCAL_TRACE 0
namespace {
const char* excp_type_to_string(uint type) {
switch (type) {
case ZX_EXCP_FATAL_PAGE_FAULT:
return "fatal page fault";
case ZX_EXCP_UNDEFINED_INSTRUCTION:
return "undefined instruction";
case ZX_EXCP_GENERAL:
return "general fault";
case ZX_EXCP_SW_BREAKPOINT:
return "software breakpoint";
case ZX_EXCP_HW_BREAKPOINT:
return "hardware breakpoint";
case ZX_EXCP_UNALIGNED_ACCESS:
return "alignment fault";
case ZX_EXCP_POLICY_ERROR:
return "policy error";
case ZX_EXCP_PROCESS_STARTING:
return "process starting";
case ZX_EXCP_THREAD_STARTING:
return "thread starting";
case ZX_EXCP_THREAD_EXITING:
return "thread exiting";
default:
return "unknown fault";
}
}
bool HasRestrictedInThreadHandler() {
RestrictedState* rs = Thread::Current::restricted_state();
return rs != nullptr && rs->in_restricted() && rs->in_thread_exceptions_enabled();
}
} // namespace
// This isn't an "iterator" in the pure c++ 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
// - process
// - debugger (in dealing with a second-chance exception)
// - job (first owning job, then its parent job, and so on up to root job)
class ExceptionHandlerIterator final {
public:
explicit ExceptionHandlerIterator(ThreadDispatcher* thread,
fbl::RefPtr<ExceptionDispatcher> exception)
: thread_(thread), exception_(ktl::move(exception)) {}
// 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 with and fills |result| if the exception was sent to a
// handler, or returns false if there are no more to try. Do not call this
// function again after it returns false.
bool Next(zx_status_t* result) {
bool sent = false;
while (true) {
// This state may change during the handling of the debugger exception
// channel. Accordingly, we check its value before the next round of
// handling to be sure of the proper sequencing.
bool second_chance = exception_->IsSecondChance();
switch (next_method_) {
case ExceptionDeliveryMethod::kFirstChanceDebugChannel:
*result =
thread_->HandleException(thread_->process()->debug_exceptionate(), exception_, &sent);
if (HasRestrictedInThreadHandler()) {
next_method_ = ExceptionDeliveryMethod::kRestrictedModeVectoredException;
} else {
next_method_ = ExceptionDeliveryMethod::kThreadChannel;
}
break;
case ExceptionDeliveryMethod::kSecondChanceDebugChannel:
*result =
thread_->HandleException(thread_->process()->debug_exceptionate(), exception_, &sent);
next_method_ = ExceptionDeliveryMethod::kJobChannel;
next_job_ = thread_->process()->job();
break;
case ExceptionDeliveryMethod::kRestrictedModeVectoredException: {
RestrictedState* rs = Thread::Current::restricted_state();
DEBUG_ASSERT(rs != nullptr);
RedirectRestrictedExceptionToNormalMode(rs);
// Write the exception report into the side-car.
auto* exception = rs->state_ptr_as<zx_restricted_exception_t>();
DEBUG_ASSERT(exception != nullptr);
exception_->FillReport(&exception->exception);
// 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 = ZX_OK;
return true;
}
case ExceptionDeliveryMethod::kThreadChannel:
*result = thread_->HandleException(thread_->exceptionate(), exception_, &sent);
next_method_ = ExceptionDeliveryMethod::kProcessChannel;
break;
case ExceptionDeliveryMethod::kProcessChannel:
*result = thread_->HandleException(thread_->process()->exceptionate(), exception_, &sent);
if (second_chance) {
next_method_ = ExceptionDeliveryMethod::kSecondChanceDebugChannel;
} else {
next_method_ = ExceptionDeliveryMethod::kJobChannel;
next_job_ = thread_->process()->job();
}
break;
case ExceptionDeliveryMethod::kJobChannel:
if (next_job_ == nullptr) {
// Reached the root job and there was no handler.
return false;
}
*result = thread_->HandleException(next_job_->exceptionate(), exception_, &sent);
next_job_ = next_job_->parent();
break;
}
// Return to the caller once a handler was activated.
if (sent) {
return true;
}
}
__UNREACHABLE;
}
private:
enum class ExceptionDeliveryMethod {
kFirstChanceDebugChannel,
kRestrictedModeVectoredException,
kThreadChannel,
kProcessChannel,
kSecondChanceDebugChannel,
kJobChannel,
};
ThreadDispatcher* thread_;
fbl::RefPtr<ExceptionDispatcher> exception_;
ExceptionDeliveryMethod next_method_ = ExceptionDeliveryMethod::kFirstChanceDebugChannel;
fbl::RefPtr<JobDispatcher> next_job_;
DISALLOW_COPY_ASSIGN_AND_MOVE(ExceptionHandlerIterator);
};
// Subroutine of dispatch_user_exception to simplify the code.
// One useful thing this does is guarantee ExceptionHandlerIterator is properly
// destructed.
//
// |*out_processed| is set to a boolean indicating if at least one
// handler processed the exception.
//
// Returns:
// ZX_OK if the thread has been resumed.
// ZX_ERR_NEXT if we ran out of handlers before the thread resumed.
// ZX_ERR_INTERNAL_INTR_KILLED if the thread was killed.
// ZX_ERR_NO_MEMORY on allocation failure (TODO(https://fxbug.dev/42108776): remove this case)
static zx_status_t exception_handler_worker(uint exception_type,
const arch_exception_context_t* context,
ThreadDispatcher* thread, bool* out_processed) {
DEBUG_ASSERT(context != nullptr);
*out_processed = false;
zx_exception_report_t report = ExceptionDispatcher::BuildArchReport(exception_type, *context);
fbl::RefPtr<ExceptionDispatcher> exception =
ExceptionDispatcher::Create(fbl::RefPtr(thread), exception_type, &report, context);
if (!exception) {
// No memory to create the exception, we just have to drop it which
// will kill the process.
printf("KERN: failed to allocate memory for %s exception in user thread %lu.%lu\n",
excp_type_to_string(exception_type), thread->process()->get_koid(), thread->get_koid());
return ZX_ERR_NO_MEMORY;
}
// 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.
auto exception_cleaner = fit::defer([&exception]() { exception->Clear(); });
ExceptionHandlerIterator iter(thread, exception);
zx_status_t status = ZX_ERR_NEXT;
while (iter.Next(&status)) {
LTRACEF("handler returned %d\n", status);
// 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 (status == ZX_ERR_NEXT) {
*out_processed = true;
continue;
}
// ZX_ERR_STOP means the handler wants to kill the thread.
if (status == ZX_ERR_STOP) {
*out_processed = true;
DEBUG_ASSERT(thread == ThreadDispatcher::GetCurrent());
ThreadDispatcher::KillCurrent();
return ZX_ERR_INTERNAL_INTR_KILLED;
}
// Anything other than ZX_ERR_NEXT means we're done.
return status;
}
if (status != ZX_ERR_NEXT) {
// 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.
printf(
"KERN: Failed to deliver %s exception to exception handler in user thread %lu.%lu with "
"status: %d\n",
excp_type_to_string(exception_type), thread->process()->get_koid(), thread->get_koid(),
status);
}
// If we got here we ran out of handlers and nobody resumed the thread.
return ZX_ERR_NEXT;
}
// Dispatches an exception to the appropriate handler. Called by arch code
// when it cannot handle an exception.
//
// If we return ZX_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 we return ZX_ERR_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.
//
// TODO(dje): Support unwinding from this exception and introducing a different
// exception?
zx_status_t dispatch_user_exception(uint exception_type,
const arch_exception_context_t* arch_context) {
LTRACEF("type %u, context %p\n", exception_type, arch_context);
ThreadDispatcher* thread = ThreadDispatcher::GetCurrent();
if (unlikely(!thread)) {
// The current thread is not a user thread; bail.
return ZX_ERR_BAD_STATE;
}
// From now until the exception is resolved the thread is in an exception.
ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::EXCEPTION);
constexpr auto get_name = [](auto* task) -> ktl::array<char, ZX_MAX_NAME_LEN> {
char name[ZX_MAX_NAME_LEN];
[[maybe_unused]] zx_status_t status = task->get_name(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(status == ZX_OK);
return ktl::to_array(name);
};
auto dump_context = [&](const auto& pname) {
printf("KERN: %s in user thread '%s' (%lu) in process '%s'\n",
excp_type_to_string(exception_type), get_name(thread).data(), thread->get_koid(),
pname.data());
arch_dump_exception_context(arch_context);
};
bool processed = false;
zx_status_t status = ZX_ERR_INTERNAL;
{
// We're about to handle the exception. Use a |ScopedThreadExceptionContext| to make the
// thread's user register state available to debuggers and exception handlers while the thread
// is "in exception".
ScopedThreadExceptionContext context(arch_context);
status = exception_handler_worker(exception_type, arch_context, thread, &processed);
}
if (status == ZX_OK) {
return ZX_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.
ProcessDispatcher* process = thread->process();
if (status == ZX_ERR_INTERNAL_INTR_KILLED) {
// An exception handler has decided the process should be terminated.
if (process->CriticalToRootJob()) {
// Terminating a critical process will likely reboot the system so dump some extra diagnostics
// to assist in debugging.
printf("KERN: fatal exception in process critical to root job!\n");
dump_context(get_name(process));
}
} else {
auto pname = get_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.
printf("KERN: exception_handler_worker returned %d\n", status);
dump_context(pname);
}
printf("KERN: terminating process '%s' (%lu)\n", pname.data(), 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::ExitCurrent();
panic("fell out of thread exit somehow!\n");
__UNREACHABLE;
}
void dump_common_exception_context(const arch_exception_context_t* 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.
printf("synth_code %u (%d), synth_data %u\n", context->user_synth_code,
static_cast<zx_status_t>(context->user_synth_code), context->user_synth_data);
}