| // Copyright 2019 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 "src/developer/debug/ipc/decode_exception.h" | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <zircon/hw/debug/x86.h> | |
| #include <zircon/syscalls/exception.h> | |
| #include "src/developer/debug/shared/arch_arm64.h" | |
| #include "src/developer/debug/shared/arch_x86.h" | |
| #include "src/developer/debug/shared/logging/logging.h" | |
| namespace debug_ipc { | |
| namespace { | |
| // clang-format off | |
| ExceptionType DecodeZircon(uint32_t code) { | |
| switch (code) { | |
| case ZX_EXCP_SW_BREAKPOINT: return ExceptionType::kSoftwareBreakpoint; | |
| case ZX_EXCP_HW_BREAKPOINT: return ExceptionType::kHardwareBreakpoint; | |
| case ZX_EXCP_GENERAL: return ExceptionType::kGeneral; | |
| case ZX_EXCP_FATAL_PAGE_FAULT: return ExceptionType::kPageFault; | |
| case ZX_EXCP_UNDEFINED_INSTRUCTION: return ExceptionType::kUndefinedInstruction; | |
| case ZX_EXCP_UNALIGNED_ACCESS: return ExceptionType::kUnalignedAccess; | |
| case ZX_EXCP_THREAD_STARTING: return ExceptionType::kThreadStarting; | |
| case ZX_EXCP_PROCESS_STARTING: return ExceptionType::kProcessStarting; | |
| case ZX_EXCP_THREAD_EXITING: return ExceptionType::kThreadExiting; | |
| case ZX_EXCP_POLICY_ERROR: return ExceptionType::kPolicyError; | |
| default: | |
| return ExceptionType::kUnknown; | |
| } | |
| } | |
| // clang-format on | |
| } // namespace | |
| // x64 --------------------------------------------------------------------------------------------- | |
| namespace { | |
| ExceptionType DecodeHardwareRegister(uint64_t dr7, int slot) { | |
| // clang-format off | |
| bool is_watchpoint = false; | |
| switch (slot) { | |
| case 0: is_watchpoint = X86_DBG_CONTROL_RW0_GET(dr7) != 0; break; | |
| case 1: is_watchpoint = X86_DBG_CONTROL_RW1_GET(dr7) != 0; break; | |
| case 2: is_watchpoint = X86_DBG_CONTROL_RW2_GET(dr7) != 0; break; | |
| case 3: is_watchpoint = X86_DBG_CONTROL_RW3_GET(dr7) != 0; break; | |
| default: | |
| FX_NOTREACHED(); | |
| return ExceptionType::kUnknown; | |
| } | |
| // clang-format on | |
| return is_watchpoint ? ExceptionType::kWatchpoint : ExceptionType::kHardwareBreakpoint; | |
| } | |
| } // namespace | |
| ExceptionType DecodeException(uint32_t code, const X64ExceptionInfo& info) { | |
| // All zircon exceptions don't need further analysis, except hardware which can represent a single | |
| // step, a hw breakpoint or a watchpoint. | |
| ExceptionType type = DecodeZircon(code); | |
| if (type != ExceptionType::kHardwareBreakpoint) | |
| return type; | |
| std::optional<X64ExceptionInfo::DebugRegs> regs; | |
| if (auto got = info.FetchDebugRegs()) { | |
| DEBUG_LOG(Archx64) << "DR6: " << debug_ipc::DR6ToString(got->dr6); | |
| regs = std::move(got.value()); | |
| } | |
| // If we could not get the registers, we return the zircon exception. In the case of the ambiguous | |
| // hardware type, we assume single step. | |
| if (!regs.has_value()) | |
| return ExceptionType::kSingleStep; | |
| // TODO(fxbug.dev/6246): This permits only one trigger per exception, when overlaps | |
| // could occur. For a first pass this is acceptable. | |
| if (X86_DBG_STATUS_BS_GET(regs->dr6)) | |
| return ExceptionType::kSingleStep; | |
| if (X86_DBG_STATUS_B0_GET(regs->dr6)) { | |
| return DecodeHardwareRegister(regs->dr7, 0); | |
| } else if (X86_DBG_STATUS_B1_GET(regs->dr6)) { | |
| return DecodeHardwareRegister(regs->dr7, 1); | |
| } else if (X86_DBG_STATUS_B2_GET(regs->dr6)) { | |
| return DecodeHardwareRegister(regs->dr7, 2); | |
| } else if (X86_DBG_STATUS_B3_GET(regs->dr6)) { | |
| return DecodeHardwareRegister(regs->dr7, 3); | |
| } else { | |
| FX_NOTREACHED() << "x86: No known hw exception set in DR6"; | |
| return ExceptionType::kUnknown; | |
| } | |
| } | |
| // arm64 ------------------------------------------------------------------------------------------- | |
| namespace { | |
| ExceptionType DecodeESR(uint32_t esr) { | |
| // The ESR register holds information about the last exception in the form of: | |
| // |31 26|25|24 0| | |
| // | EC |IL| ISS | | |
| // | |
| // Where: | |
| // - EC: Exception class field (what exception occurred). | |
| // - IL: Instruction length (whether the trap was 16-bit of 32-bit instruction). | |
| // - ISS: Instruction Specific Syndrome. The value is specific to each EC. | |
| uint32_t ec = esr >> 26; | |
| switch (ec) { | |
| case 0b111000: /* BRK from arm32 */ | |
| case 0b111100: /* BRK from arm64 */ | |
| return ExceptionType::kSoftwareBreakpoint; | |
| case 0b110000: /* HW breakpoint from a lower level */ | |
| case 0b110001: /* HW breakpoint from same level */ | |
| return ExceptionType::kHardwareBreakpoint; | |
| case 0b110010: /* software step from lower level */ | |
| case 0b110011: /* software step from same level */ | |
| return ExceptionType::kSingleStep; | |
| case 0b110100: /* HW watchpoint from a lower level */ | |
| case 0b110101: /* HW watchpoint from same level */ | |
| return ExceptionType::kWatchpoint; | |
| default: | |
| break; | |
| } | |
| return ExceptionType::kUnknown; | |
| } | |
| } // namespace | |
| ExceptionType DecodeException(uint32_t code, const Arm64ExceptionInfo& info) { | |
| // HW exceptions have to be analysed further. | |
| ExceptionType type = DecodeZircon(code); | |
| if (type != ExceptionType::kHardwareBreakpoint) | |
| return type; | |
| uint32_t esr; | |
| if (auto got = info.FetchESR()) { | |
| esr = *got; | |
| } else { | |
| return ExceptionType::kUnknown; | |
| } | |
| auto decoded_type = DecodeESR(esr); | |
| if (decoded_type == ExceptionType::kUnknown) { | |
| FX_NOTREACHED() << "Received invalid ESR value: 0x" << std::hex << esr << " (EC: 0x" | |
| << (esr >> 26) << ")."; | |
| return debug_ipc::ExceptionType::kUnknown; | |
| } | |
| return decoded_type; | |
| } | |
| } // namespace debug_ipc |