blob: 05bf355cb68a62087c43d7b981c54e75a1d9889f [file] [log] [blame]
// Copyright 2018 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 "garnet/bin/debug_agent/arch.h"
#include <zircon/syscalls/exception.h>
#include <zircon/status.h>
#include "garnet/lib/debug_ipc/register_desc.h"
#include "garnet/public/lib/fxl/strings/string_printf.h"
#include "lib/fxl/logging.h"
namespace debug_agent {
namespace arch {
// "BRK 0" instruction.
// - Low 5 bits = 0.
// - High 11 bits = 11010100001
// - In between 16 bits is the argument to the BRK instruction (in this case
// zero).
const BreakInstructionType kBreakInstruction = 0xd4200000;
uint64_t ArchProvider::BreakpointInstructionForSoftwareExceptionAddress(
uint64_t exception_addr) {
// ARM reports the exception for the exception instruction itself.
return exception_addr;
}
uint64_t ArchProvider::NextInstructionForSoftwareExceptionAddress(
uint64_t exception_addr) {
// For software exceptions, the exception address is the one that caused it,
// so next one is just 4 bytes following.
//
// TODO(brettw) handle THUMB. When a software breakpoint is hit, ESR_EL1
// will contain the "instruction length" field which for T32 instructions
// will be 0 (indicating 16-bits). This exception state somehow needs to be
// plumbed down to our exception handler.
return exception_addr + 4;
}
bool ArchProvider::IsBreakpointInstruction(zx::process& process,
uint64_t address) {
BreakInstructionType data;
size_t actual_read = 0;
if (process.read_memory(address, &data, sizeof(BreakInstructionType),
&actual_read) != ZX_OK ||
actual_read != sizeof(BreakInstructionType))
return false;
// The BRK instruction could have any number associated with it, even though
// we only write "BRK 0", so check for the low 5 and high 11 bytes as
// described above.
constexpr BreakInstructionType kMask = 0b11111111111000000000000000011111;
return (data & kMask) == kBreakInstruction;
}
uint64_t* ArchProvider::IPInRegs(zx_thread_state_general_regs* regs) {
return &regs->pc;
}
uint64_t* ArchProvider::SPInRegs(zx_thread_state_general_regs* regs) {
return &regs->sp;
}
uint64_t* ArchProvider::BPInRegs(zx_thread_state_general_regs* regs) {
return &regs->r[29];
}
::debug_ipc::Arch ArchProvider::GetArch() { return ::debug_ipc::Arch::kArm64; }
namespace {
using debug_ipc::RegisterID;
debug_ipc::Register CreateRegister(RegisterID id, uint32_t length,
const void* val_ptr) {
debug_ipc::Register reg;
reg.id = id;
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(val_ptr);
reg.data.assign(ptr, ptr + length);
return reg;
}
zx_status_t ReadGeneralRegs(const zx::thread& thread,
std::vector<debug_ipc::Register>* out) {
// We get the general state registers.
zx_thread_state_general_regs gen_regs;
zx_status_t status = thread.read_state(ZX_THREAD_STATE_GENERAL_REGS,
&gen_regs, sizeof(gen_regs));
if (status != ZX_OK)
return status;
// We add the X0-X29 registers.
uint32_t base = static_cast<uint32_t>(RegisterID::kARMv8_x0);
for (int i = 0; i < 30; i++) {
RegisterID type = static_cast<RegisterID>(base + i);
out->push_back(CreateRegister(type, 8u, &gen_regs.r[i]));
}
// Add the named out.
out->push_back(CreateRegister(RegisterID::kARMv8_lr, 8u, &gen_regs.lr));
out->push_back(CreateRegister(RegisterID::kARMv8_sp, 8u, &gen_regs.sp));
out->push_back(CreateRegister(RegisterID::kARMv8_pc, 8u, &gen_regs.pc));
out->push_back(CreateRegister(RegisterID::kARMv8_cpsr, 8u, &gen_regs.cpsr));
return ZX_OK;
}
zx_status_t ReadVectorRegs(const zx::thread& thread,
std::vector<debug_ipc::Register>* out) {
zx_thread_state_vector_regs vec_regs;
zx_status_t status = thread.read_state(ZX_THREAD_STATE_VECTOR_REGS, &vec_regs,
sizeof(vec_regs));
if (status != ZX_OK)
return status;
out->push_back(CreateRegister(RegisterID::kARMv8_fpcr, 4u, &vec_regs.fpcr));
out->push_back(CreateRegister(RegisterID::kARMv8_fpsr, 4u, &vec_regs.fpsr));
auto base = static_cast<uint32_t>(RegisterID::kARMv8_v0);
for (size_t i = 0; i < 32; i++) {
auto reg_id = static_cast<RegisterID>(base + i);
out->push_back(CreateRegister(reg_id, 16u, &vec_regs.v[i]));
}
return ZX_OK;
}
zx_status_t ReadDebugRegs(const zx::thread& thread,
std::vector<debug_ipc::Register>* out) {
zx_thread_state_debug_regs_t debug_regs;
zx_status_t status = thread.read_state(ZX_THREAD_STATE_DEBUG_REGS,
&debug_regs, sizeof(debug_regs));
if (status != ZX_OK)
return status;
if (debug_regs.hw_bps_count >= AARCH64_MAX_HW_BREAKPOINTS) {
FXL_LOG(ERROR) << "Received too many HW breakpoints: "
<< debug_regs.hw_bps_count
<< " (max: " << AARCH64_MAX_HW_BREAKPOINTS << ").";
return ZX_ERR_INVALID_ARGS;
}
auto bcr_base = static_cast<uint32_t>(RegisterID::kARMv8_dbgbcr0_el1);
auto bvr_base = static_cast<uint32_t>(RegisterID::kARMv8_dbgbvr0_el1);
for (size_t i = 0; i < debug_regs.hw_bps_count; i++) {
auto bcr_id = static_cast<RegisterID>(bcr_base + i);
out->push_back(CreateRegister(bcr_id, sizeof(debug_regs.hw_bps[i].dbgbcr),
&debug_regs.hw_bps[i].dbgbcr));
auto bvr_id = static_cast<RegisterID>(bvr_base + i);
out->push_back(CreateRegister(bvr_id, sizeof(debug_regs.hw_bps[i].dbgbvr),
&debug_regs.hw_bps[i].dbgbvr));
}
// TODO(donosoc): Currently this registers that are platform information are
// being hacked out as HW breakpoint values in order to know
// what the actual settings are.
// This should be changed to get the actual values instead, but
// check in for now in order to continue.
out->push_back(CreateRegister(
RegisterID::kARMv8_id_aa64dfr0_el1, 8u,
&debug_regs.hw_bps[AARCH64_MAX_HW_BREAKPOINTS - 1].dbgbvr));
out->push_back(CreateRegister(
RegisterID::kARMv8_mdscr_el1, 8u,
&debug_regs.hw_bps[AARCH64_MAX_HW_BREAKPOINTS - 2].dbgbvr));
return ZX_OK;
}
} // namespace
zx_status_t ArchProvider::ReadRegisters(
const debug_ipc::RegisterCategory::Type& cat, const zx::thread& thread,
std::vector<debug_ipc::Register>* out) {
switch (cat) {
case debug_ipc::RegisterCategory::Type::kGeneral:
return ReadGeneralRegs(thread, out);
case debug_ipc::RegisterCategory::Type::kFP:
// No FP registers
return true;
case debug_ipc::RegisterCategory::Type::kVector:
return ReadVectorRegs(thread, out);
case debug_ipc::RegisterCategory::Type::kDebug:
return ReadDebugRegs(thread, out);
default:
FXL_LOG(ERROR) << "Invalid category: " << static_cast<uint32_t>(cat);
return ZX_ERR_INVALID_ARGS;
}
}
zx_status_t ArchProvider::WriteRegisters(const debug_ipc::RegisterCategory&,
zx::thread*) {
// TODO(donosoc): Implement.
return ZX_ERR_NOT_SUPPORTED;
}
debug_ipc::NotifyException::Type HardwareNotificationType(const zx::thread&) {
// TODO: For now zxdb only supports single step.
return debug_ipc::NotifyException::Type::kSingleStep;
}
debug_ipc::NotifyException::Type ArchProvider::DecodeExceptionType(
const DebuggedThread& thread, uint32_t exception_type) {
switch (exception_type) {
case ZX_EXCP_SW_BREAKPOINT:
return debug_ipc::NotifyException::Type::kSoftware;
case ZX_EXCP_HW_BREAKPOINT:
// For now HW exception means single step.
return debug_ipc::NotifyException::Type::kSingleStep;
default:
return debug_ipc::NotifyException::Type::kGeneral;
}
FXL_NOTREACHED();
return debug_ipc::NotifyException::Type::kLast;
}
// HW Breakpoints --------------------------------------------------------------
uint64_t ArchProvider::BreakpointInstructionForHardwareExceptionAddress(
uint64_t exception_addr) {
FXL_NOTREACHED() << "NOT IMPLEMENTED";
return exception_addr;
}
uint64_t ArchProvider::NextInstructionForHardwareExceptionAddress(
uint64_t exception_addr) {
FXL_NOTREACHED() << "NOT IMPLEMENTED";
return exception_addr;
}
debug_ipc::NotifyException::Type HardwareNotificationType(
const DebuggedThread& thread) {
// TODO(donosoc): Implement hw exception detection logic.
return debug_ipc::NotifyException::Type::kSingleStep;
}
zx_status_t ArchProvider::InstallHWBreakpoint(zx::thread*, uint64_t address) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ArchProvider::UninstallHWBreakpoint(zx::thread*, uint64_t address) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ArchProvider::InstallWatchpoint(zx::thread*,
const debug_ipc::AddressRange&) {
FXL_NOTIMPLEMENTED();
return ZX_OK;
}
zx_status_t ArchProvider::UninstallWatchpoint(zx::thread*,
const debug_ipc::AddressRange&) {
FXL_NOTIMPLEMENTED();
return ZX_OK;
}
} // namespace arch
} // namespace debug_agent