| // 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 <stdio.h> |
| #include <trace.h> |
| #include <zircon/errors.h> |
| #include <zircon/syscalls/object.h> |
| #include <zircon/types.h> |
| |
| #include <arch/exception.h> |
| #include <fbl/auto_call.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 = fbl::MakeAutoCall([&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; |
| } |
| |
| // Anything other than ZX_ERR_NEXT means we're done. |
| return 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(); |
| |
| #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 pname[ZX_MAX_NAME_LEN]; |
| process->get_name(pname); |
| 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\n"); |
| 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; |
| } |