| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "inspector/inspector.h" |
| |
| #include <inttypes.h> |
| #include <string.h> |
| |
| #include <lib/backtrace-request/backtrace-request-utils.h> |
| #include <zircon/assert.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/exception.h> |
| #include <zircon/types.h> |
| |
| #include "utils-impl.h" |
| |
| namespace inspector { |
| |
| static const char* excp_type_to_str(const zx_excp_type_t type) { |
| switch (type) { |
| case ZX_EXCP_GENERAL: |
| return "general fault"; |
| case ZX_EXCP_FATAL_PAGE_FAULT: |
| return "fatal page fault"; |
| case ZX_EXCP_UNDEFINED_INSTRUCTION: |
| return "undefined instruction"; |
| case ZX_EXCP_SW_BREAKPOINT: |
| return "sw breakpoint"; |
| case ZX_EXCP_HW_BREAKPOINT: |
| return "hw breakpoint"; |
| case ZX_EXCP_UNALIGNED_ACCESS: |
| return "alignment fault"; |
| case ZX_EXCP_POLICY_ERROR: |
| return "policy error"; |
| default: |
| // Note: To get a compilation failure when a new exception type has |
| // been added without having also updated this function, compile with |
| // -Wswitch-enum. |
| return "unknown fault"; |
| } |
| } |
| |
| // How much memory to dump, in bytes. |
| static constexpr size_t kMemoryDumpSize = 256; |
| |
| static zx_koid_t get_koid(zx_handle_t handle) { |
| zx_info_handle_basic_t info; |
| zx_status_t status = zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), NULL, NULL); |
| if (status != ZX_OK) { |
| printf("failed to get koid\n"); |
| return ZX_HANDLE_INVALID; |
| } |
| return info.koid; |
| } |
| |
| } // namespace inspector |
| |
| __EXPORT void inspector_print_debug_info(zx_handle_t process, zx_handle_t thread, |
| zx_excp_type_t* type, |
| zx_thread_state_general_regs_t* regs) { |
| // If the caller didn't supply |type| or |regs| use a local copy. |
| zx_excp_type_t local_type; |
| if (!type) { |
| type = &local_type; |
| } |
| zx_thread_state_general_regs_t local_regs; |
| if (!regs) { |
| regs = &local_regs; |
| } |
| |
| zx_koid_t pid = inspector::get_koid(process); |
| zx_koid_t tid = inspector::get_koid(thread); |
| |
| zx_exception_report_t report; |
| zx_status_t status = zx_object_get_info(thread, ZX_INFO_THREAD_EXCEPTION_REPORT, |
| &report, sizeof(report), NULL, NULL); |
| if (status != ZX_OK) { |
| printf("failed to get exception report for [%" PRIu64 ".%" PRIu64 "] : error %d\n", pid, tid, status); |
| return; |
| } |
| |
| *type = report.header.type; |
| |
| if (!ZX_EXCP_IS_ARCH(*type) && *type != ZX_EXCP_POLICY_ERROR) { |
| return; |
| } |
| |
| auto context = report.context; |
| |
| zx_vaddr_t pc = 0, sp = 0, fp = 0; |
| const char* arch = "unknown"; |
| |
| if (inspector_read_general_regs(thread, regs) != ZX_OK) { |
| return; |
| } |
| |
| #if defined(__x86_64__) |
| arch = "x86_64"; |
| pc = regs->rip; |
| sp = regs->rsp; |
| fp = regs->rbp; |
| #elif defined(__aarch64__) |
| arch = "aarch64"; |
| pc = regs->pc; |
| sp = regs->sp; |
| fp = regs->r[29]; |
| #else |
| #error unsupported architecture |
| #endif |
| |
| const char* fatal = "fatal "; |
| // We don't want to print "fatal" when we are printing the debug info from a |
| // backtrace request as we will resume the thread at the end. |
| if (is_backtrace_request(*type, regs)) { |
| fatal = ""; |
| } |
| |
| char process_name[ZX_MAX_NAME_LEN]; |
| status = zx_object_get_property(process, ZX_PROP_NAME, process_name, sizeof(process_name)); |
| if (status != ZX_OK) { |
| strlcpy(process_name, "unknown", sizeof(process_name)); |
| } |
| |
| char thread_name[ZX_MAX_NAME_LEN]; |
| status = zx_object_get_property(thread, ZX_PROP_NAME, thread_name, sizeof(thread_name)); |
| if (status != ZX_OK) { |
| strlcpy(thread_name, "unknown", sizeof(thread_name)); |
| } |
| |
| printf("<== %sexception: process %s[%" PRIu64 "] thread %s[%" PRIu64 "]\n", fatal, |
| process_name, pid, thread_name, tid); |
| |
| if (report.header.type == ZX_EXCP_FATAL_PAGE_FAULT) { |
| const char* access_type; |
| const char* violation; |
| #if defined(__x86_64__) |
| static constexpr uint32_t kErrCodeInstrFetch = (1 << 4); |
| static constexpr uint32_t kErrCodeWrite = (1 << 1); |
| static constexpr uint32_t kErrCodeProtectionViolation = (1 << 0); |
| if (context.arch.u.x86_64.err_code & kErrCodeInstrFetch) { |
| access_type = "execute"; |
| } else if (context.arch.u.x86_64.err_code & kErrCodeWrite) { |
| access_type = "write"; |
| } else { |
| access_type = "read"; |
| } |
| |
| if (context.arch.u.x86_64.err_code & kErrCodeProtectionViolation) { |
| violation = "protection"; |
| } else { |
| violation = "not-present"; |
| } |
| #elif defined(__aarch64__) |
| // The one ec bit that's different between a data and instruction abort |
| static constexpr uint32_t kEcDataAbortBit = (1 << 28); |
| static constexpr uint32_t kIssCacheOp = (1 << 8); |
| static constexpr uint32_t kIssWrite = (1 << 6); |
| static constexpr uint32_t kDccNoLvlMask = 0b111100; |
| static constexpr uint32_t kDccPermissionFault = 0b1100; |
| |
| if (context.arch.u.arm_64.esr & kEcDataAbortBit) { |
| if (context.arch.u.arm_64.esr & kIssWrite |
| && !(context.arch.u.arm_64.esr & kIssCacheOp)) { |
| access_type = "write"; |
| } else { |
| access_type = "read"; |
| } |
| } else { |
| access_type = "execute"; |
| } |
| |
| if ((context.arch.u.arm_64.esr & kDccNoLvlMask) == kDccPermissionFault) { |
| violation = "protection"; |
| } else { |
| violation = "not-present"; |
| } |
| #else |
| #error unsupported architecture |
| #endif |
| printf("<== %s %s page fault, PC at 0x%" PRIxPTR "\n", access_type, violation , pc); |
| } else { |
| printf("<== %s, PC at 0x%" PRIxPTR "\n", inspector::excp_type_to_str(report.header.type), |
| pc); |
| } |
| |
| #if defined(__x86_64__) |
| inspector_print_general_regs(stdout, regs, &context.arch.u.x86_64); |
| #elif defined(__aarch64__) |
| inspector_print_general_regs(stdout, regs, &context.arch.u.arm_64); |
| |
| // Only output the Fault address register and ESR if there's a data or |
| // alignment fault. |
| if (ZX_EXCP_FATAL_PAGE_FAULT == report.header.type || |
| ZX_EXCP_UNALIGNED_ACCESS == report.header.type) { |
| printf(" far %#18" PRIx64 " esr %#18x\n", |
| context.arch.u.arm_64.far, context.arch.u.arm_64.esr); |
| } |
| #else |
| #error unsupported architecture |
| #endif |
| |
| printf("bottom of user stack:\n"); |
| inspector_print_memory(stdout, process, sp, inspector::kMemoryDumpSize); |
| |
| printf("arch: %s\n", arch); |
| |
| { |
| // Whether to use libunwind or not. |
| // If not then we use a simple algorithm that assumes ABI-specific |
| // frame pointers are present. |
| const bool use_libunwind = true; |
| |
| // TODO (jakehehrlich): Remove old dso format. |
| inspector_dsoinfo_t* dso_list = inspector_dso_fetch_list(process); |
| inspector_dso_print_list(stdout, dso_list); |
| inspector_print_markup_context(stdout, process); |
| // TODO (jakehehrlich): Remove the old backtrace format. |
| inspector_print_backtrace(stdout, process, thread, dso_list, |
| pc, sp, fp, use_libunwind); |
| inspector_print_backtrace_markup(stdout, process, thread, dso_list, |
| pc, sp, fp, use_libunwind); |
| } |
| |
| // TODO(ZX-588): Print a backtrace of all other threads in the process. |
| |
| if (inspector::verbosity_level >= 1) |
| printf("Done handling thread %" PRIu64 ".%" PRIu64 ".\n", pid, tid); |
| } |