summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
Diffstat (limited to 'Userland/Libraries')
-rw-r--r--Userland/Libraries/LibC/sys/ptrace.h3
-rw-r--r--Userland/Libraries/LibDebug/DebugSession.cpp88
-rw-r--r--Userland/Libraries/LibDebug/DebugSession.h54
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) {