blob: 0012cf273a203381c46ff930a19e6556af46401e [file] [log] [blame]
// 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);
}