mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 04:27:44 +00:00
LibDebug: Implement ability to set watchpoints
Now we can set hardware watchpoints for our variables! :^) These watchpoints will be automatically removed when they go out of scope.
This commit is contained in:
parent
3123ffb19d
commit
7a1396f509
3 changed files with 145 additions and 0 deletions
|
@ -42,6 +42,9 @@ __BEGIN_DECLS
|
||||||
#define PT_POKEDEBUG 10
|
#define PT_POKEDEBUG 10
|
||||||
#define PT_PEEKDEBUG 11
|
#define PT_PEEKDEBUG 11
|
||||||
|
|
||||||
|
#define DEBUG_STATUS_REGISTER 6
|
||||||
|
#define DEBUG_CONTROL_REGISTER 7
|
||||||
|
|
||||||
// FIXME: PID/TID ISSUE
|
// FIXME: PID/TID ISSUE
|
||||||
// Affects the entirety of LibDebug and Userland/strace.cpp.
|
// Affects the entirety of LibDebug and Userland/strace.cpp.
|
||||||
// See also Kernel/Ptrace.cpp
|
// See also Kernel/Ptrace.cpp
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
#include <LibRegex/Regex.h>
|
#include <LibRegex/Regex.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
namespace Debug {
|
namespace Debug {
|
||||||
|
|
||||||
|
@ -52,6 +53,11 @@ DebugSession::~DebugSession()
|
||||||
}
|
}
|
||||||
m_breakpoints.clear();
|
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) {
|
if (ptrace(PT_DETACH, m_debuggee_pid, 0, 0) < 0) {
|
||||||
perror("PT_DETACH");
|
perror("PT_DETACH");
|
||||||
}
|
}
|
||||||
|
@ -141,6 +147,24 @@ Optional<u32> DebugSession::peek(u32* address) const
|
||||||
return result;
|
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)
|
bool DebugSession::insert_breakpoint(void* address)
|
||||||
{
|
{
|
||||||
// We insert a software breakpoint by
|
// We insert a software breakpoint by
|
||||||
|
@ -209,6 +233,68 @@ bool DebugSession::breakpoint_exists(void* address) const
|
||||||
return m_breakpoints.contains(address);
|
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 DebugSession::get_registers() const
|
||||||
{
|
{
|
||||||
PtraceRegisters regs;
|
PtraceRegisters regs;
|
||||||
|
@ -278,6 +364,8 @@ void DebugSession::detach()
|
||||||
for (auto& breakpoint : m_breakpoints.keys()) {
|
for (auto& breakpoint : m_breakpoints.keys()) {
|
||||||
remove_breakpoint(breakpoint);
|
remove_breakpoint(breakpoint);
|
||||||
}
|
}
|
||||||
|
for (auto& watchpoint : m_watchpoints.keys())
|
||||||
|
remove_watchpoint(watchpoint);
|
||||||
continue_debuggee();
|
continue_debuggee();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,9 @@ public:
|
||||||
bool poke(u32* address, u32 data);
|
bool poke(u32* address, u32 data);
|
||||||
Optional<u32> peek(u32* address) const;
|
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 {
|
enum class BreakPointState {
|
||||||
Enabled,
|
Enabled,
|
||||||
Disabled,
|
Disabled,
|
||||||
|
@ -87,6 +90,17 @@ public:
|
||||||
bool remove_breakpoint(void* address);
|
bool remove_breakpoint(void* address);
|
||||||
bool breakpoint_exists(void* address) const;
|
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()
|
void dump_breakpoints()
|
||||||
{
|
{
|
||||||
for (auto addr : m_breakpoints.keys()) {
|
for (auto addr : m_breakpoints.keys()) {
|
||||||
|
@ -182,6 +196,7 @@ private:
|
||||||
bool m_is_debuggee_dead { false };
|
bool m_is_debuggee_dead { false };
|
||||||
|
|
||||||
HashMap<void*, BreakPoint> m_breakpoints;
|
HashMap<void*, BreakPoint> m_breakpoints;
|
||||||
|
HashMap<void*, WatchPoint> m_watchpoints;
|
||||||
|
|
||||||
// Maps from base address to loaded library
|
// Maps from base address to loaded library
|
||||||
HashMap<String, NonnullOwnPtr<LoadedLibrary>> m_loaded_libraries;
|
HashMap<String, NonnullOwnPtr<LoadedLibrary>> m_loaded_libraries;
|
||||||
|
@ -223,6 +238,45 @@ void DebugSession::run(DesiredInitialDebugeeState initial_debugee_state, Callbac
|
||||||
state = State::FreeRun;
|
state = State::FreeRun;
|
||||||
|
|
||||||
auto regs = get_registers();
|
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;
|
Optional<BreakPoint> current_breakpoint;
|
||||||
|
|
||||||
if (state == State::FreeRun || state == State::Syscall) {
|
if (state == State::FreeRun || state == State::Syscall) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue