blob: 5f6671eabd911f9c38d9515fe1df5fcb9771c112 [file] [log] [blame]
// 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 DecodeX64Exception(uint32_t code,
fit::function<std::optional<X64DebugRegs>()> fetch_debug_regs) {
// 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<X64DebugRegs> regs;
if (auto got = fetch_debug_regs()) {
DEBUG_LOG(Archx64) << "DR6: " << debug::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(https://fxbug.dev/42140857): 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 DecodeArm64Exception(uint32_t code,
fit::function<std::optional<uint32_t>()> fetch_esr) {
// HW exceptions have to be analysed further.
ExceptionType type = DecodeZircon(code);
if (type != ExceptionType::kHardwareBreakpoint)
return type;
uint32_t esr;
if (auto got = fetch_esr()) {
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;
}
ExceptionType DecodeRiscv64Exception(uint32_t code) {
// RV64 doesn't support single-stepping/hardware breakpoints/watchpoints yet.
return DecodeZircon(code);
}
} // namespace debug_ipc