blob: a7e430f711ff1d788b20885a00b79de82ba747ab [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 <object/exception_dispatcher.h>
#include <object/job_dispatcher.h>
#include <object/process_dispatcher.h>
#include <object/thread_dispatcher.h>
#define LOCAL_TRACE 0
#define TRACE_EXCEPTIONS 1
static 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";
}
}
// 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_type_) {
case ZX_EXCEPTION_CHANNEL_TYPE_DEBUGGER:
*result = thread_->HandleException(
thread_->process()->exceptionate(Exceptionate::Type::kDebug), exception_, &sent);
if (second_chance) {
next_type_ = ZX_EXCEPTION_CHANNEL_TYPE_JOB;
next_job_ = thread_->process()->job();
} else {
next_type_ = ZX_EXCEPTION_CHANNEL_TYPE_THREAD;
}
break;
case ZX_EXCEPTION_CHANNEL_TYPE_THREAD:
*result = thread_->HandleException(thread_->exceptionate(), exception_, &sent);
next_type_ = ZX_EXCEPTION_CHANNEL_TYPE_PROCESS;
break;
case ZX_EXCEPTION_CHANNEL_TYPE_PROCESS:
*result = thread_->HandleException(
thread_->process()->exceptionate(Exceptionate::Type::kStandard), exception_, &sent);
if (second_chance) {
next_type_ = ZX_EXCEPTION_CHANNEL_TYPE_DEBUGGER;
} else {
next_type_ = ZX_EXCEPTION_CHANNEL_TYPE_JOB;
next_job_ = thread_->process()->job();
}
break;
case ZX_EXCEPTION_CHANNEL_TYPE_JOB:
if (next_job_ == nullptr) {
// Reached the root job and there was no handler.
return false;
}
*result = thread_->HandleException(next_job_->exceptionate(Exceptionate::Type::kStandard),
exception_, &sent);
next_job_ = next_job_->parent();
break;
default:
ASSERT_MSG(0, "unexpected exception type %u", next_type_);
__UNREACHABLE;
}
// Return to the caller once a handler was activated.
if (sent) {
return true;
}
}
__UNREACHABLE;
}
private:
ThreadDispatcher* thread_;
fbl::RefPtr<ExceptionDispatcher> exception_;
uint32_t next_type_ = ZX_EXCEPTION_CHANNEL_TYPE_DEBUGGER;
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(fxbug.dev/33566): remove this case)
static zx_status_t exception_handler_worker(uint exception_type,
const arch_exception_context_t* context,
ThreadDispatcher* thread, bool* out_processed) {
*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);
bool processed = false;
zx_status_t status;
{
// 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 (status != ZX_ERR_INTERNAL_INTR_KILLED) {
auto process = thread->process();
char pname[ZX_MAX_NAME_LEN];
process->get_name(pname);
#if TRACE_EXCEPTIONS
// If no handlers even saw the exception, dump some info. Normally at least
// crashsvc will handle the exception and make a smarter decision about what
// to do with it, but in case it doesn't, dump some info to the kernel logs.
if (!processed) {
char tname[ZX_MAX_NAME_LEN];
thread->get_name(tname);
printf("KERN: exception_handler_worker returned %d\n", status);
printf("KERN: %s in user thread '%s' in process '%s'\n", excp_type_to_string(exception_type),
tname, pname);
arch_dump_exception_context(arch_context);
}
#endif
printf("KERN: terminating process '%s' (%lu)\n", 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::ExitCurrent();
panic("fell out of thread exit somehow!\n");
__UNREACHABLE;
}