diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 3647be44..ebfdde5a 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -358,6 +358,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 DebugBreakpoint { std::string module; @@ -365,6 +376,8 @@ namespace BinaryNinjaDebuggerAPI { uint64_t address; bool enabled; std::string condition; + DebugBreakpointType type = SoftwareBreakpoint; + size_t size = 1; // Size in bytes for hardware breakpoints/watchpoints (1, 2, 4, 8) }; @@ -735,6 +748,18 @@ namespace BinaryNinjaDebuggerAPI { std::string GetBreakpointCondition(uint64_t address); std::string GetBreakpointCondition(const ModuleNameAndOffset& address); + // 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 ca95854c..d4c5173b 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -733,6 +733,8 @@ std::vector DebuggerController::GetBreakpoints() bp.address = breakpoints[i].address; bp.enabled = breakpoints[i].enabled; bp.condition = breakpoints[i].condition ? breakpoints[i].condition : ""; + bp.type = (DebugBreakpointType)breakpoints[i].type; + bp.size = breakpoints[i].size; result[i] = bp; } @@ -831,6 +833,56 @@ std::string DebuggerController::GetBreakpointCondition(const ModuleNameAndOffset } +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); +} + + +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); +} + + +// 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 2c836228..ebc23813 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -126,6 +126,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 @@ -134,6 +144,8 @@ extern "C" uint64_t address; bool enabled; char* condition; // NULL if no condition + BNDebugBreakpointType type; + size_t size; // Size in bytes for hardware breakpoints/watchpoints (1, 2, 4, 8) } BNDebugBreakpoint; @@ -250,16 +262,8 @@ extern "C" TargetExitedEventType, DetachedEventType, - AbsoluteBreakpointAddedEvent, - RelativeBreakpointAddedEvent, - AbsoluteBreakpointRemovedEvent, - RelativeBreakpointRemovedEvent, - AbsoluteBreakpointEnabledEvent, - RelativeBreakpointEnabledEvent, - AbsoluteBreakpointDisabledEvent, - RelativeBreakpointDisabledEvent, - AbsoluteBreakpointConditionChangedEvent, - RelativeBreakpointConditionChangedEvent, + // Unified breakpoint change event - use this for all breakpoint changes (add/remove/enable/disable) + BreakpointChangedEvent, ActiveThreadChangedEvent, @@ -610,6 +614,26 @@ extern "C" DEBUGGER_FFI_API char* BNDebuggerGetBreakpointConditionRelative( 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 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); + + // 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/api/python/debuggercontroller.py b/api/python/debuggercontroller.py index dd4ee243..b24d8d77 100644 --- a/api/python/debuggercontroller.py +++ b/api/python/debuggercontroller.py @@ -15,6 +15,7 @@ import ctypes import traceback +from dataclasses import dataclass import binaryninja # import debugger @@ -362,6 +363,7 @@ def __iter__(self): return iter(self.breakpoints) +@dataclass(frozen=True) class DebugBreakpoint: """ DebugBreakpoint represents a breakpoint in the target. It has the following fields: @@ -371,39 +373,37 @@ class DebugBreakpoint: * ``address``: the absolute address of the breakpoint * ``enabled``: whether the breakpoint is enabled (read-only) * ``condition``: the condition expression for the breakpoint (empty if no condition) + * ``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, condition=""): - self.module = module - self.offset = offset - self.address = address - self.enabled = enabled - self.condition = condition - - 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 - - def __ne__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - return not (self == other) - - def __hash__(self): - return hash((self.module, self.offset, self.address, self.enabled)) - - def __setattr__(self, name, value): - try: - object.__setattr__(self, name, value) - except AttributeError: - raise AttributeError(f"attribute '{name}' is read only") + module: str + offset: int + address: int + enabled: bool + condition: str = "" + type: DebugBreakpointType = DebugBreakpointType.BNSoftwareBreakpoint + size: int = 1 def __repr__(self): status = "enabled" if self.enabled else "disabled" cond_str = f", condition='{self.condition}'" if self.condition else "" - 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: @@ -2059,7 +2059,8 @@ def breakpoints(self) -> DebugBreakpoints: result = [] for i in range(0, count.value): condition = breakpoints[i].condition if breakpoints[i].condition else "" - bp = DebugBreakpoint(breakpoints[i].module, breakpoints[i].offset, breakpoints[i].address, breakpoints[i].enabled, condition) + bp = DebugBreakpoint(breakpoints[i].module, breakpoints[i].offset, breakpoints[i].address, + breakpoints[i].enabled, condition, breakpoints[i].type, breakpoints[i].size) result.append(bp) dbgcore.BNDebuggerFreeBreakpoints(breakpoints, count.value) @@ -2097,6 +2098,46 @@ def add_breakpoint(self, address): else: raise NotImplementedError + def add_hardware_breakpoint(self, address, bp_type: DebugBreakpointType, size: int = 1) -> bool: + """ + Add a hardware breakpoint + + The input can be either an absolute address, or a ModuleNameAndOffset, which specifies a relative address to the + start of a module. The latter is useful for ASLR. + + :param address: the address of breakpoint to add + :param bp_type: the type of hardware breakpoint (DebugBreakpointType.BNHardwareExecuteBreakpoint, + BNHardwareReadBreakpoint, BNHardwareWriteBreakpoint, or BNHardwareAccessBreakpoint) + :param size: the size in bytes for the watchpoint (1, 2, 4, or 8) + :return: True if successful, False otherwise + """ + if isinstance(address, int): + return dbgcore.BNDebuggerAddHardwareBreakpoint(self.handle, address, bp_type, size) + elif isinstance(address, ModuleNameAndOffset): + return dbgcore.BNDebuggerAddRelativeHardwareBreakpoint(self.handle, address.module, address.offset, bp_type, size) + else: + raise NotImplementedError + + def delete_hardware_breakpoint(self, address, bp_type: DebugBreakpointType, size: int = 1) -> bool: + """ + Delete a hardware breakpoint + + The input can be either an absolute address, or a ModuleNameAndOffset, which specifies a relative address to the + start of a module. The latter is useful for ASLR. + + :param address: the address of breakpoint to delete + :param bp_type: the type of hardware breakpoint (DebugBreakpointType.BNHardwareExecuteBreakpoint, + BNHardwareReadBreakpoint, BNHardwareWriteBreakpoint, or BNHardwareAccessBreakpoint) + :param size: the size in bytes for the watchpoint (1, 2, 4, or 8) + :return: True if successful, False otherwise + """ + if isinstance(address, int): + return dbgcore.BNDebuggerRemoveHardwareBreakpoint(self.handle, address, bp_type, size) + elif isinstance(address, ModuleNameAndOffset): + return dbgcore.BNDebuggerRemoveRelativeHardwareBreakpoint(self.handle, address.module, address.offset, bp_type, size) + else: + raise NotImplementedError + def has_breakpoint(self, address) -> bool: """ Checks whether a breakpoint exists at the specified address diff --git a/cli/main.cpp b/cli/main.cpp index 0afc4d47..142a73a9 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/corelliumadapter.cpp b/core/adapters/corelliumadapter.cpp index d2e5b758..3e646e14 100644 --- a/core/adapters/corelliumadapter.cpp +++ b/core/adapters/corelliumadapter.cpp @@ -230,6 +230,9 @@ bool CorelliumAdapter::Connect(const std::string& server, std::uint32_t port) this->m_processPid = (uint32_t)map["thread"]; m_isTargetRunning = false; + // Apply any pending breakpoints that were added before connecting + CheckApplyPendingBreakpoints(); + Ref settings = Settings::Instance(); if (settings->Get("debugger.corellium.prefetch_reg_bytes")) { @@ -956,6 +959,163 @@ bool CorelliumAdapter::SupportFeature(DebugAdapterCapacity feature) } +bool CorelliumAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + if (m_isTargetRunning || !m_rspConnector) + return false; + + 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 CorelliumAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + if (m_isTargetRunning || !m_rspConnector) + return false; + + 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 CorelliumAdapter::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + uint64_t base{}; + if (GetModuleBase(location.module, base)) + { + uint64_t address = base + location.offset; + return AddHardwareBreakpoint(address, type, size); + } + return false; +} + + +bool CorelliumAdapter::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + uint64_t base{}; + if (GetModuleBase(location.module, base)) + { + uint64_t address = base + location.offset; + return RemoveHardwareBreakpoint(address, type, size); + } + return false; +} + + +bool CorelliumAdapter::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 CorelliumAdapter::CheckApplyPendingBreakpoints() +{ + // Apply pending software breakpoints + for (auto it = m_pendingBreakpoints.begin(); it != m_pendingBreakpoints.end(); ) + { + uint64_t base{}; + if (GetModuleBase(it->address.module, base)) + { + uint64_t addr = base + it->address.offset; + // TODO: more robust check of whether the operation succeeds + if (AddBreakpoint(addr, it->type).m_address != 0) + { + it = m_pendingBreakpoints.erase(it); + continue; + } + } + it++; + } + + // Apply pending hardware breakpoints + for (auto it = m_pendingHardwareBreakpoints.begin(); it != m_pendingHardwareBreakpoints.end(); ) + { + bool success = false; + if (it->isRelative) + { + // Module+offset based hardware breakpoint + success = AddHardwareBreakpoint(it->location, it->type, it->size); + } + else + { + // Absolute address hardware breakpoint + success = AddHardwareBreakpoint(it->address, it->type, it->size); + } + + if (success) + { + it = m_pendingHardwareBreakpoints.erase(it); + } + else + { + it++; + } + } +} + + void CorelliumAdapter::InvalidateCache() { m_regCache.reset(); @@ -1051,7 +1211,17 @@ bool CorelliumAdapter::ResumeThread(std::uint32_t tid) DebugBreakpoint CorelliumAdapter::AddBreakpoint(const ModuleNameAndOffset& address, unsigned long breakpoint_type) { - return {}; + uint64_t base{}; + if (GetModuleBase(address.module, base)) + { + auto addr = base + address.offset; + return AddBreakpoint(addr, breakpoint_type); + } + else + { + m_pendingBreakpoints.emplace_back(address, breakpoint_type); + return {}; + } } diff --git a/core/adapters/corelliumadapter.h b/core/adapters/corelliumadapter.h index b37001a6..1574e2a1 100644 --- a/core/adapters/corelliumadapter.h +++ b/core/adapters/corelliumadapter.h @@ -34,6 +34,14 @@ namespace BinaryNinjaDebugger std::uint32_t m_offset{}; }; + struct PendingBreakpoint + { + ModuleNameAndOffset address; + unsigned long type; + + PendingBreakpoint(ModuleNameAndOffset address, unsigned long type) : address(address), type(type) {} + }; + DebugStopReason m_lastStopReason{}; using register_pair = std::pair; @@ -47,6 +55,9 @@ namespace BinaryNinjaDebugger std::uint32_t m_internalBreakpointId{}; std::vector m_debugBreakpoints{}; + std::vector m_pendingBreakpoints {}; + std::vector m_pendingHardwareBreakpoints {}; + std::uint32_t m_lastActiveThreadId{}; std::uint32_t m_processPid{}; uint8_t m_exitCode{}; @@ -69,6 +80,9 @@ namespace BinaryNinjaDebugger virtual DebugStopReason SignalToStopReason(std::unordered_map& map); + void CheckApplyPendingBreakpoints(); + void ClearCachedBreakpoints() { m_debugBreakpoints.clear(); } + bool m_prefetchRegBytes = false; bool m_prefetchStackBytes = false; @@ -133,6 +147,13 @@ namespace BinaryNinjaDebugger bool ResumeThread(std::uint32_t tid) override; DebugBreakpoint AddBreakpoint(const ModuleNameAndOffset& address, unsigned long breakpoint_type = 0) override; + // Hardware breakpoint 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; + bool GetModuleBase(const std::string& moduleName, uint64_t& base); + void GenerateDefaultAdapterSettings(BinaryView* data); Ref GetAdapterSettings() override; }; diff --git a/core/adapters/dbgengadapter.cpp b/core/adapters/dbgengadapter.cpp index 6ed9e660..2b13b64b 100644 --- a/core/adapters/dbgengadapter.cpp +++ b/core/adapters/dbgengadapter.cpp @@ -669,6 +669,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); @@ -1170,10 +1191,10 @@ DebugBreakpoint DbgEngAdapter::AddBreakpoint(const std::uintptr_t address, unsig if (debug_breakpoint->SetOffset(address) != S_OK) return {}; - if (debug_breakpoint->SetFlags(DEBUG_BREAKPOINT_ENABLED | breakpoint_flags) != S_OK) + if (debug_breakpoint->SetFlags(DEBUG_BREAKPOINT_ENABLED) != 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; @@ -1257,6 +1278,14 @@ bool DbgEngAdapter::RemoveBreakpoint(const DebugBreakpoint& breakpoint) // Once the ID info is also valid, we can call GetBreakpointById2() to get the breakpoint by ID. if (address == breakpoint.m_address) { + // Verify this is a software breakpoint (DEBUG_BREAKPOINT_CODE) before removing + ULONG breakType {}, procType {}; + if (bp->GetType(&breakType, &procType) != S_OK) + continue; + + if (breakType != DEBUG_BREAKPOINT_CODE) + continue; + m_debugControl->RemoveBreakpoint2(bp); done = true; break; @@ -1289,13 +1318,255 @@ std::vector DbgEngAdapter::GetBreakpointList() const return {}; } + +bool DbgEngAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + if (!m_dbgengInitialized) + { + // 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) + { + 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) + { + // 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; + } + + // 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; + } + + // Iterate through all breakpoints to find the hardware breakpoint at this address + bool done = false; + ULONG numBreakpoints {}; + if (m_debugControl->GetNumberBreakpoints(&numBreakpoints) != S_OK) + return false; + + for (ULONG i = 0; i < numBreakpoints; i++) + { + IDebugBreakpoint2* bp {}; + if (m_debugControl->GetBreakpointByIndex2(i, &bp) != S_OK) + continue; + + ULONG64 bpAddress {}; + if (bp->GetOffset(&bpAddress) != S_OK) + continue; + + if (bpAddress == address) + { + // Verify this is a hardware breakpoint (DEBUG_BREAKPOINT_DATA) before removing + ULONG breakType {}, procType {}; + if (bp->GetType(&breakType, &procType) != S_OK) + continue; + + if (breakType != DEBUG_BREAKPOINT_DATA) + continue; + + m_debugControl->RemoveBreakpoint2(bp); + done = true; + break; + } + } + + return done; +} + + +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) + { + // 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; + 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 for (const auto bp : m_pendingBreakpoints) { AddBreakpoint(bp); } m_pendingBreakpoints.clear(); + + // 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()) + { + m_needsHardwareBreakpointReapplication = true; + } } DebugRegister DbgEngAdapter::ReadRegister(const std::string& reg) diff --git a/core/adapters/dbgengadapter.h b/core/adapters/dbgengadapter.h index f96cd43c..10402f06 100644 --- a/core/adapters/dbgengadapter.h +++ b/core/adapters/dbgengadapter.h @@ -145,6 +145,9 @@ namespace BinaryNinjaDebugger { unsigned long m_exitCode {}; std::vector m_pendingBreakpoints {}; + std::vector m_pendingHardwareBreakpoints {}; + std::vector m_deferredHardwareBreakpoints {}; + bool m_needsHardwareBreakpointReapplication = false; ULONG64 m_server {}; bool m_connectedToDebugServer = false; @@ -200,6 +203,12 @@ 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; + 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; DebugRegister ReadRegister(const std::string& reg) override; diff --git a/core/adapters/esrevenadapter.cpp b/core/adapters/esrevenadapter.cpp index 2c55a648..c0f51a85 100644 --- a/core/adapters/esrevenadapter.cpp +++ b/core/adapters/esrevenadapter.cpp @@ -262,6 +262,9 @@ bool EsrevenAdapter::Connect(const std::string& server, std::uint32_t port) this->m_processPid = (uint32_t)map["thread"]; m_isTargetRunning = false; + // Apply any pending breakpoints that were added before connecting + CheckApplyPendingBreakpoints(); + if (Settings::Instance()->Get("debugger.stopAtEntryPoint") && m_hasEntryFunction) AddBreakpoint(ModuleNameAndOffset(inputFile, m_entryPoint - m_start)); @@ -422,7 +425,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; @@ -1199,20 +1202,148 @@ 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; + { + // 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: + // 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 this->m_rspConnector->TransmitAndReceive(RspData("Z2,{:x},{}", address, 1)).AsString() == "OK"; + 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) + { + // 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) + { + 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 this->m_rspConnector->TransmitAndReceive(RspData("z2,{:x},{}", address, 1)).AsString() == "OK"; + return m_rspConnector->TransmitAndReceive(RspData(command)).AsString() == "OK"; +} + + +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 + return AddHardwareBreakpoint(address, HardwareWriteBreakpoint, 1); +} + +bool EsrevenAdapter::RemoveHardwareWriteBreakpoint(uint64_t address) +{ + // Delegate to new standardized method + return RemoveHardwareBreakpoint(address, HardwareWriteBreakpoint, 1); } bool EsrevenAdapter::StepReturnReverse() @@ -1720,6 +1851,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{}; @@ -1735,6 +1867,31 @@ void EsrevenAdapter::CheckApplyPendingBreakpoints() } it++; } + + // Apply pending hardware breakpoints + for (auto it = m_pendingHardwareBreakpoints.begin(); it != m_pendingHardwareBreakpoints.end(); ) + { + bool success = false; + if (it->isRelative) + { + // Module+offset based hardware breakpoint + success = AddHardwareBreakpoint(it->location, it->type, it->size); + } + else + { + // Absolute address hardware breakpoint + success = AddHardwareBreakpoint(it->address, it->type, it->size); + } + + if (success) + { + it = m_pendingHardwareBreakpoints.erase(it); + } + else + { + it++; + } + } } diff --git a/core/adapters/esrevenadapter.h b/core/adapters/esrevenadapter.h index 432a9772..b702bfe5 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{}; @@ -158,7 +159,13 @@ 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; + 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); bool RemoveHardwareWriteBreakpoint(uint64_t address); diff --git a/core/adapters/gdbadapter.cpp b/core/adapters/gdbadapter.cpp index 86b86d79..c362a8ca 100644 --- a/core/adapters/gdbadapter.cpp +++ b/core/adapters/gdbadapter.cpp @@ -262,6 +262,9 @@ bool GdbAdapter::Connect(const std::string& server, std::uint32_t port) this->m_processPid = (uint32_t)map["thread"]; m_isTargetRunning = false; + // Apply any pending breakpoints that were added before connecting + CheckApplyPendingBreakpoints(); + if (Settings::Instance()->Get("debugger.stopAtEntryPoint") && m_hasEntryFunction) AddBreakpoint(ModuleNameAndOffset(inputFile, m_entryPoint - m_start)); @@ -420,7 +423,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; @@ -1120,20 +1123,148 @@ 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; + { + // 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: + // 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 this->m_rspConnector->TransmitAndReceive(RspData("Z2,{:x},{}", address, 1)).AsString() != "OK"; + 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) + { + // 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) + { + 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); +} - return this->m_rspConnector->TransmitAndReceive(RspData("Z2,{:x},{}", address, 1)).AsString() != "OK"; +bool GdbAdapter::RemoveHardwareWriteBreakpoint(uint64_t address) +{ + // Delegate to new standardized method + 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() @@ -1413,6 +1544,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{}; @@ -1428,6 +1560,31 @@ void GdbAdapter::CheckApplyPendingBreakpoints() } it++; } + + // Apply pending hardware breakpoints + for (auto it = m_pendingHardwareBreakpoints.begin(); it != m_pendingHardwareBreakpoints.end(); ) + { + bool success = false; + if (it->isRelative) + { + // Module+offset based hardware breakpoint + success = AddHardwareBreakpoint(it->location, it->type, it->size); + } + else + { + // Absolute address hardware breakpoint + success = AddHardwareBreakpoint(it->address, it->type, it->size); + } + + if (success) + { + it = m_pendingHardwareBreakpoints.erase(it); + } + else + { + it++; + } + } } diff --git a/core/adapters/gdbadapter.h b/core/adapters/gdbadapter.h index 41c24007..dd62b1b5 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{}; @@ -158,7 +159,13 @@ 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; + 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); bool RemoveHardwareWriteBreakpoint(uint64_t address); diff --git a/core/adapters/gdbmiadapter.cpp b/core/adapters/gdbmiadapter.cpp index d7e8c7c9..fbe63799 100644 --- a/core/adapters/gdbmiadapter.cpp +++ b/core/adapters/gdbmiadapter.cpp @@ -301,6 +301,10 @@ void GdbMiAdapter::ScheduleStateRefresh() UpdateThreadList(); UpdateAllRegisters(); UpdateStackFrames(m_currentTid); + // Apply any pending breakpoints that were added while target was running + // or couldn't be resolved earlier (modules not loaded yet) + ApplyBreakpoints(); + ApplyPendingHardwareBreakpoints(); } DebuggerEvent ev; @@ -544,6 +548,7 @@ bool GdbMiAdapter::Connect(const std::string& server, uint32_t port) { LogInfo("Applying breakpoints..."); ApplyBreakpoints(); + ApplyPendingHardwareBreakpoints(); return true; } @@ -719,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) { @@ -1083,6 +1098,198 @@ bool GdbMiAdapter::SupportFeature(DebugAdapterCapacity feature) { } } +bool GdbMiAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + 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) +{ + 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) +{ + 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) +{ + 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 + // Only remove from the list if the breakpoint was successfully added + for (auto it = m_pendingHardwareBreakpoints.begin(); it != m_pendingHardwareBreakpoints.end(); ) + { + bool success = false; + if (it->isRelative) + { + // Module+offset based hardware breakpoint + success = AddHardwareBreakpoint(it->location, it->type, it->size); + } + else + { + // Absolute address hardware breakpoint + success = AddHardwareBreakpoint(it->address, it->type, it->size); + } + + if (success) + { + it = m_pendingHardwareBreakpoints.erase(it); + } + else + { + it++; + } + } +} + // --- Adapter Type Registration --- GdbMiAdapterType::GdbMiAdapterType() : DebugAdapterType("GDB MI") {} @@ -1162,11 +1369,23 @@ Ref GdbMiAdapterType::RegisterAdapterSettings() void GdbMiAdapter::ApplyBreakpoints() { - for (const auto& bp : m_pendingBreakpoints) + // Apply pending breakpoints + // Only remove from the list if the breakpoint was successfully added + for (auto it = m_pendingBreakpoints.begin(); it != m_pendingBreakpoints.end(); ) { - AddBreakpoint(bp, 0); + uint64_t base{}; + if (GetModuleBase(it->module, base)) + { + uint64_t addr = base + it->offset; + // Only remove if breakpoint was successfully set + if (AddBreakpoint(addr, 0).m_address != 0) + { + it = m_pendingBreakpoints.erase(it); + continue; + } + } + it++; } - m_pendingBreakpoints.clear(); } diff --git a/core/adapters/gdbmiadapter.h b/core/adapters/gdbmiadapter.h index 7c9136ca..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); @@ -104,6 +107,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 3952d1f9..a0b4cfa9 100644 --- a/core/adapters/lldbadapter.cpp +++ b/core/adapters/lldbadapter.cpp @@ -334,6 +334,7 @@ void BinaryNinjaDebugger::InitLldbAdapterType() void LldbAdapter::ApplyBreakpoints() { + // Apply pending software breakpoints immediately - these work fine before process starts for (const auto& bp : m_pendingBreakpoints) { AddBreakpoint(bp); @@ -341,6 +342,37 @@ 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(); + + // 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()) + { + m_needsHardwareBreakpointReapplication = true; + } } @@ -925,11 +957,39 @@ 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, HardwareExecuteBreakpoint); + else + return DebugBreakpoint {}; + } + + // Default software breakpoint SBBreakpoint bp = m_target.BreakpointCreateByAddress(address); if (!bp.IsValid()) return DebugBreakpoint {}; - return DebugBreakpoint(address, bp.GetID(), bp.IsEnabled()); + return DebugBreakpoint(address, bp.GetID(), bp.IsEnabled(), SoftwareBreakpoint); +} + + +bool LldbAdapter::ResolveModuleAddress(const ModuleNameAndOffset& location, uint64_t& address) +{ + // Try to find the module in the loaded module list + auto modules = GetModuleList(); + for (const auto& module : modules) + { + if (module.IsSameBaseModule(location.module)) + { + address = module.m_address + location.offset; + return true; + } + } + + // Module not found - caller should fall back to module+offset handling + return false; } @@ -1001,6 +1061,262 @@ std::vector LldbAdapter::GetBreakpointList() const } +bool LldbAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + if (!m_targetActive) + { + // 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) + { + case HardwareExecuteBreakpoint: + { + // Hardware execution breakpoints are not implemented in LLDB on x86/x64 Linux + // The underlying NativeRegisterContextDBReg_x86 only implements watchpoints, not breakpoints + // See: https://github.com/llvm/llvm-project/issues/XXXXX (to be filed) + std::string arch = GetTargetArchitecture(); + if (arch == "x86_64" || arch == "i386") + { + LogWarn("Hardware execution breakpoints are not supported on x86/x64 with LLDB"); + return false; + } + + // 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) + { + // 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; + } + + 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; + } +} + + +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 - try to resolve module+offset to absolute address + uint64_t addr; + bool resolved = ResolveModuleAddress(location, addr); + + switch (type) + { + case HardwareExecuteBreakpoint: + { + // Hardware execution breakpoints are not implemented in LLDB on x86/x64 Linux + // The underlying NativeRegisterContextDBReg_x86 only implements watchpoints, not breakpoints + // See: https://github.com/llvm/llvm-project/issues/XXXXX (to be filed) + std::string arch = GetTargetArchitecture(); + if (arch == "x86_64" || arch == "i386") + { + LogWarn("Hardware execution breakpoints are not supported on x86/x64 with LLDB"); + return false; + } + + std::string command; + if (resolved) + { + // Module was resolved - use absolute address + command = fmt::format("breakpoint set --address 0x{:x} -H", addr); + } + else + { + // Module not resolved - use module+offset syntax + // Calculate address relative to original image base for LLDB + addr = location.offset + m_originalImageBase; + 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 an absolute address + // LLDB watchpoints don't have direct module+offset syntax + if (!resolved) + { + // If module not resolved, we can't set the watchpoint yet + // Add to pending list + PendingHardwareBreakpoint pending(location, type, size); + 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; + } + 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 - try to resolve module+offset to absolute address and remove + uint64_t address; + if (ResolveModuleAddress(location, address)) + { + return RemoveHardwareBreakpoint(address, type, size); + } + // Module not resolved - cannot remove (it may be in pending list) + return false; + } +} + + static intx::uint512 SBValueToUint512(lldb::SBValue& reg_val) { using namespace lldb; using namespace intx; @@ -1806,6 +2122,28 @@ 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 + // 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; + } + DebuggerEvent dbgevt; dbgevt.type = AdapterStoppedEventType; // LLDB sometimes fails to update the process status when it is already sending eStateStopped event. @@ -1888,22 +2226,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); } } @@ -1914,23 +2244,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/lldbadapter.h b/core/adapters/lldbadapter.h index 1d173a6e..b5e5d4df 100644 --- a/core/adapters/lldbadapter.h +++ b/core/adapters/lldbadapter.h @@ -35,6 +35,12 @@ namespace BinaryNinjaDebugger { bool m_targetActive; 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. @@ -49,6 +55,11 @@ namespace BinaryNinjaDebugger { bool m_userRequestedQuit = false; + // Helper to resolve module+offset to absolute address + // Returns true if the module was found in the loaded module list and address was resolved + // Returns false if the module was not found (caller should fall back to module+offset handling) + bool ResolveModuleAddress(const ModuleNameAndOffset& location, uint64_t& address); + public: LldbAdapter(BinaryView* data); virtual ~LldbAdapter(); @@ -96,6 +107,12 @@ 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; + 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; DebugRegister ReadRegister(const std::string& reg) override; diff --git a/core/adapters/lldbcoredumpadapter.cpp b/core/adapters/lldbcoredumpadapter.cpp index 10db3eee..d9c712f3 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); } } @@ -1213,6 +1196,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 63b2263b..0db12457 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 7c5981cc..048e402d 100644 --- a/core/debugadapter.cpp +++ b/core/debugadapter.cpp @@ -253,3 +253,5 @@ bool DebugAdapter::SetTTDPosition(const TTDPosition& position) // Default implementation returns false for adapters that don't support TTD return false; } + + diff --git a/core/debugadapter.h b/core/debugadapter.h index 5273d97c..f733014f 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -122,12 +122,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() {} @@ -136,6 +138,41 @@ 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 + { + 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) + : 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 + { + 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; + } + } + }; + struct DebugRegister { std::string m_name {}; @@ -276,6 +313,18 @@ namespace BinaryNinjaDebugger { virtual std::vector GetBreakpointList() const = 0; + // Hardware breakpoint and watchpoint support + // 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; virtual DebugRegister ReadRegister(const std::string& reg) = 0; diff --git a/core/debuggercommon.h b/core/debuggercommon.h index e1fbb0bc..9c3b2e53 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 diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 5fc337bf..a51e0d3a 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,19 +127,106 @@ void DebuggerController::DisableBreakpoint(const ModuleNameAndOffset& address) { m_state->DisableBreakpoint(address); DebuggerEvent event; - event.type = RelativeBreakpointDisabledEvent; - event.data.relativeAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } +bool DebuggerController::ContainsBreakpoint(const ModuleNameAndOffset& address) +{ + return m_state->GetBreakpoints()->ContainsOffset(address); +} + + +bool DebuggerController::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + bool result = m_state->AddHardwareBreakpoint(address, type, size); + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); + return result; +} + + +bool DebuggerController::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + bool result = m_state->RemoveHardwareBreakpoint(address, type, size); + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); + return result; +} + + +bool DebuggerController::EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + bool result = m_state->EnableHardwareBreakpoint(address, type, size); + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); + return result; +} + + +bool DebuggerController::DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + bool result = m_state->DisableHardwareBreakpoint(address, type, size); + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); + return result; +} + + +// 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); + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); + return result; +} + + +bool DebuggerController::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + bool result = m_state->RemoveHardwareBreakpoint(location, type, size); + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); + return result; +} + + +bool DebuggerController::EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + bool result = m_state->EnableHardwareBreakpoint(location, type, size); + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); + return result; +} + + +bool DebuggerController::DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + bool result = m_state->DisableHardwareBreakpoint(location, type, size); + DebuggerEvent event; + event.type = BreakpointChangedEvent; + PostDebuggerEvent(event); + return result; +} + + bool DebuggerController::SetBreakpointCondition(uint64_t address, const std::string& condition) { bool result = m_state->GetBreakpoints()->SetConditionAbsolute(address, condition); if (result) { DebuggerEvent event; - event.type = AbsoluteBreakpointConditionChangedEvent; + event.type = BreakpointChangedEvent; event.data.absoluteAddress = address; PostDebuggerEvent(event); } @@ -160,7 +240,7 @@ bool DebuggerController::SetBreakpointCondition(const ModuleNameAndOffset& addre if (result) { DebuggerEvent event; - event.type = RelativeBreakpointConditionChangedEvent; + event.type = BreakpointChangedEvent; event.data.relativeAddress = address; PostDebuggerEvent(event); } diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index 36ed49e3..1014bb97 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -234,12 +234,26 @@ namespace BinaryNinjaDebugger { void EnableBreakpoint(const ModuleNameAndOffset& address); void DisableBreakpoint(uint64_t address); void DisableBreakpoint(const ModuleNameAndOffset& address); + bool ContainsBreakpoint(const ModuleNameAndOffset& address); DebugBreakpoint GetAllBreakpoints(); bool SetBreakpointCondition(uint64_t address, const std::string& condition); bool SetBreakpointCondition(const ModuleNameAndOffset& address, const std::string& condition); std::string GetBreakpointCondition(uint64_t address); std::string GetBreakpointCondition(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); + // 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 09cfb614..c98a5192 100644 --- a/core/debuggerstate.cpp +++ b/core/debuggerstate.cpp @@ -527,7 +527,7 @@ std::vector::iterator DebuggerBreakpoints::FindBreakpoint(const const uint64_t targetAbsolute = m_state->GetModules()->RelativeAddressToAbsolute(address); for (auto it = m_breakpoints.begin(); it != m_breakpoints.end(); ++it) { - if (m_state->GetModules()->RelativeAddressToAbsolute(it->address) == targetAbsolute) + if (m_state->GetModules()->RelativeAddressToAbsolute(it->location) == targetAbsolute) return it; } } @@ -535,7 +535,7 @@ std::vector::iterator DebuggerBreakpoints::FindBreakpoint(const { for (auto it = m_breakpoints.begin(); it != m_breakpoints.end(); ++it) { - if (it->address == address) + if (it->location == address) return it; } } @@ -611,7 +611,7 @@ bool DebuggerBreakpoints::RemoveOffset(const ModuleNameAndOffset& address) if (it == m_breakpoints.end()) return false; - ModuleNameAndOffset actualAddr = it->address; + ModuleNameAndOffset actualAddr = it->location; m_breakpoints.erase(it); SerializeMetadata(); @@ -642,7 +642,7 @@ bool DebuggerBreakpoints::EnableOffset(const ModuleNameAndOffset& address) if (m_state->GetAdapter() && m_state->IsConnected()) { - uint64_t remoteAddress = m_state->GetModules()->RelativeAddressToAbsolute(it->address); + uint64_t remoteAddress = m_state->GetModules()->RelativeAddressToAbsolute(it->location); m_state->GetAdapter()->AddBreakpoint(remoteAddress); } return true; @@ -667,7 +667,7 @@ bool DebuggerBreakpoints::DisableOffset(const ModuleNameAndOffset& address) if (m_state->GetAdapter() && m_state->IsConnected()) { - uint64_t remoteAddress = m_state->GetModules()->RelativeAddressToAbsolute(it->address); + uint64_t remoteAddress = m_state->GetModules()->RelativeAddressToAbsolute(it->location); m_state->GetAdapter()->RemoveBreakpoint(remoteAddress); } return true; @@ -704,7 +704,7 @@ bool DebuggerBreakpoints::ContainsAbsolute(uint64_t address) for (const auto& bp : m_breakpoints) { - if (m_state->GetModules()->RelativeAddressToAbsolute(bp.address) == address) + if (m_state->GetModules()->RelativeAddressToAbsolute(bp.location) == address) return true; } return false; @@ -734,7 +734,7 @@ std::string DebuggerBreakpoints::GetConditionAbsolute(const uint64_t address) { for (const auto& bp : m_breakpoints) { - if (m_state->GetModules()->RelativeAddressToAbsolute(bp.address) == address) + if (m_state->GetModules()->RelativeAddressToAbsolute(bp.location) == address) return bp.condition; } return ""; @@ -752,7 +752,7 @@ bool DebuggerBreakpoints::HasConditionAbsolute(const uint64_t address) { for (const auto& bp : m_breakpoints) { - if (m_state->GetModules()->RelativeAddressToAbsolute(bp.address) == address) + if (m_state->GetModules()->RelativeAddressToAbsolute(bp.location) == address) return !bp.condition.empty(); } return false; @@ -766,14 +766,305 @@ bool DebuggerBreakpoints::HasConditionOffset(const ModuleNameAndOffset& address) } +bool DebuggerBreakpoints::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + // 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 + + if (ContainsHardwareBreakpoint(address, type, size)) + return true; // Already exists + + // If adapter is connected, try to add there first - only add to internal storage if successful + if (m_state->GetAdapter() && m_state->IsConnected()) + { + bool adapterResult = m_state->GetAdapter()->AddHardwareBreakpoint(address, type, size); + if (!adapterResult) + return false; // Adapter failed, don't add to internal storage + } + + // 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); + BreakpointEntry bp; + bp.location = info; + bp.enabled = true; + bp.type = type; + bp.size = size; + bp.address = address; + bp.isRelative = true; + m_breakpoints.push_back(bp); + SerializeMetadata(); + + return true; +} + + +bool DebuggerBreakpoints::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + // 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) + { + 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); + SerializeMetadata(); + break; + } + } + } + + // 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) +{ + // Similar to ContainsAbsolute, we need to handle both relative and absolute hardware breakpoints + // For relative hardware breakpoints, convert to absolute and compare + for (const BreakpointEntry& 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; +} + + +bool DebuggerBreakpoints::EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + if (!ContainsHardwareBreakpoint(address, type, size)) + return false; + + // Find and enable the hardware breakpoint - need to handle both relative and absolute breakpoints + for (auto& bp : m_breakpoints) + { + 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(); + + // 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 - need to handle both relative and absolute breakpoints + for (auto& bp : m_breakpoints) + { + 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(); + + // 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; +} + + +// ========== Hardware Breakpoint Module+Offset Methods (ASLR-safe) ========== + +bool DebuggerBreakpoints::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + if (ContainsHardwareBreakpoint(location, type, size)) + return true; // Already exists + + // If adapter is connected, try to add there first - only add to internal storage if successful + if (m_state->GetAdapter() && m_state->IsConnected()) + { + bool adapterResult = m_state->GetAdapter()->AddHardwareBreakpoint(location, type, size); + if (!adapterResult) + return false; // Adapter failed, don't add to internal storage + } + + // Add to internal storage (either adapter succeeded, or no adapter connected yet) + BreakpointEntry bp; + bp.location = location; + bp.enabled = true; + bp.type = type; + bp.size = size; + bp.isRelative = true; + m_breakpoints.push_back(bp); + SerializeMetadata(); + + return true; +} + + +bool DebuggerBreakpoints::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + // Remove from our list + auto iter = std::find_if(m_breakpoints.begin(), m_breakpoints.end(), [&](const BreakpointEntry& bp) { + return bp.IsHardware() && bp.location == location && bp.type == type && bp.size == size; + }); + 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) +{ + return std::find_if(m_breakpoints.begin(), m_breakpoints.end(), [&](const BreakpointEntry& bp) { + return bp.IsHardware() && bp.location == location && bp.type == type && bp.size == size; + }) != 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 + auto iter = std::find_if(m_breakpoints.begin(), m_breakpoints.end(), [&](const BreakpointEntry& bp) { + return bp.IsHardware() && bp.location == location && bp.type == type && bp.size == size; + }); + 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 + auto iter = std::find_if(m_breakpoints.begin(), m_breakpoints.end(), [&](const BreakpointEntry& bp) { + return bp.IsHardware() && bp.location == location && bp.type == type && bp.size == size; + }); + 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; +} + + void DebuggerBreakpoints::SerializeMetadata() { std::vector> breakpoints; for (const auto& bp : m_breakpoints) { std::map> info; - info["module"] = new Metadata(bp.address.module); - info["offset"] = new Metadata(bp.address.offset); + info["module"] = new Metadata(bp.location.module); + info["offset"] = new Metadata(bp.location.offset); info["enabled"] = new Metadata(bp.enabled); if (!bp.condition.empty()) @@ -806,8 +1097,8 @@ void DebuggerBreakpoints::UnserializedMetadata() continue; BreakpointEntry bp; - bp.address.module = info["module"]->GetString(); - bp.address.offset = info["offset"]->GetUnsignedInteger(); + bp.location.module = info["module"]->GetString(); + bp.location.offset = info["offset"]->GetUnsignedInteger(); bp.enabled = (info["enabled"] && info["enabled"]->IsBoolean()) ? info["enabled"]->GetBoolean() : true; bp.condition = (info["condition"] && info["condition"]->IsString()) ? info["condition"]->GetString() : ""; @@ -823,8 +1114,29 @@ void DebuggerBreakpoints::Apply() for (const auto& bp : m_breakpoints) { - if (bp.enabled) - m_state->GetAdapter()->AddBreakpoint(bp.address); + // Only apply enabled breakpoints + if (!bp.enabled) + continue; + + if (bp.IsSoftware()) + { + // Software breakpoints always use module+offset + m_state->GetAdapter()->AddBreakpoint(bp.location); + } + else + { + // 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); + } + } } } @@ -1140,6 +1452,56 @@ 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); +} + + +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); +} + + +// 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 7949ae42..c36fa7df 100644 --- a/core/debuggerstate.h +++ b/core/debuggerstate.h @@ -80,9 +80,18 @@ namespace BinaryNinjaDebugger { struct BreakpointEntry { - ModuleNameAndOffset address; + ModuleNameAndOffset location; bool enabled = true; std::string condition; + + uint64_t address = 0; // Absolute address (for absolute addressing or resolved relative) + DebugBreakpointType type = SoftwareBreakpoint; // Breakpoint type (Software, HardwareExecute, etc.) + size_t size = 1; // Size for hardware watchpoints + bool isRelative = true; // True if using module+offset, false if using absolute address + + // Helper methods + bool IsSoftware() const { return type == SoftwareBreakpoint; } + bool IsHardware() const { return type != SoftwareBreakpoint; } }; class DebuggerBreakpoints @@ -117,6 +126,20 @@ namespace BinaryNinjaDebugger { bool HasConditionAbsolute(uint64_t address); bool HasConditionOffset(const ModuleNameAndOffset& address); + // 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); + + // 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); + private: // Find breakpoint by address, handling module name differences via absolute address comparison std::vector::iterator FindBreakpoint(const ModuleNameAndOffset& address); @@ -268,6 +291,18 @@ namespace BinaryNinjaDebugger { void DisableBreakpoint(uint64_t address); void DisableBreakpoint(const ModuleNameAndOffset& address); + // 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 35b12f88..5f51c8cb 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -809,11 +809,25 @@ BNDebugBreakpoint* BNDebuggerGetBreakpoints(BNDebuggerController* controller, si for (size_t i = 0; i < breakpoints.size(); i++) { const auto& bp = breakpoints[i]; - result[i].module = BNDebuggerAllocString(bp.address.module.c_str()); - result[i].offset = bp.address.offset; - result[i].address = state->GetModules()->RelativeAddressToAbsolute(bp.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(bp.location); + } + else + { + // For absolute addressing, use the stored address directly + remoteAddress = bp.address; + } + + result[i].module = BNDebuggerAllocString(bp.location.module.c_str()); + result[i].offset = bp.location.offset; + result[i].address = remoteAddress; result[i].enabled = bp.enabled; result[i].condition = BNDebuggerAllocString(bp.condition.c_str()); + result[i].type = (BNDebugBreakpointType)breakpoints[i].type; + result[i].size = breakpoints[i].size; } return result; } @@ -913,15 +927,57 @@ bool BNDebuggerContainsAbsoluteBreakpoint(BNDebuggerController* controller, uint bool BNDebuggerContainsRelativeBreakpoint(BNDebuggerController* controller, const char* module, uint64_t offset) { - DebuggerState* state = controller->object->GetState(); - if (!state) - return false; + return controller->object->ContainsBreakpoint(ModuleNameAndOffset(module, offset)); +} + + +bool BNDebuggerAddHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, BNDebugBreakpointType type, size_t size) +{ + return controller->object->AddHardwareBreakpoint(address, (DebugBreakpointType)type, size); +} - DebuggerBreakpoints* breakpoints = state->GetBreakpoints(); - if (!breakpoints) - return false; - return breakpoints->ContainsOffset(ModuleNameAndOffset(module, offset)); +bool BNDebuggerRemoveHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, BNDebugBreakpointType type, size_t size) +{ + return controller->object->RemoveHardwareBreakpoint(address, (DebugBreakpointType)type, size); +} + + +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); +} + + +// 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); } diff --git a/docs/guide/index.md b/docs/guide/index.md index 465ff17e..4061c0b6 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -595,9 +595,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 diff --git a/test/debugger_test.py b/test/debugger_test.py index 0753738e..eae41aa3 100644 --- a/test/debugger_test.py +++ b/test/debugger_test.py @@ -12,9 +12,9 @@ from binaryninja import load try: - from debugger import DebuggerController, DebugStopReason + from debugger import DebuggerController, DebugStopReason, DebugBreakpointType except: - from binaryninja.debugger import DebuggerController, DebugStopReason + from binaryninja.debugger import DebuggerController, DebugStopReason, DebugBreakpointType # 'helloworld' -> '{BN_SOURCE_ROOT}\public\debugger\test\binaries\Windows-x64\helloworld.exe' (windows) # 'helloworld' -> '{BN_SOURCE_ROOT}/public/debugger/test/binaries/Darwin/arm64/helloworld' (linux, macOS) @@ -269,6 +269,31 @@ def test_breakpoints_list_and_repr(self): dbg.quit_and_wait() + @unittest.skipIf(platform.system() == 'Linux', 'Hardware breakpoints not yet supported on Linux') + def test_hardware_breakpoint(self): + """Test hardware breakpoint add and delete""" + fpath = name_to_fpath('helloworld', self.arch) + bv = load(fpath) + dbg = DebuggerController(bv) + self.assertNotIn(dbg.launch_and_wait(), [DebugStopReason.ProcessExited, DebugStopReason.InternalError]) + + entry = dbg.data.entry_point + + # Test adding hardware execution breakpoint + self.assertTrue(dbg.add_hardware_breakpoint(entry, DebugBreakpointType.BNHardwareExecuteBreakpoint)) + + # Test adding hardware write watchpoint at a different address + # Note: Hardware data breakpoints must be aligned to their size on x86/x64 + # A 4-byte watchpoint must be at a 4-byte aligned address + watch_addr = (entry + 0x100) & ~0x3 # Align to 4-byte boundary + self.assertTrue(dbg.add_hardware_breakpoint(watch_addr, DebugBreakpointType.BNHardwareWriteBreakpoint, size=4)) + + # Test deleting hardware breakpoints + self.assertTrue(dbg.delete_hardware_breakpoint(entry, DebugBreakpointType.BNHardwareExecuteBreakpoint)) + self.assertTrue(dbg.delete_hardware_breakpoint(watch_addr, DebugBreakpointType.BNHardwareWriteBreakpoint, size=4)) + + dbg.quit_and_wait() + def test_register_read_write(self): fpath = name_to_fpath('helloworld', self.arch) bv = load(fpath) diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index 51bfb214..c0a58186 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -19,12 +19,17 @@ limitations under the License. #include #include #include +#include #include #include #include #include #include +#include +#include +#include #include "breakpointswidget.h" +#include "hardwarebreakpointdialog.h" #include "ui.h" #include "menus.h" #include "fmt/format.h" @@ -33,11 +38,33 @@ using namespace BinaryNinjaDebuggerAPI; using namespace BinaryNinja; using namespace std; -BreakpointItem::BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t address, const std::string& condition) : - m_enabled(enabled), m_location(location), m_address(address), m_condition(condition) +BreakpointItem::BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t address, + const std::string& condition, DebugBreakpointType type, size_t size) : + m_enabled(enabled), m_location(location), m_address(address), m_condition(condition), m_type(type), + m_size(size) {} +std::string BreakpointItem::typeString() const +{ + switch (m_type) + { + case SoftwareBreakpoint: + return "S"; + case HardwareExecuteBreakpoint: + return "HE"; + case HardwareReadBreakpoint: + return "HR"; + case HardwareWriteBreakpoint: + return "HW"; + case HardwareAccessBreakpoint: + return "HA"; + default: + return "Unknown"; + } +} + + bool BreakpointItem::operator==(const BreakpointItem& other) const { // While we shouldn't technically have breakpoints at the same address with different @@ -109,6 +136,10 @@ QVariant DebugBreakpointsListModel::data(const QModelIndex& index, int role) con { case DebugBreakpointsListModel::EnabledColumn: { + if (role == Qt::ToolTipRole) + { + return item->enabled() ? "Breakpoint is enabled" : "Breakpoint is disabled"; + } QString text = item->enabled() ? "β˜‘" : "☐"; return QVariant(text); } @@ -140,6 +171,33 @@ QVariant DebugBreakpointsListModel::data(const QModelIndex& index, int role) con return QVariant(text); } + 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()); + + return QVariant(text); + } case DebugBreakpointsListModel::ConditionColumn: { QString condition = QString::fromStdString(item->condition()); @@ -168,13 +226,15 @@ QVariant DebugBreakpointsListModel::headerData(int column, Qt::Orientation orien switch (column) { case DebugBreakpointsListModel::EnabledColumn: - return ""; + return "E"; case DebugBreakpointsListModel::LocationColumn: return "Location"; case DebugBreakpointsListModel::AddressColumn: return "Remote Address"; case DebugBreakpointsListModel::ConditionColumn: return "Condition"; + case DebugBreakpointsListModel::TypeColumn: + return "Type"; } return QVariant(); } @@ -215,8 +275,30 @@ 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: { painter->setFont(m_font); painter->setPen(option.palette.color(QPalette::WindowText).rgba()); @@ -289,7 +371,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); @@ -317,7 +402,12 @@ 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..."); + 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")); @@ -398,11 +488,34 @@ 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.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 location for relative breakpoints, address for absolute + if (bp.enabled()) + { + 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 + { + 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 } @@ -467,6 +580,41 @@ void DebugBreakpointsWidget::jump() void DebugBreakpointsWidget::add() +{ + // Keep this for backward compatibility - show menu + UIContext* ctxt = UIContext::contextForWidget(this); + if (!ctxt) + return; + + ViewFrame* frame = ctxt->getCurrentViewFrame(); + if (!frame) + return; + + auto view = frame->getCurrentBinaryView(); + if (!view) + return; + + // 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; + + if (chosen == softwareAction) + { + addSoftwareBreakpoint(); + } + else if (chosen == hardwareAction) + { + addHardwareBreakpoint(); + } +} + + +void DebugBreakpointsWidget::addSoftwareBreakpoint() { UIContext* ctxt = UIContext::contextForWidget(this); if (!ctxt) @@ -504,48 +652,131 @@ void DebugBreakpointsWidget::add() } +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() { + // TODO: refactor to use breakpoint index instead of address/location for these operations QModelIndexList sel = selectionModel()->selectedRows(); 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 location for relative breakpoints, address for absolute + if (bp.enabled()) + { + 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 + { + if (!bp.location().module.empty()) + m_controller->EnableHardwareBreakpoint(bp.location(), bp.type(), bp.size()); + else + m_controller->EnableHardwareBreakpoint(bp.address(), bp.type(), bp.size()); + } + } } } 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) { - 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 + { + // 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); + } + } } } 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) { - 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 + { + // 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); + } + } } } void DebugBreakpointsWidget::soloSelected() { + // TODO: refactor to use breakpoint index instead of address/location for these operations QModelIndexList sel = selectionModel()->selectedRows(); if (sel.empty()) return; @@ -557,14 +788,43 @@ void DebugBreakpointsWidget::soloSelected() 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 + { + // 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); + } + } } // Enable the selected breakpoint - m_controller->EnableBreakpoint(selectedBp.location()); + if (selectedBp.type() == SoftwareBreakpoint) + { + m_controller->EnableBreakpoint(selectedBp.location()); + } + else + { + // 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()); + } } @@ -589,19 +849,35 @@ void DebugBreakpointsWidget::editCondition() void DebugBreakpointsWidget::remove() { + // TODO: refactor to use breakpoint index instead of address/location for these operations 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 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()); + } + } } @@ -615,7 +891,7 @@ void DebugBreakpointsWidget::updateContent() ModuleNameAndOffset info; info.module = bp.module; info.offset = bp.offset; - bps.emplace_back(bp.enabled, info, bp.address, bp.condition); + bps.emplace_back(bp.enabled, info, bp.address, bp.condition, bp.type, bp.size); } m_model->updateRows(bps); diff --git a/ui/breakpointswidget.h b/ui/breakpointswidget.h index 882642e3..509ce7c2 100644 --- a/ui/breakpointswidget.h +++ b/ui/breakpointswidget.h @@ -40,13 +40,19 @@ class BreakpointItem ModuleNameAndOffset m_location; uint64_t m_address; std::string m_condition; + DebugBreakpointType m_type; + size_t m_size; // Size in bytes for hardware breakpoints/watchpoints public: - BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t remoteAddress, const std::string& condition = ""); + BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t remoteAddress, + const std::string& condition = "", 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; } std::string condition() const { return m_condition; } + 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; bool operator<(const BreakpointItem& other) const; @@ -71,6 +77,7 @@ class DebugBreakpointsListModel : public QAbstractTableModel LocationColumn, AddressColumn, ConditionColumn, + TypeColumn, }; DebugBreakpointsListModel(QWidget* parent, ViewFrame* view); @@ -86,7 +93,7 @@ class DebugBreakpointsListModel : public QAbstractTableModel virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override { (void)parent; - return 4; + return 5; } BreakpointItem getRow(int row) const; virtual QVariant data(const QModelIndex& i, int role) const override; @@ -151,6 +158,8 @@ private slots: void remove(); void onDoubleClicked(); void add(); + void addSoftwareBreakpoint(); + void addHardwareBreakpoint(); void toggleSelected(); void enableAll(); void disableAll(); diff --git a/ui/debuggerwidget.cpp b/ui/debuggerwidget.cpp index 46bacf58..a25472bd 100644 --- a/ui/debuggerwidget.cpp +++ b/ui/debuggerwidget.cpp @@ -127,16 +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 AbsoluteBreakpointConditionChangedEvent: - case RelativeBreakpointConditionChangedEvent: + case BreakpointChangedEvent: m_breakpointsWidget->updateContent(); break; default: diff --git a/ui/hardwarebreakpointdialog.cpp b/ui/hardwarebreakpointdialog.cpp new file mode 100644 index 00000000..dbe3e0a6 --- /dev/null +++ b/ui/hardwarebreakpointdialog.cpp @@ -0,0 +1,224 @@ +/* +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)); + + // 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) + 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(); + 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 +{ + bool ok; + QString text = m_sizeCombo->currentText(); + size_t size = text.toULongLong(&ok); + return ok ? size : 1; +} + +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 = 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", + "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_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_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. 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. 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. 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 new file mode 100644 index 00000000..d0eae183 --- /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; + QComboBox* m_sizeCombo; + 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 diff --git a/ui/renderlayer.cpp b/ui/renderlayer.cpp index 61a9ff64..5bc57027 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 diff --git a/ui/ui.cpp b/ui/ui.cpp index 83ddce1a..244ef530 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" @@ -975,6 +976,24 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) })); debuggerMenu->addAction("Edit Condition...", "Breakpoint"); + // Register "Add Hardware Breakpoint" action + UIAction::registerAction("Add Hardware Breakpoint...", QKeySequence(Qt::Key_F3)); + 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( @@ -1455,14 +1474,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) @@ -1800,16 +1814,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 AbsoluteBreakpointConditionChangedEvent: - case RelativeBreakpointConditionChangedEvent: + case BreakpointChangedEvent: { m_context->refreshCurrentViewContents(); break; diff --git a/ui/uinotification.cpp b/ui/uinotification.cpp index 02ce2d84..fe2badaa 100644 --- a/ui/uinotification.cpp +++ b/ui/uinotification.cpp @@ -179,6 +179,7 @@ void NotificationListener::OnContextMenuCreated(UIContext *context, View* view, menu.addAction("Debugger", "Enable Breakpoint", "Breakpoint"); menu.addAction("Debugger", "Solo Breakpoint", "Breakpoint"); menu.addAction("Debugger", "Edit Condition...", "Breakpoint"); + menu.addAction("Debugger", "Add Hardware Breakpoint...", "Breakpoint"); menu.addAction("Debugger", "Launch", "Control"); menu.addAction("Debugger", "Pause", "Control"); menu.addAction("Debugger", "Restart", "Control");