From cc9cbe0752ecf79f820b3a2c14e5aca31c3f88b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Sep 2025 10:27:50 +0000 Subject: [PATCH 01/30] Initial plan From a40c0907d5976d27d2369eb79180c0e1ae80c4e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Sep 2025 10:36:06 +0000 Subject: [PATCH 02/30] Add hardware breakpoint type enum and standardized interface Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- core/adapters/esrevenadapter.cpp | 68 +++++++++++++++++++-- core/adapters/esrevenadapter.h | 6 +- core/adapters/gdbadapter.cpp | 68 +++++++++++++++++++-- core/adapters/gdbadapter.h | 6 +- core/adapters/lldbadapter.cpp | 100 +++++++++++++++++++++++++++++++ core/adapters/lldbadapter.h | 4 ++ core/debugadapter.cpp | 14 +++++ core/debugadapter.h | 4 ++ core/debuggercommon.h | 20 +++++-- 9 files changed, 275 insertions(+), 15 deletions(-) diff --git a/core/adapters/esrevenadapter.cpp b/core/adapters/esrevenadapter.cpp index 6fc7cac4..a1c53f92 100644 --- a/core/adapters/esrevenadapter.cpp +++ b/core/adapters/esrevenadapter.cpp @@ -1199,20 +1199,80 @@ bool EsrevenAdapter::StepOverReverse() return true; } -bool EsrevenAdapter::AddHardwareWriteBreakpoint(uint64_t address) +bool EsrevenAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (m_isTargetRunning || !m_rspConnector) return false; - return this->m_rspConnector->TransmitAndReceive(RspData("Z2,{:x},{}", address, 1)).AsString() == "OK"; + std::string command; + switch (type) + { + case HardwareExecuteBreakpoint: + // Z1 = hardware execution breakpoint + command = fmt::format("Z1,{:x},{}", address, size); + break; + case HardwareReadBreakpoint: + // Z3 = hardware read watchpoint + command = fmt::format("Z3,{:x},{}", address, size); + break; + case HardwareWriteBreakpoint: + // Z2 = hardware write watchpoint + command = fmt::format("Z2,{:x},{}", address, size); + break; + case HardwareAccessBreakpoint: + // Z4 = hardware access watchpoint (read/write) + command = fmt::format("Z4,{:x},{}", address, size); + break; + default: + return false; + } + + return m_rspConnector->TransmitAndReceive(RspData(command)).AsString() == "OK"; } -bool EsrevenAdapter::RemoveHardwareWriteBreakpoint(uint64_t address) + +bool EsrevenAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (m_isTargetRunning || !m_rspConnector) return false; - return this->m_rspConnector->TransmitAndReceive(RspData("z2,{:x},{}", address, 1)).AsString() == "OK"; + std::string command; + switch (type) + { + case HardwareExecuteBreakpoint: + // z1 = remove hardware execution breakpoint + command = fmt::format("z1,{:x},{}", address, size); + break; + case HardwareReadBreakpoint: + // z3 = remove hardware read watchpoint + command = fmt::format("z3,{:x},{}", address, size); + break; + case HardwareWriteBreakpoint: + // z2 = remove hardware write watchpoint + command = fmt::format("z2,{:x},{}", address, size); + break; + case HardwareAccessBreakpoint: + // z4 = remove hardware access watchpoint (read/write) + command = fmt::format("z4,{:x},{}", address, size); + break; + default: + return false; + } + + return m_rspConnector->TransmitAndReceive(RspData(command)).AsString() == "OK"; +} + + +bool EsrevenAdapter::AddHardwareWriteBreakpoint(uint64_t address) +{ + // Delegate to new standardized method + return AddHardwareBreakpoint(address, HardwareWriteBreakpoint, 1); +} + +bool EsrevenAdapter::RemoveHardwareWriteBreakpoint(uint64_t address) +{ + // Delegate to new standardized method + return RemoveHardwareBreakpoint(address, HardwareWriteBreakpoint, 1); } bool EsrevenAdapter::StepReturnReverse() diff --git a/core/adapters/esrevenadapter.h b/core/adapters/esrevenadapter.h index 04a5d314..dd1b9407 100644 --- a/core/adapters/esrevenadapter.h +++ b/core/adapters/esrevenadapter.h @@ -158,7 +158,11 @@ namespace BinaryNinjaDebugger bool ResumeThread(std::uint32_t tid) override; DebugBreakpoint AddBreakpoint(const ModuleNameAndOffset& address, unsigned long breakpoint_type = 0) override; - // Temporary internal methods + // Hardware breakpoint and watchpoint support + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + + // Legacy methods - kept for backward compatibility bool AddHardwareWriteBreakpoint(uint64_t address); bool RemoveHardwareWriteBreakpoint(uint64_t address); diff --git a/core/adapters/gdbadapter.cpp b/core/adapters/gdbadapter.cpp index 2dd6c7e1..2c210df2 100644 --- a/core/adapters/gdbadapter.cpp +++ b/core/adapters/gdbadapter.cpp @@ -1120,20 +1120,80 @@ bool GdbAdapter::StepOverReverse() return status != InternalError; } -bool GdbAdapter::AddHardwareWriteBreakpoint(uint64_t address) +bool GdbAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (m_isTargetRunning || !m_rspConnector) return false; - return this->m_rspConnector->TransmitAndReceive(RspData("Z2,{:x},{}", address, 1)).AsString() != "OK"; + std::string command; + switch (type) + { + case HardwareExecuteBreakpoint: + // Z1 = hardware execution breakpoint + command = fmt::format("Z1,{:x},{}", address, size); + break; + case HardwareReadBreakpoint: + // Z3 = hardware read watchpoint + command = fmt::format("Z3,{:x},{}", address, size); + break; + case HardwareWriteBreakpoint: + // Z2 = hardware write watchpoint + command = fmt::format("Z2,{:x},{}", address, size); + break; + case HardwareAccessBreakpoint: + // Z4 = hardware access watchpoint (read/write) + command = fmt::format("Z4,{:x},{}", address, size); + break; + default: + return false; + } + + return m_rspConnector->TransmitAndReceive(RspData(command)).AsString() == "OK"; } -bool GdbAdapter::RemoveHardwareWriteBreakpoint(uint64_t address) + +bool GdbAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (m_isTargetRunning || !m_rspConnector) return false; - return this->m_rspConnector->TransmitAndReceive(RspData("Z2,{:x},{}", address, 1)).AsString() != "OK"; + std::string command; + switch (type) + { + case HardwareExecuteBreakpoint: + // z1 = remove hardware execution breakpoint + command = fmt::format("z1,{:x},{}", address, size); + break; + case HardwareReadBreakpoint: + // z3 = remove hardware read watchpoint + command = fmt::format("z3,{:x},{}", address, size); + break; + case HardwareWriteBreakpoint: + // z2 = remove hardware write watchpoint + command = fmt::format("z2,{:x},{}", address, size); + break; + case HardwareAccessBreakpoint: + // z4 = remove hardware access watchpoint (read/write) + command = fmt::format("z4,{:x},{}", address, size); + break; + default: + return false; + } + + return m_rspConnector->TransmitAndReceive(RspData(command)).AsString() == "OK"; +} + + +bool GdbAdapter::AddHardwareWriteBreakpoint(uint64_t address) +{ + // Delegate to new standardized method + return AddHardwareBreakpoint(address, HardwareWriteBreakpoint, 1); +} + +bool GdbAdapter::RemoveHardwareWriteBreakpoint(uint64_t address) +{ + // Delegate to new standardized method + return RemoveHardwareBreakpoint(address, HardwareWriteBreakpoint, 1); } bool GdbAdapter::StepReturnReverse() diff --git a/core/adapters/gdbadapter.h b/core/adapters/gdbadapter.h index ed25167a..97814dd9 100644 --- a/core/adapters/gdbadapter.h +++ b/core/adapters/gdbadapter.h @@ -158,7 +158,11 @@ namespace BinaryNinjaDebugger bool ResumeThread(std::uint32_t tid) override; DebugBreakpoint AddBreakpoint(const ModuleNameAndOffset& address, unsigned long breakpoint_type = 0) override; - // Temporary internal methods + // Hardware breakpoint and watchpoint support + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + + // Legacy methods - kept for backward compatibility bool AddHardwareWriteBreakpoint(uint64_t address); bool RemoveHardwareWriteBreakpoint(uint64_t address); diff --git a/core/adapters/lldbadapter.cpp b/core/adapters/lldbadapter.cpp index 86573bb7..3b420205 100644 --- a/core/adapters/lldbadapter.cpp +++ b/core/adapters/lldbadapter.cpp @@ -917,6 +917,16 @@ std::vector LldbAdapter::GetFramesOfThread(uint32_t tid) DebugBreakpoint LldbAdapter::AddBreakpoint(const std::uintptr_t address, unsigned long breakpoint_type) { + // Check if this is a hardware breakpoint type + if (breakpoint_type == HardwareExecuteBreakpoint) + { + if (AddHardwareBreakpoint(address, HardwareExecuteBreakpoint)) + return DebugBreakpoint(address, 0, true); // Use 0 as ID for hardware breakpoints for now + else + return DebugBreakpoint {}; + } + + // Default software breakpoint SBBreakpoint bp = m_target.BreakpointCreateByAddress(address); if (!bp.IsValid()) return DebugBreakpoint {}; @@ -993,6 +1003,96 @@ std::vector LldbAdapter::GetBreakpointList() const } +bool LldbAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + if (!m_targetActive) + return false; + + switch (type) + { + case HardwareExecuteBreakpoint: + { + // Use LLDB command to set hardware execution breakpoint + std::string command = fmt::format("breakpoint set --address 0x{:x} -H", address); + auto result = InvokeBackendCommand(command); + return result.find("Breakpoint") != std::string::npos; + } + case HardwareReadBreakpoint: + { + // Use LLDB watchpoint command for read + std::string command = fmt::format("watchpoint set expression -w read -s {} -- 0x{:x}", size, address); + auto result = InvokeBackendCommand(command); + return result.find("Watchpoint") != std::string::npos; + } + case HardwareWriteBreakpoint: + { + // Use LLDB watchpoint command for write + std::string command = fmt::format("watchpoint set expression -w write -s {} -- 0x{:x}", size, address); + auto result = InvokeBackendCommand(command); + return result.find("Watchpoint") != std::string::npos; + } + case HardwareAccessBreakpoint: + { + // Use LLDB watchpoint command for read/write + std::string command = fmt::format("watchpoint set expression -w read_write -s {} -- 0x{:x}", size, address); + auto result = InvokeBackendCommand(command); + return result.find("Watchpoint") != std::string::npos; + } + default: + return false; + } +} + + +bool LldbAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + if (!m_targetActive) + return false; + + switch (type) + { + case HardwareExecuteBreakpoint: + { + // Find and delete hardware breakpoint at address + for (size_t i = 0; i < m_target.GetNumBreakpoints(); i++) + { + auto bp = m_target.GetBreakpointAtIndex(i); + if (bp.IsHardware()) + { + for (size_t j = 0; j < bp.GetNumLocations(); j++) + { + auto location = bp.GetLocationAtIndex(j); + auto bpAddress = location.GetAddress().GetLoadAddress(m_target); + if (address == bpAddress) + { + return m_target.BreakpointDelete(bp.GetID()); + } + } + } + } + return false; + } + case HardwareReadBreakpoint: + case HardwareWriteBreakpoint: + case HardwareAccessBreakpoint: + { + // Find and delete watchpoint at address + for (size_t i = 0; i < m_target.GetNumWatchpoints(); i++) + { + auto wp = m_target.GetWatchpointAtIndex(i); + if (wp.GetWatchAddress() == address) + { + return m_target.DeleteWatchpoint(wp.GetID()); + } + } + return false; + } + default: + return false; + } +} + + static intx::uint512 SBValueToUint512(lldb::SBValue& reg_val) { using namespace lldb; using namespace intx; diff --git a/core/adapters/lldbadapter.h b/core/adapters/lldbadapter.h index b974cf57..77b80e4f 100644 --- a/core/adapters/lldbadapter.h +++ b/core/adapters/lldbadapter.h @@ -96,6 +96,10 @@ namespace BinaryNinjaDebugger { std::vector GetBreakpointList() const override; + // Hardware breakpoint and watchpoint support + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + std::unordered_map ReadAllRegisters() override; DebugRegister ReadRegister(const std::string& reg) override; diff --git a/core/debugadapter.cpp b/core/debugadapter.cpp index 1e27bffb..0b069914 100644 --- a/core/debugadapter.cpp +++ b/core/debugadapter.cpp @@ -253,3 +253,17 @@ bool DebugAdapter::SetTTDPosition(const TTDPosition& position) // Default implementation returns false for adapters that don't support TTD return false; } + + +bool DebugAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + // Default implementation returns false for adapters that don't support hardware breakpoints + return false; +} + + +bool DebugAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + // Default implementation returns false for adapters that don't support hardware breakpoints + return false; +} diff --git a/core/debugadapter.h b/core/debugadapter.h index 268abb05..655ec80e 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -272,6 +272,10 @@ namespace BinaryNinjaDebugger { virtual std::vector GetBreakpointList() const = 0; + // Hardware breakpoint and watchpoint support + virtual bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); + virtual bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); + virtual std::unordered_map ReadAllRegisters() = 0; virtual DebugRegister ReadRegister(const std::string& reg) = 0; diff --git a/core/debuggercommon.h b/core/debuggercommon.h index 5e1e37df..e00d42da 100644 --- a/core/debuggercommon.h +++ b/core/debuggercommon.h @@ -175,7 +175,7 @@ namespace BinaryNinjaDebugger { uint64_t size; // Size of the module in bytes uint32_t checksum; // Checksum of the module uint32_t timestamp; // Timestamp of the module - + TTDModule() : address(0), size(0), checksum(0), timestamp(0) {} }; @@ -188,7 +188,7 @@ namespace BinaryNinjaDebugger { TTDPosition lifetimeEnd; // Lifetime end position TTDPosition activeTimeStart; // Active time start position TTDPosition activeTimeEnd; // Active time end position - + TTDThread() : uniqueId(0), id(0) {} }; @@ -208,7 +208,7 @@ namespace BinaryNinjaDebugger { uint32_t flags; // Exception flags uint64_t recordAddress; // Where in memory the exception record is found TTDPosition position; // Position where exception occurred - + TTDException() : type(TTDExceptionSoftware), programCounter(0), code(0), flags(0), recordAddress(0) {} }; @@ -217,13 +217,23 @@ namespace BinaryNinjaDebugger { { TTDEventType type; // Type of event TTDPosition position; // Position where event occurred - + // Optional child objects - existence depends on event type std::optional module; // For ModuleLoaded/ModuleUnloaded events std::optional thread; // For ThreadCreated/ThreadTerminated events std::optional exception; // For Exception events - + TTDEvent() : type(TTDEventThreadCreated) {} TTDEvent(TTDEventType eventType) : type(eventType) {} }; + + // Breakpoint types - used to specify the type of breakpoint to set + enum DebugBreakpointType + { + SoftwareBreakpoint = 0, // Default software breakpoint + HardwareExecuteBreakpoint = 1, // Hardware execution breakpoint + HardwareReadBreakpoint = 2, // Hardware read watchpoint + HardwareWriteBreakpoint = 3, // Hardware write watchpoint + HardwareAccessBreakpoint = 4 // Hardware read/write watchpoint + }; }; // namespace BinaryNinjaDebugger From 4821d720c86e3b614f8df6b3ca6d6e7c4f086d24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Sep 2025 10:42:44 +0000 Subject: [PATCH 03/30] Add API and FFI support for hardware breakpoints Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- api/debuggerapi.h | 15 +++++++++++++++ api/debuggercontroller.cpp | 12 ++++++++++++ api/ffi.h | 16 ++++++++++++++++ core/ffi.cpp | 28 ++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+) diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 48e33f31..39c076c8 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -363,6 +363,17 @@ namespace BinaryNinjaDebuggerAPI { }; + // Breakpoint types - used to specify the type of breakpoint to set + enum DebugBreakpointType + { + SoftwareBreakpoint = 0, // Default software breakpoint + HardwareExecuteBreakpoint = 1, // Hardware execution breakpoint + HardwareReadBreakpoint = 2, // Hardware read watchpoint + HardwareWriteBreakpoint = 3, // Hardware write watchpoint + HardwareAccessBreakpoint = 4 // Hardware read/write watchpoint + }; + + struct ModuleNameAndOffset { std::string module; @@ -726,6 +737,10 @@ namespace BinaryNinjaDebuggerAPI { bool ContainsBreakpoint(uint64_t address); bool ContainsBreakpoint(const ModuleNameAndOffset& breakpoint); + // Hardware breakpoint and watchpoint support + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); + uint64_t IP(); uint64_t GetLastIP(); bool SetIP(uint64_t address); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index 944c8c1e..c5d745d6 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -799,6 +799,18 @@ bool DebuggerController::ContainsBreakpoint(const ModuleNameAndOffset& breakpoin } +bool DebuggerController::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + return BNDebuggerAddHardwareBreakpoint(m_object, address, (BNDebugBreakpointType)type, size); +} + + +bool DebuggerController::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + return BNDebuggerRemoveHardwareBreakpoint(m_object, address, (BNDebugBreakpointType)type, size); +} + + uint64_t DebuggerController::RelativeAddressToAbsolute(const ModuleNameAndOffset& address) { return BNDebuggerRelativeAddressToAbsolute(m_object, address.module.c_str(), address.offset); diff --git a/api/ffi.h b/api/ffi.h index c6bc133c..206489b8 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -218,6 +218,16 @@ extern "C" } BNDebugAdapterTargetStatus; + typedef enum BNDebugBreakpointType + { + BNSoftwareBreakpoint = 0, // Default software breakpoint + BNHardwareExecuteBreakpoint = 1, // Hardware execution breakpoint + BNHardwareReadBreakpoint = 2, // Hardware read watchpoint + BNHardwareWriteBreakpoint = 3, // Hardware write watchpoint + BNHardwareAccessBreakpoint = 4 // Hardware read/write watchpoint + } BNDebugBreakpointType; + + typedef enum BNDebuggerEventType { LaunchEventType, @@ -597,6 +607,12 @@ extern "C" DEBUGGER_FFI_API bool BNDebuggerContainsRelativeBreakpoint( BNDebuggerController* controller, const char* module, uint64_t offset); + // Hardware breakpoint and watchpoint support + DEBUGGER_FFI_API bool BNDebuggerAddHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, + BNDebugBreakpointType type, size_t size); + DEBUGGER_FFI_API bool BNDebuggerRemoveHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, + BNDebugBreakpointType type, size_t size); + DEBUGGER_FFI_API uint64_t BNDebuggerGetIP(BNDebuggerController* controller); DEBUGGER_FFI_API uint64_t BNDebuggerGetLastIP(BNDebuggerController* controller); DEBUGGER_FFI_API bool BNDebuggerSetIP(BNDebuggerController* controller, uint64_t address); diff --git a/core/ffi.cpp b/core/ffi.cpp index cb3c082e..bd40064d 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -927,6 +927,34 @@ bool BNDebuggerContainsRelativeBreakpoint(BNDebuggerController* controller, cons } +bool BNDebuggerAddHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, BNDebugBreakpointType type, size_t size) +{ + DebuggerState* state = controller->object->GetState(); + if (!state) + return false; + + DebugAdapter* adapter = state->GetAdapter(); + if (!adapter) + return false; + + return adapter->AddHardwareBreakpoint(address, (DebugBreakpointType)type, size); +} + + +bool BNDebuggerRemoveHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, BNDebugBreakpointType type, size_t size) +{ + DebuggerState* state = controller->object->GetState(); + if (!state) + return false; + + DebugAdapter* adapter = state->GetAdapter(); + if (!adapter) + return false; + + return adapter->RemoveHardwareBreakpoint(address, (DebugBreakpointType)type, size); +} + + uint64_t BNDebuggerRelativeAddressToAbsolute(BNDebuggerController* controller, const char* module, uint64_t offset) { DebuggerState* state = controller->object->GetState(); From 02b5be8eda5dd47599221a61d709d9d0b3b65721 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Sep 2025 10:45:33 +0000 Subject: [PATCH 04/30] Update documentation with hardware breakpoint API usage Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- docs/guide/index.md | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/docs/guide/index.md b/docs/guide/index.md index 7d8f4d6f..25d498af 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -512,9 +512,44 @@ dbg.execute_backend_command('image list') ### Hardware Breakpoints/Watchpoints -Hardware breakpoints and watchpoints are very useful and we plan to add better support for it soon. It is tracked by -this [issue](https://github.com/Vector35/debugger/issues/53). For now, we can run a backend command directly to set -hardware breakpoints/watchpoints. +Hardware breakpoints and watchpoints are now supported through both the debugger API and direct backend commands. + +#### Using the Debugger API + +Hardware breakpoints can be set using the following methods in Python: + +```python +from debugger import DebuggerController, DebugBreakpointType + +# Get the controller for your binary view +controller = DebuggerController(bv) + +# Set hardware execution breakpoint +controller.add_hardware_breakpoint(0x12345678, DebugBreakpointType.HardwareExecuteBreakpoint) + +# Set hardware read watchpoint (1 byte) +controller.add_hardware_breakpoint(0x12345678, DebugBreakpointType.HardwareReadBreakpoint, 1) + +# Set hardware write watchpoint (4 bytes) +controller.add_hardware_breakpoint(0x12345678, DebugBreakpointType.HardwareWriteBreakpoint, 4) + +# Set hardware access (read/write) watchpoint (8 bytes) +controller.add_hardware_breakpoint(0x12345678, DebugBreakpointType.HardwareAccessBreakpoint, 8) + +# Remove hardware breakpoint +controller.remove_hardware_breakpoint(0x12345678, DebugBreakpointType.HardwareExecuteBreakpoint) +``` + +The supported breakpoint types are: +- `SoftwareBreakpoint`: Regular software breakpoint (default) +- `HardwareExecuteBreakpoint`: Hardware execution breakpoint +- `HardwareReadBreakpoint`: Hardware read watchpoint +- `HardwareWriteBreakpoint`: Hardware write watchpoint +- `HardwareAccessBreakpoint`: Hardware read/write watchpoint + +#### Using Backend Commands + +For cases where you need more control or the API is not available, you can use backend commands directly. #### WinDbg/DbgEng From 8eb9cb552b09da21babd3ab2bb55137da19e6f25 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Sep 2025 10:47:45 +0000 Subject: [PATCH 05/30] Complete hardware breakpoint implementation with examples Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- core/adapters/esrevenadapter.cpp | 9 ++ core/adapters/gdbadapter.cpp | 9 ++ docs/examples/hardware_breakpoints.py | 184 ++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 docs/examples/hardware_breakpoints.py diff --git a/core/adapters/esrevenadapter.cpp b/core/adapters/esrevenadapter.cpp index a1c53f92..7e5edd45 100644 --- a/core/adapters/esrevenadapter.cpp +++ b/core/adapters/esrevenadapter.cpp @@ -412,6 +412,15 @@ DebugBreakpoint EsrevenAdapter::AddBreakpoint(const std::uintptr_t address, unsi DebugBreakpoint(address)) != this->m_debugBreakpoints.end()) return {}; + // Handle hardware breakpoint types + if (breakpoint_type != SoftwareBreakpoint) + { + if (AddHardwareBreakpoint(address, (DebugBreakpointType)breakpoint_type)) + return DebugBreakpoint(address, 0, true); // Use 0 as ID for hardware breakpoints for now + else + return DebugBreakpoint{}; + } + /* TODO: replace %d with the actual breakpoint size as it differs per architecture */ size_t kind = 1; if (m_remoteArch == "aarch64") diff --git a/core/adapters/gdbadapter.cpp b/core/adapters/gdbadapter.cpp index 2c210df2..24823f99 100644 --- a/core/adapters/gdbadapter.cpp +++ b/core/adapters/gdbadapter.cpp @@ -410,6 +410,15 @@ DebugBreakpoint GdbAdapter::AddBreakpoint(const std::uintptr_t address, unsigned DebugBreakpoint(address)) != this->m_debugBreakpoints.end()) return {}; + // Handle hardware breakpoint types + if (breakpoint_type != SoftwareBreakpoint) + { + if (AddHardwareBreakpoint(address, (DebugBreakpointType)breakpoint_type)) + return DebugBreakpoint(address, 0, true); // Use 0 as ID for hardware breakpoints for now + else + return DebugBreakpoint{}; + } + /* TODO: replace %d with the actual breakpoint size as it differs per architecture */ size_t kind = 1; if (m_remoteArch == "aarch64") diff --git a/docs/examples/hardware_breakpoints.py b/docs/examples/hardware_breakpoints.py new file mode 100644 index 00000000..09c2148d --- /dev/null +++ b/docs/examples/hardware_breakpoints.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +""" +Example script demonstrating hardware breakpoint usage + +This script shows how to use the new hardware breakpoint functionality +introduced in the debugger. +""" + +try: + from binaryninja import load + from debugger import DebuggerController, DebugBreakpointType +except ImportError: + from binaryninja import load + from binaryninja.debugger import DebuggerController, DebugBreakpointType + + +def hardware_breakpoint_example(binary_path): + """ + Example showing how to use hardware breakpoints + """ + # Load the binary + bv = load(binary_path) + if not bv: + print(f"Failed to load binary: {binary_path}") + return + + # Get the debugger controller + controller = DebuggerController(bv) + + print("Setting up hardware breakpoints...") + + # Example 1: Hardware execution breakpoint at entry point + entry_point = bv.entry_point + print(f"Setting hardware execution breakpoint at entry point: 0x{entry_point:x}") + success = controller.add_hardware_breakpoint( + entry_point, + DebugBreakpointType.HardwareExecuteBreakpoint + ) + if success: + print("✓ Hardware execution breakpoint set successfully") + else: + print("✗ Failed to set hardware execution breakpoint") + + # Example 2: Hardware write watchpoint on a data address + # In a real scenario, you'd find a data address from your binary analysis + data_address = 0x1000 # Example address + print(f"Setting hardware write watchpoint at 0x{data_address:x} (4 bytes)") + success = controller.add_hardware_breakpoint( + data_address, + DebugBreakpointType.HardwareWriteBreakpoint, + 4 # Watch 4 bytes + ) + if success: + print("✓ Hardware write watchpoint set successfully") + else: + print("✗ Failed to set hardware write watchpoint") + + # Example 3: Hardware read watchpoint + print(f"Setting hardware read watchpoint at 0x{data_address + 8:x} (8 bytes)") + success = controller.add_hardware_breakpoint( + data_address + 8, + DebugBreakpointType.HardwareReadBreakpoint, + 8 # Watch 8 bytes + ) + if success: + print("✓ Hardware read watchpoint set successfully") + else: + print("✗ Failed to set hardware read watchpoint") + + # Example 4: Hardware access (read/write) watchpoint + print(f"Setting hardware access watchpoint at 0x{data_address + 16:x} (1 byte)") + success = controller.add_hardware_breakpoint( + data_address + 16, + DebugBreakpointType.HardwareAccessBreakpoint, + 1 # Watch 1 byte + ) + if success: + print("✓ Hardware access watchpoint set successfully") + else: + print("✗ Failed to set hardware access watchpoint") + + print("\nLaunching target...") + stop_reason = controller.launch_and_wait() + print(f"Target stopped with reason: {stop_reason}") + + # Continue execution to test breakpoints + print("Continuing execution...") + stop_reason = controller.go_and_wait() + print(f"Target stopped with reason: {stop_reason}") + + # Clean up - remove hardware breakpoints + print("\nCleaning up hardware breakpoints...") + + controller.remove_hardware_breakpoint( + entry_point, + DebugBreakpointType.HardwareExecuteBreakpoint + ) + + controller.remove_hardware_breakpoint( + data_address, + DebugBreakpointType.HardwareWriteBreakpoint, + 4 + ) + + controller.remove_hardware_breakpoint( + data_address + 8, + DebugBreakpointType.HardwareReadBreakpoint, + 8 + ) + + controller.remove_hardware_breakpoint( + data_address + 16, + DebugBreakpointType.HardwareAccessBreakpoint, + 1 + ) + + print("Hardware breakpoints removed") + + # Quit the debugger + controller.quit_and_wait() + print("Debugging session ended") + + +def backend_command_example(binary_path): + """ + Example showing how to use hardware breakpoints via backend commands + (useful for advanced scenarios or when the API is not sufficient) + """ + bv = load(binary_path) + controller = DebuggerController(bv) + + print("Using backend commands for hardware breakpoints...") + + # Launch the target first + controller.launch_and_wait() + + # For LLDB adapter: + if controller.get_adapter_type() == "LLDB": + print("Using LLDB commands:") + + # Hardware execution breakpoint + result = controller.send_command("breakpoint set --address 0x100000000 -H") + print(f"LLDB hardware execution breakpoint: {result}") + + # Hardware write watchpoint + result = controller.send_command("watchpoint set expression -w write -s 4 -- 0x100001000") + print(f"LLDB hardware write watchpoint: {result}") + + # List breakpoints and watchpoints + result = controller.send_command("breakpoint list") + print(f"LLDB breakpoints: {result}") + + result = controller.send_command("watchpoint list") + print(f"LLDB watchpoints: {result}") + + # For GDB RSP adapter: + elif "GDB" in controller.get_adapter_type(): + print("Using GDB RSP commands:") + + # Hardware execution breakpoint (Z1) + result = controller.send_command("Z1,100000000,1") + print(f"GDB hardware execution breakpoint: {result}") + + # Hardware write watchpoint (Z2) + result = controller.send_command("Z2,100001000,4") + print(f"GDB hardware write watchpoint: {result}") + + controller.quit_and_wait() + + +if __name__ == "__main__": + import sys + + if len(sys.argv) != 2: + print("Usage: python hardware_breakpoints.py ") + sys.exit(1) + + binary_path = sys.argv[1] + + print("=== Hardware Breakpoint API Example ===") + hardware_breakpoint_example(binary_path) + + print("\n=== Backend Command Example ===") + backend_command_example(binary_path) \ No newline at end of file From 6c7f08fc253d7efb3ffefd64d5aff7e09539e823 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Sep 2025 06:15:18 +0000 Subject: [PATCH 06/30] Add hardware breakpoint support to DbgEng adapter and UI enhancements Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- api/debuggerapi.h | 1 + core/adapters/dbgengadapter.cpp | 93 +++++++++++++++- core/adapters/dbgengadapter.h | 4 + core/adapters/esrevenadapter.cpp | 4 +- core/adapters/gdbadapter.cpp | 4 +- core/adapters/lldbadapter.cpp | 4 +- core/debugadapter.h | 8 +- ui/breakpointswidget.cpp | 90 ++++++++++++--- ui/breakpointswidget.h | 6 +- ui/hardwarebreakpointdialog.cpp | 186 +++++++++++++++++++++++++++++++ ui/hardwarebreakpointdialog.h | 58 ++++++++++ 11 files changed, 429 insertions(+), 29 deletions(-) create mode 100644 ui/hardwarebreakpointdialog.cpp create mode 100644 ui/hardwarebreakpointdialog.h diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 39c076c8..d73c1b9a 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -360,6 +360,7 @@ namespace BinaryNinjaDebuggerAPI { uint64_t offset; uint64_t address; bool enabled; + DebugBreakpointType type = SoftwareBreakpoint; }; diff --git a/core/adapters/dbgengadapter.cpp b/core/adapters/dbgengadapter.cpp index b06ecf81..1cb1d5d7 100644 --- a/core/adapters/dbgengadapter.cpp +++ b/core/adapters/dbgengadapter.cpp @@ -1014,6 +1014,15 @@ bool DbgEngAdapter::ResumeThread(std::uint32_t tid) DebugBreakpoint DbgEngAdapter::AddBreakpoint(const std::uintptr_t address, unsigned long breakpoint_flags) { + // Handle hardware breakpoint types + if (breakpoint_flags != SoftwareBreakpoint) + { + if (AddHardwareBreakpoint(address, (DebugBreakpointType)breakpoint_flags)) + return DebugBreakpoint(address, 0, true, (DebugBreakpointType)breakpoint_flags); + else + return DebugBreakpoint{}; + } + IDebugBreakpoint2* debug_breakpoint {}; /* attempt to read at breakpoint location to confirm its valid */ @@ -1039,7 +1048,7 @@ DebugBreakpoint DbgEngAdapter::AddBreakpoint(const std::uintptr_t address, unsig if (debug_breakpoint->SetFlags(DEBUG_BREAKPOINT_ENABLED | breakpoint_flags) != S_OK) return {}; - const auto new_breakpoint = DebugBreakpoint(address, id, true); + const auto new_breakpoint = DebugBreakpoint(address, id, true, SoftwareBreakpoint); this->m_debug_breakpoints.push_back(new_breakpoint); return new_breakpoint; @@ -1155,6 +1164,88 @@ std::vector DbgEngAdapter::GetBreakpointList() const return {}; } + +bool DbgEngAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + if (!m_dbgengInitialized) + return false; + + std::string command; + switch (type) + { + case HardwareExecuteBreakpoint: + // ba e
: hardware execution breakpoint + command = fmt::format("ba e{} 0x{:x}", size, address); + break; + case HardwareReadBreakpoint: + // ba r
: hardware read breakpoint + command = fmt::format("ba r{} 0x{:x}", size, address); + break; + case HardwareWriteBreakpoint: + // ba w
: hardware write breakpoint + command = fmt::format("ba w{} 0x{:x}", size, address); + break; + case HardwareAccessBreakpoint: + // ba a
: hardware access (read/write) breakpoint + command = fmt::format("ba a{} 0x{:x}", size, address); + break; + default: + return false; + } + + // Execute the command and check if it succeeded + auto result = InvokeBackendCommand(command); + // DbgEng typically returns an empty string or specific success message for successful ba commands + // If the command fails, it usually contains an error message + return result.find("error") == std::string::npos && result.find("Error") == std::string::npos; +} + + +bool DbgEngAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + if (!m_dbgengInitialized) + return false; + + // List all breakpoints to find the ID of the hardware breakpoint at this address + auto result = InvokeBackendCommand("bl"); + + // Parse the breakpoint list to find the ID + // DbgEng breakpoint list format is typically: + // 0 e
+ // 1 r
etc. + std::stringstream ss(result); + std::string line; + + while (std::getline(ss, line)) + { + // Look for lines containing our address + if (line.find(fmt::format("{:x}", address)) != std::string::npos) + { + // Extract breakpoint ID (first number in the line) + std::istringstream iss(line); + std::string id_str; + if (iss >> id_str) + { + try + { + int bp_id = std::stoi(id_str); + // Remove the breakpoint using bc (breakpoint clear) command + auto clear_result = InvokeBackendCommand(fmt::format("bc {}", bp_id)); + return clear_result.find("error") == std::string::npos && + clear_result.find("Error") == std::string::npos; + } + catch (...) + { + // Continue searching if this line doesn't contain a valid ID + continue; + } + } + } + } + + return false; +} + void DbgEngAdapter::ApplyBreakpoints() { for (const auto bp : m_pendingBreakpoints) diff --git a/core/adapters/dbgengadapter.h b/core/adapters/dbgengadapter.h index 88b4fd24..d76daa45 100644 --- a/core/adapters/dbgengadapter.h +++ b/core/adapters/dbgengadapter.h @@ -200,6 +200,10 @@ namespace BinaryNinjaDebugger { std::vector GetBreakpointList() const override; + // Hardware breakpoint and watchpoint support + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + std::string GetRegisterNameByIndex(std::uint32_t index) const; std::unordered_map ReadAllRegisters() override; DebugRegister ReadRegister(const std::string& reg) override; diff --git a/core/adapters/esrevenadapter.cpp b/core/adapters/esrevenadapter.cpp index 7e5edd45..5546d68e 100644 --- a/core/adapters/esrevenadapter.cpp +++ b/core/adapters/esrevenadapter.cpp @@ -416,7 +416,7 @@ DebugBreakpoint EsrevenAdapter::AddBreakpoint(const std::uintptr_t address, unsi if (breakpoint_type != SoftwareBreakpoint) { if (AddHardwareBreakpoint(address, (DebugBreakpointType)breakpoint_type)) - return DebugBreakpoint(address, 0, true); // Use 0 as ID for hardware breakpoints for now + return DebugBreakpoint(address, 0, true, (DebugBreakpointType)breakpoint_type); else return DebugBreakpoint{}; } @@ -431,7 +431,7 @@ DebugBreakpoint EsrevenAdapter::AddBreakpoint(const std::uintptr_t address, unsi if (this->m_rspConnector->TransmitAndReceive(RspData("Z0,{:x},{}", address, kind)).AsString() != "OK" ) return DebugBreakpoint{}; - const auto new_breakpoint = DebugBreakpoint(address, this->m_internalBreakpointId++, true); + const auto new_breakpoint = DebugBreakpoint(address, this->m_internalBreakpointId++, true, SoftwareBreakpoint); this->m_debugBreakpoints.push_back(new_breakpoint); return new_breakpoint; diff --git a/core/adapters/gdbadapter.cpp b/core/adapters/gdbadapter.cpp index 24823f99..8d5cf689 100644 --- a/core/adapters/gdbadapter.cpp +++ b/core/adapters/gdbadapter.cpp @@ -414,7 +414,7 @@ DebugBreakpoint GdbAdapter::AddBreakpoint(const std::uintptr_t address, unsigned if (breakpoint_type != SoftwareBreakpoint) { if (AddHardwareBreakpoint(address, (DebugBreakpointType)breakpoint_type)) - return DebugBreakpoint(address, 0, true); // Use 0 as ID for hardware breakpoints for now + return DebugBreakpoint(address, 0, true, (DebugBreakpointType)breakpoint_type); else return DebugBreakpoint{}; } @@ -429,7 +429,7 @@ DebugBreakpoint GdbAdapter::AddBreakpoint(const std::uintptr_t address, unsigned if (this->m_rspConnector->TransmitAndReceive(RspData("Z0,{:x},{}", address, kind)).AsString() != "OK" ) return DebugBreakpoint{}; - const auto new_breakpoint = DebugBreakpoint(address, this->m_internalBreakpointId++, true); + const auto new_breakpoint = DebugBreakpoint(address, this->m_internalBreakpointId++, true, SoftwareBreakpoint); this->m_debugBreakpoints.push_back(new_breakpoint); return new_breakpoint; diff --git a/core/adapters/lldbadapter.cpp b/core/adapters/lldbadapter.cpp index 3b420205..42d8161f 100644 --- a/core/adapters/lldbadapter.cpp +++ b/core/adapters/lldbadapter.cpp @@ -921,7 +921,7 @@ DebugBreakpoint LldbAdapter::AddBreakpoint(const std::uintptr_t address, unsigne if (breakpoint_type == HardwareExecuteBreakpoint) { if (AddHardwareBreakpoint(address, HardwareExecuteBreakpoint)) - return DebugBreakpoint(address, 0, true); // Use 0 as ID for hardware breakpoints for now + return DebugBreakpoint(address, 0, true, HardwareExecuteBreakpoint); else return DebugBreakpoint {}; } @@ -931,7 +931,7 @@ DebugBreakpoint LldbAdapter::AddBreakpoint(const std::uintptr_t address, unsigne if (!bp.IsValid()) return DebugBreakpoint {}; - return DebugBreakpoint(address, bp.GetID(), bp.IsEnabled()); + return DebugBreakpoint(address, bp.GetID(), bp.IsEnabled(), SoftwareBreakpoint); } diff --git a/core/debugadapter.h b/core/debugadapter.h index 655ec80e..4f1f59f8 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -118,12 +118,14 @@ namespace BinaryNinjaDebugger { std::uintptr_t m_address {}; unsigned long m_id {}; bool m_is_active {}; + DebugBreakpointType m_type = SoftwareBreakpoint; - DebugBreakpoint(std::uintptr_t address, unsigned long id, bool active) : - m_address(address), m_id(id), m_is_active(active) + DebugBreakpoint(std::uintptr_t address, unsigned long id, bool active, DebugBreakpointType type = SoftwareBreakpoint) : + m_address(address), m_id(id), m_is_active(active), m_type(type) {} - DebugBreakpoint(std::uintptr_t address) : m_address(address) {} + DebugBreakpoint(std::uintptr_t address, DebugBreakpointType type = SoftwareBreakpoint) : + m_address(address), m_type(type) {} DebugBreakpoint() {} diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index 83c6b8d8..844904e9 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -23,7 +23,10 @@ limitations under the License. #include #include #include +#include +#include #include "breakpointswidget.h" +#include "hardwarebreakpointdialog.h" #include "ui.h" #include "menus.h" #include "fmt/format.h" @@ -32,11 +35,31 @@ using namespace BinaryNinjaDebuggerAPI; using namespace BinaryNinja; using namespace std; -BreakpointItem::BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t address) : - m_enabled(enabled), m_location(location), m_address(address) +BreakpointItem::BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t address, DebugBreakpointType type) : + m_enabled(enabled), m_location(location), m_address(address), m_type(type) {} +std::string BreakpointItem::typeString() const +{ + switch (m_type) + { + case SoftwareBreakpoint: + return "Software"; + case HardwareExecuteBreakpoint: + return "Hardware Exec"; + case HardwareReadBreakpoint: + return "Hardware Read"; + case HardwareWriteBreakpoint: + return "Hardware Write"; + case HardwareAccessBreakpoint: + return "Hardware Access"; + default: + return "Unknown"; + } +} + + bool BreakpointItem::operator==(const BreakpointItem& other) const { return (m_enabled == other.enabled()) && (m_location == other.location()) && (m_address == other.address()); @@ -138,6 +161,14 @@ QVariant DebugBreakpointsListModel::data(const QModelIndex& index, int role) con return QVariant(text); } + case DebugBreakpointsListModel::TypeColumn: + { + QString text = QString::fromStdString(item->typeString()); + if (role == Qt::SizeHintRole) + return QVariant((qulonglong)text.size()); + + return QVariant(text); + } } return QVariant(); } @@ -159,6 +190,8 @@ QVariant DebugBreakpointsListModel::headerData(int column, Qt::Orientation orien return "Location"; case DebugBreakpointsListModel::AddressColumn: return "Remote Address"; + case DebugBreakpointsListModel::TypeColumn: + return "Type"; } return QVariant(); } @@ -201,6 +234,7 @@ void DebugBreakpointsItemDelegate::paint( case DebugBreakpointsListModel::EnabledColumn: case DebugBreakpointsListModel::LocationColumn: case DebugBreakpointsListModel::AddressColumn: + case DebugBreakpointsListModel::TypeColumn: { painter->setFont(m_font); painter->setPen(option.palette.color(QPalette::WindowText).rgba()); @@ -438,26 +472,46 @@ void DebugBreakpointsWidget::add() if (!view) return; - uint64_t address = 0; - if (!ViewFrame::getAddressFromInput(frame, view, address, - frame->getCurrentOffset(), "Add Breakpoint", "The address of the breakpoint:", true)) + // Show options for software or hardware breakpoint + QMenu menu(this); + QAction* softwareAction = menu.addAction("Software Breakpoint"); + QAction* hardwareAction = menu.addAction("Hardware Breakpoint..."); + + QAction* chosen = menu.exec(QCursor::pos()); + if (!chosen) return; - bool isAbsoluteAddress = false; - auto controller = DebuggerController::GetController(view); - if (controller->IsConnected()) - isAbsoluteAddress = true; - - if (isAbsoluteAddress) + if (chosen == softwareAction) { - m_controller->AddBreakpoint(address); + // Original software breakpoint logic + uint64_t address = 0; + if (!ViewFrame::getAddressFromInput(frame, view, address, + frame->getCurrentOffset(), "Add Breakpoint", "The address of the breakpoint:", true)) + return; + + bool isAbsoluteAddress = false; + auto controller = DebuggerController::GetController(view); + if (controller->IsConnected()) + isAbsoluteAddress = true; + + if (isAbsoluteAddress) + { + m_controller->AddBreakpoint(address); + } + else + { + std::string filename = m_controller->GetInputFile(); + uint64_t offset = address - m_controller->GetViewFileSegmentsStart(); + ModuleNameAndOffset info = {filename, offset}; + m_controller->AddBreakpoint(info); + } } - else + else if (chosen == hardwareAction) { - std::string filename = m_controller->GetInputFile(); - uint64_t offset = address - m_controller->GetViewFileSegmentsStart(); - ModuleNameAndOffset info = {filename, offset}; - m_controller->AddBreakpoint(info); + // Hardware breakpoint dialog + uint64_t suggestedAddress = frame->getCurrentOffset(); + HardwareBreakpointDialog dialog(this, m_controller, suggestedAddress); + dialog.exec(); } } @@ -554,7 +608,7 @@ void DebugBreakpointsWidget::updateContent() ModuleNameAndOffset info; info.module = bp.module; info.offset = bp.offset; - bps.emplace_back(bp.enabled, info, bp.address); + bps.emplace_back(bp.enabled, info, bp.address, bp.type); } m_model->updateRows(bps); diff --git a/ui/breakpointswidget.h b/ui/breakpointswidget.h index 2df230a8..b25bb34d 100644 --- a/ui/breakpointswidget.h +++ b/ui/breakpointswidget.h @@ -39,12 +39,15 @@ class BreakpointItem bool m_enabled; ModuleNameAndOffset m_location; uint64_t m_address; + DebugBreakpointType m_type; public: - BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t remoteAddress); + BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t remoteAddress, DebugBreakpointType type = SoftwareBreakpoint); bool enabled() const { return m_enabled; } ModuleNameAndOffset location() const { return m_location; } uint64_t address() const { return m_address; } + DebugBreakpointType type() const { return m_type; } + std::string typeString() const; bool operator==(const BreakpointItem& other) const; bool operator!=(const BreakpointItem& other) const; bool operator<(const BreakpointItem& other) const; @@ -68,6 +71,7 @@ class DebugBreakpointsListModel : public QAbstractTableModel EnabledColumn, LocationColumn, AddressColumn, + TypeColumn, }; DebugBreakpointsListModel(QWidget* parent, ViewFrame* view); diff --git a/ui/hardwarebreakpointdialog.cpp b/ui/hardwarebreakpointdialog.cpp new file mode 100644 index 00000000..b4f3a85a --- /dev/null +++ b/ui/hardwarebreakpointdialog.cpp @@ -0,0 +1,186 @@ +/* +Copyright 2020-2025 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "hardwarebreakpointdialog.h" +#include +#include + +HardwareBreakpointDialog::HardwareBreakpointDialog(QWidget* parent, DbgRef controller, uint64_t suggestedAddress) : + QDialog(parent), m_controller(controller), m_suggestedAddress(suggestedAddress) +{ + setWindowTitle("Add Hardware Breakpoint"); + setModal(true); + resize(400, 200); + + // Create form layout + QFormLayout* formLayout = new QFormLayout(); + + // Address input + m_addressEdit = new QLineEdit(); + if (suggestedAddress != 0) + m_addressEdit->setText(QString("0x%1").arg(suggestedAddress, 0, 16)); + formLayout->addRow("Address:", m_addressEdit); + + // Type selection + m_typeCombo = new QComboBox(); + m_typeCombo->addItem("Hardware Execute", static_cast(HardwareExecuteBreakpoint)); + m_typeCombo->addItem("Hardware Read", static_cast(HardwareReadBreakpoint)); + m_typeCombo->addItem("Hardware Write", static_cast(HardwareWriteBreakpoint)); + m_typeCombo->addItem("Hardware Access (Read/Write)", static_cast(HardwareAccessBreakpoint)); + formLayout->addRow("Type:", m_typeCombo); + + // Size selection (for watchpoints) + m_sizeSpin = new QSpinBox(); + m_sizeSpin->setMinimum(1); + m_sizeSpin->setMaximum(8); + m_sizeSpin->setValue(1); + m_sizeSpin->setSuffix(" byte(s)"); + // Only allow powers of 2 + m_sizeSpin->setSpecialValueText("1 byte"); + formLayout->addRow("Size:", m_sizeSpin); + + // Help label + m_helpLabel = new QLabel(); + m_helpLabel->setWordWrap(true); + m_helpLabel->setStyleSheet("QLabel { color: gray; font-size: 10px; }"); + formLayout->addRow(m_helpLabel); + + // Button box + m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + // Main layout + QVBoxLayout* mainLayout = new QVBoxLayout(); + mainLayout->addLayout(formLayout); + mainLayout->addWidget(m_buttonBox); + setLayout(mainLayout); + + // Connect signals + connect(m_buttonBox, &QDialogButtonBox::accepted, this, &HardwareBreakpointDialog::addBreakpoint); + connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(m_addressEdit, &QLineEdit::textChanged, this, &HardwareBreakpointDialog::validateInput); + connect(m_typeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &HardwareBreakpointDialog::typeChanged); + + // Initial setup + typeChanged(); + validateInput(); +} + +uint64_t HardwareBreakpointDialog::getAddress() const +{ + QString text = m_addressEdit->text().trimmed(); + if (text.startsWith("0x") || text.startsWith("0X")) + text = text.mid(2); + + bool ok; + uint64_t address = text.toULongLong(&ok, 16); + return ok ? address : 0; +} + +DebugBreakpointType HardwareBreakpointDialog::getType() const +{ + return static_cast(m_typeCombo->currentData().toInt()); +} + +size_t HardwareBreakpointDialog::getSize() const +{ + return static_cast(m_sizeSpin->value()); +} + +void HardwareBreakpointDialog::addBreakpoint() +{ + uint64_t address = getAddress(); + if (address == 0) + { + QMessageBox::warning(this, "Invalid Address", "Please enter a valid hexadecimal address."); + return; + } + + DebugBreakpointType type = getType(); + size_t size = getSize(); + + // Validate size for powers of 2 + if (type != HardwareExecuteBreakpoint && (size & (size - 1)) != 0) + { + QMessageBox::warning(this, "Invalid Size", "Watchpoint size must be a power of 2 (1, 2, 4, or 8 bytes)."); + return; + } + + if (m_controller) + { + bool success = m_controller->AddHardwareBreakpoint(address, type, size); + if (success) + { + accept(); + } + else + { + QMessageBox::warning(this, "Failed to Add Breakpoint", + "Failed to add hardware breakpoint. The target may not support hardware breakpoints or all hardware breakpoint slots may be in use."); + } + } +} + +void HardwareBreakpointDialog::validateInput() +{ + uint64_t address = getAddress(); + bool valid = (address != 0); + + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); + + if (!valid && !m_addressEdit->text().isEmpty()) + { + m_helpLabel->setText("Please enter a valid hexadecimal address (e.g., 0x401000)"); + m_helpLabel->setStyleSheet("QLabel { color: red; font-size: 10px; }"); + } + else + { + typeChanged(); // Update help text + } +} + +void HardwareBreakpointDialog::typeChanged() +{ + DebugBreakpointType type = getType(); + + // Enable/disable size control based on type + bool needSize = (type != HardwareExecuteBreakpoint); + m_sizeSpin->setEnabled(needSize); + + // Update help text + QString helpText; + switch (type) + { + case HardwareExecuteBreakpoint: + helpText = "Hardware execution breakpoint will trigger when the CPU executes code at the specified address."; + m_sizeSpin->setValue(1); // Execution breakpoints are always 1 byte + break; + case HardwareReadBreakpoint: + helpText = "Hardware read watchpoint will trigger when the CPU reads from the specified memory range."; + break; + case HardwareWriteBreakpoint: + helpText = "Hardware write watchpoint will trigger when the CPU writes to the specified memory range."; + break; + case HardwareAccessBreakpoint: + helpText = "Hardware access watchpoint will trigger when the CPU reads from or writes to the specified memory range."; + break; + default: + helpText = ""; + break; + } + + m_helpLabel->setText(helpText); + m_helpLabel->setStyleSheet("QLabel { color: gray; font-size: 10px; }"); +} \ No newline at end of file diff --git a/ui/hardwarebreakpointdialog.h b/ui/hardwarebreakpointdialog.h new file mode 100644 index 00000000..41458810 --- /dev/null +++ b/ui/hardwarebreakpointdialog.h @@ -0,0 +1,58 @@ +/* +Copyright 2020-2025 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "debuggerapi.h" + +using namespace BinaryNinjaDebuggerAPI; + +class HardwareBreakpointDialog : public QDialog +{ + Q_OBJECT + +private: + DbgRef m_controller; + QLineEdit* m_addressEdit; + QComboBox* m_typeCombo; + QSpinBox* m_sizeSpin; + QLabel* m_helpLabel; + QDialogButtonBox* m_buttonBox; + + uint64_t m_suggestedAddress; + +public: + HardwareBreakpointDialog(QWidget* parent, DbgRef controller, uint64_t suggestedAddress = 0); + + uint64_t getAddress() const; + DebugBreakpointType getType() const; + size_t getSize() const; + +private Q_SLOTS: + void addBreakpoint(); + void validateInput(); + void typeChanged(); +}; \ No newline at end of file From eb6fa675ba410e9c32c3ca56470a1eefd7859cdc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Sep 2025 06:17:52 +0000 Subject: [PATCH 07/30] Add comprehensive hardware breakpoint support for all debug adapters with UI integration Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- breakpoint_widget_enhanced.png | Bin 0 -> 37935 bytes hardware_breakpoint_dialog.png | Bin 0 -> 11251 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 breakpoint_widget_enhanced.png create mode 100644 hardware_breakpoint_dialog.png diff --git a/breakpoint_widget_enhanced.png b/breakpoint_widget_enhanced.png new file mode 100644 index 0000000000000000000000000000000000000000..00897fac5a3f36981304aacf19a03befe164b5d3 GIT binary patch literal 37935 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 NOi1cwzM!_({{WHwX`}!E literal 0 HcmV?d00001 diff --git a/hardware_breakpoint_dialog.png b/hardware_breakpoint_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..89694aad8cbb6331064a224afe9f0a1821dccb35 GIT binary patch literal 11251 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 Date: Fri, 5 Dec 2025 16:32:30 +0800 Subject: [PATCH 08/30] Fix build --- api/debuggerapi.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/api/debuggerapi.h b/api/debuggerapi.h index d73c1b9a..7c799067 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -354,27 +354,27 @@ namespace BinaryNinjaDebuggerAPI { }; - struct DebugBreakpoint - { - std::string module; - uint64_t offset; - uint64_t address; - bool enabled; - DebugBreakpointType type = SoftwareBreakpoint; - }; - - // Breakpoint types - used to specify the type of breakpoint to set enum DebugBreakpointType { SoftwareBreakpoint = 0, // Default software breakpoint HardwareExecuteBreakpoint = 1, // Hardware execution breakpoint HardwareReadBreakpoint = 2, // Hardware read watchpoint - HardwareWriteBreakpoint = 3, // Hardware write watchpoint + HardwareWriteBreakpoint = 3, // Hardware write watchpoint HardwareAccessBreakpoint = 4 // Hardware read/write watchpoint }; + struct DebugBreakpoint + { + std::string module; + uint64_t offset; + uint64_t address; + bool enabled; + DebugBreakpointType type = SoftwareBreakpoint; + }; + + struct ModuleNameAndOffset { std::string module; From f28f7700b49153ec2a56adb0bf503976a605c738 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 5 Dec 2025 16:41:04 +0800 Subject: [PATCH 09/30] Enhance breakpoint widget UI with improved hardware breakpoint support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Show breakpoint type column in the breakpoint widget table - Add separate top-level menu items for "Add Breakpoint" and "Add Hardware Breakpoint" - Update hardware breakpoint dialog to use combobox for size selection - Predefined options: 1, 2, 4, 8 bytes - Editable combobox allows custom size input - Both actions are available in the context menu - Improved help text in hardware breakpoint dialog 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- ui/breakpointswidget.cpp | 98 +++++++++++++++++++++++---------- ui/breakpointswidget.h | 4 +- ui/hardwarebreakpointdialog.cpp | 37 +++++++------ ui/hardwarebreakpointdialog.h | 2 +- 4 files changed, 94 insertions(+), 47 deletions(-) diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index 844904e9..c29814a3 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -324,7 +324,13 @@ DebugBreakpointsWidget::DebugBreakpointsWidget(ViewFrame* view, BinaryViewRef da UIAction::registerAction(addBreakpointActionName); m_menu->addAction(addBreakpointActionName, "Options", MENU_ORDER_NORMAL); m_actionHandler.bindAction( - addBreakpointActionName, UIAction([&]() { add(); })); + addBreakpointActionName, UIAction([&]() { addSoftwareBreakpoint(); })); + + QString addHardwareBreakpointActionName = QString::fromStdString("Add Hardware Breakpoint..."); + UIAction::registerAction(addHardwareBreakpointActionName); + m_menu->addAction(addHardwareBreakpointActionName, "Options", MENU_ORDER_NORMAL); + m_actionHandler.bindAction( + addHardwareBreakpointActionName, UIAction([&]() { addHardwareBreakpoint(); })); QString toggleEnabledActionName = QString::fromStdString("Toggle Enabled"); UIAction::registerAction(toggleEnabledActionName, QKeySequence("Ctrl+Shift+B")); @@ -460,6 +466,7 @@ void DebugBreakpointsWidget::jump() void DebugBreakpointsWidget::add() { + // Keep this for backward compatibility - show menu UIContext* ctxt = UIContext::contextForWidget(this); if (!ctxt) return; @@ -476,46 +483,81 @@ void DebugBreakpointsWidget::add() QMenu menu(this); QAction* softwareAction = menu.addAction("Software Breakpoint"); QAction* hardwareAction = menu.addAction("Hardware Breakpoint..."); - + QAction* chosen = menu.exec(QCursor::pos()); if (!chosen) return; if (chosen == softwareAction) { - // Original software breakpoint logic - uint64_t address = 0; - if (!ViewFrame::getAddressFromInput(frame, view, address, - frame->getCurrentOffset(), "Add Breakpoint", "The address of the breakpoint:", true)) - return; - - bool isAbsoluteAddress = false; - auto controller = DebuggerController::GetController(view); - if (controller->IsConnected()) - isAbsoluteAddress = true; - - if (isAbsoluteAddress) - { - m_controller->AddBreakpoint(address); - } - else - { - std::string filename = m_controller->GetInputFile(); - uint64_t offset = address - m_controller->GetViewFileSegmentsStart(); - ModuleNameAndOffset info = {filename, offset}; - m_controller->AddBreakpoint(info); - } + addSoftwareBreakpoint(); } else if (chosen == hardwareAction) { - // Hardware breakpoint dialog - uint64_t suggestedAddress = frame->getCurrentOffset(); - HardwareBreakpointDialog dialog(this, m_controller, suggestedAddress); - dialog.exec(); + addHardwareBreakpoint(); } } +void DebugBreakpointsWidget::addSoftwareBreakpoint() +{ + UIContext* ctxt = UIContext::contextForWidget(this); + if (!ctxt) + return; + + ViewFrame* frame = ctxt->getCurrentViewFrame(); + if (!frame) + return; + + auto view = frame->getCurrentBinaryView(); + if (!view) + return; + + uint64_t address = 0; + if (!ViewFrame::getAddressFromInput(frame, view, address, + frame->getCurrentOffset(), "Add Breakpoint", "The address of the breakpoint:", true)) + return; + + bool isAbsoluteAddress = false; + auto controller = DebuggerController::GetController(view); + if (controller->IsConnected()) + isAbsoluteAddress = true; + + if (isAbsoluteAddress) + { + m_controller->AddBreakpoint(address); + } + else + { + std::string filename = m_controller->GetInputFile(); + uint64_t offset = address - m_controller->GetViewFileSegmentsStart(); + ModuleNameAndOffset info = {filename, offset}; + m_controller->AddBreakpoint(info); + } +} + + +void DebugBreakpointsWidget::addHardwareBreakpoint() +{ + UIContext* ctxt = UIContext::contextForWidget(this); + if (!ctxt) + return; + + ViewFrame* frame = ctxt->getCurrentViewFrame(); + if (!frame) + return; + + auto view = frame->getCurrentBinaryView(); + if (!view) + return; + + // Hardware breakpoint dialog + uint64_t suggestedAddress = frame->getCurrentOffset(); + HardwareBreakpointDialog dialog(this, m_controller, suggestedAddress); + dialog.exec(); +} + + void DebugBreakpointsWidget::toggleSelected() { QModelIndexList sel = selectionModel()->selectedRows(); diff --git a/ui/breakpointswidget.h b/ui/breakpointswidget.h index b25bb34d..78f2fc43 100644 --- a/ui/breakpointswidget.h +++ b/ui/breakpointswidget.h @@ -87,7 +87,7 @@ class DebugBreakpointsListModel : public QAbstractTableModel virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override { (void)parent; - return 3; + return 4; } BreakpointItem getRow(int row) const; virtual QVariant data(const QModelIndex& i, int role) const override; @@ -153,6 +153,8 @@ private slots: void remove(); void onDoubleClicked(); void add(); + void addSoftwareBreakpoint(); + void addHardwareBreakpoint(); void toggleSelected(); void enableAll(); void disableAll(); diff --git a/ui/hardwarebreakpointdialog.cpp b/ui/hardwarebreakpointdialog.cpp index b4f3a85a..c87bc3fd 100644 --- a/ui/hardwarebreakpointdialog.cpp +++ b/ui/hardwarebreakpointdialog.cpp @@ -43,14 +43,14 @@ HardwareBreakpointDialog::HardwareBreakpointDialog(QWidget* parent, DbgRefaddRow("Type:", m_typeCombo); // Size selection (for watchpoints) - m_sizeSpin = new QSpinBox(); - m_sizeSpin->setMinimum(1); - m_sizeSpin->setMaximum(8); - m_sizeSpin->setValue(1); - m_sizeSpin->setSuffix(" byte(s)"); - // Only allow powers of 2 - m_sizeSpin->setSpecialValueText("1 byte"); - formLayout->addRow("Size:", m_sizeSpin); + m_sizeCombo = new QComboBox(); + m_sizeCombo->setEditable(true); + m_sizeCombo->addItem("1", 1); + m_sizeCombo->addItem("2", 2); + m_sizeCombo->addItem("4", 4); + m_sizeCombo->addItem("8", 8); + m_sizeCombo->setCurrentIndex(0); + formLayout->addRow("Size:", m_sizeCombo); // Help label m_helpLabel = new QLabel(); @@ -96,7 +96,10 @@ DebugBreakpointType HardwareBreakpointDialog::getType() const size_t HardwareBreakpointDialog::getSize() const { - return static_cast(m_sizeSpin->value()); + bool ok; + QString text = m_sizeCombo->currentText(); + size_t size = text.toULongLong(&ok); + return ok ? size : 1; } void HardwareBreakpointDialog::addBreakpoint() @@ -154,33 +157,33 @@ void HardwareBreakpointDialog::validateInput() void HardwareBreakpointDialog::typeChanged() { DebugBreakpointType type = getType(); - + // Enable/disable size control based on type bool needSize = (type != HardwareExecuteBreakpoint); - m_sizeSpin->setEnabled(needSize); - + m_sizeCombo->setEnabled(needSize); + // Update help text QString helpText; switch (type) { case HardwareExecuteBreakpoint: helpText = "Hardware execution breakpoint will trigger when the CPU executes code at the specified address."; - m_sizeSpin->setValue(1); // Execution breakpoints are always 1 byte + m_sizeCombo->setCurrentIndex(0); // Execution breakpoints are always 1 byte break; case HardwareReadBreakpoint: - helpText = "Hardware read watchpoint will trigger when the CPU reads from the specified memory range."; + helpText = "Hardware read watchpoint will trigger when the CPU reads from the specified memory range. Size must be a power of 2 (1, 2, 4, or 8 bytes)."; break; case HardwareWriteBreakpoint: - helpText = "Hardware write watchpoint will trigger when the CPU writes to the specified memory range."; + helpText = "Hardware write watchpoint will trigger when the CPU writes to the specified memory range. Size must be a power of 2 (1, 2, 4, or 8 bytes)."; break; case HardwareAccessBreakpoint: - helpText = "Hardware access watchpoint will trigger when the CPU reads from or writes to the specified memory range."; + helpText = "Hardware access watchpoint will trigger when the CPU reads from or writes to the specified memory range. Size must be a power of 2 (1, 2, 4, or 8 bytes)."; break; default: helpText = ""; break; } - + m_helpLabel->setText(helpText); m_helpLabel->setStyleSheet("QLabel { color: gray; font-size: 10px; }"); } \ No newline at end of file diff --git a/ui/hardwarebreakpointdialog.h b/ui/hardwarebreakpointdialog.h index 41458810..d0eae183 100644 --- a/ui/hardwarebreakpointdialog.h +++ b/ui/hardwarebreakpointdialog.h @@ -38,7 +38,7 @@ class HardwareBreakpointDialog : public QDialog DbgRef m_controller; QLineEdit* m_addressEdit; QComboBox* m_typeCombo; - QSpinBox* m_sizeSpin; + QComboBox* m_sizeCombo; QLabel* m_helpLabel; QDialogButtonBox* m_buttonBox; From 8ebc5ecccd8b79d12dd4ef582af26de7fde87d07 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 5 Dec 2025 17:01:51 +0800 Subject: [PATCH 10/30] Refactor FFI methods and add hardware breakpoint events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move ContainsBreakpoint logic from ffi.cpp to core/debuggercontroller.cpp - BNDebuggerContainsRelativeBreakpoint now calls controller->object->ContainsBreakpoint() - Follows proper FFI pattern of delegating to controller methods - Add AddHardwareBreakpoint and RemoveHardwareBreakpoint to core DebuggerController - These methods now post debugger events (AbsoluteBreakpointAddedEvent/RemovedEvent) - Consistent with software breakpoint event handling - Simplify hardware breakpoint FFI methods in core/ffi.cpp - BNDebuggerAddHardwareBreakpoint calls controller->object->AddHardwareBreakpoint() - BNDebuggerRemoveHardwareBreakpoint calls controller->object->RemoveHardwareBreakpoint() - No longer directly access DebuggerState and DebugAdapter This ensures hardware breakpoints trigger UI updates and are properly tracked in the event system, matching the behavior of software breakpoints. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- core/debuggercontroller.cpp | 42 +++++++++++++++++++++++++++++++++++++ core/debuggercontroller.h | 5 +++++ core/ffi.cpp | 30 +++----------------------- 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 1cadab60..b46ac120 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -140,6 +140,48 @@ void DebuggerController::DisableBreakpoint(const ModuleNameAndOffset& address) } +bool DebuggerController::ContainsBreakpoint(const ModuleNameAndOffset& address) +{ + return m_state->GetBreakpoints()->ContainsOffset(address); +} + + +bool DebuggerController::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + DebugAdapter* adapter = m_state->GetAdapter(); + if (!adapter) + return false; + + bool result = adapter->AddHardwareBreakpoint(address, type, size); + if (result) + { + DebuggerEvent event; + event.type = AbsoluteBreakpointAddedEvent; + event.data.absoluteAddress = address; + PostDebuggerEvent(event); + } + return result; +} + + +bool DebuggerController::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + DebugAdapter* adapter = m_state->GetAdapter(); + if (!adapter) + return false; + + bool result = adapter->RemoveHardwareBreakpoint(address, type, size); + if (result) + { + DebuggerEvent event; + event.type = AbsoluteBreakpointRemovedEvent; + event.data.absoluteAddress = address; + PostDebuggerEvent(event); + } + return result; +} + + bool DebuggerController::SetIP(uint64_t address) { std::string ipRegisterName; diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index 54d10813..4b117383 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -231,8 +231,13 @@ namespace BinaryNinjaDebugger { void EnableBreakpoint(const ModuleNameAndOffset& address); void DisableBreakpoint(uint64_t address); void DisableBreakpoint(const ModuleNameAndOffset& address); + bool ContainsBreakpoint(const ModuleNameAndOffset& address); DebugBreakpoint GetAllBreakpoints(); + // hardware breakpoints + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + // registers intx::uint512 GetRegisterValue(const std::string& name); bool SetRegisterValue(const std::string& name, intx::uint512 value); diff --git a/core/ffi.cpp b/core/ffi.cpp index bd40064d..60c4feeb 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -915,43 +915,19 @@ bool BNDebuggerContainsAbsoluteBreakpoint(BNDebuggerController* controller, uint bool BNDebuggerContainsRelativeBreakpoint(BNDebuggerController* controller, const char* module, uint64_t offset) { - DebuggerState* state = controller->object->GetState(); - if (!state) - return false; - - DebuggerBreakpoints* breakpoints = state->GetBreakpoints(); - if (!breakpoints) - return false; - - return breakpoints->ContainsOffset(ModuleNameAndOffset(module, offset)); + return controller->object->ContainsBreakpoint(ModuleNameAndOffset(module, offset)); } bool BNDebuggerAddHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, BNDebugBreakpointType type, size_t size) { - DebuggerState* state = controller->object->GetState(); - if (!state) - return false; - - DebugAdapter* adapter = state->GetAdapter(); - if (!adapter) - return false; - - return adapter->AddHardwareBreakpoint(address, (DebugBreakpointType)type, size); + return controller->object->AddHardwareBreakpoint(address, (DebugBreakpointType)type, size); } bool BNDebuggerRemoveHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, BNDebugBreakpointType type, size_t size) { - DebuggerState* state = controller->object->GetState(); - if (!state) - return false; - - DebugAdapter* adapter = state->GetAdapter(); - if (!adapter) - return false; - - return adapter->RemoveHardwareBreakpoint(address, (DebugBreakpointType)type, size); + return controller->object->RemoveHardwareBreakpoint(address, (DebugBreakpointType)type, size); } From 6fac994ccf062d80a87107b4ae5ca61376aa7563 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 5 Dec 2025 17:24:15 +0800 Subject: [PATCH 11/30] Track hardware breakpoints in DebuggerBreakpoints for proper list display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hardware breakpoints are now properly managed through the DebuggerState layer, just like software breakpoints. This allows them to appear in the breakpoint list and be properly tracked across the debugging session. Changes: - Add HardwareBreakpointInfo structure to track hardware breakpoint details - Stores address, type (Execute/Read/Write/Access), and size - Implements comparison operators for container operations - Extend DebuggerBreakpoints class - Add m_hardwareBreakpoints vector to track active hardware breakpoints - Add AddHardwareBreakpoint/RemoveHardwareBreakpoint/ContainsHardwareBreakpoint - Hardware breakpoints are added to adapter AND tracked in the list - GetHardwareBreakpointList() for UI to query active hardware breakpoints - Update DebuggerState - Add AddHardwareBreakpoint/RemoveHardwareBreakpoint methods - Delegate to DebuggerBreakpoints for management - Update DebuggerController - Now calls m_state->AddHardwareBreakpoint instead of adapter directly - Ensures hardware breakpoints go through the proper tracking layer This ensures hardware breakpoints show up in the breakpoint widget and are properly persisted in the debugger state, consistent with software breakpoints. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- core/debuggercontroller.cpp | 12 ++----- core/debuggerstate.cpp | 65 +++++++++++++++++++++++++++++++++++++ core/debuggerstate.h | 34 +++++++++++++++++++ 3 files changed, 101 insertions(+), 10 deletions(-) diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index b46ac120..be6107d8 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -148,11 +148,7 @@ bool DebuggerController::ContainsBreakpoint(const ModuleNameAndOffset& address) bool DebuggerController::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { - DebugAdapter* adapter = m_state->GetAdapter(); - if (!adapter) - return false; - - bool result = adapter->AddHardwareBreakpoint(address, type, size); + bool result = m_state->AddHardwareBreakpoint(address, type, size); if (result) { DebuggerEvent event; @@ -166,11 +162,7 @@ bool DebuggerController::AddHardwareBreakpoint(uint64_t address, DebugBreakpoint bool DebuggerController::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { - DebugAdapter* adapter = m_state->GetAdapter(); - if (!adapter) - return false; - - bool result = adapter->RemoveHardwareBreakpoint(address, type, size); + bool result = m_state->RemoveHardwareBreakpoint(address, type, size); if (result) { DebuggerEvent event; diff --git a/core/debuggerstate.cpp b/core/debuggerstate.cpp index 6153b9e5..af180d19 100644 --- a/core/debuggerstate.cpp +++ b/core/debuggerstate.cpp @@ -709,6 +709,59 @@ bool DebuggerBreakpoints::ContainsAbsolute(uint64_t address) } +bool DebuggerBreakpoints::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + if (!m_state->GetAdapter()) + return false; + + bool result = false; + // Add the hardware breakpoint to the adapter if connected + if (m_state->IsConnected()) + { + result = m_state->GetAdapter()->AddHardwareBreakpoint(address, type, size); + if (!result) + return false; + } + + // Check if this hardware breakpoint already exists + HardwareBreakpointInfo info(address, type, size); + if (!ContainsHardwareBreakpoint(address, type, size)) + { + m_hardwareBreakpoints.push_back(info); + } + + return result; +} + + +bool DebuggerBreakpoints::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + HardwareBreakpointInfo info(address, type, size); + + // Remove from our list + auto iter = std::find(m_hardwareBreakpoints.begin(), m_hardwareBreakpoints.end(), info); + if (iter != m_hardwareBreakpoints.end()) + { + m_hardwareBreakpoints.erase(iter); + } + + // Remove from the adapter if connected + if (m_state->GetAdapter() && m_state->IsConnected()) + { + return m_state->GetAdapter()->RemoveHardwareBreakpoint(address, type, size); + } + + return true; +} + + +bool DebuggerBreakpoints::ContainsHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + HardwareBreakpointInfo info(address, type, size); + return std::find(m_hardwareBreakpoints.begin(), m_hardwareBreakpoints.end(), info) != m_hardwareBreakpoints.end(); +} + + void DebuggerBreakpoints::SerializeMetadata() { // TODO: who should free these Metadata objects? @@ -1078,6 +1131,18 @@ void DebuggerState::DisableBreakpoint(const ModuleNameAndOffset& address) } +bool DebuggerState::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + return m_breakpoints->AddHardwareBreakpoint(address, type, size); +} + + +bool DebuggerState::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + return m_breakpoints->RemoveHardwareBreakpoint(address, type, size); +} + + uint64_t DebuggerState::IP() { if (!IsConnected()) diff --git a/core/debuggerstate.h b/core/debuggerstate.h index 065edf1d..d5fce4c3 100644 --- a/core/debuggerstate.h +++ b/core/debuggerstate.h @@ -78,12 +78,36 @@ namespace BinaryNinjaDebugger { }; + // Structure to track hardware breakpoints with their type and size + struct HardwareBreakpointInfo + { + uint64_t address; + DebugBreakpointType type; + size_t size; + + HardwareBreakpointInfo(uint64_t addr, DebugBreakpointType t, size_t s) + : address(addr), type(t), size(s) {} + + bool operator==(const HardwareBreakpointInfo& other) const + { + return address == other.address && type == other.type && size == other.size; + } + + bool operator<(const HardwareBreakpointInfo& other) const + { + if (address != other.address) return address < other.address; + if (type != other.type) return type < other.type; + return size < other.size; + } + }; + class DebuggerBreakpoints { private: DebuggerState* m_state; std::vector m_breakpoints; std::map m_enabledState; + std::vector m_hardwareBreakpoints; public: DebuggerBreakpoints(DebuggerState* state, std::vector initial = {}); @@ -103,6 +127,12 @@ namespace BinaryNinjaDebugger { void SerializeMetadata(); void UnserializedMetadata(); std::vector GetBreakpointList() const { return m_breakpoints; } + + // Hardware breakpoint methods + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool ContainsHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + std::vector GetHardwareBreakpointList() const { return m_hardwareBreakpoints; } }; @@ -250,6 +280,10 @@ namespace BinaryNinjaDebugger { void DisableBreakpoint(uint64_t address); void DisableBreakpoint(const ModuleNameAndOffset& address); + // Hardware breakpoints + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + uint64_t IP(); uint64_t StackPointer(); From 0141eb8d52559560f80534aea83801cf66b43fe3 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 5 Dec 2025 18:23:21 +0800 Subject: [PATCH 12/30] Unify software and hardware breakpoints --- api/ffi.h | 21 +++---- core/debuggerstate.cpp | 128 +++++++++++++++++++++++++++++------------ core/debuggerstate.h | 55 ++++++++++++------ core/ffi.cpp | 14 +++-- 4 files changed, 148 insertions(+), 70 deletions(-) diff --git a/api/ffi.h b/api/ffi.h index 206489b8..9f2a4c80 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -125,6 +125,16 @@ extern "C" } BNDebugRegister; + typedef enum BNDebugBreakpointType + { + BNSoftwareBreakpoint = 0, // Default software breakpoint + BNHardwareExecuteBreakpoint = 1, // Hardware execution breakpoint + BNHardwareReadBreakpoint = 2, // Hardware read watchpoint + BNHardwareWriteBreakpoint = 3, // Hardware write watchpoint + BNHardwareAccessBreakpoint = 4 // Hardware read/write watchpoint + } BNDebugBreakpointType; + + typedef struct BNDebugBreakpoint { // TODO: we should add an absolute address to this, along with a boolean telling whether it is valid @@ -132,6 +142,7 @@ extern "C" uint64_t offset; uint64_t address; bool enabled; + BNDebugBreakpointType type; } BNDebugBreakpoint; @@ -218,16 +229,6 @@ extern "C" } BNDebugAdapterTargetStatus; - typedef enum BNDebugBreakpointType - { - BNSoftwareBreakpoint = 0, // Default software breakpoint - BNHardwareExecuteBreakpoint = 1, // Hardware execution breakpoint - BNHardwareReadBreakpoint = 2, // Hardware read watchpoint - BNHardwareWriteBreakpoint = 3, // Hardware write watchpoint - BNHardwareAccessBreakpoint = 4 // Hardware read/write watchpoint - } BNDebugBreakpointType; - - typedef enum BNDebuggerEventType { LaunchEventType, diff --git a/core/debuggerstate.cpp b/core/debuggerstate.cpp index af180d19..dc1bccff 100644 --- a/core/debuggerstate.cpp +++ b/core/debuggerstate.cpp @@ -513,8 +513,14 @@ std::vector DebuggerModules::GetAllModules() DebuggerBreakpoints::DebuggerBreakpoints(DebuggerState* state, std::vector initial) : - m_state(state), m_breakpoints(std::move(initial)) -{} + m_state(state) +{ + // Convert initial software breakpoints to BreakpointInfo + for (const auto& bp : initial) + { + m_breakpoints.push_back(BreakpointInfo(bp)); + } +} bool DebuggerBreakpoints::AddAbsolute(uint64_t remoteAddress) @@ -533,8 +539,9 @@ bool DebuggerBreakpoints::AddAbsolute(uint64_t remoteAddress) if (!ContainsAbsolute(remoteAddress)) { ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(remoteAddress); - m_breakpoints.push_back(info); - m_enabledState[info] = true; // Enable by default + BreakpointInfo bp(info); + bp.address = remoteAddress; + m_breakpoints.push_back(bp); SerializeMetadata(); } @@ -546,8 +553,7 @@ bool DebuggerBreakpoints::AddOffset(const ModuleNameAndOffset& address) { if (!ContainsOffset(address)) { - m_breakpoints.push_back(address); - m_enabledState[address] = true; // Enable by default + m_breakpoints.push_back(BreakpointInfo(address)); SerializeMetadata(); // If the adapter is already created, we ask it to add the breakpoint. @@ -571,12 +577,12 @@ bool DebuggerBreakpoints::RemoveAbsolute(uint64_t remoteAddress) ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(remoteAddress); if (ContainsOffset(info)) { - auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), info); + BreakpointInfo toFind(info); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); if (iter != m_breakpoints.end()) { m_breakpoints.erase(iter); } - m_enabledState.erase(info); // Remove enabled state SerializeMetadata(); m_state->GetAdapter()->RemoveBreakpoint(remoteAddress); return true; @@ -589,10 +595,10 @@ bool DebuggerBreakpoints::RemoveOffset(const ModuleNameAndOffset& address) { if (ContainsOffset(address)) { - if (auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), address); iter != m_breakpoints.end()) + BreakpointInfo toFind(address); + if (auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); iter != m_breakpoints.end()) m_breakpoints.erase(iter); - m_enabledState.erase(address); // Remove enabled state SerializeMetadata(); if (m_state->GetAdapter() && m_state->IsConnected()) @@ -619,7 +625,13 @@ bool DebuggerBreakpoints::EnableOffset(const ModuleNameAndOffset& address) if (!ContainsOffset(address)) return false; - m_enabledState[address] = true; + // Find and enable the breakpoint + BreakpointInfo toFind(address); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + { + iter->enabled = true; + } SerializeMetadata(); // If connected, make sure the breakpoint is active in the target @@ -645,7 +657,13 @@ bool DebuggerBreakpoints::DisableOffset(const ModuleNameAndOffset& address) if (!ContainsOffset(address)) return false; - m_enabledState[address] = false; + // Find and disable the breakpoint + BreakpointInfo toFind(address); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + { + iter->enabled = false; + } SerializeMetadata(); // If connected, remove the breakpoint from the target but keep it in our list @@ -668,10 +686,11 @@ bool DebuggerBreakpoints::IsEnabledAbsolute(uint64_t address) bool DebuggerBreakpoints::IsEnabledOffset(const ModuleNameAndOffset& address) { - auto iter = m_enabledState.find(address); - if (iter != m_enabledState.end()) - return iter->second; - + BreakpointInfo toFind(address); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + return iter->enabled; + // Default to enabled if not explicitly set return true; } @@ -681,8 +700,9 @@ bool DebuggerBreakpoints::ContainsOffset(const ModuleNameAndOffset& address) { // If there is no backend, then only check if the breakpoint is in the list // This is useful when we deal with the breakpoint before the target is launched + BreakpointInfo toFind(address); if (!m_state->GetAdapter()) - return std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end(); + return std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind) != m_breakpoints.end(); // When the backend is live, convert the relative address to absolute address and check its existence uint64_t absolute = m_state->GetModules()->RelativeAddressToAbsolute(address); @@ -699,11 +719,18 @@ bool DebuggerBreakpoints::ContainsAbsolute(uint64_t address) // Because every ModuleAndOffset can be converted to an absolute address, but there is no guarantee that it works // backward // Well, that is because lldb does not report the size of the loaded libraries, so it is currently screwed up - for (const ModuleNameAndOffset& breakpoint : m_breakpoints) + for (const BreakpointInfo& breakpoint : m_breakpoints) { - uint64_t absolute = m_state->GetModules()->RelativeAddressToAbsolute(breakpoint); - if (absolute == address) + if (breakpoint.IsSoftware()) + { + uint64_t absolute = m_state->GetModules()->RelativeAddressToAbsolute(breakpoint.location); + if (absolute == address) + return true; + } + else if (breakpoint.address == address) + { return true; + } } return false; } @@ -724,10 +751,10 @@ bool DebuggerBreakpoints::AddHardwareBreakpoint(uint64_t address, DebugBreakpoin } // Check if this hardware breakpoint already exists - HardwareBreakpointInfo info(address, type, size); if (!ContainsHardwareBreakpoint(address, type, size)) { - m_hardwareBreakpoints.push_back(info); + BreakpointInfo bp(address, type, size); + m_breakpoints.push_back(bp); } return result; @@ -736,13 +763,13 @@ bool DebuggerBreakpoints::AddHardwareBreakpoint(uint64_t address, DebugBreakpoin bool DebuggerBreakpoints::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { - HardwareBreakpointInfo info(address, type, size); + BreakpointInfo toFind(address, type, size); // Remove from our list - auto iter = std::find(m_hardwareBreakpoints.begin(), m_hardwareBreakpoints.end(), info); - if (iter != m_hardwareBreakpoints.end()) + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) { - m_hardwareBreakpoints.erase(iter); + m_breakpoints.erase(iter); } // Remove from the adapter if connected @@ -757,21 +784,39 @@ bool DebuggerBreakpoints::RemoveHardwareBreakpoint(uint64_t address, DebugBreakp bool DebuggerBreakpoints::ContainsHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { - HardwareBreakpointInfo info(address, type, size); - return std::find(m_hardwareBreakpoints.begin(), m_hardwareBreakpoints.end(), info) != m_hardwareBreakpoints.end(); + BreakpointInfo toFind(address, type, size); + return std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind) != m_breakpoints.end(); +} + + +std::vector DebuggerBreakpoints::GetSoftwareBreakpointList() const +{ + std::vector result; + for (const BreakpointInfo& bp : m_breakpoints) + { + if (bp.IsSoftware()) + { + result.push_back(bp.location); + } + } + return result; } void DebuggerBreakpoints::SerializeMetadata() { // TODO: who should free these Metadata objects? + // Only serialize software breakpoints (hardware breakpoints are transient) std::vector> breakpoints; - for (const ModuleNameAndOffset& bp : m_breakpoints) + for (const BreakpointInfo& bp : m_breakpoints) { - std::map> info; - info["module"] = new Metadata(bp.module); - info["offset"] = new Metadata(bp.offset); - breakpoints.push_back(new Metadata(info)); + if (bp.IsSoftware()) + { + std::map> info; + info["module"] = new Metadata(bp.location.module); + info["offset"] = new Metadata(bp.location.offset); + breakpoints.push_back(new Metadata(info)); + } } m_state->GetController()->GetData()->StoreMetadata("debugger.breakpoints", new Metadata(breakpoints)); } @@ -784,7 +829,7 @@ void DebuggerBreakpoints::UnserializedMetadata() return; vector> array = metadata->GetArray(); - std::vector newBreakpoints; + std::vector newBreakpoints; for (auto& element : array) { @@ -803,7 +848,7 @@ void DebuggerBreakpoints::UnserializedMetadata() continue; address.offset = info["offset"]->GetUnsignedInteger(); - newBreakpoints.push_back(address); + newBreakpoints.push_back(BreakpointInfo(address)); } m_breakpoints = newBreakpoints; @@ -815,8 +860,17 @@ void DebuggerBreakpoints::Apply() if (!m_state->GetAdapter()) return; - for (const ModuleNameAndOffset& address : m_breakpoints) - m_state->GetAdapter()->AddBreakpoint(address); + for (const BreakpointInfo& bp : m_breakpoints) + { + if (bp.IsSoftware()) + { + m_state->GetAdapter()->AddBreakpoint(bp.location); + } + else + { + m_state->GetAdapter()->AddHardwareBreakpoint(bp.address, bp.type, bp.size); + } + } } diff --git a/core/debuggerstate.h b/core/debuggerstate.h index d5fce4c3..92e2fa7c 100644 --- a/core/debuggerstate.h +++ b/core/debuggerstate.h @@ -78,26 +78,45 @@ namespace BinaryNinjaDebugger { }; - // Structure to track hardware breakpoints with their type and size - struct HardwareBreakpointInfo + // Unified structure to track both software and hardware breakpoints + struct BreakpointInfo { - uint64_t address; - DebugBreakpointType type; - size_t size; + ModuleNameAndOffset location; // Module + offset for software breakpoints + uint64_t address; // Absolute address (for hardware or resolved software) + DebugBreakpointType type; // Breakpoint type (Software, HardwareExecute, etc.) + size_t size; // Size for hardware watchpoints + bool enabled; // Enabled state - HardwareBreakpointInfo(uint64_t addr, DebugBreakpointType t, size_t s) - : address(addr), type(t), size(s) {} + // Create a software breakpoint + BreakpointInfo(const ModuleNameAndOffset& loc) + : location(loc), address(0), type(SoftwareBreakpoint), size(1), enabled(true) {} - bool operator==(const HardwareBreakpointInfo& other) const + // Create a hardware breakpoint + BreakpointInfo(uint64_t addr, DebugBreakpointType bpType, size_t bpSize) + : location(), address(addr), type(bpType), size(bpSize), enabled(true) {} + + bool IsSoftware() const { return type == SoftwareBreakpoint; } + bool IsHardware() const { return type != SoftwareBreakpoint; } + + bool operator==(const BreakpointInfo& other) const { - return address == other.address && type == other.type && size == other.size; + if (type != other.type) return false; + if (IsSoftware()) + return location == other.location; + else + return address == other.address && size == other.size; } - bool operator<(const HardwareBreakpointInfo& other) const + bool operator<(const BreakpointInfo& other) const { - if (address != other.address) return address < other.address; if (type != other.type) return type < other.type; - return size < other.size; + if (IsSoftware()) + return location < other.location; + else + { + if (address != other.address) return address < other.address; + return size < other.size; + } } }; @@ -105,9 +124,7 @@ namespace BinaryNinjaDebugger { { private: DebuggerState* m_state; - std::vector m_breakpoints; - std::map m_enabledState; - std::vector m_hardwareBreakpoints; + std::vector m_breakpoints; public: DebuggerBreakpoints(DebuggerState* state, std::vector initial = {}); @@ -126,13 +143,17 @@ namespace BinaryNinjaDebugger { void Apply(); void SerializeMetadata(); void UnserializedMetadata(); - std::vector GetBreakpointList() const { return m_breakpoints; } + + // Get all breakpoints (both software and hardware) + std::vector GetBreakpointList() const { return m_breakpoints; } + + // Get only software breakpoints (for backward compatibility) + std::vector GetSoftwareBreakpointList() const; // Hardware breakpoint methods bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool ContainsHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); - std::vector GetHardwareBreakpointList() const { return m_hardwareBreakpoints; } }; diff --git a/core/ffi.cpp b/core/ffi.cpp index 60c4feeb..4e20640c 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -802,7 +802,7 @@ void BNDebuggerSetCommandLineArguments(BNDebuggerController* controller, const c BNDebugBreakpoint* BNDebuggerGetBreakpoints(BNDebuggerController* controller, size_t* count) { DebuggerState* state = controller->object->GetState(); - std::vector breakpoints = state->GetBreakpoints()->GetBreakpointList(); + std::vector breakpoints = state->GetBreakpoints()->GetBreakpointList(); *count = breakpoints.size(); //std::vector remoteList; @@ -812,12 +812,14 @@ BNDebugBreakpoint* BNDebuggerGetBreakpoints(BNDebuggerController* controller, si BNDebugBreakpoint* result = new BNDebugBreakpoint[breakpoints.size()]; for (size_t i = 0; i < breakpoints.size(); i++) { - uint64_t remoteAddress = state->GetModules()->RelativeAddressToAbsolute(breakpoints[i]); - bool enabled = state->GetBreakpoints()->IsEnabledOffset(breakpoints[i]); - result[i].module = BNDebuggerAllocString(breakpoints[i].module.c_str()); - result[i].offset = breakpoints[i].offset; + uint64_t remoteAddress = breakpoints[i].IsSoftware() ? + state->GetModules()->RelativeAddressToAbsolute(breakpoints[i].location) : + breakpoints[i].address; + result[i].module = BNDebuggerAllocString(breakpoints[i].location.module.c_str()); + result[i].offset = breakpoints[i].location.offset; result[i].address = remoteAddress; - result[i].enabled = enabled; + result[i].enabled = breakpoints[i].enabled; + result[i].type = (BNDebugBreakpointType)breakpoints[i].type; } return result; } From 2188fdf89f2352e937644614a20ae7a1584e81e2 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Tue, 9 Dec 2025 12:08:09 +0800 Subject: [PATCH 13/30] Fix hardware breakpoint display in the breakpoint widget --- api/debuggercontroller.cpp | 1 + ui/breakpointswidget.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index c5d745d6..76080b67 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -731,6 +731,7 @@ std::vector DebuggerController::GetBreakpoints() bp.offset = breakpoints[i].offset; bp.address = breakpoints[i].address; bp.enabled = breakpoints[i].enabled; + bp.type = (DebugBreakpointType)breakpoints[i].type; result[i] = bp; } diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index c29814a3..b3b787e0 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -45,15 +45,15 @@ std::string BreakpointItem::typeString() const switch (m_type) { case SoftwareBreakpoint: - return "Software"; + return "S"; case HardwareExecuteBreakpoint: - return "Hardware Exec"; + return "HE"; case HardwareReadBreakpoint: - return "Hardware Read"; + return "HR"; case HardwareWriteBreakpoint: - return "Hardware Write"; + return "HW"; case HardwareAccessBreakpoint: - return "Hardware Access"; + return "HA"; default: return "Unknown"; } From e8e3978ad4c1bd4b6c10d8dfac625525cb479491 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Tue, 9 Dec 2025 14:20:52 +0800 Subject: [PATCH 14/30] Fix hardware breakpoint caching for all debug adapters --- core/adapters/dbgengadapter.cpp | 29 +++++++++++++++++++++++++- core/adapters/dbgengadapter.h | 1 + core/adapters/esrevenadapter.cpp | 35 +++++++++++++++++++++++++++++++- core/adapters/esrevenadapter.h | 1 + core/adapters/gdbadapter.cpp | 35 +++++++++++++++++++++++++++++++- core/adapters/gdbadapter.h | 1 + core/adapters/lldbadapter.cpp | 30 ++++++++++++++++++++++++++- core/adapters/lldbadapter.h | 1 + core/debugadapter.h | 16 +++++++++++++++ 9 files changed, 145 insertions(+), 4 deletions(-) diff --git a/core/adapters/dbgengadapter.cpp b/core/adapters/dbgengadapter.cpp index 1cb1d5d7..5c2289ee 100644 --- a/core/adapters/dbgengadapter.cpp +++ b/core/adapters/dbgengadapter.cpp @@ -1168,7 +1168,16 @@ std::vector DbgEngAdapter::GetBreakpointList() const bool DbgEngAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (!m_dbgengInitialized) - return false; + { + // Cache the hardware breakpoint to be applied when debugger becomes initialized + PendingHardwareBreakpoint pending(address, type, size); + if (std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending) + == m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.push_back(pending); + } + return true; + } std::string command; switch (type) @@ -1204,7 +1213,17 @@ bool DbgEngAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType bool DbgEngAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (!m_dbgengInitialized) + { + // Remove from pending list if debugger is not initialized + PendingHardwareBreakpoint pending(address, type, size); + auto it = std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending); + if (it != m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.erase(it); + return true; + } return false; + } // List all breakpoints to find the ID of the hardware breakpoint at this address auto result = InvokeBackendCommand("bl"); @@ -1248,11 +1267,19 @@ bool DbgEngAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointTy void DbgEngAdapter::ApplyBreakpoints() { + // Apply pending software breakpoints for (const auto bp : m_pendingBreakpoints) { AddBreakpoint(bp); } m_pendingBreakpoints.clear(); + + // Apply pending hardware breakpoints + for (const auto& hwbp : m_pendingHardwareBreakpoints) + { + AddHardwareBreakpoint(hwbp.address, hwbp.type, hwbp.size); + } + m_pendingHardwareBreakpoints.clear(); } DebugRegister DbgEngAdapter::ReadRegister(const std::string& reg) diff --git a/core/adapters/dbgengadapter.h b/core/adapters/dbgengadapter.h index d76daa45..22901620 100644 --- a/core/adapters/dbgengadapter.h +++ b/core/adapters/dbgengadapter.h @@ -145,6 +145,7 @@ namespace BinaryNinjaDebugger { unsigned long m_exitCode {}; std::vector m_pendingBreakpoints {}; + std::vector m_pendingHardwareBreakpoints {}; ULONG64 m_server {}; bool m_connectedToDebugServer = false; diff --git a/core/adapters/esrevenadapter.cpp b/core/adapters/esrevenadapter.cpp index 5546d68e..546c970a 100644 --- a/core/adapters/esrevenadapter.cpp +++ b/core/adapters/esrevenadapter.cpp @@ -1211,7 +1211,16 @@ bool EsrevenAdapter::StepOverReverse() bool EsrevenAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (m_isTargetRunning || !m_rspConnector) - return false; + { + // Cache the hardware breakpoint to be applied when target stops or connector becomes available + PendingHardwareBreakpoint pending(address, type, size); + if (std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending) + == m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.push_back(pending); + } + return true; + } std::string command; switch (type) @@ -1243,7 +1252,17 @@ bool EsrevenAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType bool EsrevenAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (m_isTargetRunning || !m_rspConnector) + { + // Remove from pending list if target is running or connector not available + PendingHardwareBreakpoint pending(address, type, size); + auto it = std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending); + if (it != m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.erase(it); + return true; + } return false; + } std::string command; switch (type) @@ -1789,6 +1808,7 @@ DebugBreakpoint EsrevenAdapter::AddBreakpoint(const ModuleNameAndOffset& address void EsrevenAdapter::CheckApplyPendingBreakpoints() { + // Apply pending software breakpoints for (auto it = m_pendingBreakpoints.begin(); it != m_pendingBreakpoints.end(); ) { uint64_t base{}; @@ -1804,6 +1824,19 @@ void EsrevenAdapter::CheckApplyPendingBreakpoints() } it++; } + + // Apply pending hardware breakpoints + for (auto it = m_pendingHardwareBreakpoints.begin(); it != m_pendingHardwareBreakpoints.end(); ) + { + if (AddHardwareBreakpoint(it->address, it->type, it->size)) + { + it = m_pendingHardwareBreakpoints.erase(it); + } + else + { + it++; + } + } } diff --git a/core/adapters/esrevenadapter.h b/core/adapters/esrevenadapter.h index dd1b9407..377f827b 100644 --- a/core/adapters/esrevenadapter.h +++ b/core/adapters/esrevenadapter.h @@ -57,6 +57,7 @@ namespace BinaryNinjaDebugger std::vector m_debugBreakpoints{}; std::vector m_pendingBreakpoints {}; + std::vector m_pendingHardwareBreakpoints {}; std::optional> m_moduleCache{}; diff --git a/core/adapters/gdbadapter.cpp b/core/adapters/gdbadapter.cpp index 8d5cf689..7b317515 100644 --- a/core/adapters/gdbadapter.cpp +++ b/core/adapters/gdbadapter.cpp @@ -1132,7 +1132,16 @@ bool GdbAdapter::StepOverReverse() bool GdbAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (m_isTargetRunning || !m_rspConnector) - return false; + { + // Cache the hardware breakpoint to be applied when target stops or connector becomes available + PendingHardwareBreakpoint pending(address, type, size); + if (std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending) + == m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.push_back(pending); + } + return true; + } std::string command; switch (type) @@ -1164,7 +1173,17 @@ bool GdbAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType typ bool GdbAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (m_isTargetRunning || !m_rspConnector) + { + // Remove from pending list if target is running or connector not available + PendingHardwareBreakpoint pending(address, type, size); + auto it = std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending); + if (it != m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.erase(it); + return true; + } return false; + } std::string command; switch (type) @@ -1482,6 +1501,7 @@ DebugBreakpoint GdbAdapter::AddBreakpoint(const ModuleNameAndOffset& address, un void GdbAdapter::CheckApplyPendingBreakpoints() { + // Apply pending software breakpoints for (auto it = m_pendingBreakpoints.begin(); it != m_pendingBreakpoints.end(); ) { uint64_t base{}; @@ -1497,6 +1517,19 @@ void GdbAdapter::CheckApplyPendingBreakpoints() } it++; } + + // Apply pending hardware breakpoints + for (auto it = m_pendingHardwareBreakpoints.begin(); it != m_pendingHardwareBreakpoints.end(); ) + { + if (AddHardwareBreakpoint(it->address, it->type, it->size)) + { + it = m_pendingHardwareBreakpoints.erase(it); + } + else + { + it++; + } + } } diff --git a/core/adapters/gdbadapter.h b/core/adapters/gdbadapter.h index 97814dd9..0ddec26c 100644 --- a/core/adapters/gdbadapter.h +++ b/core/adapters/gdbadapter.h @@ -57,6 +57,7 @@ namespace BinaryNinjaDebugger std::vector m_debugBreakpoints{}; std::vector m_pendingBreakpoints {}; + std::vector m_pendingHardwareBreakpoints {}; std::optional> m_moduleCache{}; diff --git a/core/adapters/lldbadapter.cpp b/core/adapters/lldbadapter.cpp index 42d8161f..142f7ee2 100644 --- a/core/adapters/lldbadapter.cpp +++ b/core/adapters/lldbadapter.cpp @@ -326,6 +326,7 @@ void BinaryNinjaDebugger::InitLldbAdapterType() void LldbAdapter::ApplyBreakpoints() { + // Apply pending software breakpoints for (const auto& bp : m_pendingBreakpoints) { AddBreakpoint(bp); @@ -333,6 +334,14 @@ void LldbAdapter::ApplyBreakpoints() // Clear the pending breakpoint list so that when the adapter launch/attach/connect to the target for the next time, // it always gets a clean list of breakpoints from the controller. m_pendingBreakpoints.clear(); + + // Apply pending hardware breakpoints + for (const auto& hwbp : m_pendingHardwareBreakpoints) + { + AddHardwareBreakpoint(hwbp.address, hwbp.type, hwbp.size); + } + // Clear the pending hardware breakpoint list + m_pendingHardwareBreakpoints.clear(); } @@ -1006,7 +1015,16 @@ std::vector LldbAdapter::GetBreakpointList() const bool LldbAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (!m_targetActive) - return false; + { + // Cache the hardware breakpoint to be applied when target becomes active + PendingHardwareBreakpoint pending(address, type, size); + if (std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending) + == m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.push_back(pending); + } + return true; + } switch (type) { @@ -1047,7 +1065,17 @@ bool LldbAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType ty bool LldbAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (!m_targetActive) + { + // Remove from pending list if target is not active + PendingHardwareBreakpoint pending(address, type, size); + auto it = std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending); + if (it != m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.erase(it); + return true; + } return false; + } switch (type) { diff --git a/core/adapters/lldbadapter.h b/core/adapters/lldbadapter.h index 77b80e4f..d680c60b 100644 --- a/core/adapters/lldbadapter.h +++ b/core/adapters/lldbadapter.h @@ -35,6 +35,7 @@ namespace BinaryNinjaDebugger { bool m_targetActive; std::vector m_pendingBreakpoints {}; + std::vector m_pendingHardwareBreakpoints {}; // Since when SBProcess::Kill() and SBProcess::ReadMemory() are called at the same time, LLDB will hang, // we must use this mutex to prevent the quit operation and read memory operation to happen at the same time. diff --git a/core/debugadapter.h b/core/debugadapter.h index 4f1f59f8..e5d85f8b 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -134,6 +134,22 @@ namespace BinaryNinjaDebugger { bool operator!() const { return !this->m_address && !this->m_id && !this->m_is_active; } }; + // Pending hardware breakpoint info (to be applied when target becomes active) + struct PendingHardwareBreakpoint + { + uint64_t address; + DebugBreakpointType type; + size_t size; + + PendingHardwareBreakpoint(uint64_t addr, DebugBreakpointType bpType, size_t bpSize) + : address(addr), type(bpType), size(bpSize) {} + + bool operator==(const PendingHardwareBreakpoint& other) const + { + return address == other.address && type == other.type && size == other.size; + } + }; + struct DebugRegister { std::string m_name {}; From 55a37d735ca01d8884e58f03466787a8596ab60d Mon Sep 17 00:00:00 2001 From: Xusheng Date: Tue, 9 Dec 2025 15:31:30 +0800 Subject: [PATCH 15/30] Temporary fix to make hardware breakpoints work across launch --- core/adapters/lldbadapter.cpp | 49 ++++++++++++++++++++++++++++++----- core/adapters/lldbadapter.h | 5 ++++ core/debuggerstate.cpp | 36 +++++++++++++++---------- 3 files changed, 70 insertions(+), 20 deletions(-) diff --git a/core/adapters/lldbadapter.cpp b/core/adapters/lldbadapter.cpp index 142f7ee2..d85e27a9 100644 --- a/core/adapters/lldbadapter.cpp +++ b/core/adapters/lldbadapter.cpp @@ -326,7 +326,7 @@ void BinaryNinjaDebugger::InitLldbAdapterType() void LldbAdapter::ApplyBreakpoints() { - // Apply pending software breakpoints + // Apply pending software breakpoints immediately - these work fine before process starts for (const auto& bp : m_pendingBreakpoints) { AddBreakpoint(bp); @@ -335,13 +335,36 @@ void LldbAdapter::ApplyBreakpoints() // it always gets a clean list of breakpoints from the controller. m_pendingBreakpoints.clear(); - // Apply pending hardware breakpoints - for (const auto& hwbp : m_pendingHardwareBreakpoints) + // DEFER hardware breakpoints instead of applying now + // + // WHY: LLDB has a known issue where hardware breakpoints set before the process starts often fail to work. + // Hardware breakpoints require the process to be running and stopped at least once so that LLDB can + // properly register them with the CPU's hardware debug registers. + // + // WHEN THIS WORKS: + // - Launch scenarios: Process will hit entry point or first instruction, we apply HW BP then + // - Attach scenarios: Process is already running, we apply on first break + // - Connect scenarios: Remote process is running, we apply on first break + // + // WHEN THIS DOESN'T WORK: + // - If the code you want to break on executes BEFORE the first stop (very rare, usually just entry point) + // - If LLDB is fixed in future versions and this workaround becomes unnecessary overhead + // - Non-stop mode debugging (not currently supported anyway) + // + // ALTERNATIVE APPROACHES CONSIDERED: + // - Applying immediately: Doesn't work due to LLDB bug + // - Remove and re-add on first stop: Works but wasteful + // - Platform-specific APIs: Same underlying issue + // + // Move hardware breakpoints to deferred list instead of applying now + m_deferredHardwareBreakpoints = std::move(m_pendingHardwareBreakpoints); + m_pendingHardwareBreakpoints.clear(); + + // Set flag to apply deferred hardware breakpoints on first stop + if (!m_deferredHardwareBreakpoints.empty()) { - AddHardwareBreakpoint(hwbp.address, hwbp.type, hwbp.size); + m_needsHardwareBreakpointReapplication = true; } - // Clear the pending hardware breakpoint list - m_pendingHardwareBreakpoints.clear(); } @@ -1926,6 +1949,20 @@ void LldbAdapter::EventListener() case lldb::eStateStopped: { FixActiveThread(); + + // Apply deferred hardware breakpoints on first stop + // See ApplyBreakpoints() for detailed explanation of why this is necessary + if (m_needsHardwareBreakpointReapplication) + { + for (const auto& hwbp : m_deferredHardwareBreakpoints) + { + // Apply the hardware breakpoint now that process is running and stopped + AddHardwareBreakpoint(hwbp.address, hwbp.type, hwbp.size); + } + m_deferredHardwareBreakpoints.clear(); + m_needsHardwareBreakpointReapplication = false; + } + DebuggerEvent dbgevt; dbgevt.type = AdapterStoppedEventType; // LLDB sometimes fails to update the process status when it is already sending eStateStopped event. diff --git a/core/adapters/lldbadapter.h b/core/adapters/lldbadapter.h index d680c60b..8a327ac3 100644 --- a/core/adapters/lldbadapter.h +++ b/core/adapters/lldbadapter.h @@ -37,6 +37,11 @@ namespace BinaryNinjaDebugger { std::vector m_pendingBreakpoints {}; std::vector m_pendingHardwareBreakpoints {}; + // LLDB BUG WORKAROUND: Hardware breakpoints set before process starts often fail to work + // We defer their application until the first stop event after launch/attach + std::vector m_deferredHardwareBreakpoints {}; + bool m_needsHardwareBreakpointReapplication = false; + // Since when SBProcess::Kill() and SBProcess::ReadMemory() are called at the same time, LLDB will hang, // we must use this mutex to prevent the quit operation and read memory operation to happen at the same time. std::mutex m_quitingMutex; diff --git a/core/debuggerstate.cpp b/core/debuggerstate.cpp index dc1bccff..57146a1e 100644 --- a/core/debuggerstate.cpp +++ b/core/debuggerstate.cpp @@ -738,26 +738,34 @@ bool DebuggerBreakpoints::ContainsAbsolute(uint64_t address) bool DebuggerBreakpoints::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { - if (!m_state->GetAdapter()) - return false; - - bool result = false; - // Add the hardware breakpoint to the adapter if connected - if (m_state->IsConnected()) - { - result = m_state->GetAdapter()->AddHardwareBreakpoint(address, type, size); - if (!result) - return false; - } - - // Check if this hardware breakpoint already exists + // TODO: ARCHITECTURAL ISSUE - This dual-path breakpoint system is problematic: + // - Software breakpoints have AddBreakpoint(ModuleNameAndOffset) that works before adapter creation + // - Hardware breakpoints only have AddHardwareBreakpoint(uint64_t) which requires absolute address + // This creates API asymmetry and prevents adding hardware breakpoints before target launch. + // + // Future refactoring options: + // 1. Add AddHardwareBreakpoint(ModuleNameAndOffset, type, size) overload for symmetry + // 2. Create unified BreakpointLocation struct that can represent both relative and absolute addressing + // 3. Merge AddBreakpoint and AddHardwareBreakpoint into single API with type parameter + // + // For now: Always add to m_breakpoints first (like software breakpoints do), then try to apply to adapter + + // Always add to m_breakpoints first - this allows hardware breakpoints to be added before adapter creation if (!ContainsHardwareBreakpoint(address, type, size)) { BreakpointInfo bp(address, type, size); m_breakpoints.push_back(bp); + SerializeMetadata(); } - return result; + // Then try to apply to adapter if it exists and is connected + if (m_state->GetAdapter() && m_state->IsConnected()) + { + return m_state->GetAdapter()->AddHardwareBreakpoint(address, type, size); + } + + // Success - breakpoint cached in m_breakpoints, will be applied when adapter becomes active + return true; } From 325acf773a6415b9b24ae668e37ad58241a7bb2d Mon Sep 17 00:00:00 2001 From: Xusheng Date: Wed, 10 Dec 2025 17:23:58 +0800 Subject: [PATCH 16/30] WIP on hardware breakpoint --- api/debuggerapi.h | 1 + api/debuggercontroller.cpp | 1 + api/ffi.h | 1 + api/python/debuggercontroller.py | 30 +++++++++++++++++++++++++----- cli/main.cpp | 26 +++++++++++++++++++++++++- core/adapters/lldbadapter.cpp | 16 ++++++++++++++++ core/ffi.cpp | 1 + ui/breakpointswidget.cpp | 24 ++++++++++++++++++------ ui/breakpointswidget.h | 4 +++- 9 files changed, 91 insertions(+), 13 deletions(-) diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 7c799067..810b2239 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -372,6 +372,7 @@ namespace BinaryNinjaDebuggerAPI { uint64_t address; bool enabled; DebugBreakpointType type = SoftwareBreakpoint; + size_t size = 1; // Size in bytes for hardware breakpoints/watchpoints (1, 2, 4, 8) }; diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index 76080b67..75d93d41 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -732,6 +732,7 @@ std::vector DebuggerController::GetBreakpoints() bp.address = breakpoints[i].address; bp.enabled = breakpoints[i].enabled; bp.type = (DebugBreakpointType)breakpoints[i].type; + bp.size = breakpoints[i].size; result[i] = bp; } diff --git a/api/ffi.h b/api/ffi.h index 9f2a4c80..7d625ce9 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -143,6 +143,7 @@ extern "C" uint64_t address; bool enabled; BNDebugBreakpointType type; + size_t size; // Size in bytes for hardware breakpoints/watchpoints (1, 2, 4, 8) } BNDebugBreakpoint; diff --git a/api/python/debuggercontroller.py b/api/python/debuggercontroller.py index 3277f1ce..fbbe4c18 100644 --- a/api/python/debuggercontroller.py +++ b/api/python/debuggercontroller.py @@ -368,19 +368,23 @@ class DebugBreakpoint: * ``offset``: the offset of the breakpoint to the start of the module * ``address``: the absolute address of the breakpoint * ``enabled``: whether the breakpoint is enabled (read-only) + * ``type``: the type of breakpoint (Software, HardwareExecute, HardwareRead, HardwareWrite, HardwareAccess) + * ``size``: the size in bytes for hardware breakpoints/watchpoints (1, 2, 4, or 8) """ - def __init__(self, module, offset, address, enabled): + def __init__(self, module, offset, address, enabled, bp_type=DebugBreakpointType.BNSoftwareBreakpoint, size=1): self.module = module self.offset = offset self.address = address self.enabled = enabled + self.type = bp_type + self.size = size def __eq__(self, other): if not isinstance(other, self.__class__): return NotImplemented return self.module == other.module and self.offset == other.offset and self.address == other.address \ - and self.enabled == other.enabled + and self.enabled == other.enabled and self.type == other.type and self.size == other.size def __ne__(self, other): if not isinstance(other, self.__class__): @@ -388,7 +392,7 @@ def __ne__(self, other): return not (self == other) def __hash__(self): - return hash((self.module, self.offset, self.address, self.enabled)) + return hash((self.module, self.offset, self.address, self.enabled, self.type, self.size)) def __setattr__(self, name, value): try: @@ -398,7 +402,22 @@ def __setattr__(self, name, value): def __repr__(self): status = "enabled" if self.enabled else "disabled" - return f"" + + # Get type string (S, HE, HR, HW, HA) + if self.type == DebugBreakpointType.BNSoftwareBreakpoint: + type_str = "S" + elif self.type == DebugBreakpointType.BNHardwareExecuteBreakpoint: + type_str = "HE" + elif self.type == DebugBreakpointType.BNHardwareReadBreakpoint: + type_str = "HR" + elif self.type == DebugBreakpointType.BNHardwareWriteBreakpoint: + type_str = "HW" + elif self.type == DebugBreakpointType.BNHardwareAccessBreakpoint: + type_str = "HA" + else: + type_str = "?" + + return f"" class ModuleNameAndOffset: @@ -2053,7 +2072,8 @@ def breakpoints(self) -> DebugBreakpoints: breakpoints = dbgcore.BNDebuggerGetBreakpoints(self.handle, count) result = [] for i in range(0, count.value): - bp = DebugBreakpoint(breakpoints[i].module, breakpoints[i].offset, breakpoints[i].address, breakpoints[i].enabled) + bp = DebugBreakpoint(breakpoints[i].module, breakpoints[i].offset, breakpoints[i].address, + breakpoints[i].enabled, breakpoints[i].type, breakpoints[i].size) result.append(bp) dbgcore.BNDebuggerFreeBreakpoints(breakpoints, count.value) diff --git a/cli/main.cpp b/cli/main.cpp index 6156b3b3..bc442854 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -540,7 +540,31 @@ int main(int argc, const char* argv[]) size_t i = 0; for (const auto& breakpoint : debugger->GetBreakpoints()) { - Log::print(" breakpoint[{}] @ 0x{:X} is {}{}\n", i, breakpoint.address, + // Convert breakpoint type to short string representation + std::string typeStr; + switch (breakpoint.type) + { + case BinaryNinjaDebuggerAPI::SoftwareBreakpoint: + typeStr = "S"; + break; + case BinaryNinjaDebuggerAPI::HardwareExecuteBreakpoint: + typeStr = "HE"; + break; + case BinaryNinjaDebuggerAPI::HardwareReadBreakpoint: + typeStr = "HR"; + break; + case BinaryNinjaDebuggerAPI::HardwareWriteBreakpoint: + typeStr = "HW"; + break; + case BinaryNinjaDebuggerAPI::HardwareAccessBreakpoint: + typeStr = "HA"; + break; + default: + typeStr = "?"; + break; + } + + Log::print(" breakpoint[{}] @ 0x{:X} type={} is {}{}\n", i, breakpoint.address, typeStr, breakpoint.enabled ? Log::Style(0, 255, 0) : Log::Style(255, 0, 0), breakpoint.enabled ? "active" : "inactive"); i++; diff --git a/core/adapters/lldbadapter.cpp b/core/adapters/lldbadapter.cpp index d85e27a9..ceccbf0c 100644 --- a/core/adapters/lldbadapter.cpp +++ b/core/adapters/lldbadapter.cpp @@ -1091,12 +1091,28 @@ bool LldbAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType { // Remove from pending list if target is not active PendingHardwareBreakpoint pending(address, type, size); + + // Check pending list first auto it = std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending); if (it != m_pendingHardwareBreakpoints.end()) { m_pendingHardwareBreakpoints.erase(it); return true; } + + // Also check deferred list (hardware breakpoints waiting for first stop due to LLDB bug workaround) + auto deferredIt = std::find(m_deferredHardwareBreakpoints.begin(), m_deferredHardwareBreakpoints.end(), pending); + if (deferredIt != m_deferredHardwareBreakpoints.end()) + { + m_deferredHardwareBreakpoints.erase(deferredIt); + // Clear the reapplication flag if no more deferred breakpoints + if (m_deferredHardwareBreakpoints.empty()) + { + m_needsHardwareBreakpointReapplication = false; + } + return true; + } + return false; } diff --git a/core/ffi.cpp b/core/ffi.cpp index 4e20640c..3b979781 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -820,6 +820,7 @@ BNDebugBreakpoint* BNDebuggerGetBreakpoints(BNDebuggerController* controller, si result[i].address = remoteAddress; result[i].enabled = breakpoints[i].enabled; result[i].type = (BNDebugBreakpointType)breakpoints[i].type; + result[i].size = breakpoints[i].size; } return result; } diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index b3b787e0..e6bc594c 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -35,8 +35,8 @@ using namespace BinaryNinjaDebuggerAPI; using namespace BinaryNinja; using namespace std; -BreakpointItem::BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t address, DebugBreakpointType type) : - m_enabled(enabled), m_location(location), m_address(address), m_type(type) +BreakpointItem::BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t address, DebugBreakpointType type, size_t size) : + m_enabled(enabled), m_location(location), m_address(address), m_type(type), m_size(size) {} @@ -625,18 +625,30 @@ void DebugBreakpointsWidget::soloSelected() void DebugBreakpointsWidget::remove() { QModelIndexList sel = selectionModel()->selectedRows(); - std::vector breakpointsToRemove; + std::vector breakpointsToRemove; for (const QModelIndex& index : sel) { // We cannot delete the breakpoint inside this loop because deleting a breakpoint will cause this widget to // remove the breakpoint from the list, which will invalidate the index of the remaining breakpoints. BreakpointItem bp = m_model->getRow(index.row()); - breakpointsToRemove.push_back(bp.location()); + breakpointsToRemove.push_back(bp); } for (const auto& bp : breakpointsToRemove) - m_controller->DeleteBreakpoint(bp); + { + // Use appropriate deletion method based on breakpoint type + if (bp.type() == SoftwareBreakpoint) + { + // Software breakpoints use module+offset deletion + m_controller->DeleteBreakpoint(bp.location()); + } + else + { + // Hardware breakpoints use address+type+size deletion + m_controller->RemoveHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + } + } } @@ -650,7 +662,7 @@ void DebugBreakpointsWidget::updateContent() ModuleNameAndOffset info; info.module = bp.module; info.offset = bp.offset; - bps.emplace_back(bp.enabled, info, bp.address, bp.type); + bps.emplace_back(bp.enabled, info, bp.address, bp.type, bp.size); } m_model->updateRows(bps); diff --git a/ui/breakpointswidget.h b/ui/breakpointswidget.h index 78f2fc43..4b3cec5e 100644 --- a/ui/breakpointswidget.h +++ b/ui/breakpointswidget.h @@ -40,13 +40,15 @@ class BreakpointItem ModuleNameAndOffset m_location; uint64_t m_address; DebugBreakpointType m_type; + size_t m_size; // Size in bytes for hardware breakpoints/watchpoints public: - BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t remoteAddress, DebugBreakpointType type = SoftwareBreakpoint); + BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t remoteAddress, DebugBreakpointType type = SoftwareBreakpoint, size_t size = 1); bool enabled() const { return m_enabled; } ModuleNameAndOffset location() const { return m_location; } uint64_t address() const { return m_address; } DebugBreakpointType type() const { return m_type; } + size_t size() const { return m_size; } std::string typeString() const; bool operator==(const BreakpointItem& other) const; bool operator!=(const BreakpointItem& other) const; From afbd7623534c1cf27724c5331ceea29aa252e7d8 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Thu, 11 Dec 2025 11:54:02 +0800 Subject: [PATCH 17/30] Support disabling hardware breakpoint --- api/debuggerapi.h | 2 + api/debuggercontroller.cpp | 12 +++++ api/ffi.h | 8 +++- core/debuggercontroller.cpp | 28 +++++++++++ core/debuggercontroller.h | 2 + core/debuggerstate.cpp | 58 +++++++++++++++++++++++ core/debuggerstate.h | 4 ++ core/ffi.cpp | 12 +++++ ui/breakpointswidget.cpp | 94 ++++++++++++++++++++++++++++--------- ui/ui.cpp | 19 ++++++++ 10 files changed, 216 insertions(+), 23 deletions(-) diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 810b2239..3de27828 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -742,6 +742,8 @@ namespace BinaryNinjaDebuggerAPI { // Hardware breakpoint and watchpoint support bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); + bool EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); + bool DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); uint64_t IP(); uint64_t GetLastIP(); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index 75d93d41..697d6017 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -813,6 +813,18 @@ bool DebuggerController::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpo } +bool DebuggerController::EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + return BNDebuggerEnableHardwareBreakpoint(m_object, address, (BNDebugBreakpointType)type, size); +} + + +bool DebuggerController::DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + return BNDebuggerDisableHardwareBreakpoint(m_object, address, (BNDebugBreakpointType)type, size); +} + + uint64_t DebuggerController::RelativeAddressToAbsolute(const ModuleNameAndOffset& address) { return BNDebuggerRelativeAddressToAbsolute(m_object, address.module.c_str(), address.offset); diff --git a/api/ffi.h b/api/ffi.h index 7d625ce9..e1d79944 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -610,9 +610,13 @@ extern "C" BNDebuggerController* controller, const char* module, uint64_t offset); // Hardware breakpoint and watchpoint support - DEBUGGER_FFI_API bool BNDebuggerAddHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, + DEBUGGER_FFI_API bool BNDebuggerAddHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, BNDebugBreakpointType type, size_t size); - DEBUGGER_FFI_API bool BNDebuggerRemoveHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, + DEBUGGER_FFI_API bool BNDebuggerRemoveHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, + BNDebugBreakpointType type, size_t size); + DEBUGGER_FFI_API bool BNDebuggerEnableHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, + BNDebugBreakpointType type, size_t size); + DEBUGGER_FFI_API bool BNDebuggerDisableHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, BNDebugBreakpointType type, size_t size); DEBUGGER_FFI_API uint64_t BNDebuggerGetIP(BNDebuggerController* controller); diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index be6107d8..5628b8f1 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -174,6 +174,34 @@ bool DebuggerController::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpo } +bool DebuggerController::EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + bool result = m_state->EnableHardwareBreakpoint(address, type, size); + if (result) + { + DebuggerEvent event; + event.type = AbsoluteBreakpointEnabledEvent; + event.data.absoluteAddress = address; + PostDebuggerEvent(event); + } + return result; +} + + +bool DebuggerController::DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + bool result = m_state->DisableHardwareBreakpoint(address, type, size); + if (result) + { + DebuggerEvent event; + event.type = AbsoluteBreakpointDisabledEvent; + event.data.absoluteAddress = address; + PostDebuggerEvent(event); + } + return result; +} + + bool DebuggerController::SetIP(uint64_t address) { std::string ipRegisterName; diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index 4b117383..6825ca7a 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -237,6 +237,8 @@ namespace BinaryNinjaDebugger { // hardware breakpoints bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); // registers intx::uint512 GetRegisterValue(const std::string& name); diff --git a/core/debuggerstate.cpp b/core/debuggerstate.cpp index 57146a1e..c6074d8a 100644 --- a/core/debuggerstate.cpp +++ b/core/debuggerstate.cpp @@ -797,6 +797,52 @@ bool DebuggerBreakpoints::ContainsHardwareBreakpoint(uint64_t address, DebugBrea } +bool DebuggerBreakpoints::EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + if (!ContainsHardwareBreakpoint(address, type, size)) + return false; + + // Find and enable the hardware breakpoint + BreakpointInfo toFind(address, type, size); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + { + iter->enabled = true; + } + SerializeMetadata(); + + // If connected, make sure the breakpoint is active in the target + if (m_state->GetAdapter() && m_state->IsConnected()) + { + return m_state->GetAdapter()->AddHardwareBreakpoint(address, type, size); + } + return true; +} + + +bool DebuggerBreakpoints::DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + if (!ContainsHardwareBreakpoint(address, type, size)) + return false; + + // Find and disable the hardware breakpoint + BreakpointInfo toFind(address, type, size); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + { + iter->enabled = false; + } + SerializeMetadata(); + + // If connected, remove the breakpoint from the target but keep it in our list + if (m_state->GetAdapter() && m_state->IsConnected()) + { + return m_state->GetAdapter()->RemoveHardwareBreakpoint(address, type, size); + } + return true; +} + + std::vector DebuggerBreakpoints::GetSoftwareBreakpointList() const { std::vector result; @@ -1205,6 +1251,18 @@ bool DebuggerState::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointTy } +bool DebuggerState::EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + return m_breakpoints->EnableHardwareBreakpoint(address, type, size); +} + + +bool DebuggerState::DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + return m_breakpoints->DisableHardwareBreakpoint(address, type, size); +} + + uint64_t DebuggerState::IP() { if (!IsConnected()) diff --git a/core/debuggerstate.h b/core/debuggerstate.h index 92e2fa7c..e2af7ef5 100644 --- a/core/debuggerstate.h +++ b/core/debuggerstate.h @@ -153,6 +153,8 @@ namespace BinaryNinjaDebugger { // Hardware breakpoint methods bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool ContainsHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); }; @@ -304,6 +306,8 @@ namespace BinaryNinjaDebugger { // Hardware breakpoints bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); uint64_t IP(); uint64_t StackPointer(); diff --git a/core/ffi.cpp b/core/ffi.cpp index 3b979781..99990939 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -934,6 +934,18 @@ bool BNDebuggerRemoveHardwareBreakpoint(BNDebuggerController* controller, uint64 } +bool BNDebuggerEnableHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, BNDebugBreakpointType type, size_t size) +{ + return controller->object->EnableHardwareBreakpoint(address, (DebugBreakpointType)type, size); +} + + +bool BNDebuggerDisableHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, BNDebugBreakpointType type, size_t size) +{ + return controller->object->DisableHardwareBreakpoint(address, (DebugBreakpointType)type, size); +} + + uint64_t BNDebuggerRelativeAddressToAbsolute(BNDebuggerController* controller, const char* module, uint64_t offset) { DebuggerState* state = controller->object->GetState(); diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index e6bc594c..edf141b4 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -403,10 +403,22 @@ void DebugBreakpointsWidget::mousePressEvent(QMouseEvent* event) { // Toggle breakpoint enabled state when clicking on enabled column BreakpointItem bp = m_model->getRow(index.row()); - if (bp.enabled()) - m_controller->DisableBreakpoint(bp.location()); + if (bp.type() == SoftwareBreakpoint) + { + // Software breakpoint - use location-based methods + if (bp.enabled()) + m_controller->DisableBreakpoint(bp.location()); + else + m_controller->EnableBreakpoint(bp.location()); + } else - m_controller->EnableBreakpoint(bp.location()); + { + // Hardware breakpoint - use address+type+size-based methods + if (bp.enabled()) + m_controller->DisableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + else + m_controller->EnableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + } return; // Don't call parent to avoid selection change } @@ -564,10 +576,22 @@ void DebugBreakpointsWidget::toggleSelected() for (const QModelIndex& index : sel) { BreakpointItem bp = m_model->getRow(index.row()); - if (bp.enabled()) - m_controller->DisableBreakpoint(bp.location()); + if (bp.type() == SoftwareBreakpoint) + { + // Software breakpoint - use location-based methods + if (bp.enabled()) + m_controller->DisableBreakpoint(bp.location()); + else + m_controller->EnableBreakpoint(bp.location()); + } else - m_controller->EnableBreakpoint(bp.location()); + { + // Hardware breakpoint - use address+type+size-based methods + if (bp.enabled()) + m_controller->DisableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + else + m_controller->EnableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + } } } @@ -577,10 +601,17 @@ void DebugBreakpointsWidget::enableAll() std::vector breakpoints = m_controller->GetBreakpoints(); for (const DebugBreakpoint& bp : breakpoints) { - ModuleNameAndOffset info; - info.module = bp.module; - info.offset = bp.offset; - m_controller->EnableBreakpoint(info); + if (bp.type == SoftwareBreakpoint) + { + ModuleNameAndOffset info; + info.module = bp.module; + info.offset = bp.offset; + m_controller->EnableBreakpoint(info); + } + else + { + m_controller->EnableHardwareBreakpoint(bp.address, bp.type, bp.size); + } } } @@ -590,10 +621,17 @@ void DebugBreakpointsWidget::disableAll() std::vector breakpoints = m_controller->GetBreakpoints(); for (const DebugBreakpoint& bp : breakpoints) { - ModuleNameAndOffset info; - info.module = bp.module; - info.offset = bp.offset; - m_controller->DisableBreakpoint(info); + if (bp.type == SoftwareBreakpoint) + { + ModuleNameAndOffset info; + info.module = bp.module; + info.offset = bp.offset; + m_controller->DisableBreakpoint(info); + } + else + { + m_controller->DisableHardwareBreakpoint(bp.address, bp.type, bp.size); + } } } @@ -606,19 +644,33 @@ void DebugBreakpointsWidget::soloSelected() // Get the selected breakpoint location BreakpointItem selectedBp = m_model->getRow(sel[0].row()); - + // Disable all breakpoints first std::vector breakpoints = m_controller->GetBreakpoints(); for (const DebugBreakpoint& bp : breakpoints) { - ModuleNameAndOffset info; - info.module = bp.module; - info.offset = bp.offset; - m_controller->DisableBreakpoint(info); + if (bp.type == SoftwareBreakpoint) + { + ModuleNameAndOffset info; + info.module = bp.module; + info.offset = bp.offset; + m_controller->DisableBreakpoint(info); + } + else + { + m_controller->DisableHardwareBreakpoint(bp.address, bp.type, bp.size); + } } - + // Enable the selected breakpoint - m_controller->EnableBreakpoint(selectedBp.location()); + if (selectedBp.type() == SoftwareBreakpoint) + { + m_controller->EnableBreakpoint(selectedBp.location()); + } + else + { + m_controller->EnableHardwareBreakpoint(selectedBp.address(), selectedBp.type(), selectedBp.size()); + } } diff --git a/ui/ui.cpp b/ui/ui.cpp index e3423145..143d1701 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -17,6 +17,7 @@ limitations under the License. #include "ui.h" #include "binaryninjaapi.h" #include "breakpointswidget.h" +#include "hardwarebreakpointdialog.h" #include "moduleswidget.h" #include "renderlayer.h" #include "uinotification.h" @@ -927,6 +928,24 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) })); debuggerMenu->addAction("Solo Breakpoint", "Breakpoint"); + // Register "Add Hardware Breakpoint" action + UIAction::registerAction("Add Hardware Breakpoint..."); + context->globalActions()->bindAction("Add Hardware Breakpoint...", + UIAction( + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return; + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller) + return; + + // Show the hardware breakpoint dialog with the current address as suggestion + HardwareBreakpointDialog dialog(context->mainWindow(), controller, ctxt.address); + dialog.exec(); + }, + requireBinaryView)); + debuggerMenu->addAction("Add Hardware Breakpoint...", "Breakpoint"); + UIAction::registerAction("Connect to Debug Server"); context->globalActions()->bindAction("Connect to Debug Server", UIAction( From ed7ba464791e5122ec8f01505e96f2243186b2f5 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Thu, 11 Dec 2025 12:16:25 +0800 Subject: [PATCH 18/30] Add "add hardware breakpoint..." to context menu --- ui/uinotification.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/uinotification.cpp b/ui/uinotification.cpp index 79f4d7cb..1ab704c9 100644 --- a/ui/uinotification.cpp +++ b/ui/uinotification.cpp @@ -178,6 +178,7 @@ void NotificationListener::OnContextMenuCreated(UIContext *context, View* view, menu.addAction("Debugger", "Toggle Breakpoint", "Breakpoint"); menu.addAction("Debugger", "Enable Breakpoint", "Breakpoint"); menu.addAction("Debugger", "Solo Breakpoint", "Breakpoint"); + menu.addAction("Debugger", "Add Hardware Breakpoint...", "Breakpoint"); menu.addAction("Debugger", "Launch", "Control"); menu.addAction("Debugger", "Pause", "Control"); menu.addAction("Debugger", "Restart", "Control"); From 59afe8abb331d04a25e41d381293fb49ace8ce83 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Thu, 11 Dec 2025 12:39:45 +0800 Subject: [PATCH 19/30] Customize the default selected entry in add hardware breakpoint dialog --- ui/hardwarebreakpointdialog.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ui/hardwarebreakpointdialog.cpp b/ui/hardwarebreakpointdialog.cpp index c87bc3fd..56f7a430 100644 --- a/ui/hardwarebreakpointdialog.cpp +++ b/ui/hardwarebreakpointdialog.cpp @@ -40,6 +40,23 @@ HardwareBreakpointDialog::HardwareBreakpointDialog(QWidget* parent, DbgRefaddItem("Hardware Read", static_cast(HardwareReadBreakpoint)); m_typeCombo->addItem("Hardware Write", static_cast(HardwareWriteBreakpoint)); m_typeCombo->addItem("Hardware Access (Read/Write)", static_cast(HardwareAccessBreakpoint)); + + // Set default type based on whether there's a function at the address + if (suggestedAddress != 0 && controller) + { + auto binaryView = controller->GetData(); + if (binaryView) + { + auto functions = binaryView->GetAnalysisFunctionsContainingAddress(suggestedAddress); + if (functions.empty()) + { + // No function at address - default to Hardware Read + m_typeCombo->setCurrentIndex(1); + } + // else: function exists - default to Hardware Execute (already index 0) + } + } + formLayout->addRow("Type:", m_typeCombo); // Size selection (for watchpoints) From ce74b2121d85c019d37c131f91de6825ead57833 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Thu, 11 Dec 2025 12:49:18 +0800 Subject: [PATCH 20/30] Fix keybinding --- ui/breakpointswidget.cpp | 1 - ui/ui.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index edf141b4..ad27534b 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -327,7 +327,6 @@ DebugBreakpointsWidget::DebugBreakpointsWidget(ViewFrame* view, BinaryViewRef da addBreakpointActionName, UIAction([&]() { addSoftwareBreakpoint(); })); QString addHardwareBreakpointActionName = QString::fromStdString("Add Hardware Breakpoint..."); - UIAction::registerAction(addHardwareBreakpointActionName); m_menu->addAction(addHardwareBreakpointActionName, "Options", MENU_ORDER_NORMAL); m_actionHandler.bindAction( addHardwareBreakpointActionName, UIAction([&]() { addHardwareBreakpoint(); })); diff --git a/ui/ui.cpp b/ui/ui.cpp index 143d1701..ca8de986 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -929,7 +929,7 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) debuggerMenu->addAction("Solo Breakpoint", "Breakpoint"); // Register "Add Hardware Breakpoint" action - UIAction::registerAction("Add Hardware Breakpoint..."); + UIAction::registerAction("Add Hardware Breakpoint...", QKeySequence(Qt::Key_F3)); context->globalActions()->bindAction("Add Hardware Breakpoint...", UIAction( [=](const UIActionContext& ctxt) { From 4e9593ea0e51ef477b2aac9c6af8c6a83549a2e4 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Thu, 11 Dec 2025 13:21:23 +0800 Subject: [PATCH 21/30] Add tooltip text for breakpoints widget --- ui/breakpointswidget.cpp | 55 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index ad27534b..6a66e219 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -19,12 +19,14 @@ limitations under the License. #include #include #include +#include #include #include #include #include #include #include +#include #include "breakpointswidget.h" #include "hardwarebreakpointdialog.h" #include "ui.h" @@ -123,13 +125,17 @@ QVariant DebugBreakpointsListModel::data(const QModelIndex& index, int role) con if (!item) return QVariant(); - if ((role != Qt::DisplayRole) && (role != Qt::SizeHintRole)) + if ((role != Qt::DisplayRole) && (role != Qt::SizeHintRole) && (role != Qt::ToolTipRole)) return QVariant(); switch (index.column()) { case DebugBreakpointsListModel::EnabledColumn: { + if (role == Qt::ToolTipRole) + { + return item->enabled() ? "Breakpoint is enabled" : "Breakpoint is disabled"; + } QString text = item->enabled() ? "☑" : "☐"; return QVariant(text); } @@ -163,6 +169,25 @@ QVariant DebugBreakpointsListModel::data(const QModelIndex& index, int role) con } case DebugBreakpointsListModel::TypeColumn: { + if (role == Qt::ToolTipRole) + { + switch (item->type()) + { + case SoftwareBreakpoint: + return "Software breakpoint"; + case HardwareExecuteBreakpoint: + return "Hardware execution breakpoint"; + case HardwareReadBreakpoint: + return "Hardware read breakpoint (watchpoint)"; + case HardwareWriteBreakpoint: + return "Hardware write breakpoint (watchpoint)"; + case HardwareAccessBreakpoint: + return "Hardware access breakpoint (read/write watchpoint)"; + default: + return "Unknown breakpoint type"; + } + } + QString text = QString::fromStdString(item->typeString()); if (role == Qt::SizeHintRole) return QVariant((qulonglong)text.size()); @@ -185,7 +210,7 @@ QVariant DebugBreakpointsListModel::headerData(int column, Qt::Orientation orien switch (column) { case DebugBreakpointsListModel::EnabledColumn: - return ""; + return "E"; case DebugBreakpointsListModel::LocationColumn: return "Location"; case DebugBreakpointsListModel::AddressColumn: @@ -232,6 +257,27 @@ void DebugBreakpointsItemDelegate::paint( switch (idx.column()) { case DebugBreakpointsListModel::EnabledColumn: + { + // Draw a proper Qt checkbox instead of using Unicode characters + QStyleOptionButton checkboxOption; + checkboxOption.state = QStyle::State_Enabled; + if (data.toString() == "☑") + checkboxOption.state |= QStyle::State_On; + else + checkboxOption.state |= QStyle::State_Off; + + // Center the checkbox in the cell + int checkboxSize = qMin(textRect.width(), textRect.height()) - 4; + checkboxOption.rect = QRect( + textRect.left() + (textRect.width() - checkboxSize) / 2, + textRect.top() + (textRect.height() - checkboxSize) / 2, + checkboxSize, + checkboxSize + ); + + QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkboxOption, painter); + break; + } case DebugBreakpointsListModel::LocationColumn: case DebugBreakpointsListModel::AddressColumn: case DebugBreakpointsListModel::TypeColumn: @@ -296,7 +342,10 @@ DebugBreakpointsWidget::DebugBreakpointsWidget(ViewFrame* view, BinaryViewRef da resizeColumnsToContents(); resizeRowsToContents(); - horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + + // Make the enabled column minimal width, and stretch the location column instead + horizontalHeader()->setSectionResizeMode(DebugBreakpointsListModel::EnabledColumn, QHeaderView::ResizeToContents); + horizontalHeader()->setSectionResizeMode(DebugBreakpointsListModel::LocationColumn, QHeaderView::Stretch); m_actionHandler.setupActionHandler(this); m_contextMenuManager = new ContextMenuManager(this); From c8c96db43c28d7afb9103157848bec10661416a2 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Thu, 11 Dec 2025 13:29:37 +0800 Subject: [PATCH 22/30] Use a different icon for hardware breakpoint --- ui/renderlayer.cpp | 84 +++++++++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/ui/renderlayer.cpp b/ui/renderlayer.cpp index 8abe3114..ecc26f12 100644 --- a/ui/renderlayer.cpp +++ b/ui/renderlayer.cpp @@ -38,12 +38,14 @@ void DebuggerRenderLayer::ApplyToBlock(Ref block, std::vectorIP(); bool paused = controller->GetTargetStatus() == DebugAdapterPausedStatus; - // Get all breakpoints with their enabled state + // Get all breakpoints with their enabled state and type std::vector breakpoints = controller->GetBreakpoints(); std::map breakpointEnabledMap; + std::map breakpointTypeMap; for (const auto& bp : breakpoints) { breakpointEnabledMap[bp.address] = bp.enabled; + breakpointTypeMap[bp.address] = bp.type; } for (auto& line : lines) @@ -55,9 +57,17 @@ void DebuggerRenderLayer::ApplyToBlock(Ref block, std::vector 0) { + // Check if it's a hardware breakpoint + if (breakpointTypeMap.count(line.addr) > 0) + { + DebugBreakpointType type = breakpointTypeMap[line.addr]; + isHardwareBreakpoint = (type != SoftwareBreakpoint); + } + if (breakpointEnabledMap[line.addr]) hasEnabledBreakpoint = true; else @@ -66,19 +76,21 @@ void DebuggerRenderLayer::ApplyToBlock(Ref block, std::vector block, std::vector block, std::vector block, std::vector function, std::ve uint64_t ipAddr = controller->IP(); bool paused = controller->GetTargetStatus() == DebugAdapterPausedStatus; - // Get all breakpoints with their enabled state + // Get all breakpoints with their enabled state and type std::vector breakpoints = controller->GetBreakpoints(); std::map breakpointEnabledMap; + std::map breakpointTypeMap; for (const auto& bp : breakpoints) { breakpointEnabledMap[bp.address] = bp.enabled; + breakpointTypeMap[bp.address] = bp.type; } for (auto& linearLine : lines) @@ -221,9 +241,17 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve bool hasPC = (line.addr == ipAddr) && paused; bool hasEnabledBreakpoint = false; bool hasDisabledBreakpoint = false; - + bool isHardwareBreakpoint = false; + if (breakpointEnabledMap.count(line.addr) > 0) { + // Check if it's a hardware breakpoint + if (breakpointTypeMap.count(line.addr) > 0) + { + DebugBreakpointType type = breakpointTypeMap[line.addr]; + isHardwareBreakpoint = (type != SoftwareBreakpoint); + } + if (breakpointEnabledMap[line.addr]) hasEnabledBreakpoint = true; else @@ -232,19 +260,21 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve if (hasPC && hasEnabledBreakpoint) { + // Use different icons for software vs hardware breakpoints + std::string icon = isHardwareBreakpoint ? "🔶➞" : "🛑➞"; bool appliedTag = false; for (size_t i = 0; i < line.tokens.size(); i++) { if (line.tokens[i].type == TagToken) { - line.tokens[i].text = "🛑➞"; + line.tokens[i].text = icon; appliedTag = true; break; } } if (!appliedTag) { - InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, "🛑➞"); + InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, icon); line.tokens.insert(line.tokens.begin(), indicator); } @@ -259,20 +289,21 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve } else if (hasPC && hasDisabledBreakpoint) { - // PC at a disabled breakpoint - show both indicators, no breakpoint highlighting + // PC at a disabled breakpoint - show both indicators using different icons for software vs hardware + std::string icon = isHardwareBreakpoint ? "◇➞" : "⭕➞"; bool appliedTag = false; for (size_t i = 0; i < line.tokens.size(); i++) { if (line.tokens[i].type == TagToken) { - line.tokens[i].text = "⭘➞"; + line.tokens[i].text = icon; appliedTag = true; break; } } if (!appliedTag) { - InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, "⭘➞"); + InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, icon); line.tokens.insert(line.tokens.begin(), indicator); } @@ -314,19 +345,22 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve } else if (hasEnabledBreakpoint) { + // Use different icons for software vs hardware breakpoints + std::string icon = isHardwareBreakpoint ? "🔶" : "🛑"; + std::string iconWithEllipsis = isHardwareBreakpoint ? "…🔶" : "…🛑"; bool appliedTag = false; for (size_t i = 0; i < line.tokens.size(); i++) { if (line.tokens[i].type == TagToken) { - line.tokens[i].text = "…🛑"; + line.tokens[i].text = iconWithEllipsis; appliedTag = true; break; } } if (!appliedTag) { - InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, "🛑"); + InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, icon); line.tokens.insert(line.tokens.begin(), indicator); } @@ -341,20 +375,22 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve } else if (hasDisabledBreakpoint) { - // Disabled breakpoint - show tag but no line highlighting + // Disabled breakpoint - use different icons for software vs hardware, no line highlighting + std::string icon = isHardwareBreakpoint ? "◇" : "⭕"; + std::string iconWithEllipsis = isHardwareBreakpoint ? "…◇" : "…⭕"; bool appliedTag = false; for (size_t i = 0; i < line.tokens.size(); i++) { if (line.tokens[i].type == TagToken) { - line.tokens[i].text = "…⭘"; + line.tokens[i].text = iconWithEllipsis; appliedTag = true; break; } } if (!appliedTag) { - InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, "⭘"); + InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, icon); line.tokens.insert(line.tokens.begin(), indicator); } // No line highlighting for disabled breakpoints From 32c003a4962529eadcca3b853ed44ea93d020092 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Thu, 11 Dec 2025 17:14:20 +0800 Subject: [PATCH 23/30] Fix hardware breakpoint not working when ASLR is active --- api/debuggerapi.h | 8 +- api/debuggercontroller.cpp | 26 +++++ api/ffi.h | 10 ++ core/adapters/corelliumadapter.cpp | 28 +++++ core/adapters/corelliumadapter.h | 6 ++ core/adapters/dbgengadapter.cpp | 103 ++++++++++++++++++ core/adapters/dbgengadapter.h | 2 + core/adapters/esrevenadapter.cpp | 49 +++++++++ core/adapters/esrevenadapter.h | 2 + core/adapters/gdbadapter.cpp | 49 +++++++++ core/adapters/gdbadapter.h | 2 + core/adapters/gdbmiadapter.cpp | 24 +++++ core/adapters/gdbmiadapter.h | 6 ++ core/adapters/lldbadapter.cpp | 93 ++++++++++++++++- core/adapters/lldbadapter.h | 2 + core/adapters/lldbcoredumpadapter.cpp | 28 +++++ core/adapters/lldbcoredumpadapter.h | 6 ++ core/debugadapter.cpp | 12 --- core/debugadapter.h | 37 ++++++- core/debuggercontroller.cpp | 58 +++++++++++ core/debuggercontroller.h | 7 ++ core/debuggerstate.cpp | 145 +++++++++++++++++++++++++- core/debuggerstate.h | 54 +++++++--- core/ffi.cpp | 40 ++++++- ui/hardwarebreakpointdialog.cpp | 22 +++- 25 files changed, 780 insertions(+), 39 deletions(-) diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 3de27828..a2ba1195 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -739,12 +739,18 @@ namespace BinaryNinjaDebuggerAPI { bool ContainsBreakpoint(uint64_t address); bool ContainsBreakpoint(const ModuleNameAndOffset& breakpoint); - // Hardware breakpoint and watchpoint support + // Hardware breakpoint and watchpoint support - absolute address bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); bool EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); bool DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); + // Hardware breakpoint and watchpoint support - module+offset (ASLR-safe) + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1); + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1); + bool EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1); + bool DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1); + uint64_t IP(); uint64_t GetLastIP(); bool SetIP(uint64_t address); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index 697d6017..81af54af 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -825,6 +825,32 @@ bool DebuggerController::DisableHardwareBreakpoint(uint64_t address, DebugBreakp } +// Hardware breakpoint methods - module+offset (ASLR-safe) + +bool DebuggerController::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + return BNDebuggerAddRelativeHardwareBreakpoint(m_object, location.module.c_str(), location.offset, (BNDebugBreakpointType)type, size); +} + + +bool DebuggerController::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + return BNDebuggerRemoveRelativeHardwareBreakpoint(m_object, location.module.c_str(), location.offset, (BNDebugBreakpointType)type, size); +} + + +bool DebuggerController::EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + return BNDebuggerEnableRelativeHardwareBreakpoint(m_object, location.module.c_str(), location.offset, (BNDebugBreakpointType)type, size); +} + + +bool DebuggerController::DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + return BNDebuggerDisableRelativeHardwareBreakpoint(m_object, location.module.c_str(), location.offset, (BNDebugBreakpointType)type, size); +} + + uint64_t DebuggerController::RelativeAddressToAbsolute(const ModuleNameAndOffset& address) { return BNDebuggerRelativeAddressToAbsolute(m_object, address.module.c_str(), address.offset); diff --git a/api/ffi.h b/api/ffi.h index e1d79944..7aa924f6 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -619,6 +619,16 @@ extern "C" DEBUGGER_FFI_API bool BNDebuggerDisableHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, BNDebugBreakpointType type, size_t size); + // Hardware breakpoint methods - module+offset (ASLR-safe) + DEBUGGER_FFI_API bool BNDebuggerAddRelativeHardwareBreakpoint(BNDebuggerController* controller, const char* module, + uint64_t offset, BNDebugBreakpointType type, size_t size); + DEBUGGER_FFI_API bool BNDebuggerRemoveRelativeHardwareBreakpoint(BNDebuggerController* controller, const char* module, + uint64_t offset, BNDebugBreakpointType type, size_t size); + DEBUGGER_FFI_API bool BNDebuggerEnableRelativeHardwareBreakpoint(BNDebuggerController* controller, const char* module, + uint64_t offset, BNDebugBreakpointType type, size_t size); + DEBUGGER_FFI_API bool BNDebuggerDisableRelativeHardwareBreakpoint(BNDebuggerController* controller, const char* module, + uint64_t offset, BNDebugBreakpointType type, size_t size); + DEBUGGER_FFI_API uint64_t BNDebuggerGetIP(BNDebuggerController* controller); DEBUGGER_FFI_API uint64_t BNDebuggerGetLastIP(BNDebuggerController* controller); DEBUGGER_FFI_API bool BNDebuggerSetIP(BNDebuggerController* controller, uint64_t address); diff --git a/core/adapters/corelliumadapter.cpp b/core/adapters/corelliumadapter.cpp index 148576c7..28a97b3c 100644 --- a/core/adapters/corelliumadapter.cpp +++ b/core/adapters/corelliumadapter.cpp @@ -956,6 +956,34 @@ bool CorelliumAdapter::SupportFeature(DebugAdapterCapacity feature) } +bool CorelliumAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not yet implemented for Corellium adapter + return false; +} + + +bool CorelliumAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not yet implemented for Corellium adapter + return false; +} + + +bool CorelliumAdapter::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not yet implemented for Corellium adapter + return false; +} + + +bool CorelliumAdapter::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not yet implemented for Corellium adapter + return false; +} + + void CorelliumAdapter::InvalidateCache() { m_regCache.reset(); diff --git a/core/adapters/corelliumadapter.h b/core/adapters/corelliumadapter.h index 5d6e2b8b..54a8c619 100644 --- a/core/adapters/corelliumadapter.h +++ b/core/adapters/corelliumadapter.h @@ -133,6 +133,12 @@ namespace BinaryNinjaDebugger bool ResumeThread(std::uint32_t tid) override; DebugBreakpoint AddBreakpoint(const ModuleNameAndOffset& address, unsigned long breakpoint_type = 0) override; + // Hardware breakpoint support - not implemented + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; + void GenerateDefaultAdapterSettings(BinaryView* data); Ref GetAdapterSettings() override; }; diff --git a/core/adapters/dbgengadapter.cpp b/core/adapters/dbgengadapter.cpp index 5c2289ee..389c967e 100644 --- a/core/adapters/dbgengadapter.cpp +++ b/core/adapters/dbgengadapter.cpp @@ -1265,6 +1265,109 @@ bool DbgEngAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointTy return false; } + +bool DbgEngAdapter::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + if (m_dbgengInitialized) + { + // DbgEng is initialized - use module+offset syntax directly + BNSettingsScope scope = SettingsResourceScope; + auto data = GetData(); + auto adapterSettings = GetAdapterSettings(); + auto inputFile = adapterSettings->Get("common.inputFile", data, &scope); + + auto moduleToUse = location.module; + if (DebugModule::IsSameBaseModule(moduleToUse, inputFile)) + { + if (m_usePDBFileName && (!m_pdbFileName.empty())) + moduleToUse = m_pdbFileName; + } + + // DbgEng does not take a full path. It can take "hello.exe", or simply "hello" + auto fileName = std::filesystem::path(moduleToUse).stem(); + + std::string command; + switch (type) + { + case HardwareExecuteBreakpoint: + // ba e @!"module"+offset: hardware execution breakpoint with module+offset + command = fmt::format("ba e{} @!\"{}\"+0x{:x}", size, EscapeModuleName(fileName.wstring()), location.offset); + break; + case HardwareReadBreakpoint: + // ba r @!"module"+offset: hardware read breakpoint with module+offset + command = fmt::format("ba r{} @!\"{}\"+0x{:x}", size, EscapeModuleName(fileName.wstring()), location.offset); + break; + case HardwareWriteBreakpoint: + // ba w @!"module"+offset: hardware write breakpoint with module+offset + command = fmt::format("ba w{} @!\"{}\"+0x{:x}", size, EscapeModuleName(fileName.wstring()), location.offset); + break; + case HardwareAccessBreakpoint: + // ba a @!"module"+offset: hardware access breakpoint with module+offset + command = fmt::format("ba a{} @!\"{}\"+0x{:x}", size, EscapeModuleName(fileName.wstring()), location.offset); + break; + default: + return false; + } + + LogDebug("Hardware breakpoint command: %s", command.c_str()); + auto result = InvokeBackendCommand(command); + return result.find("error") == std::string::npos && result.find("Error") == std::string::npos; + } + else + { + // DbgEng not initialized - cache as pending with module+offset + PendingHardwareBreakpoint pending(location, type, size); + // Also populate the address field for UI display purposes + pending.address = location.offset + m_originalImageBase; + if (std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending) + == m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.push_back(pending); + } + return true; + } +} + + +bool DbgEngAdapter::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + // For removal, we need to resolve to absolute address to find the breakpoint ID + // DbgEng doesn't provide a direct way to remove by module+offset + if (m_dbgengInitialized) + { + // Get module base and resolve to absolute address + auto modules = GetModuleList(); + uint64_t base = 0; + for (const auto& module : modules) + { + if (DebugModule::IsSameBaseModule(module.m_name, location.module)) + { + base = module.m_address; + break; + } + } + + if (base != 0) + { + uint64_t address = base + location.offset; + return RemoveHardwareBreakpoint(address, type, size); + } + return false; + } + else + { + // Not initialized - remove from pending list using module+offset + PendingHardwareBreakpoint pending(location, type, size); + auto it = std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending); + if (it != m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.erase(it); + return true; + } + return false; + } +} + void DbgEngAdapter::ApplyBreakpoints() { // Apply pending software breakpoints diff --git a/core/adapters/dbgengadapter.h b/core/adapters/dbgengadapter.h index 22901620..e429629e 100644 --- a/core/adapters/dbgengadapter.h +++ b/core/adapters/dbgengadapter.h @@ -204,6 +204,8 @@ namespace BinaryNinjaDebugger { // Hardware breakpoint and watchpoint support bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; std::string GetRegisterNameByIndex(std::uint32_t index) const; std::unordered_map ReadAllRegisters() override; diff --git a/core/adapters/esrevenadapter.cpp b/core/adapters/esrevenadapter.cpp index 546c970a..43b54bf7 100644 --- a/core/adapters/esrevenadapter.cpp +++ b/core/adapters/esrevenadapter.cpp @@ -1291,6 +1291,55 @@ bool EsrevenAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointT } +bool EsrevenAdapter::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + uint64_t base{}; + if (GetModuleBase(location.module, base)) + { + // Module is loaded - resolve to absolute address and delegate + uint64_t address = base + location.offset; + return AddHardwareBreakpoint(address, type, size); + } + else + { + // Module not loaded yet - add to pending list with module+offset + PendingHardwareBreakpoint pending(location, type, size); + // Also populate the address field for UI display purposes + pending.address = location.offset + m_originalImageBase; + if (std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending) + == m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.push_back(pending); + } + return true; + } +} + + +bool EsrevenAdapter::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + uint64_t base{}; + if (GetModuleBase(location.module, base)) + { + // Module is loaded - resolve to absolute address and delegate + uint64_t address = base + location.offset; + return RemoveHardwareBreakpoint(address, type, size); + } + else + { + // Module not loaded yet - remove from pending list using module+offset + PendingHardwareBreakpoint pending(location, type, size); + auto it = std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending); + if (it != m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.erase(it); + return true; + } + return false; + } +} + + bool EsrevenAdapter::AddHardwareWriteBreakpoint(uint64_t address) { // Delegate to new standardized method diff --git a/core/adapters/esrevenadapter.h b/core/adapters/esrevenadapter.h index 377f827b..4946d939 100644 --- a/core/adapters/esrevenadapter.h +++ b/core/adapters/esrevenadapter.h @@ -162,6 +162,8 @@ namespace BinaryNinjaDebugger // Hardware breakpoint and watchpoint support bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; // Legacy methods - kept for backward compatibility bool AddHardwareWriteBreakpoint(uint64_t address); diff --git a/core/adapters/gdbadapter.cpp b/core/adapters/gdbadapter.cpp index 7b317515..e1ef5f53 100644 --- a/core/adapters/gdbadapter.cpp +++ b/core/adapters/gdbadapter.cpp @@ -1224,6 +1224,55 @@ bool GdbAdapter::RemoveHardwareWriteBreakpoint(uint64_t address) return RemoveHardwareBreakpoint(address, HardwareWriteBreakpoint, 1); } + +bool GdbAdapter::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + uint64_t base{}; + if (GetModuleBase(location.module, base)) + { + // Module is loaded - resolve to absolute address and delegate + uint64_t address = base + location.offset; + return AddHardwareBreakpoint(address, type, size); + } + else + { + // Module not loaded yet - add to pending list with module+offset + PendingHardwareBreakpoint pending(location, type, size); + // Also populate the address field for UI display purposes + pending.address = location.offset + m_originalImageBase; + if (std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending) + == m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.push_back(pending); + } + return true; + } +} + + +bool GdbAdapter::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + uint64_t base{}; + if (GetModuleBase(location.module, base)) + { + // Module is loaded - resolve to absolute address and delegate + uint64_t address = base + location.offset; + return RemoveHardwareBreakpoint(address, type, size); + } + else + { + // Module not loaded yet - remove from pending list using module+offset + PendingHardwareBreakpoint pending(location, type, size); + auto it = std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending); + if (it != m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.erase(it); + return true; + } + return false; + } +} + bool GdbAdapter::StepReturnReverse() { LogWarn("GdbAdapter does not support StepReturnReverse() yet"); diff --git a/core/adapters/gdbadapter.h b/core/adapters/gdbadapter.h index 0ddec26c..a97b9073 100644 --- a/core/adapters/gdbadapter.h +++ b/core/adapters/gdbadapter.h @@ -162,6 +162,8 @@ namespace BinaryNinjaDebugger // Hardware breakpoint and watchpoint support bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; // Legacy methods - kept for backward compatibility bool AddHardwareWriteBreakpoint(uint64_t address); diff --git a/core/adapters/gdbmiadapter.cpp b/core/adapters/gdbmiadapter.cpp index e7362020..eff58590 100644 --- a/core/adapters/gdbmiadapter.cpp +++ b/core/adapters/gdbmiadapter.cpp @@ -1083,6 +1083,30 @@ bool GdbMiAdapter::SupportFeature(DebugAdapterCapacity feature) { } } +bool GdbMiAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not yet implemented for GDB MI adapter + return false; +} + +bool GdbMiAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not yet implemented for GDB MI adapter + return false; +} + +bool GdbMiAdapter::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not yet implemented for GDB MI adapter + return false; +} + +bool GdbMiAdapter::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not yet implemented for GDB MI adapter + return false; +} + // --- Adapter Type Registration --- GdbMiAdapterType::GdbMiAdapterType() : DebugAdapterType("GDB MI") {} diff --git a/core/adapters/gdbmiadapter.h b/core/adapters/gdbmiadapter.h index 7c9136ca..37d53d7f 100644 --- a/core/adapters/gdbmiadapter.h +++ b/core/adapters/gdbmiadapter.h @@ -104,6 +104,12 @@ class GdbMiAdapter : public BinaryNinjaDebugger::DebugAdapter uint64_t GetStackPointer() override; bool SupportFeature(BinaryNinjaDebugger::DebugAdapterCapacity feature) override; + // Hardware breakpoint support - not implemented + bool AddHardwareBreakpoint(uint64_t address, BinaryNinjaDebugger::DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(uint64_t address, BinaryNinjaDebugger::DebugBreakpointType type, size_t size = 1) override; + bool AddHardwareBreakpoint(const BinaryNinjaDebugger::ModuleNameAndOffset& location, BinaryNinjaDebugger::DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(const BinaryNinjaDebugger::ModuleNameAndOffset& location, BinaryNinjaDebugger::DebugBreakpointType type, size_t size = 1) override; + void GenerateDefaultAdapterSettings(BinaryView* data); Ref GetAdapterSettings() override; }; diff --git a/core/adapters/lldbadapter.cpp b/core/adapters/lldbadapter.cpp index ceccbf0c..f7380ad0 100644 --- a/core/adapters/lldbadapter.cpp +++ b/core/adapters/lldbadapter.cpp @@ -1160,6 +1160,89 @@ bool LldbAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType } +bool LldbAdapter::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + if (!m_targetActive) + { + // Target not active - add to pending list with module+offset + PendingHardwareBreakpoint pending(location, type, size); + // Also populate the address field for UI display purposes + pending.address = location.offset + m_originalImageBase; + if (std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending) + == m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.push_back(pending); + } + return true; + } + else + { + // Target is active - use LLDB's module-aware syntax + uint64_t addr = location.offset + m_originalImageBase; + std::string command; + + switch (type) + { + case HardwareExecuteBreakpoint: + { + // Use breakpoint set with module and address, plus -H for hardware + command = fmt::format("breakpoint set --shlib \"{}\" --address 0x{:x} -H", location.module, addr); + auto result = InvokeBackendCommand(command); + return result.find("Breakpoint") != std::string::npos; + } + case HardwareReadBreakpoint: + case HardwareWriteBreakpoint: + case HardwareAccessBreakpoint: + { + // For watchpoints, we need to resolve to absolute address first + // LLDB watchpoints don't have direct module+offset syntax + // So we delegate to the absolute address version which will resolve at runtime + return AddHardwareBreakpoint(addr, type, size); + } + default: + return false; + } + } +} + + +bool LldbAdapter::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + if (!m_targetActive) + { + // Target not active - remove from pending list using module+offset + PendingHardwareBreakpoint pending(location, type, size); + + auto it = std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending); + if (it != m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.erase(it); + return true; + } + + // Also check deferred list + auto deferredIt = std::find(m_deferredHardwareBreakpoints.begin(), m_deferredHardwareBreakpoints.end(), pending); + if (deferredIt != m_deferredHardwareBreakpoints.end()) + { + m_deferredHardwareBreakpoints.erase(deferredIt); + if (m_deferredHardwareBreakpoints.empty()) + { + m_needsHardwareBreakpointReapplication = false; + } + return true; + } + + return false; + } + else + { + // Target is active - resolve to absolute address and remove + uint64_t address = location.offset + m_originalImageBase; + return RemoveHardwareBreakpoint(address, type, size); + } +} + + static intx::uint512 SBValueToUint512(lldb::SBValue& reg_val) { using namespace lldb; using namespace intx; @@ -1973,7 +2056,15 @@ void LldbAdapter::EventListener() for (const auto& hwbp : m_deferredHardwareBreakpoints) { // Apply the hardware breakpoint now that process is running and stopped - AddHardwareBreakpoint(hwbp.address, hwbp.type, hwbp.size); + // Check addressing mode and call appropriate variant + if (hwbp.isRelative) + { + AddHardwareBreakpoint(hwbp.location, hwbp.type, hwbp.size); + } + else + { + AddHardwareBreakpoint(hwbp.address, hwbp.type, hwbp.size); + } } m_deferredHardwareBreakpoints.clear(); m_needsHardwareBreakpointReapplication = false; diff --git a/core/adapters/lldbadapter.h b/core/adapters/lldbadapter.h index 8a327ac3..cb867ced 100644 --- a/core/adapters/lldbadapter.h +++ b/core/adapters/lldbadapter.h @@ -105,6 +105,8 @@ namespace BinaryNinjaDebugger { // Hardware breakpoint and watchpoint support bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; std::unordered_map ReadAllRegisters() override; diff --git a/core/adapters/lldbcoredumpadapter.cpp b/core/adapters/lldbcoredumpadapter.cpp index 50d15f80..606ef17e 100644 --- a/core/adapters/lldbcoredumpadapter.cpp +++ b/core/adapters/lldbcoredumpadapter.cpp @@ -1213,6 +1213,34 @@ bool LldbCoreDumpAdapter::DisconnectDebugServer() } +bool LldbCoreDumpAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not supported for core dumps + return false; +} + + +bool LldbCoreDumpAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not supported for core dumps + return false; +} + + +bool LldbCoreDumpAdapter::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not supported for core dumps + return false; +} + + +bool LldbCoreDumpAdapter::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not supported for core dumps + return false; +} + + Ref LldbCoreDumpAdapter::GetAdapterSettings() { return LldbCoreDumpAdapterType::GetAdapterSettings(); diff --git a/core/adapters/lldbcoredumpadapter.h b/core/adapters/lldbcoredumpadapter.h index 1989c13a..821b6af5 100644 --- a/core/adapters/lldbcoredumpadapter.h +++ b/core/adapters/lldbcoredumpadapter.h @@ -135,6 +135,12 @@ namespace BinaryNinjaDebugger { bool DisconnectDebugServer() override; + // Hardware breakpoint support - not supported for core dumps + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; + void GenerateDefaultAdapterSettings(BinaryView* data); Ref GetAdapterSettings() override; }; diff --git a/core/debugadapter.cpp b/core/debugadapter.cpp index 0b069914..3858ee0d 100644 --- a/core/debugadapter.cpp +++ b/core/debugadapter.cpp @@ -255,15 +255,3 @@ bool DebugAdapter::SetTTDPosition(const TTDPosition& position) } -bool DebugAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) -{ - // Default implementation returns false for adapters that don't support hardware breakpoints - return false; -} - - -bool DebugAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) -{ - // Default implementation returns false for adapters that don't support hardware breakpoints - return false; -} diff --git a/core/debugadapter.h b/core/debugadapter.h index e5d85f8b..b730b0e6 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -137,16 +137,35 @@ namespace BinaryNinjaDebugger { // Pending hardware breakpoint info (to be applied when target becomes active) struct PendingHardwareBreakpoint { - uint64_t address; + ModuleNameAndOffset location; // Module + offset (for relative addressing) + uint64_t address; // Absolute address (for absolute addressing or resolved relative) DebugBreakpointType type; size_t size; + bool isRelative; // True if using module+offset, false if using absolute address + // Constructor for absolute address PendingHardwareBreakpoint(uint64_t addr, DebugBreakpointType bpType, size_t bpSize) - : address(addr), type(bpType), size(bpSize) {} + : location(), address(addr), type(bpType), size(bpSize), isRelative(false) {} + + // Constructor for module+offset + PendingHardwareBreakpoint(const ModuleNameAndOffset& loc, DebugBreakpointType bpType, size_t bpSize) + : location(loc), address(0), type(bpType), size(bpSize), isRelative(true) {} bool operator==(const PendingHardwareBreakpoint& other) const { - return address == other.address && type == other.type && size == other.size; + if (isRelative != other.isRelative) + return false; + + if (isRelative) + { + // Compare by module+offset + return location == other.location && type == other.type && size == other.size; + } + else + { + // Compare by absolute address + return address == other.address && type == other.type && size == other.size; + } } }; @@ -291,8 +310,16 @@ namespace BinaryNinjaDebugger { virtual std::vector GetBreakpointList() const = 0; // Hardware breakpoint and watchpoint support - virtual bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); - virtual bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1); + // Note: Adapters that don't support hardware breakpoints should return false from both methods + + // Hardware breakpoints - absolute address + virtual bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) = 0; + virtual bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) = 0; + + // Hardware breakpoints - module+offset (ASLR-safe) + // Each adapter must implement this to handle module resolution in its own way + virtual bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) = 0; + virtual bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) = 0; virtual std::unordered_map ReadAllRegisters() = 0; diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 5628b8f1..1f6ff182 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -202,6 +202,64 @@ bool DebuggerController::DisableHardwareBreakpoint(uint64_t address, DebugBreakp } +// Hardware breakpoint methods - module+offset (ASLR-safe) + +bool DebuggerController::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + bool result = m_state->AddHardwareBreakpoint(location, type, size); + if (result) + { + DebuggerEvent event; + event.type = RelativeBreakpointAddedEvent; + event.data.relativeAddress = location; + PostDebuggerEvent(event); + } + return result; +} + + +bool DebuggerController::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + bool result = m_state->RemoveHardwareBreakpoint(location, type, size); + if (result) + { + DebuggerEvent event; + event.type = RelativeBreakpointRemovedEvent; + event.data.relativeAddress = location; + PostDebuggerEvent(event); + } + return result; +} + + +bool DebuggerController::EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + bool result = m_state->EnableHardwareBreakpoint(location, type, size); + if (result) + { + DebuggerEvent event; + event.type = RelativeBreakpointEnabledEvent; + event.data.relativeAddress = location; + PostDebuggerEvent(event); + } + return result; +} + + +bool DebuggerController::DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + bool result = m_state->DisableHardwareBreakpoint(location, type, size); + if (result) + { + DebuggerEvent event; + event.type = RelativeBreakpointDisabledEvent; + event.data.relativeAddress = location; + PostDebuggerEvent(event); + } + return result; +} + + bool DebuggerController::SetIP(uint64_t address) { std::string ipRegisterName; diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index 6825ca7a..25054273 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -235,11 +235,18 @@ namespace BinaryNinjaDebugger { DebugBreakpoint GetAllBreakpoints(); // hardware breakpoints + // Hardware breakpoint methods - absolute address bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + // Hardware breakpoint methods - module+offset (ASLR-safe) + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + // registers intx::uint512 GetRegisterValue(const std::string& name); bool SetRegisterValue(const std::string& name, intx::uint512 value); diff --git a/core/debuggerstate.cpp b/core/debuggerstate.cpp index c6074d8a..0cbaab1e 100644 --- a/core/debuggerstate.cpp +++ b/core/debuggerstate.cpp @@ -843,6 +843,108 @@ bool DebuggerBreakpoints::DisableHardwareBreakpoint(uint64_t address, DebugBreak } +// ========== Hardware Breakpoint Module+Offset Methods (ASLR-safe) ========== + +bool DebuggerBreakpoints::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + // Always add to m_breakpoints first - this allows hardware breakpoints to be added before adapter creation + if (!ContainsHardwareBreakpoint(location, type, size)) + { + BreakpointInfo bp(location, type, size); // Uses the new constructor for module+offset + m_breakpoints.push_back(bp); + SerializeMetadata(); + } + + // Then try to apply to adapter if it exists and is connected + if (m_state->GetAdapter() && m_state->IsConnected()) + { + // Call adapter with module+offset directly - adapter will handle resolution + return m_state->GetAdapter()->AddHardwareBreakpoint(location, type, size); + } + + // Success - breakpoint cached in m_breakpoints, will be applied when adapter becomes active + return true; +} + + +bool DebuggerBreakpoints::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + BreakpointInfo toFind(location, type, size); + + // Remove from our list + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + { + m_breakpoints.erase(iter); + SerializeMetadata(); + } + + // Remove from the adapter if connected + if (m_state->GetAdapter() && m_state->IsConnected()) + { + // Call adapter with module+offset directly - adapter will handle resolution + return m_state->GetAdapter()->RemoveHardwareBreakpoint(location, type, size); + } + + return true; +} + + +bool DebuggerBreakpoints::ContainsHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + BreakpointInfo toFind(location, type, size); + return std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind) != m_breakpoints.end(); +} + + +bool DebuggerBreakpoints::EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + if (!ContainsHardwareBreakpoint(location, type, size)) + return false; + + // Find and enable the hardware breakpoint + BreakpointInfo toFind(location, type, size); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + { + iter->enabled = true; + } + SerializeMetadata(); + + // If connected, make sure the breakpoint is active in the target + if (m_state->GetAdapter() && m_state->IsConnected()) + { + // Call adapter with module+offset directly - adapter will handle resolution + return m_state->GetAdapter()->AddHardwareBreakpoint(location, type, size); + } + return true; +} + + +bool DebuggerBreakpoints::DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + if (!ContainsHardwareBreakpoint(location, type, size)) + return false; + + // Find and disable the hardware breakpoint + BreakpointInfo toFind(location, type, size); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + { + iter->enabled = false; + } + SerializeMetadata(); + + // If connected, remove the breakpoint from the target but keep it in our list + if (m_state->GetAdapter() && m_state->IsConnected()) + { + // Call adapter with module+offset directly - adapter will handle resolution + return m_state->GetAdapter()->RemoveHardwareBreakpoint(location, type, size); + } + return true; +} + + std::vector DebuggerBreakpoints::GetSoftwareBreakpointList() const { std::vector result; @@ -918,11 +1020,26 @@ void DebuggerBreakpoints::Apply() { if (bp.IsSoftware()) { + // Software breakpoints always use module+offset m_state->GetAdapter()->AddBreakpoint(bp.location); } else { - m_state->GetAdapter()->AddHardwareBreakpoint(bp.address, bp.type, bp.size); + // Hardware breakpoints - only add if enabled + if (!bp.enabled) + continue; + + // Hardware breakpoints can use either module+offset or absolute address + if (bp.isRelative) + { + // Use module+offset - adapter will handle resolution + m_state->GetAdapter()->AddHardwareBreakpoint(bp.location, bp.type, bp.size); + } + else + { + // Use absolute address + m_state->GetAdapter()->AddHardwareBreakpoint(bp.address, bp.type, bp.size); + } } } } @@ -1263,6 +1380,32 @@ bool DebuggerState::DisableHardwareBreakpoint(uint64_t address, DebugBreakpointT } +// Hardware breakpoint methods - module+offset (ASLR-safe) + +bool DebuggerState::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + return m_breakpoints->AddHardwareBreakpoint(location, type, size); +} + + +bool DebuggerState::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + return m_breakpoints->RemoveHardwareBreakpoint(location, type, size); +} + + +bool DebuggerState::EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + return m_breakpoints->EnableHardwareBreakpoint(location, type, size); +} + + +bool DebuggerState::DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + return m_breakpoints->DisableHardwareBreakpoint(location, type, size); +} + + uint64_t DebuggerState::IP() { if (!IsConnected()) diff --git a/core/debuggerstate.h b/core/debuggerstate.h index e2af7ef5..df92cf67 100644 --- a/core/debuggerstate.h +++ b/core/debuggerstate.h @@ -81,19 +81,24 @@ namespace BinaryNinjaDebugger { // Unified structure to track both software and hardware breakpoints struct BreakpointInfo { - ModuleNameAndOffset location; // Module + offset for software breakpoints - uint64_t address; // Absolute address (for hardware or resolved software) + ModuleNameAndOffset location; // Module + offset (for relative addressing) + uint64_t address; // Absolute address (for absolute addressing or resolved relative) DebugBreakpointType type; // Breakpoint type (Software, HardwareExecute, etc.) size_t size; // Size for hardware watchpoints bool enabled; // Enabled state + bool isRelative; // True if using module+offset, false if using absolute address - // Create a software breakpoint + // Create a software breakpoint (always relative) BreakpointInfo(const ModuleNameAndOffset& loc) - : location(loc), address(0), type(SoftwareBreakpoint), size(1), enabled(true) {} + : location(loc), address(0), type(SoftwareBreakpoint), size(1), enabled(true), isRelative(true) {} - // Create a hardware breakpoint + // Create a hardware breakpoint with absolute address BreakpointInfo(uint64_t addr, DebugBreakpointType bpType, size_t bpSize) - : location(), address(addr), type(bpType), size(bpSize), enabled(true) {} + : location(), address(addr), type(bpType), size(bpSize), enabled(true), isRelative(false) {} + + // Create a hardware breakpoint with module+offset (ASLR-safe) + BreakpointInfo(const ModuleNameAndOffset& loc, DebugBreakpointType bpType, size_t bpSize) + : location(loc), address(0), type(bpType), size(bpSize), enabled(true), isRelative(true) {} bool IsSoftware() const { return type == SoftwareBreakpoint; } bool IsHardware() const { return type != SoftwareBreakpoint; } @@ -101,22 +106,29 @@ namespace BinaryNinjaDebugger { bool operator==(const BreakpointInfo& other) const { if (type != other.type) return false; - if (IsSoftware()) + if (size != other.size) return false; + if (isRelative != other.isRelative) return false; + + // For relative addressing, compare module+offset + if (isRelative) return location == other.location; + // For absolute addressing, compare address else - return address == other.address && size == other.size; + return address == other.address; } bool operator<(const BreakpointInfo& other) const { if (type != other.type) return type < other.type; - if (IsSoftware()) + if (size != other.size) return size < other.size; + if (isRelative != other.isRelative) return isRelative < other.isRelative; + + // For relative addressing, compare module+offset + if (isRelative) return location < other.location; + // For absolute addressing, compare address else - { - if (address != other.address) return address < other.address; - return size < other.size; - } + return address < other.address; } }; @@ -151,11 +163,19 @@ namespace BinaryNinjaDebugger { std::vector GetSoftwareBreakpointList() const; // Hardware breakpoint methods + // Hardware breakpoint methods - absolute address bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool ContainsHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + + // Hardware breakpoint methods - module+offset (ASLR-safe) + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool ContainsHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); }; @@ -303,12 +323,18 @@ namespace BinaryNinjaDebugger { void DisableBreakpoint(uint64_t address); void DisableBreakpoint(const ModuleNameAndOffset& address); - // Hardware breakpoints + // Hardware breakpoint methods - absolute address bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); bool DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + // Hardware breakpoint methods - module+offset (ASLR-safe) + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + uint64_t IP(); uint64_t StackPointer(); diff --git a/core/ffi.cpp b/core/ffi.cpp index 99990939..b8f8ec5a 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -812,9 +812,17 @@ BNDebugBreakpoint* BNDebuggerGetBreakpoints(BNDebuggerController* controller, si BNDebugBreakpoint* result = new BNDebugBreakpoint[breakpoints.size()]; for (size_t i = 0; i < breakpoints.size(); i++) { - uint64_t remoteAddress = breakpoints[i].IsSoftware() ? - state->GetModules()->RelativeAddressToAbsolute(breakpoints[i].location) : - breakpoints[i].address; + uint64_t remoteAddress; + if (breakpoints[i].isRelative) + { + // For relative addressing (both software and hardware), convert module+offset to absolute address + remoteAddress = state->GetModules()->RelativeAddressToAbsolute(breakpoints[i].location); + } + else + { + // For absolute addressing, use the stored address directly + remoteAddress = breakpoints[i].address; + } result[i].module = BNDebuggerAllocString(breakpoints[i].location.module.c_str()); result[i].offset = breakpoints[i].location.offset; result[i].address = remoteAddress; @@ -946,6 +954,32 @@ bool BNDebuggerDisableHardwareBreakpoint(BNDebuggerController* controller, uint6 } +// Hardware breakpoint methods - module+offset (ASLR-safe) + +bool BNDebuggerAddRelativeHardwareBreakpoint(BNDebuggerController* controller, const char* module, uint64_t offset, BNDebugBreakpointType type, size_t size) +{ + return controller->object->AddHardwareBreakpoint(ModuleNameAndOffset(module, offset), (DebugBreakpointType)type, size); +} + + +bool BNDebuggerRemoveRelativeHardwareBreakpoint(BNDebuggerController* controller, const char* module, uint64_t offset, BNDebugBreakpointType type, size_t size) +{ + return controller->object->RemoveHardwareBreakpoint(ModuleNameAndOffset(module, offset), (DebugBreakpointType)type, size); +} + + +bool BNDebuggerEnableRelativeHardwareBreakpoint(BNDebuggerController* controller, const char* module, uint64_t offset, BNDebugBreakpointType type, size_t size) +{ + return controller->object->EnableHardwareBreakpoint(ModuleNameAndOffset(module, offset), (DebugBreakpointType)type, size); +} + + +bool BNDebuggerDisableRelativeHardwareBreakpoint(BNDebuggerController* controller, const char* module, uint64_t offset, BNDebugBreakpointType type, size_t size) +{ + return controller->object->DisableHardwareBreakpoint(ModuleNameAndOffset(module, offset), (DebugBreakpointType)type, size); +} + + uint64_t BNDebuggerRelativeAddressToAbsolute(BNDebuggerController* controller, const char* module, uint64_t offset) { DebuggerState* state = controller->object->GetState(); diff --git a/ui/hardwarebreakpointdialog.cpp b/ui/hardwarebreakpointdialog.cpp index 56f7a430..dbe3e0a6 100644 --- a/ui/hardwarebreakpointdialog.cpp +++ b/ui/hardwarebreakpointdialog.cpp @@ -140,14 +140,32 @@ void HardwareBreakpointDialog::addBreakpoint() if (m_controller) { - bool success = m_controller->AddHardwareBreakpoint(address, type, size); + bool success = false; + + // Determine if we should use absolute or relative addressing + bool isAbsoluteAddress = m_controller->IsConnected(); + + if (isAbsoluteAddress) + { + // Use absolute address (target is connected, ASLR already applied) + success = m_controller->AddHardwareBreakpoint(address, type, size); + } + else + { + // Use module+offset for ASLR safety (target not connected yet) + std::string filename = m_controller->GetInputFile(); + uint64_t offset = address - m_controller->GetViewFileSegmentsStart(); + ModuleNameAndOffset info = {filename, offset}; + success = m_controller->AddHardwareBreakpoint(info, type, size); + } + if (success) { accept(); } else { - QMessageBox::warning(this, "Failed to Add Breakpoint", + QMessageBox::warning(this, "Failed to Add Breakpoint", "Failed to add hardware breakpoint. The target may not support hardware breakpoints or all hardware breakpoint slots may be in use."); } } From f0d57d4488b38034e89cf06a59c9bd3a2c9fbef8 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 12 Dec 2025 12:39:54 +0800 Subject: [PATCH 24/30] Fix hardware breakpoint toggling and disabling --- ui/breakpointswidget.cpp | 90 ++++++++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 12 deletions(-) diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index 6a66e219..f0de3850 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -450,6 +450,7 @@ void DebugBreakpointsWidget::mousePressEvent(QMouseEvent* event) if (index.isValid() && index.column() == DebugBreakpointsListModel::EnabledColumn) { // Toggle breakpoint enabled state when clicking on enabled column + // TODO: refactor to use breakpoint index instead of address/location for these operations BreakpointItem bp = m_model->getRow(index.row()); if (bp.type() == SoftwareBreakpoint) { @@ -461,11 +462,21 @@ void DebugBreakpointsWidget::mousePressEvent(QMouseEvent* event) } else { - // Hardware breakpoint - use address+type+size-based methods + // Hardware breakpoint - use location for relative breakpoints, address for absolute if (bp.enabled()) - m_controller->DisableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + { + if (!bp.location().module.empty()) + m_controller->DisableHardwareBreakpoint(bp.location(), bp.type(), bp.size()); + else + m_controller->DisableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + } else - m_controller->EnableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + { + if (!bp.location().module.empty()) + m_controller->EnableHardwareBreakpoint(bp.location(), bp.type(), bp.size()); + else + m_controller->EnableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + } } return; // Don't call parent to avoid selection change } @@ -620,6 +631,7 @@ void DebugBreakpointsWidget::addHardwareBreakpoint() void DebugBreakpointsWidget::toggleSelected() { + // TODO: refactor to use breakpoint index instead of address/location for these operations QModelIndexList sel = selectionModel()->selectedRows(); for (const QModelIndex& index : sel) { @@ -634,11 +646,21 @@ void DebugBreakpointsWidget::toggleSelected() } else { - // Hardware breakpoint - use address+type+size-based methods + // Hardware breakpoint - use location for relative breakpoints, address for absolute if (bp.enabled()) - m_controller->DisableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + { + if (!bp.location().module.empty()) + m_controller->DisableHardwareBreakpoint(bp.location(), bp.type(), bp.size()); + else + m_controller->DisableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + } else - m_controller->EnableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + { + if (!bp.location().module.empty()) + m_controller->EnableHardwareBreakpoint(bp.location(), bp.type(), bp.size()); + else + m_controller->EnableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + } } } } @@ -646,6 +668,7 @@ void DebugBreakpointsWidget::toggleSelected() void DebugBreakpointsWidget::enableAll() { + // TODO: refactor to use breakpoint index instead of address/location for these operations std::vector breakpoints = m_controller->GetBreakpoints(); for (const DebugBreakpoint& bp : breakpoints) { @@ -658,7 +681,18 @@ void DebugBreakpointsWidget::enableAll() } else { - m_controller->EnableHardwareBreakpoint(bp.address, bp.type, bp.size); + // Hardware breakpoint - use location for relative breakpoints, address for absolute + if (!bp.module.empty()) + { + ModuleNameAndOffset info; + info.module = bp.module; + info.offset = bp.offset; + m_controller->EnableHardwareBreakpoint(info, bp.type, bp.size); + } + else + { + m_controller->EnableHardwareBreakpoint(bp.address, bp.type, bp.size); + } } } } @@ -666,6 +700,7 @@ void DebugBreakpointsWidget::enableAll() void DebugBreakpointsWidget::disableAll() { + // TODO: refactor to use breakpoint index instead of address/location for these operations std::vector breakpoints = m_controller->GetBreakpoints(); for (const DebugBreakpoint& bp : breakpoints) { @@ -678,7 +713,18 @@ void DebugBreakpointsWidget::disableAll() } else { - m_controller->DisableHardwareBreakpoint(bp.address, bp.type, bp.size); + // Hardware breakpoint - use location for relative breakpoints, address for absolute + if (!bp.module.empty()) + { + ModuleNameAndOffset info; + info.module = bp.module; + info.offset = bp.offset; + m_controller->DisableHardwareBreakpoint(info, bp.type, bp.size); + } + else + { + m_controller->DisableHardwareBreakpoint(bp.address, bp.type, bp.size); + } } } } @@ -686,6 +732,7 @@ void DebugBreakpointsWidget::disableAll() void DebugBreakpointsWidget::soloSelected() { + // TODO: refactor to use breakpoint index instead of address/location for these operations QModelIndexList sel = selectionModel()->selectedRows(); if (sel.empty()) return; @@ -706,7 +753,18 @@ void DebugBreakpointsWidget::soloSelected() } else { - m_controller->DisableHardwareBreakpoint(bp.address, bp.type, bp.size); + // Hardware breakpoint - use location for relative breakpoints, address for absolute + if (!bp.module.empty()) + { + ModuleNameAndOffset info; + info.module = bp.module; + info.offset = bp.offset; + m_controller->DisableHardwareBreakpoint(info, bp.type, bp.size); + } + else + { + m_controller->DisableHardwareBreakpoint(bp.address, bp.type, bp.size); + } } } @@ -717,13 +775,18 @@ void DebugBreakpointsWidget::soloSelected() } else { - m_controller->EnableHardwareBreakpoint(selectedBp.address(), selectedBp.type(), selectedBp.size()); + // Hardware breakpoint - use location for relative breakpoints, address for absolute + if (!selectedBp.location().module.empty()) + m_controller->EnableHardwareBreakpoint(selectedBp.location(), selectedBp.type(), selectedBp.size()); + else + m_controller->EnableHardwareBreakpoint(selectedBp.address(), selectedBp.type(), selectedBp.size()); } } void DebugBreakpointsWidget::remove() { + // TODO: refactor to use breakpoint index instead of address/location for these operations QModelIndexList sel = selectionModel()->selectedRows(); std::vector breakpointsToRemove; @@ -745,8 +808,11 @@ void DebugBreakpointsWidget::remove() } else { - // Hardware breakpoints use address+type+size deletion - m_controller->RemoveHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + // Hardware breakpoint - use location for relative breakpoints, address for absolute + if (!bp.location().module.empty()) + m_controller->RemoveHardwareBreakpoint(bp.location(), bp.type(), bp.size()); + else + m_controller->RemoveHardwareBreakpoint(bp.address(), bp.type(), bp.size()); } } } From d0a5c28d2b3ff0a5dbbd353a7d45e64a3f09e8ef Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 12 Dec 2025 13:37:45 +0800 Subject: [PATCH 25/30] Translate hardware breakpoints added during debugging via address to module+offset --- core/debuggerstate.cpp | 107 +++++++++++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 19 deletions(-) diff --git a/core/debuggerstate.cpp b/core/debuggerstate.cpp index 0cbaab1e..95ab464c 100644 --- a/core/debuggerstate.cpp +++ b/core/debuggerstate.cpp @@ -753,7 +753,10 @@ bool DebuggerBreakpoints::AddHardwareBreakpoint(uint64_t address, DebugBreakpoin // Always add to m_breakpoints first - this allows hardware breakpoints to be added before adapter creation if (!ContainsHardwareBreakpoint(address, type, size)) { - BreakpointInfo bp(address, type, size); + // Convert absolute address to module+offset for ASLR-safe storage (like AddAbsolute does for software breakpoints) + ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(address); + BreakpointInfo bp(info, type, size); + bp.address = address; m_breakpoints.push_back(bp); SerializeMetadata(); } @@ -771,13 +774,29 @@ bool DebuggerBreakpoints::AddHardwareBreakpoint(uint64_t address, DebugBreakpoin bool DebuggerBreakpoints::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { - BreakpointInfo toFind(address, type, size); - - // Remove from our list - auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); - if (iter != m_breakpoints.end()) + // Find and remove from our list - need to handle both relative and absolute breakpoints + for (auto iter = m_breakpoints.begin(); iter != m_breakpoints.end(); ++iter) { - m_breakpoints.erase(iter); + if (iter->IsHardware() && iter->type == type && iter->size == size) + { + bool matches = false; + if (iter->isRelative) + { + // Convert module+offset to absolute address and compare + uint64_t absolute = m_state->GetModules()->RelativeAddressToAbsolute(iter->location); + matches = (absolute == address); + } + else + { + matches = (iter->address == address); + } + + if (matches) + { + m_breakpoints.erase(iter); + break; + } + } } // Remove from the adapter if connected @@ -792,8 +811,26 @@ bool DebuggerBreakpoints::RemoveHardwareBreakpoint(uint64_t address, DebugBreakp bool DebuggerBreakpoints::ContainsHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { - BreakpointInfo toFind(address, type, size); - return std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind) != m_breakpoints.end(); + // Similar to ContainsAbsolute, we need to handle both relative and absolute hardware breakpoints + // For relative hardware breakpoints, convert to absolute and compare + for (const BreakpointInfo& breakpoint : m_breakpoints) + { + if (breakpoint.IsHardware() && breakpoint.type == type && breakpoint.size == size) + { + if (breakpoint.isRelative) + { + // Convert module+offset to absolute address and compare + uint64_t absolute = m_state->GetModules()->RelativeAddressToAbsolute(breakpoint.location); + if (absolute == address) + return true; + } + else if (breakpoint.address == address) + { + return true; + } + } + } + return false; } @@ -802,12 +839,28 @@ bool DebuggerBreakpoints::EnableHardwareBreakpoint(uint64_t address, DebugBreakp if (!ContainsHardwareBreakpoint(address, type, size)) return false; - // Find and enable the hardware breakpoint - BreakpointInfo toFind(address, type, size); - auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); - if (iter != m_breakpoints.end()) + // Find and enable the hardware breakpoint - need to handle both relative and absolute breakpoints + for (auto& bp : m_breakpoints) { - iter->enabled = true; + if (bp.IsHardware() && bp.type == type && bp.size == size) + { + bool matches = false; + if (bp.isRelative) + { + uint64_t absolute = m_state->GetModules()->RelativeAddressToAbsolute(bp.location); + matches = (absolute == address); + } + else + { + matches = (bp.address == address); + } + + if (matches) + { + bp.enabled = true; + break; + } + } } SerializeMetadata(); @@ -825,12 +878,28 @@ bool DebuggerBreakpoints::DisableHardwareBreakpoint(uint64_t address, DebugBreak if (!ContainsHardwareBreakpoint(address, type, size)) return false; - // Find and disable the hardware breakpoint - BreakpointInfo toFind(address, type, size); - auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); - if (iter != m_breakpoints.end()) + // Find and disable the hardware breakpoint - need to handle both relative and absolute breakpoints + for (auto& bp : m_breakpoints) { - iter->enabled = false; + if (bp.IsHardware() && bp.type == type && bp.size == size) + { + bool matches = false; + if (bp.isRelative) + { + uint64_t absolute = m_state->GetModules()->RelativeAddressToAbsolute(bp.location); + matches = (absolute == address); + } + else + { + matches = (bp.address == address); + } + + if (matches) + { + bp.enabled = false; + break; + } + } } SerializeMetadata(); From 82d4f38ad3af983e07b0fe6e229511713aab95df Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 12 Dec 2025 16:11:57 +0800 Subject: [PATCH 26/30] Fix hardware breakpoint when it is added during debugging --- core/debuggerstate.cpp | 52 +++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/core/debuggerstate.cpp b/core/debuggerstate.cpp index 95ab464c..d0285621 100644 --- a/core/debuggerstate.cpp +++ b/core/debuggerstate.cpp @@ -747,27 +747,26 @@ bool DebuggerBreakpoints::AddHardwareBreakpoint(uint64_t address, DebugBreakpoin // 1. Add AddHardwareBreakpoint(ModuleNameAndOffset, type, size) overload for symmetry // 2. Create unified BreakpointLocation struct that can represent both relative and absolute addressing // 3. Merge AddBreakpoint and AddHardwareBreakpoint into single API with type parameter - // - // For now: Always add to m_breakpoints first (like software breakpoints do), then try to apply to adapter - // Always add to m_breakpoints first - this allows hardware breakpoints to be added before adapter creation - if (!ContainsHardwareBreakpoint(address, type, size)) - { - // Convert absolute address to module+offset for ASLR-safe storage (like AddAbsolute does for software breakpoints) - ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(address); - BreakpointInfo bp(info, type, size); - bp.address = address; - m_breakpoints.push_back(bp); - SerializeMetadata(); - } + if (ContainsHardwareBreakpoint(address, type, size)) + return true; // Already exists - // Then try to apply to adapter if it exists and is connected + // If adapter is connected, try to add there first - only add to internal storage if successful if (m_state->GetAdapter() && m_state->IsConnected()) { - return m_state->GetAdapter()->AddHardwareBreakpoint(address, type, size); + bool adapterResult = m_state->GetAdapter()->AddHardwareBreakpoint(address, type, size); + if (!adapterResult) + return false; // Adapter failed, don't add to internal storage } - // Success - breakpoint cached in m_breakpoints, will be applied when adapter becomes active + // Add to internal storage (either adapter succeeded, or no adapter connected yet) + // Convert absolute address to module+offset for ASLR-safe storage (like AddAbsolute does for software breakpoints) + ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(address); + BreakpointInfo bp(info, type, size); + bp.address = address; + m_breakpoints.push_back(bp); + SerializeMetadata(); + return true; } @@ -794,6 +793,7 @@ bool DebuggerBreakpoints::RemoveHardwareBreakpoint(uint64_t address, DebugBreakp if (matches) { m_breakpoints.erase(iter); + SerializeMetadata(); break; } } @@ -916,22 +916,22 @@ bool DebuggerBreakpoints::DisableHardwareBreakpoint(uint64_t address, DebugBreak bool DebuggerBreakpoints::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) { - // Always add to m_breakpoints first - this allows hardware breakpoints to be added before adapter creation - if (!ContainsHardwareBreakpoint(location, type, size)) - { - BreakpointInfo bp(location, type, size); // Uses the new constructor for module+offset - m_breakpoints.push_back(bp); - SerializeMetadata(); - } + if (ContainsHardwareBreakpoint(location, type, size)) + return true; // Already exists - // Then try to apply to adapter if it exists and is connected + // If adapter is connected, try to add there first - only add to internal storage if successful if (m_state->GetAdapter() && m_state->IsConnected()) { - // Call adapter with module+offset directly - adapter will handle resolution - return m_state->GetAdapter()->AddHardwareBreakpoint(location, type, size); + bool adapterResult = m_state->GetAdapter()->AddHardwareBreakpoint(location, type, size); + if (!adapterResult) + return false; // Adapter failed, don't add to internal storage } - // Success - breakpoint cached in m_breakpoints, will be applied when adapter becomes active + // Add to internal storage (either adapter succeeded, or no adapter connected yet) + BreakpointInfo bp(location, type, size); // Uses the constructor for module+offset + m_breakpoints.push_back(bp); + SerializeMetadata(); + return true; } From 6ee4668e04e10a5055978485e8d07cfda635cc20 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 12 Dec 2025 16:23:45 +0800 Subject: [PATCH 27/30] Fix hw breakpoint not removed from the breakpoint widget list after they are removed --- api/ffi.h | 10 +-- core/adapters/lldbadapter.cpp | 25 +------ core/adapters/lldbcoredumpadapter.cpp | 25 +------ core/debuggercontroller.cpp | 104 ++++++++------------------ ui/debuggerwidget.cpp | 9 +-- ui/ui.cpp | 20 +---- 6 files changed, 47 insertions(+), 146 deletions(-) diff --git a/api/ffi.h b/api/ffi.h index 7aa924f6..24334060 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -260,14 +260,8 @@ extern "C" TargetExitedEventType, DetachedEventType, - AbsoluteBreakpointAddedEvent, - RelativeBreakpointAddedEvent, - AbsoluteBreakpointRemovedEvent, - RelativeBreakpointRemovedEvent, - AbsoluteBreakpointEnabledEvent, - RelativeBreakpointEnabledEvent, - AbsoluteBreakpointDisabledEvent, - RelativeBreakpointDisabledEvent, + // Unified breakpoint change event - use this for all breakpoint changes (add/remove/enable/disable) + BreakpointChangedEvent, ActiveThreadChangedEvent, diff --git a/core/adapters/lldbadapter.cpp b/core/adapters/lldbadapter.cpp index f7380ad0..ab8bc4d3 100644 --- a/core/adapters/lldbadapter.cpp +++ b/core/adapters/lldbadapter.cpp @@ -2152,22 +2152,14 @@ void LldbAdapter::EventListener() if (module.IsValid()) { SBAddress headerAddress = module.GetObjectFileHeaderAddress(); - uint64_t moduleBase = headerAddress.GetLoadAddress(m_target); - uint64_t bpAddress = location.GetAddress().GetLoadAddress(m_target); - auto fileSpec = module.GetFileSpec(); - char path[1024]; - size_t bytes = fileSpec.GetPath(path, sizeof(path)); DebuggerEvent evt; - evt.type = RelativeBreakpointAddedEvent; - evt.data.relativeAddress.module = std::string(path, bytes); - evt.data.relativeAddress.offset = bpAddress - moduleBase; + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } else { DebuggerEvent evt; - evt.type = AbsoluteBreakpointAddedEvent; - evt.data.absoluteAddress = location.GetAddress().GetLoadAddress(m_target); + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } } @@ -2178,23 +2170,14 @@ void LldbAdapter::EventListener() auto module = address.GetModule(); if (module.IsValid()) { - SBAddress headerAddress = module.GetObjectFileHeaderAddress(); - uint64_t moduleBase = headerAddress.GetLoadAddress(m_target); - uint64_t bpAddress = location.GetAddress().GetLoadAddress(m_target); - auto fileSpec = module.GetFileSpec(); - char path[1024]; - size_t bytes = fileSpec.GetPath(path, sizeof(path)); DebuggerEvent evt; - evt.type = RelativeBreakpointRemovedEvent; - evt.data.relativeAddress.module = std::string(path, bytes); - evt.data.relativeAddress.offset = bpAddress - moduleBase; + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } else { DebuggerEvent evt; - evt.type = AbsoluteBreakpointRemovedEvent; - evt.data.absoluteAddress = location.GetAddress().GetLoadAddress(m_target); + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } } diff --git a/core/adapters/lldbcoredumpadapter.cpp b/core/adapters/lldbcoredumpadapter.cpp index 606ef17e..730cc0ce 100644 --- a/core/adapters/lldbcoredumpadapter.cpp +++ b/core/adapters/lldbcoredumpadapter.cpp @@ -1112,22 +1112,14 @@ void LldbCoreDumpAdapter::EventListener() if (module.IsValid()) { SBAddress headerAddress = module.GetObjectFileHeaderAddress(); - uint64_t moduleBase = headerAddress.GetLoadAddress(m_target); - uint64_t bpAddress = location.GetAddress().GetLoadAddress(m_target); - auto fileSpec = module.GetFileSpec(); - char path[1024]; - size_t bytes = fileSpec.GetPath(path, sizeof(path)); DebuggerEvent evt; - evt.type = RelativeBreakpointAddedEvent; - evt.data.relativeAddress.module = std::string(path, bytes); - evt.data.relativeAddress.offset = bpAddress - moduleBase; + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } else { DebuggerEvent evt; - evt.type = AbsoluteBreakpointAddedEvent; - evt.data.absoluteAddress = location.GetAddress().GetLoadAddress(m_target); + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } } @@ -1138,23 +1130,14 @@ void LldbCoreDumpAdapter::EventListener() auto module = address.GetModule(); if (module.IsValid()) { - SBAddress headerAddress = module.GetObjectFileHeaderAddress(); - uint64_t moduleBase = headerAddress.GetLoadAddress(m_target); - uint64_t bpAddress = location.GetAddress().GetLoadAddress(m_target); - auto fileSpec = module.GetFileSpec(); - char path[1024]; - size_t bytes = fileSpec.GetPath(path, sizeof(path)); DebuggerEvent evt; - evt.type = RelativeBreakpointRemovedEvent; - evt.data.relativeAddress.module = std::string(path, bytes); - evt.data.relativeAddress.offset = bpAddress - moduleBase; + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } else { DebuggerEvent evt; - evt.type = AbsoluteBreakpointRemovedEvent; - evt.data.absoluteAddress = location.GetAddress().GetLoadAddress(m_target); + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } } diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 1f6ff182..8b0bfa6d 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -64,8 +64,7 @@ void DebuggerController::AddBreakpoint(uint64_t address) { m_state->AddBreakpoint(address); DebuggerEvent event; - event.type = AbsoluteBreakpointAddedEvent; - event.data.absoluteAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -74,8 +73,7 @@ void DebuggerController::AddBreakpoint(const ModuleNameAndOffset& address) { m_state->AddBreakpoint(address); DebuggerEvent event; - event.type = RelativeBreakpointAddedEvent; - event.data.relativeAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -84,8 +82,7 @@ void DebuggerController::DeleteBreakpoint(uint64_t address) { m_state->DeleteBreakpoint(address); DebuggerEvent event; - event.type = AbsoluteBreakpointRemovedEvent; - event.data.absoluteAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -94,8 +91,7 @@ void DebuggerController::DeleteBreakpoint(const ModuleNameAndOffset& address) { m_state->DeleteBreakpoint(address); DebuggerEvent event; - event.type = RelativeBreakpointRemovedEvent; - event.data.relativeAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -104,8 +100,7 @@ void DebuggerController::EnableBreakpoint(uint64_t address) { m_state->EnableBreakpoint(address); DebuggerEvent event; - event.type = AbsoluteBreakpointEnabledEvent; - event.data.absoluteAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -114,8 +109,7 @@ void DebuggerController::EnableBreakpoint(const ModuleNameAndOffset& address) { m_state->EnableBreakpoint(address); DebuggerEvent event; - event.type = RelativeBreakpointEnabledEvent; - event.data.relativeAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -124,8 +118,7 @@ void DebuggerController::DisableBreakpoint(uint64_t address) { m_state->DisableBreakpoint(address); DebuggerEvent event; - event.type = AbsoluteBreakpointDisabledEvent; - event.data.absoluteAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -134,8 +127,7 @@ void DebuggerController::DisableBreakpoint(const ModuleNameAndOffset& address) { m_state->DisableBreakpoint(address); DebuggerEvent event; - event.type = RelativeBreakpointDisabledEvent; - event.data.relativeAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -149,13 +141,9 @@ bool DebuggerController::ContainsBreakpoint(const ModuleNameAndOffset& address) bool DebuggerController::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { bool result = m_state->AddHardwareBreakpoint(address, type, size); - if (result) - { - DebuggerEvent event; - event.type = AbsoluteBreakpointAddedEvent; - event.data.absoluteAddress = address; - PostDebuggerEvent(event); - } + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); return result; } @@ -163,13 +151,9 @@ bool DebuggerController::AddHardwareBreakpoint(uint64_t address, DebugBreakpoint bool DebuggerController::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { bool result = m_state->RemoveHardwareBreakpoint(address, type, size); - if (result) - { - DebuggerEvent event; - event.type = AbsoluteBreakpointRemovedEvent; - event.data.absoluteAddress = address; - PostDebuggerEvent(event); - } + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); return result; } @@ -177,13 +161,9 @@ bool DebuggerController::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpo bool DebuggerController::EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { bool result = m_state->EnableHardwareBreakpoint(address, type, size); - if (result) - { - DebuggerEvent event; - event.type = AbsoluteBreakpointEnabledEvent; - event.data.absoluteAddress = address; - PostDebuggerEvent(event); - } + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); return result; } @@ -191,13 +171,9 @@ bool DebuggerController::EnableHardwareBreakpoint(uint64_t address, DebugBreakpo bool DebuggerController::DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { bool result = m_state->DisableHardwareBreakpoint(address, type, size); - if (result) - { - DebuggerEvent event; - event.type = AbsoluteBreakpointDisabledEvent; - event.data.absoluteAddress = address; - PostDebuggerEvent(event); - } + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); return result; } @@ -207,13 +183,9 @@ bool DebuggerController::DisableHardwareBreakpoint(uint64_t address, DebugBreakp bool DebuggerController::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) { bool result = m_state->AddHardwareBreakpoint(location, type, size); - if (result) - { - DebuggerEvent event; - event.type = RelativeBreakpointAddedEvent; - event.data.relativeAddress = location; - PostDebuggerEvent(event); - } + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); return result; } @@ -221,13 +193,9 @@ bool DebuggerController::AddHardwareBreakpoint(const ModuleNameAndOffset& locati bool DebuggerController::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) { bool result = m_state->RemoveHardwareBreakpoint(location, type, size); - if (result) - { - DebuggerEvent event; - event.type = RelativeBreakpointRemovedEvent; - event.data.relativeAddress = location; - PostDebuggerEvent(event); - } + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); return result; } @@ -235,13 +203,9 @@ bool DebuggerController::RemoveHardwareBreakpoint(const ModuleNameAndOffset& loc bool DebuggerController::EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) { bool result = m_state->EnableHardwareBreakpoint(location, type, size); - if (result) - { - DebuggerEvent event; - event.type = RelativeBreakpointEnabledEvent; - event.data.relativeAddress = location; - PostDebuggerEvent(event); - } + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); return result; } @@ -249,13 +213,9 @@ bool DebuggerController::EnableHardwareBreakpoint(const ModuleNameAndOffset& loc bool DebuggerController::DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) { bool result = m_state->DisableHardwareBreakpoint(location, type, size); - if (result) - { - DebuggerEvent event; - event.type = RelativeBreakpointDisabledEvent; - event.data.relativeAddress = location; - PostDebuggerEvent(event); - } + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); return result; } diff --git a/ui/debuggerwidget.cpp b/ui/debuggerwidget.cpp index f2cf4d3e..1c90439b 100644 --- a/ui/debuggerwidget.cpp +++ b/ui/debuggerwidget.cpp @@ -127,14 +127,7 @@ void DebuggerWidget::uiEventHandler(const DebuggerEvent& event) case RegisterChangedEvent: updateContent(); break; - case RelativeBreakpointAddedEvent: - case AbsoluteBreakpointAddedEvent: - case RelativeBreakpointRemovedEvent: - case AbsoluteBreakpointRemovedEvent: - case AbsoluteBreakpointEnabledEvent: - case RelativeBreakpointEnabledEvent: - case AbsoluteBreakpointDisabledEvent: - case RelativeBreakpointDisabledEvent: + case BreakpointChangedEvent: m_breakpointsWidget->updateContent(); break; default: diff --git a/ui/ui.cpp b/ui/ui.cpp index ca8de986..e38e8bdd 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -1417,14 +1417,9 @@ DebuggerUI::DebuggerUI(UIContext* context, DebuggerControllerRef controller) : // Since the Controller is constructed earlier than the UI, any breakpoints added before the construction of the UI, // e.g. the entry point breakpoint, will be missing the visual indicator. // Here, we forcibly add them. - for (auto bp : m_controller->GetBreakpoints()) - { - DebuggerEvent event; - event.type = RelativeBreakpointAddedEvent; - event.data.relativeAddress.module = bp.module; - event.data.relativeAddress.offset = bp.offset; - updateUI(event); - } + DebuggerEvent event; + event.type = BreakpointChangedEvent; + updateUI(event); m_uiCallbacks = new DebuggerUICallbacks; m_uiCallbacks->rebaseBinaryViewImpl = [&](uint64_t address) @@ -1751,14 +1746,7 @@ void DebuggerUI::updateUI(const DebuggerEvent& event) break; } - case RelativeBreakpointAddedEvent: - case AbsoluteBreakpointAddedEvent: - case RelativeBreakpointRemovedEvent: - case AbsoluteBreakpointRemovedEvent: - case RelativeBreakpointEnabledEvent: - case AbsoluteBreakpointEnabledEvent: - case RelativeBreakpointDisabledEvent: - case AbsoluteBreakpointDisabledEvent: + case BreakpointChangedEvent: { m_context->refreshCurrentViewContents(); break; From 02db70e64fb80f08551b309aadb802c4912dc119 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 19 Dec 2025 16:45:03 +0800 Subject: [PATCH 28/30] Add hardware breakpoint for GDB MI adapter --- core/adapters/gdbmiadapter.cpp | 175 +++++++++++++++++++++++++++++++-- core/adapters/gdbmiadapter.h | 3 + 2 files changed, 172 insertions(+), 6 deletions(-) diff --git a/core/adapters/gdbmiadapter.cpp b/core/adapters/gdbmiadapter.cpp index eff58590..5c47c820 100644 --- a/core/adapters/gdbmiadapter.cpp +++ b/core/adapters/gdbmiadapter.cpp @@ -301,6 +301,8 @@ void GdbMiAdapter::ScheduleStateRefresh() UpdateThreadList(); UpdateAllRegisters(); UpdateStackFrames(m_currentTid); + // Apply any pending hardware breakpoints that were added while target was running + ApplyPendingHardwareBreakpoints(); } DebuggerEvent ev; @@ -544,6 +546,7 @@ bool GdbMiAdapter::Connect(const std::string& server, uint32_t port) { LogInfo("Applying breakpoints..."); ApplyBreakpoints(); + ApplyPendingHardwareBreakpoints(); return true; } @@ -1085,28 +1088,188 @@ bool GdbMiAdapter::SupportFeature(DebugAdapterCapacity feature) { bool GdbMiAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { - // Hardware breakpoints are not yet implemented for GDB MI adapter + if (m_targetRunningAtomic || !m_mi) + { + // Cache the hardware breakpoint to be applied when target stops or connector becomes available + PendingHardwareBreakpoint pending(address, type, size); + if (std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending) + == m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.push_back(pending); + } + return true; + } + + std::string command; + switch (type) + { + case HardwareExecuteBreakpoint: + // Hardware execution breakpoint: -break-insert -h *0xADDRESS + command = fmt::format("-break-insert -h *0x{:x}", address); + break; + case HardwareReadBreakpoint: + // Hardware read watchpoint: -break-watch -r *((char[SIZE]*)0xADDRESS) + command = fmt::format("-break-watch -r *((char[{}]*)0x{:x})", size, address); + break; + case HardwareWriteBreakpoint: + // Hardware write watchpoint: -break-watch *((char[SIZE]*)0xADDRESS) + command = fmt::format("-break-watch *((char[{}]*)0x{:x})", size, address); + break; + case HardwareAccessBreakpoint: + // Hardware access watchpoint: -break-watch -a *((char[SIZE]*)0xADDRESS) + command = fmt::format("-break-watch -a *((char[{}]*)0x{:x})", size, address); + break; + default: + return false; + } + + LogDebug("GdbMiAdapter: %s", command.c_str()); + auto result = m_mi->SendCommand(command); + if (result.command == "done") + { + DebuggerEvent evt; + evt.type = BackendMessageEventType; + evt.data.messageData.message = result.payload; + PostDebuggerEvent(evt); + return true; + } + + LogWarn("Failed to set hardware breakpoint at 0x%" PRIx64 ": %s", address, result.fullLine.c_str()); return false; } bool GdbMiAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { - // Hardware breakpoints are not yet implemented for GDB MI adapter - return false; + if (m_targetRunningAtomic || !m_mi) + { + // Remove from pending list if target is running or connector not available + PendingHardwareBreakpoint pending(address, type, size); + auto it = std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending); + if (it != m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.erase(it); + return true; + } + return false; + } + + // Get breakpoint list and find matching hardware breakpoint by address + auto breakpoints = GetBreakpointList(); + int removed = 0; + for (const auto& bp : breakpoints) + { + if (bp.m_address == address) + { + auto result = m_mi->SendCommand(fmt::format("-break-delete {}", bp.m_id)); + if (result.command == "done") + { + DebuggerEvent evt; + evt.type = BackendMessageEventType; + evt.data.messageData.message = result.payload; + PostDebuggerEvent(evt); + removed++; + } + } + } + + if (removed == 0) + { + LogWarn("Failed to remove hardware breakpoint at 0x%" PRIx64, address); + return false; + } + return true; } bool GdbMiAdapter::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) { - // Hardware breakpoints are not yet implemented for GDB MI adapter - return false; + uint64_t base{}; + if (GetModuleBase(location.module, base)) + { + // Module is loaded - resolve to absolute address and delegate + uint64_t address = base + location.offset; + return AddHardwareBreakpoint(address, type, size); + } + else + { + // Module not loaded yet - add to pending list with module+offset + PendingHardwareBreakpoint pending(location, type, size); + // Also populate the address field for UI display purposes + pending.address = location.offset + m_originalImageBase; + if (std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending) + == m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.push_back(pending); + } + return true; + } } bool GdbMiAdapter::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) { - // Hardware breakpoints are not yet implemented for GDB MI adapter + uint64_t base{}; + if (GetModuleBase(location.module, base)) + { + // Module is loaded - resolve to absolute address and delegate + uint64_t address = base + location.offset; + return RemoveHardwareBreakpoint(address, type, size); + } + else + { + // Module not loaded yet - remove from pending list using module+offset + PendingHardwareBreakpoint pending(location, type, size); + auto it = std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending); + if (it != m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.erase(it); + return true; + } + return false; + } +} + +bool GdbMiAdapter::GetModuleBase(const std::string& moduleName, uint64_t& base) +{ + if (moduleName.empty()) + { + base = 0; + return true; + } + + auto modules = GetModuleList(); + for (const auto& module : modules) + { + if (module.IsSameBaseModule(moduleName)) + { + base = module.m_address; + return true; + } + } + + base = 0; return false; } +void GdbMiAdapter::ApplyPendingHardwareBreakpoints() +{ + // Apply pending hardware breakpoints that were added before the target stopped + std::vector pendingCopy = m_pendingHardwareBreakpoints; + m_pendingHardwareBreakpoints.clear(); + + for (const auto& pending : pendingCopy) + { + if (pending.isRelative) + { + // Module+offset based hardware breakpoint + AddHardwareBreakpoint(pending.location, pending.type, pending.size); + } + else + { + // Absolute address hardware breakpoint + AddHardwareBreakpoint(pending.address, pending.type, pending.size); + } + } +} + // --- Adapter Type Registration --- GdbMiAdapterType::GdbMiAdapterType() : DebugAdapterType("GDB MI") {} diff --git a/core/adapters/gdbmiadapter.h b/core/adapters/gdbmiadapter.h index 37d53d7f..7fdcabfb 100644 --- a/core/adapters/gdbmiadapter.h +++ b/core/adapters/gdbmiadapter.h @@ -47,7 +47,10 @@ class GdbMiAdapter : public BinaryNinjaDebugger::DebugAdapter bool RunMonitorCommand(const std::string& command) const; void ApplyBreakpoints(); + void ApplyPendingHardwareBreakpoints(); + bool GetModuleBase(const std::string& moduleName, uint64_t& base); std::vector m_pendingBreakpoints {}; + std::vector m_pendingHardwareBreakpoints {}; public: GdbMiAdapter(BinaryView* data); From 4fec779532e2a5d39ea11d2bb8510d2e44b672b9 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Mon, 22 Dec 2025 15:13:44 +0800 Subject: [PATCH 29/30] Add hardware breakpoint for GDB MI adapter --- core/adapters/gdbmiadapter.cpp | 36 ++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/core/adapters/gdbmiadapter.cpp b/core/adapters/gdbmiadapter.cpp index 5c47c820..68a3ff38 100644 --- a/core/adapters/gdbmiadapter.cpp +++ b/core/adapters/gdbmiadapter.cpp @@ -301,7 +301,9 @@ void GdbMiAdapter::ScheduleStateRefresh() UpdateThreadList(); UpdateAllRegisters(); UpdateStackFrames(m_currentTid); - // Apply any pending hardware breakpoints that were added while target was running + // Apply any pending breakpoints that were added while target was running + // or couldn't be resolved earlier (modules not loaded yet) + ApplyBreakpoints(); ApplyPendingHardwareBreakpoints(); } @@ -722,17 +724,27 @@ DebugBreakpoint GdbMiAdapter::AddBreakpoint(std::uintptr_t address, unsigned lon DebugBreakpoint GdbMiAdapter::AddBreakpoint(const ModuleNameAndOffset& address, unsigned long breakpoint_type) { if (!m_mi) { + // Not connected yet - add to pending list if (std::ranges::find(m_pendingBreakpoints, address) == m_pendingBreakpoints.end()) m_pendingBreakpoints.push_back(address); + return {}; } - else - { - uint64_t addr = address.offset + m_originalImageBase; - - AddBreakpoint(addr, breakpoint_type); - } - return {}; + // Try to resolve the module base address + uint64_t base{}; + if (GetModuleBase(address.module, base)) + { + // Module is loaded - resolve to absolute address + uint64_t addr = base + address.offset; + return AddBreakpoint(addr, breakpoint_type); + } + else + { + // Module not loaded yet - add to pending list for deferred application + if (std::ranges::find(m_pendingBreakpoints, address) == m_pendingBreakpoints.end()) + m_pendingBreakpoints.push_back(address); + return {}; + } } bool GdbMiAdapter::RemoveBreakpoint(const DebugBreakpoint& breakpoint) { @@ -1349,11 +1361,15 @@ Ref GdbMiAdapterType::RegisterAdapterSettings() void GdbMiAdapter::ApplyBreakpoints() { - for (const auto& bp : m_pendingBreakpoints) + // Make a copy and clear the original list - AddBreakpoint will re-add + // any breakpoints that can't be resolved yet + std::vector pendingCopy = m_pendingBreakpoints; + m_pendingBreakpoints.clear(); + + for (const auto& bp : pendingCopy) { AddBreakpoint(bp, 0); } - m_pendingBreakpoints.clear(); } From db33a5cf1ac81458f62056cc81449d0bb0669e7b Mon Sep 17 00:00:00 2001 From: Xusheng Date: Mon, 22 Dec 2025 17:05:08 +0800 Subject: [PATCH 30/30] Fix hardware breakpoint for dbgeng adapter --- core/adapters/dbgengadapter.cpp | 64 ++++++++++++++++++++++++++++++--- core/adapters/dbgengadapter.h | 2 ++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/core/adapters/dbgengadapter.cpp b/core/adapters/dbgengadapter.cpp index 389c967e..10373c32 100644 --- a/core/adapters/dbgengadapter.cpp +++ b/core/adapters/dbgengadapter.cpp @@ -667,6 +667,27 @@ void DbgEngAdapter::EngineLoop() { if (m_lastExecutionStatus != DEBUG_STATUS_BREAK) { + // Apply deferred hardware breakpoints on first stop + // See ApplyBreakpoints() for detailed explanation of why this is necessary + if (m_needsHardwareBreakpointReapplication) + { + for (const auto& hwbp : m_deferredHardwareBreakpoints) + { + // Apply the hardware breakpoint now that process is running and stopped + // Check addressing mode and call appropriate variant + if (hwbp.isRelative) + { + AddHardwareBreakpoint(hwbp.location, hwbp.type, hwbp.size); + } + else + { + AddHardwareBreakpoint(hwbp.address, hwbp.type, hwbp.size); + } + } + m_deferredHardwareBreakpoints.clear(); + m_needsHardwareBreakpointReapplication = false; + } + if (outputStateOnStop) { // m_debugRegisters->OutputRegisters(DEBUG_OUTCTL_THIS_CLIENT, DEBUG_REGISTERS_DEFAULT); @@ -1225,6 +1246,17 @@ bool DbgEngAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointTy return false; } + // Also check deferred list (hardware breakpoints waiting for first stop) + PendingHardwareBreakpoint pending(address, type, size); + auto deferredIt = std::find(m_deferredHardwareBreakpoints.begin(), m_deferredHardwareBreakpoints.end(), pending); + if (deferredIt != m_deferredHardwareBreakpoints.end()) + { + m_deferredHardwareBreakpoints.erase(deferredIt); + if (m_deferredHardwareBreakpoints.empty()) + m_needsHardwareBreakpointReapplication = false; + return true; + } + // List all breakpoints to find the ID of the hardware breakpoint at this address auto result = InvokeBackendCommand("bl"); @@ -1335,6 +1367,19 @@ bool DbgEngAdapter::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location // DbgEng doesn't provide a direct way to remove by module+offset if (m_dbgengInitialized) { + // First check deferred list (hardware breakpoints waiting for first stop) + // This needs to be checked before resolving to address because deferred entries + // use isRelative=true and won't be found by address-based lookup + PendingHardwareBreakpoint pending(location, type, size); + auto deferredIt = std::find(m_deferredHardwareBreakpoints.begin(), m_deferredHardwareBreakpoints.end(), pending); + if (deferredIt != m_deferredHardwareBreakpoints.end()) + { + m_deferredHardwareBreakpoints.erase(deferredIt); + if (m_deferredHardwareBreakpoints.empty()) + m_needsHardwareBreakpointReapplication = false; + return true; + } + // Get module base and resolve to absolute address auto modules = GetModuleList(); uint64_t base = 0; @@ -1377,12 +1422,23 @@ void DbgEngAdapter::ApplyBreakpoints() } m_pendingBreakpoints.clear(); - // Apply pending hardware breakpoints - for (const auto& hwbp : m_pendingHardwareBreakpoints) + // DEFER hardware breakpoints instead of applying now + // + // Hardware breakpoints use CPU debug registers (DR0-DR3) which are part of the thread context. + // At the system entry point (ntdll!LdrInitializeThunk), the process is in early initialization + // and the debug registers may not be properly accessible or may get overwritten during loader + // initialization. Hardware breakpoints work reliably once the process is fully initialized + // (at the program entry point or later). + // + // Move hardware breakpoints to deferred list instead of applying now + m_deferredHardwareBreakpoints = std::move(m_pendingHardwareBreakpoints); + m_pendingHardwareBreakpoints.clear(); + + // Set flag to apply deferred hardware breakpoints on first stop in EngineLoop + if (!m_deferredHardwareBreakpoints.empty()) { - AddHardwareBreakpoint(hwbp.address, hwbp.type, hwbp.size); + m_needsHardwareBreakpointReapplication = true; } - m_pendingHardwareBreakpoints.clear(); } DebugRegister DbgEngAdapter::ReadRegister(const std::string& reg) diff --git a/core/adapters/dbgengadapter.h b/core/adapters/dbgengadapter.h index e429629e..2a06492a 100644 --- a/core/adapters/dbgengadapter.h +++ b/core/adapters/dbgengadapter.h @@ -146,6 +146,8 @@ namespace BinaryNinjaDebugger { std::vector m_pendingBreakpoints {}; std::vector m_pendingHardwareBreakpoints {}; + std::vector m_deferredHardwareBreakpoints {}; + bool m_needsHardwareBreakpointReapplication = false; ULONG64 m_server {}; bool m_connectedToDebugServer = false;