diff options
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibC/sys/ptrace.h | 3 | ||||
-rw-r--r-- | Userland/Libraries/LibDebug/DebugSession.cpp | 88 | ||||
-rw-r--r-- | Userland/Libraries/LibDebug/DebugSession.h | 54 |
3 files changed, 145 insertions, 0 deletions
diff --git a/Userland/Libraries/LibC/sys/ptrace.h b/Userland/Libraries/LibC/sys/ptrace.h index ad5de3fb6e..3caadac462 100644 --- a/Userland/Libraries/LibC/sys/ptrace.h +++ b/Userland/Libraries/LibC/sys/ptrace.h @@ -42,6 +42,9 @@ __BEGIN_DECLS #define PT_POKEDEBUG 10 #define PT_PEEKDEBUG 11 +#define DEBUG_STATUS_REGISTER 6 +#define DEBUG_CONTROL_REGISTER 7 + // FIXME: PID/TID ISSUE // Affects the entirety of LibDebug and Userland/strace.cpp. // See also Kernel/Ptrace.cpp diff --git a/Userland/Libraries/LibDebug/DebugSession.cpp b/Userland/Libraries/LibDebug/DebugSession.cpp index bc584be00c..3484ee2183 100644 --- a/Userland/Libraries/LibDebug/DebugSession.cpp +++ b/Userland/Libraries/LibDebug/DebugSession.cpp @@ -32,6 +32,7 @@ #include <LibCore/File.h> #include <LibRegex/Regex.h> #include <stdlib.h> +#include <sys/mman.h> namespace Debug { @@ -52,6 +53,11 @@ DebugSession::~DebugSession() } m_breakpoints.clear(); + for (const auto& wp : m_watchpoints) { + disable_watchpoint(wp.key); + } + m_watchpoints.clear(); + if (ptrace(PT_DETACH, m_debuggee_pid, 0, 0) < 0) { perror("PT_DETACH"); } @@ -141,6 +147,24 @@ Optional<u32> DebugSession::peek(u32* address) const return result; } +bool DebugSession::poke_debug(u32 register_index, u32 data) +{ + if (ptrace(PT_POKEDEBUG, m_debuggee_pid, reinterpret_cast<u32*>(register_index), data) < 0) { + perror("PT_POKEDEBUG"); + return false; + } + return true; +} + +Optional<u32> DebugSession::peek_debug(u32 register_index) const +{ + Optional<u32> result; + int rc = ptrace(PT_PEEKDEBUG, m_debuggee_pid, reinterpret_cast<u32*>(register_index), 0); + if (errno == 0) + result = static_cast<u32>(rc); + return result; +} + bool DebugSession::insert_breakpoint(void* address) { // We insert a software breakpoint by @@ -209,6 +233,68 @@ bool DebugSession::breakpoint_exists(void* address) const return m_breakpoints.contains(address); } +bool DebugSession::insert_watchpoint(void* address, u32 ebp) +{ + auto current_register_status = peek_debug(DEBUG_CONTROL_REGISTER); + if (!current_register_status.has_value()) + return false; + u32 dr7_value = current_register_status.value(); + u32 next_available_index; + for (next_available_index = 0; next_available_index < 4; next_available_index++) { + auto bitmask = 1 << (next_available_index * 2); + if ((dr7_value & bitmask) == 0) + break; + } + if (next_available_index > 3) + return false; + WatchPoint watchpoint { address, next_available_index, ebp }; + + if (!poke_debug(next_available_index, reinterpret_cast<uintptr_t>(address))) + return false; + + dr7_value |= (1u << (next_available_index * 2)); // Enable local breakpoint for our index + auto condition_shift = 16 + (next_available_index * 4); + dr7_value &= ~(0b11u << condition_shift); + dr7_value |= 1u << condition_shift; // Trigger on writes + auto length_shift = 18 + (next_available_index * 4); + dr7_value &= ~(0b11u << length_shift); + // FIXME: take variable size into account? + dr7_value |= 0b11u << length_shift; // 4 bytes wide + if (!poke_debug(DEBUG_CONTROL_REGISTER, dr7_value)) + return false; + + m_watchpoints.set(address, watchpoint); + return true; +} + +bool DebugSession::remove_watchpoint(void* address) +{ + if (!disable_watchpoint(address)) + return false; + return m_watchpoints.remove(address); +} + +bool DebugSession::disable_watchpoint(void* address) +{ + VERIFY(watchpoint_exists(address)); + auto watchpoint = m_watchpoints.get(address).value(); + if (!poke_debug(watchpoint.debug_register_index, 0)) + return false; + auto current_register_status = peek_debug(DEBUG_CONTROL_REGISTER); + if (!current_register_status.has_value()) + return false; + u32 dr7_value = current_register_status.value(); + dr7_value &= ~(1u << watchpoint.debug_register_index * 2); + if (!poke_debug(watchpoint.debug_register_index, dr7_value)) + return false; + return true; +} + +bool DebugSession::watchpoint_exists(void* address) const +{ + return m_watchpoints.contains(address); +} + PtraceRegisters DebugSession::get_registers() const { PtraceRegisters regs; @@ -278,6 +364,8 @@ void DebugSession::detach() for (auto& breakpoint : m_breakpoints.keys()) { remove_breakpoint(breakpoint); } + for (auto& watchpoint : m_watchpoints.keys()) + remove_watchpoint(watchpoint); continue_debuggee(); } diff --git a/Userland/Libraries/LibDebug/DebugSession.h b/Userland/Libraries/LibDebug/DebugSession.h index 50e9e96370..993ca4c805 100644 --- a/Userland/Libraries/LibDebug/DebugSession.h +++ b/Userland/Libraries/LibDebug/DebugSession.h @@ -54,6 +54,9 @@ public: bool poke(u32* address, u32 data); Optional<u32> peek(u32* address) const; + bool poke_debug(u32 register_index, u32 data); + Optional<u32> peek_debug(u32 register_index) const; + enum class BreakPointState { Enabled, Disabled, @@ -87,6 +90,17 @@ public: bool remove_breakpoint(void* address); bool breakpoint_exists(void* address) const; + struct WatchPoint { + void* address { nullptr }; + u32 debug_register_index { 0 }; + u32 ebp { 0 }; + }; + + bool insert_watchpoint(void* address, u32 ebp); + bool remove_watchpoint(void* address); + bool disable_watchpoint(void* address); + bool watchpoint_exists(void* address) const; + void dump_breakpoints() { for (auto addr : m_breakpoints.keys()) { @@ -182,6 +196,7 @@ private: bool m_is_debuggee_dead { false }; HashMap<void*, BreakPoint> m_breakpoints; + HashMap<void*, WatchPoint> m_watchpoints; // Maps from base address to loaded library HashMap<String, NonnullOwnPtr<LoadedLibrary>> m_loaded_libraries; @@ -223,6 +238,45 @@ void DebugSession::run(DesiredInitialDebugeeState initial_debugee_state, Callbac state = State::FreeRun; auto regs = get_registers(); + + auto debug_status = peek_debug(DEBUG_STATUS_REGISTER); + if (debug_status.has_value() && (debug_status.value() & 0b1111) > 0) { + // Tripped a watchpoint + auto watchpoint_index = debug_status.value() & 0b1111; + Optional<WatchPoint> watchpoint {}; + for (auto wp : m_watchpoints) { + if ((watchpoint_index & (1 << wp.value.debug_register_index)) == 0) + continue; + watchpoint = wp.value; + break; + } + if (watchpoint.has_value()) { + auto required_ebp = watchpoint.value().ebp; + auto found_ebp = false; + + u32 current_ebp = regs.ebp; + u32 current_instruction = regs.eip; + do { + if (current_ebp == required_ebp) { + found_ebp = true; + break; + } + auto return_address = peek(reinterpret_cast<u32*>(current_ebp + sizeof(FlatPtr))); + auto next_ebp = peek(reinterpret_cast<u32*>(current_ebp)); + VERIFY(return_address.has_value()); + VERIFY(next_ebp.has_value()); + current_instruction = return_address.value(); + current_ebp = next_ebp.value(); + } while (current_ebp && current_instruction); + + if (!found_ebp) { + dbgln("Removing watchpoint at {:p} because it went out of scope!", watchpoint.value().address); + remove_watchpoint(watchpoint.value().address); + continue; + } + } + } + Optional<BreakPoint> current_breakpoint; if (state == State::FreeRun || state == State::Syscall) { |