| // 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 <lib/syslog/cpp/macros.h> | |
| #include <zircon/status.h> | |
| #include <zircon/syscalls/exception.h> | |
| #include "src/developer/debug/debug_agent/arch.h" | |
| #include "src/developer/debug/debug_agent/debugged_process.h" | |
| #include "src/developer/debug/debug_agent/debugged_thread.h" | |
| #include "src/developer/debug/ipc/decode_exception.h" | |
| #include "src/developer/debug/ipc/register_desc.h" | |
| #include "src/developer/debug/shared/arch_x86.h" | |
| #include "src/developer/debug/shared/logging/logging.h" | |
| // Notes on x64 architecture: | |
| // | |
| // Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3 (3A, 3B, 3C & 3D): | |
| // Chapter 17 holds the debug spefications: | |
| // https://software.intel.com/sites/default/files/managed/a4/60/325383-sdm-vol-2abcd.pdf | |
| // | |
| // Hardware Breakpoints/Watchpoints | |
| // ------------------------------------------------------------------------------------------------- | |
| // | |
| // Hardware breakpoints permits to stop a thread when it accesses an address setup in one of the | |
| // hw breakpoints registers. They will work independent whether the address in question is | |
| // read-only or not. | |
| // | |
| // Watchpoints are meant to throw an exception whenever the given address is read or written to, | |
| // depending on the configuration. | |
| // | |
| // DR0 to DR4 registers: There registers are the address to which the hw breakpoint/watchpoint | |
| // refers to. How it is interpreted depends on the associated configuration on the register DR7. | |
| // | |
| // DR6: Debug Status Register. | |
| // | |
| // This register is updated when the CPU encounters a #DB harware exception. This registers permits | |
| // users to interpret the result of an exception, such as if it was a single-step, hardware | |
| // breakpoint, etc. | |
| // | |
| // zircon/system/public/zircon/hw/debug/x86.h holds a good description of what each bit within the | |
| // register means. | |
| // | |
| // DR7: Debug Control Register. | |
| // | |
| // This register is used to establish the breakpoint conditions for the address breakpoint registers | |
| // (DR0-DR3) and to enable debug exceptions for each of them individually. | |
| // | |
| // The following fields are accepted by the user. All other fields are ignored (masked): | |
| // | |
| // - L0, L1, L2, L3: These defines whether breakpoint/watchpoint <n> is enabled or not. | |
| // | |
| // - LEN0, LEN1, LEN2, LEN3: Defines the "length" of the breakpoint/watchpoint. | |
| // 00: 1 byte. | |
| // 01: 2 byte. DRn must be 2 byte aligned. | |
| // 10: 8 byte. DRn must be 8 byte aligned. | |
| // 11: 4 byte. DRn must be 4 byte aligned. | |
| // p | |
| // - RW0, RW1, RW2, RW3: The "mode" of the registers. | |
| // 00: Only instruction execution (hw breakpoint). | |
| // 01: Only data write (write watchpoint). | |
| // 10: Dependant by CR4.DE. Not supported by Zircon. | |
| // - CR4.DE = 0: Undefined. | |
| // - CR4.DE = 1: Only on I/0 read/write. | |
| // 11: Only on data read/write (read/write watchpoint). | |
| namespace debug_agent { | |
| namespace arch { | |
| namespace { | |
| using debug_ipc::Register; | |
| using debug_ipc::RegisterID; | |
| // Implements a case statement for calling WriteRegisterValue assuming the Zircon register | |
| // field matches the enum name. This avoids implementation typos where the names don't match. | |
| #define IMPLEMENT_CASE_WRITE_REGISTER_VALUE(name) \ | |
| case RegisterID::kX64_##name: \ | |
| status = WriteRegisterValue(reg, ®s->name); \ | |
| break; | |
| zx_status_t ReadGeneralRegs(const zx::thread& thread, std::vector<debug_ipc::Register>& out) { | |
| 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; | |
| SaveGeneralRegs(gen_regs, out); | |
| return ZX_OK; | |
| } | |
| zx_status_t ReadFPRegs(const zx::thread& thread, std::vector<debug_ipc::Register>& out) { | |
| zx_thread_state_fp_regs fp_regs; | |
| zx_status_t status = thread.read_state(ZX_THREAD_STATE_FP_REGS, &fp_regs, sizeof(fp_regs)); | |
| if (status != ZX_OK) | |
| return status; | |
| out.emplace_back(RegisterID::kX64_fcw, fp_regs.fcw); | |
| out.emplace_back(RegisterID::kX64_fsw, fp_regs.fsw); | |
| out.emplace_back(RegisterID::kX64_ftw, fp_regs.ftw); | |
| out.emplace_back(RegisterID::kX64_fop, fp_regs.fop); | |
| out.emplace_back(RegisterID::kX64_fip, fp_regs.fip); | |
| out.emplace_back(RegisterID::kX64_fdp, fp_regs.fdp); | |
| // Each entry is 16 bytes long, but only 10 are actually used. | |
| out.emplace_back(RegisterID::kX64_st0, 16u, &fp_regs.st[0]); | |
| out.emplace_back(RegisterID::kX64_st1, 16u, &fp_regs.st[1]); | |
| out.emplace_back(RegisterID::kX64_st2, 16u, &fp_regs.st[2]); | |
| out.emplace_back(RegisterID::kX64_st3, 16u, &fp_regs.st[3]); | |
| out.emplace_back(RegisterID::kX64_st4, 16u, &fp_regs.st[4]); | |
| out.emplace_back(RegisterID::kX64_st5, 16u, &fp_regs.st[5]); | |
| out.emplace_back(RegisterID::kX64_st6, 16u, &fp_regs.st[6]); | |
| out.emplace_back(RegisterID::kX64_st7, 16u, &fp_regs.st[7]); | |
| 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.emplace_back(RegisterID::kX64_mxcsr, vec_regs.mxcsr); | |
| auto base = static_cast<uint32_t>(RegisterID::kX64_zmm0); | |
| for (size_t i = 0; i < 32; i++) | |
| out.emplace_back(static_cast<RegisterID>(base + i), 64u, &vec_regs.zmm[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; | |
| out.emplace_back(RegisterID::kX64_dr0, debug_regs.dr[0]); | |
| out.emplace_back(RegisterID::kX64_dr1, debug_regs.dr[1]); | |
| out.emplace_back(RegisterID::kX64_dr2, debug_regs.dr[2]); | |
| out.emplace_back(RegisterID::kX64_dr3, debug_regs.dr[3]); | |
| out.emplace_back(RegisterID::kX64_dr6, debug_regs.dr6); | |
| out.emplace_back(RegisterID::kX64_dr7, debug_regs.dr7); | |
| return ZX_OK; | |
| } | |
| // Adapter class to allow the exception decoder to get the debug registers if needed. | |
| class ExceptionInfo : public debug_ipc::X64ExceptionInfo { | |
| public: | |
| explicit ExceptionInfo(const zx::thread& thread) : thread_(thread) {} | |
| std::optional<debug_ipc::X64ExceptionInfo::DebugRegs> FetchDebugRegs() const override { | |
| 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) { | |
| DEBUG_LOG(Archx64) << "Could not get debug regs: " << zx_status_get_string(status); | |
| return std::nullopt; | |
| } | |
| debug_ipc::X64ExceptionInfo::DebugRegs ret; | |
| ret.dr0 = debug_regs.dr[0]; | |
| ret.dr1 = debug_regs.dr[1]; | |
| ret.dr2 = debug_regs.dr[2]; | |
| ret.dr3 = debug_regs.dr[3]; | |
| ret.dr6 = debug_regs.dr6; | |
| ret.dr7 = debug_regs.dr7; | |
| return ret; | |
| } | |
| private: | |
| const zx::thread& thread_; | |
| }; | |
| } // namespace | |
| const BreakInstructionType kBreakInstruction = 0xCC; | |
| // An X86 exception is 1 byte and a breakpoint exception is triggered with RIP pointing to the | |
| // following instruction. | |
| const int64_t kExceptionOffsetForSoftwareBreakpoint = 1; | |
| ::debug_ipc::Arch GetCurrentArch() { return ::debug_ipc::Arch::kX64; } | |
| void SaveGeneralRegs(const zx_thread_state_general_regs& input, | |
| std::vector<debug_ipc::Register>& out) { | |
| out.emplace_back(RegisterID::kX64_rax, input.rax); | |
| out.emplace_back(RegisterID::kX64_rbx, input.rbx); | |
| out.emplace_back(RegisterID::kX64_rcx, input.rcx); | |
| out.emplace_back(RegisterID::kX64_rdx, input.rdx); | |
| out.emplace_back(RegisterID::kX64_rsi, input.rsi); | |
| out.emplace_back(RegisterID::kX64_rdi, input.rdi); | |
| out.emplace_back(RegisterID::kX64_rbp, input.rbp); | |
| out.emplace_back(RegisterID::kX64_rsp, input.rsp); | |
| out.emplace_back(RegisterID::kX64_r8, input.r8); | |
| out.emplace_back(RegisterID::kX64_r9, input.r9); | |
| out.emplace_back(RegisterID::kX64_r10, input.r10); | |
| out.emplace_back(RegisterID::kX64_r11, input.r11); | |
| out.emplace_back(RegisterID::kX64_r12, input.r12); | |
| out.emplace_back(RegisterID::kX64_r13, input.r13); | |
| out.emplace_back(RegisterID::kX64_r14, input.r14); | |
| out.emplace_back(RegisterID::kX64_r15, input.r15); | |
| out.emplace_back(RegisterID::kX64_rip, input.rip); | |
| out.emplace_back(RegisterID::kX64_rflags, input.rflags); | |
| out.emplace_back(RegisterID::kX64_fsbase, input.fs_base); | |
| out.emplace_back(RegisterID::kX64_gsbase, input.gs_base); | |
| } | |
| zx_status_t ReadRegisters(const zx::thread& thread, const debug_ipc::RegisterCategory& cat, | |
| std::vector<debug_ipc::Register>& out) { | |
| switch (cat) { | |
| case debug_ipc::RegisterCategory::kGeneral: | |
| return ReadGeneralRegs(thread, out); | |
| case debug_ipc::RegisterCategory::kFloatingPoint: | |
| return ReadFPRegs(thread, out); | |
| case debug_ipc::RegisterCategory::kVector: | |
| return ReadVectorRegs(thread, out); | |
| case debug_ipc::RegisterCategory::kDebug: | |
| return ReadDebugRegs(thread, out); | |
| case debug_ipc::RegisterCategory::kNone: | |
| case debug_ipc::RegisterCategory::kLast: | |
| FX_LOGS(ERROR) << "Asking to get none/last category"; | |
| return ZX_ERR_INVALID_ARGS; | |
| } | |
| } | |
| zx_status_t WriteRegisters(zx::thread& thread, const debug_ipc::RegisterCategory& category, | |
| const std::vector<debug_ipc::Register>& registers) { | |
| switch (category) { | |
| case debug_ipc::RegisterCategory::kGeneral: { | |
| zx_thread_state_general_regs_t regs; | |
| zx_status_t res = thread.read_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs)); | |
| if (res != ZX_OK) | |
| return res; | |
| // Overwrite the values. | |
| res = WriteGeneralRegisters(registers, ®s); | |
| if (res != ZX_OK) | |
| return res; | |
| return thread.write_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs)); | |
| } | |
| case debug_ipc::RegisterCategory::kFloatingPoint: { | |
| zx_thread_state_fp_regs_t regs; | |
| zx_status_t res = thread.read_state(ZX_THREAD_STATE_FP_REGS, ®s, sizeof(regs)); | |
| if (res != ZX_OK) | |
| return res; | |
| // Overwrite the values. | |
| res = WriteFloatingPointRegisters(registers, ®s); | |
| if (res != ZX_OK) | |
| return res; | |
| return thread.write_state(ZX_THREAD_STATE_FP_REGS, ®s, sizeof(regs)); | |
| } | |
| case debug_ipc::RegisterCategory::kVector: { | |
| zx_thread_state_vector_regs_t regs; | |
| zx_status_t res = thread.read_state(ZX_THREAD_STATE_VECTOR_REGS, ®s, sizeof(regs)); | |
| if (res != ZX_OK) | |
| return res; | |
| // Overwrite the values. | |
| res = WriteVectorRegisters(registers, ®s); | |
| if (res != ZX_OK) | |
| return res; | |
| return thread.write_state(ZX_THREAD_STATE_VECTOR_REGS, ®s, sizeof(regs)); | |
| } | |
| case debug_ipc::RegisterCategory::kDebug: { | |
| zx_thread_state_debug_regs_t regs; | |
| zx_status_t res = thread.read_state(ZX_THREAD_STATE_DEBUG_REGS, ®s, sizeof(regs)); | |
| if (res != ZX_OK) | |
| return res; | |
| res = WriteDebugRegisters(registers, ®s); | |
| if (res != ZX_OK) | |
| return res; | |
| return thread.write_state(ZX_THREAD_STATE_DEBUG_REGS, ®s, sizeof(regs)); | |
| } | |
| case debug_ipc::RegisterCategory::kNone: | |
| case debug_ipc::RegisterCategory::kLast: | |
| break; | |
| } | |
| FX_NOTREACHED(); | |
| return ZX_ERR_INVALID_ARGS; | |
| } | |
| zx_status_t WriteGeneralRegisters(const std::vector<Register>& updates, | |
| zx_thread_state_general_regs_t* regs) { | |
| uint32_t begin = static_cast<uint32_t>(RegisterID::kX64_rax); | |
| uint32_t last = static_cast<uint32_t>(RegisterID::kX64_rflags); | |
| uint64_t* output_array = reinterpret_cast<uint64_t*>(regs); | |
| for (const Register& reg : updates) { | |
| if (reg.data.size() != 8) | |
| return ZX_ERR_INVALID_ARGS; | |
| // zx_thread_state_general_regs has the same layout as the RegisterID enum for x64 general | |
| // registers. | |
| uint32_t id = static_cast<uint32_t>(reg.id); | |
| if (id < begin || id > last) | |
| return ZX_ERR_INVALID_ARGS; | |
| // Insert the value to the correct offset. | |
| output_array[id - begin] = *reinterpret_cast<const uint64_t*>(reg.data.data()); | |
| } | |
| return ZX_OK; | |
| } | |
| zx_status_t WriteFloatingPointRegisters(const std::vector<Register>& updates, | |
| zx_thread_state_fp_regs_t* regs) { | |
| for (const auto& reg : updates) { | |
| zx_status_t status = ZX_OK; | |
| if (reg.id >= RegisterID::kX64_st0 && reg.id <= RegisterID::kX64_st7) { | |
| // FP stack value. | |
| uint32_t stack_index = | |
| static_cast<uint32_t>(reg.id) - static_cast<uint32_t>(RegisterID::kX64_st0); | |
| status = WriteRegisterValue(reg, ®s->st[stack_index]); | |
| } else { | |
| // FP control registers. | |
| switch (reg.id) { | |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(fcw); | |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(fsw); | |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(ftw); | |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(fop); | |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(fip); | |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(fdp); | |
| default: | |
| status = ZX_ERR_INVALID_ARGS; | |
| break; | |
| } | |
| } | |
| if (status != ZX_OK) | |
| return status; | |
| } | |
| return ZX_OK; | |
| } | |
| zx_status_t WriteVectorRegisters(const std::vector<Register>& updates, | |
| zx_thread_state_vector_regs_t* regs) { | |
| for (const auto& reg : updates) { | |
| zx_status_t status = ZX_OK; | |
| if (static_cast<uint32_t>(reg.id) >= static_cast<uint32_t>(RegisterID::kX64_zmm0) && | |
| static_cast<uint32_t>(reg.id) <= static_cast<uint32_t>(RegisterID::kX64_zmm31)) { | |
| uint32_t stack_index = | |
| static_cast<uint32_t>(reg.id) - static_cast<uint32_t>(RegisterID::kX64_zmm0); | |
| status = WriteRegisterValue(reg, ®s->zmm[stack_index]); | |
| } else { | |
| switch (reg.id) { | |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(mxcsr); | |
| default: | |
| status = ZX_ERR_INVALID_ARGS; | |
| break; | |
| } | |
| if (status != ZX_OK) | |
| return status; | |
| } | |
| } | |
| return ZX_OK; | |
| } | |
| zx_status_t WriteDebugRegisters(const std::vector<Register>& updates, | |
| zx_thread_state_debug_regs_t* regs) { | |
| for (const auto& reg : updates) { | |
| zx_status_t status = ZX_OK; | |
| switch (reg.id) { | |
| case RegisterID::kX64_dr0: | |
| status = WriteRegisterValue(reg, ®s->dr[0]); | |
| break; | |
| case RegisterID::kX64_dr1: | |
| status = WriteRegisterValue(reg, ®s->dr[1]); | |
| break; | |
| case RegisterID::kX64_dr2: | |
| status = WriteRegisterValue(reg, ®s->dr[2]); | |
| break; | |
| case RegisterID::kX64_dr3: | |
| status = WriteRegisterValue(reg, ®s->dr[3]); | |
| break; | |
| case RegisterID::kX64_dr6: | |
| status = WriteRegisterValue(reg, ®s->dr6); | |
| break; | |
| case RegisterID::kX64_dr7: | |
| status = WriteRegisterValue(reg, ®s->dr7); | |
| break; | |
| default: | |
| status = ZX_ERR_INVALID_ARGS; | |
| break; | |
| } | |
| if (status != ZX_OK) | |
| return status; | |
| } | |
| return ZX_OK; | |
| } | |
| debug_ipc::ExceptionType DecodeExceptionType(const zx::thread& thread, uint32_t exception_type) { | |
| ExceptionInfo info(thread); | |
| return debug_ipc::DecodeException(exception_type, info); | |
| } | |
| debug_ipc::ExceptionRecord FillExceptionRecord(const zx_exception_report_t& in) { | |
| debug_ipc::ExceptionRecord record; | |
| record.valid = true; | |
| record.arch.x64.vector = in.context.arch.u.x86_64.vector; | |
| record.arch.x64.err_code = in.context.arch.u.x86_64.err_code; | |
| record.arch.x64.cr2 = in.context.arch.u.x86_64.cr2; | |
| return record; | |
| } | |
| uint64_t NextInstructionForSoftwareExceptionAddress(uint64_t exception_addr) { | |
| // Exception address is the one following the instruction that caused it, so nothing needs to be | |
| // done. | |
| return exception_addr; | |
| } | |
| bool IsBreakpointInstruction(BreakInstructionType instruction) { | |
| // This handles the normal encoding of debug breakpoints (0xCC). It's also possible to cause an | |
| // interrupt 3 to happen using the opcode sequence 0xCD 0x03 but this has slightly different | |
| // semantics and no assemblers emit this. We can't easily check for that here since the | |
| // computation for the instruction address that is passed in assumes a 1-byte instruction. It | |
| // should be OK to ignore this case in practice. | |
| return instruction == kBreakInstruction; | |
| } | |
| uint64_t BreakpointInstructionForHardwareExceptionAddress(uint64_t exception_addr) { | |
| // x86 returns the instruction *about* to be executed when hitting the hw breakpoint. | |
| return exception_addr; | |
| } | |
| } // namespace arch | |
| } // namespace debug_agent |