| // 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 <arch/exception.h> |
| #include <assert.h> |
| #include <err.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <trace.h> |
| |
| #include <object/excp_port.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"; |
| 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 ports are tried in the following order: |
| // - debugger |
| // - thread |
| // - process |
| // - job (first owning job, then its parent job, and so on up to root job) |
| // - system |
| class ExceptionPortIterator final { |
| public: |
| explicit ExceptionPortIterator(ThreadDispatcher* thread) |
| : thread_(thread), |
| previous_type_(ExceptionPort::Type::NONE) { |
| } |
| |
| // Returns true with |out_eport| filled in for the next one to try. |
| // Returns false if there are no more to try. |
| bool Next(fbl::RefPtr<ExceptionPort>* out_eport) { |
| fbl::RefPtr<ExceptionPort> eport; |
| ExceptionPort::Type expected_type = ExceptionPort::Type::NONE; |
| |
| while (true) { |
| switch (previous_type_) { |
| case ExceptionPort::Type::NONE: |
| eport = thread_->process()->debugger_exception_port(); |
| expected_type = ExceptionPort::Type::DEBUGGER; |
| break; |
| case ExceptionPort::Type::DEBUGGER: |
| eport = thread_->exception_port(); |
| expected_type = ExceptionPort::Type::THREAD; |
| break; |
| case ExceptionPort::Type::THREAD: |
| eport = thread_->process()->exception_port(); |
| expected_type = ExceptionPort::Type::PROCESS; |
| break; |
| case ExceptionPort::Type::PROCESS: |
| previous_job_ = thread_->process()->job(); |
| eport = previous_job_->exception_port(); |
| expected_type = ExceptionPort::Type::JOB; |
| break; |
| case ExceptionPort::Type::JOB: |
| previous_job_ = previous_job_->parent(); |
| if (previous_job_) { |
| eport = previous_job_->exception_port(); |
| expected_type = ExceptionPort::Type::JOB; |
| } else { |
| // Reached the root job and there was no handler. |
| return false; |
| } |
| break; |
| default: |
| ASSERT_MSG(0, "unexpected exception type %d", |
| static_cast<int>(previous_type_)); |
| __UNREACHABLE; |
| } |
| previous_type_ = expected_type; |
| if (eport) { |
| DEBUG_ASSERT(eport->type() == expected_type); |
| *out_eport = ktl::move(eport); |
| return true; |
| } |
| } |
| __UNREACHABLE; |
| } |
| |
| private: |
| ThreadDispatcher* thread_; |
| ExceptionPort::Type previous_type_; |
| // Jobs are traversed up their hierarchy. This is the previous one. |
| fbl::RefPtr<JobDispatcher> previous_job_; |
| |
| DISALLOW_COPY_ASSIGN_AND_MOVE(ExceptionPortIterator); |
| }; |
| |
| static zx_status_t try_exception_handler(fbl::RefPtr<ExceptionPort> eport, |
| ThreadDispatcher* thread, |
| const zx_exception_report_t* report, |
| const arch_exception_context_t* arch_context, |
| ThreadState::Exception* estatus) { |
| LTRACEF("Trying exception port type %d\n", static_cast<int>(eport->type())); |
| auto status = thread->ExceptionHandlerExchange(eport, report, arch_context, estatus); |
| LTRACEF("ExceptionHandlerExchange returned status %d, estatus %d\n", status, static_cast<int>(*estatus)); |
| |
| return status; |
| } |
| |
| enum handler_status_t { |
| // thread is to be resumed |
| HS_RESUME, |
| // thread was killed |
| HS_KILLED, |
| // exception not handled (process will be killed) |
| HS_NOT_HANDLED |
| }; |
| |
| // Subroutine of dispatch_user_exception to simplify the code. |
| // One useful thing this does is guarantee ExceptionPortIterator is properly |
| // destructed. |
| // |*out_processed| is set to a boolean indicating if at least one |
| // handler processed the exception. |
| |
| static handler_status_t exception_handler_worker(uint exception_type, |
| arch_exception_context_t* context, |
| ThreadDispatcher* thread, |
| bool* out_processed) { |
| *out_processed = false; |
| |
| zx_exception_report_t report; |
| ExceptionPort::BuildArchReport(&report, exception_type, context); |
| |
| ExceptionPortIterator iter(thread); |
| fbl::RefPtr<ExceptionPort> eport; |
| |
| while (iter.Next(&eport)) { |
| // Initialize for paranoia's sake. |
| ThreadState::Exception estatus = ThreadState::Exception::UNPROCESSED; |
| auto status = try_exception_handler(eport, thread, &report, context, &estatus); |
| LTRACEF("handler returned %d/%d\n", |
| static_cast<int>(status), static_cast<int>(estatus)); |
| switch (status) { |
| case ZX_ERR_INTERNAL_INTR_KILLED: |
| // thread was killed, probably with zx_task_kill |
| return HS_KILLED; |
| case ZX_OK: |
| switch (estatus) { |
| case ThreadState::Exception::TRY_NEXT: |
| *out_processed = true; |
| break; |
| case ThreadState::Exception::RESUME: |
| return HS_RESUME; |
| default: |
| ASSERT_MSG(0, "invalid exception status %d", |
| static_cast<int>(estatus)); |
| __UNREACHABLE; |
| } |
| break; |
| default: |
| // Instead of requiring exception processing to only return |
| // specific kinds of errors (and thus requiring us to be updated |
| // every time a change causes a new error to be returned), treat |
| // all other errors as fatal. It's debatable whether to give the |
| // next handler a try or immediately kill the task. By immediately |
| // killing the task we bypass the root job exception handler, |
| // but it feels safer. |
| // TODO(ZX-2853): Are there times when we should try harder to |
| // process the exception? |
| // Print something to give the user a clue. |
| printf("KERN: Error %d processing exception in user thread %lu.%lu\n", |
| status, thread->process()->get_koid(), thread->get_koid()); |
| // Still mark the exception as processed so that we don't trigger |
| // later bare-bones crash reporting (TRACE_EXCEPTIONS). |
| *out_processed = true; |
| return HS_NOT_HANDLED; |
| } |
| } |
| |
| return HS_NOT_HANDLED; |
| } |
| |
| // 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, |
| arch_exception_context_t* context) { |
| LTRACEF("type %u, context %p\n", exception_type, 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; |
| handler_status_t hstatus = exception_handler_worker(exception_type, context, |
| thread, &processed); |
| switch (hstatus) { |
| case HS_RESUME: |
| return ZX_OK; |
| case HS_KILLED: |
| thread->Exit(); |
| __UNREACHABLE; |
| case HS_NOT_HANDLED: |
| break; |
| default: |
| ASSERT_MSG(0, "unexpected exception worker result %d", static_cast<int>(hstatus)); |
| __UNREACHABLE; |
| } |
| |
| auto process = thread->process(); |
| |
| #if TRACE_EXCEPTIONS |
| if (!processed) { |
| // only print this if an exception handler wasn't involved |
| // in handling the exception |
| char pname[ZX_MAX_NAME_LEN]; |
| process->get_name(pname); |
| char tname[ZX_MAX_NAME_LEN]; |
| thread->get_name(tname); |
| printf("KERN: %s in user thread '%s' in process '%s'\n", |
| excp_type_to_string(exception_type), tname, pname); |
| |
| arch_dump_exception_context(context); |
| } |
| #endif |
| |
| // kill our process |
| process->Kill(); |
| |
| // exit |
| thread->Exit(); |
| |
| // should not get here |
| panic("fell out of thread exit somehow!\n"); |
| __UNREACHABLE; |
| } |