diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 48e33f31..a2ba1195 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -354,12 +354,25 @@ 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; uint64_t offset; uint64_t address; bool enabled; + DebugBreakpointType type = SoftwareBreakpoint; + size_t size = 1; // Size in bytes for hardware breakpoints/watchpoints (1, 2, 4, 8) }; @@ -726,6 +739,18 @@ namespace BinaryNinjaDebuggerAPI { bool ContainsBreakpoint(uint64_t address); bool ContainsBreakpoint(const ModuleNameAndOffset& breakpoint); + // 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 944c8c1e..81af54af 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -731,6 +731,8 @@ std::vector DebuggerController::GetBreakpoints() bp.offset = breakpoints[i].offset; bp.address = breakpoints[i].address; bp.enabled = breakpoints[i].enabled; + bp.type = (DebugBreakpointType)breakpoints[i].type; + bp.size = breakpoints[i].size; result[i] = bp; } @@ -799,6 +801,56 @@ bool DebuggerController::ContainsBreakpoint(const ModuleNameAndOffset& breakpoin } +bool DebuggerController::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + return BNDebuggerAddHardwareBreakpoint(m_object, address, (BNDebugBreakpointType)type, size); +} + + +bool DebuggerController::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + return BNDebuggerRemoveHardwareBreakpoint(m_object, address, (BNDebugBreakpointType)type, size); +} + + +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 c6bc133c..24334060 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -125,6 +125,16 @@ extern "C" } BNDebugRegister; + typedef enum BNDebugBreakpointType + { + BNSoftwareBreakpoint = 0, // Default software breakpoint + BNHardwareExecuteBreakpoint = 1, // Hardware execution breakpoint + BNHardwareReadBreakpoint = 2, // Hardware read watchpoint + BNHardwareWriteBreakpoint = 3, // Hardware write watchpoint + BNHardwareAccessBreakpoint = 4 // Hardware read/write watchpoint + } BNDebugBreakpointType; + + typedef struct BNDebugBreakpoint { // TODO: we should add an absolute address to this, along with a boolean telling whether it is valid @@ -132,6 +142,8 @@ extern "C" uint64_t offset; uint64_t address; bool enabled; + BNDebugBreakpointType type; + size_t size; // Size in bytes for hardware breakpoints/watchpoints (1, 2, 4, 8) } BNDebugBreakpoint; @@ -248,14 +260,8 @@ extern "C" TargetExitedEventType, DetachedEventType, - AbsoluteBreakpointAddedEvent, - RelativeBreakpointAddedEvent, - AbsoluteBreakpointRemovedEvent, - RelativeBreakpointRemovedEvent, - AbsoluteBreakpointEnabledEvent, - RelativeBreakpointEnabledEvent, - AbsoluteBreakpointDisabledEvent, - RelativeBreakpointDisabledEvent, + // Unified breakpoint change event - use this for all breakpoint changes (add/remove/enable/disable) + BreakpointChangedEvent, ActiveThreadChangedEvent, @@ -597,6 +603,26 @@ extern "C" DEBUGGER_FFI_API bool BNDebuggerContainsRelativeBreakpoint( BNDebuggerController* controller, const char* module, uint64_t offset); + // Hardware breakpoint and watchpoint support + DEBUGGER_FFI_API bool BNDebuggerAddHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, + BNDebugBreakpointType type, size_t size); + DEBUGGER_FFI_API bool BNDebuggerRemoveHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, + BNDebugBreakpointType type, size_t size); + DEBUGGER_FFI_API 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 3277f1ce..fbbe4c18 100644 --- a/api/python/debuggercontroller.py +++ b/api/python/debuggercontroller.py @@ -368,19 +368,23 @@ class DebugBreakpoint: * ``offset``: the offset of the breakpoint to the start of the module * ``address``: the absolute address of the breakpoint * ``enabled``: whether the breakpoint is enabled (read-only) + * ``type``: the type of breakpoint (Software, HardwareExecute, HardwareRead, HardwareWrite, HardwareAccess) + * ``size``: the size in bytes for hardware breakpoints/watchpoints (1, 2, 4, or 8) """ - def __init__(self, module, offset, address, enabled): + def __init__(self, module, offset, address, enabled, bp_type=DebugBreakpointType.BNSoftwareBreakpoint, size=1): self.module = module self.offset = offset self.address = address self.enabled = enabled + self.type = bp_type + self.size = size def __eq__(self, other): if not isinstance(other, self.__class__): return NotImplemented return self.module == other.module and self.offset == other.offset and self.address == other.address \ - and self.enabled == other.enabled + and self.enabled == other.enabled and self.type == other.type and self.size == other.size def __ne__(self, other): if not isinstance(other, self.__class__): @@ -388,7 +392,7 @@ def __ne__(self, other): return not (self == other) def __hash__(self): - return hash((self.module, self.offset, self.address, self.enabled)) + return hash((self.module, self.offset, self.address, self.enabled, self.type, self.size)) def __setattr__(self, name, value): try: @@ -398,7 +402,22 @@ def __setattr__(self, name, value): def __repr__(self): status = "enabled" if self.enabled else "disabled" - return f"" + + # Get type string (S, HE, HR, HW, HA) + if self.type == DebugBreakpointType.BNSoftwareBreakpoint: + type_str = "S" + elif self.type == DebugBreakpointType.BNHardwareExecuteBreakpoint: + type_str = "HE" + elif self.type == DebugBreakpointType.BNHardwareReadBreakpoint: + type_str = "HR" + elif self.type == DebugBreakpointType.BNHardwareWriteBreakpoint: + type_str = "HW" + elif self.type == DebugBreakpointType.BNHardwareAccessBreakpoint: + type_str = "HA" + else: + type_str = "?" + + return f"" class ModuleNameAndOffset: @@ -2053,7 +2072,8 @@ def breakpoints(self) -> DebugBreakpoints: breakpoints = dbgcore.BNDebuggerGetBreakpoints(self.handle, count) result = [] for i in range(0, count.value): - bp = DebugBreakpoint(breakpoints[i].module, breakpoints[i].offset, breakpoints[i].address, breakpoints[i].enabled) + bp = DebugBreakpoint(breakpoints[i].module, breakpoints[i].offset, breakpoints[i].address, + breakpoints[i].enabled, breakpoints[i].type, breakpoints[i].size) result.append(bp) dbgcore.BNDebuggerFreeBreakpoints(breakpoints, count.value) diff --git a/breakpoint_widget_enhanced.png b/breakpoint_widget_enhanced.png new file mode 100644 index 00000000..00897fac Binary files /dev/null and b/breakpoint_widget_enhanced.png differ diff --git a/cli/main.cpp b/cli/main.cpp index 6156b3b3..bc442854 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -540,7 +540,31 @@ int main(int argc, const char* argv[]) size_t i = 0; for (const auto& breakpoint : debugger->GetBreakpoints()) { - Log::print(" breakpoint[{}] @ 0x{:X} is {}{}\n", i, breakpoint.address, + // Convert breakpoint type to short string representation + std::string typeStr; + switch (breakpoint.type) + { + case BinaryNinjaDebuggerAPI::SoftwareBreakpoint: + typeStr = "S"; + break; + case BinaryNinjaDebuggerAPI::HardwareExecuteBreakpoint: + typeStr = "HE"; + break; + case BinaryNinjaDebuggerAPI::HardwareReadBreakpoint: + typeStr = "HR"; + break; + case BinaryNinjaDebuggerAPI::HardwareWriteBreakpoint: + typeStr = "HW"; + break; + case BinaryNinjaDebuggerAPI::HardwareAccessBreakpoint: + typeStr = "HA"; + break; + default: + typeStr = "?"; + break; + } + + Log::print(" breakpoint[{}] @ 0x{:X} type={} is {}{}\n", i, breakpoint.address, typeStr, breakpoint.enabled ? Log::Style(0, 255, 0) : Log::Style(255, 0, 0), breakpoint.enabled ? "active" : "inactive"); i++; diff --git a/core/adapters/corelliumadapter.cpp b/core/adapters/corelliumadapter.cpp index 148576c7..28a97b3c 100644 --- a/core/adapters/corelliumadapter.cpp +++ b/core/adapters/corelliumadapter.cpp @@ -956,6 +956,34 @@ bool CorelliumAdapter::SupportFeature(DebugAdapterCapacity feature) } +bool CorelliumAdapter::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not yet implemented for Corellium adapter + return false; +} + + +bool CorelliumAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not yet implemented for Corellium adapter + return false; +} + + +bool CorelliumAdapter::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not yet implemented for Corellium adapter + return false; +} + + +bool CorelliumAdapter::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + // Hardware breakpoints are not yet implemented for Corellium adapter + return false; +} + + void CorelliumAdapter::InvalidateCache() { m_regCache.reset(); diff --git a/core/adapters/corelliumadapter.h b/core/adapters/corelliumadapter.h index 5d6e2b8b..54a8c619 100644 --- a/core/adapters/corelliumadapter.h +++ b/core/adapters/corelliumadapter.h @@ -133,6 +133,12 @@ namespace BinaryNinjaDebugger bool ResumeThread(std::uint32_t tid) override; DebugBreakpoint AddBreakpoint(const ModuleNameAndOffset& address, unsigned long breakpoint_type = 0) override; + // Hardware breakpoint support - not implemented + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; + void GenerateDefaultAdapterSettings(BinaryView* data); Ref GetAdapterSettings() override; }; diff --git a/core/adapters/dbgengadapter.cpp b/core/adapters/dbgengadapter.cpp index b06ecf81..10373c32 100644 --- a/core/adapters/dbgengadapter.cpp +++ b/core/adapters/dbgengadapter.cpp @@ -667,6 +667,27 @@ void DbgEngAdapter::EngineLoop() { if (m_lastExecutionStatus != DEBUG_STATUS_BREAK) { + // Apply deferred hardware breakpoints on first stop + // See ApplyBreakpoints() for detailed explanation of why this is necessary + if (m_needsHardwareBreakpointReapplication) + { + for (const auto& hwbp : m_deferredHardwareBreakpoints) + { + // Apply the hardware breakpoint now that process is running and stopped + // Check addressing mode and call appropriate variant + if (hwbp.isRelative) + { + AddHardwareBreakpoint(hwbp.location, hwbp.type, hwbp.size); + } + else + { + AddHardwareBreakpoint(hwbp.address, hwbp.type, hwbp.size); + } + } + m_deferredHardwareBreakpoints.clear(); + m_needsHardwareBreakpointReapplication = false; + } + if (outputStateOnStop) { // m_debugRegisters->OutputRegisters(DEBUG_OUTCTL_THIS_CLIENT, DEBUG_REGISTERS_DEFAULT); @@ -1014,6 +1035,15 @@ bool DbgEngAdapter::ResumeThread(std::uint32_t tid) DebugBreakpoint DbgEngAdapter::AddBreakpoint(const std::uintptr_t address, unsigned long breakpoint_flags) { + // Handle hardware breakpoint types + if (breakpoint_flags != SoftwareBreakpoint) + { + if (AddHardwareBreakpoint(address, (DebugBreakpointType)breakpoint_flags)) + return DebugBreakpoint(address, 0, true, (DebugBreakpointType)breakpoint_flags); + else + return DebugBreakpoint{}; + } + IDebugBreakpoint2* debug_breakpoint {}; /* attempt to read at breakpoint location to confirm its valid */ @@ -1039,7 +1069,7 @@ DebugBreakpoint DbgEngAdapter::AddBreakpoint(const std::uintptr_t address, unsig if (debug_breakpoint->SetFlags(DEBUG_BREAKPOINT_ENABLED | breakpoint_flags) != S_OK) return {}; - const auto new_breakpoint = DebugBreakpoint(address, id, true); + const auto new_breakpoint = DebugBreakpoint(address, id, true, SoftwareBreakpoint); this->m_debug_breakpoints.push_back(new_breakpoint); return new_breakpoint; @@ -1155,13 +1185,260 @@ 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; + } + + // List all breakpoints to find the ID of the hardware breakpoint at this address + auto result = InvokeBackendCommand("bl"); + + // Parse the breakpoint list to find the ID + // DbgEng breakpoint list format is typically: + // 0 e
+ // 1 r
etc. + std::stringstream ss(result); + std::string line; + + while (std::getline(ss, line)) + { + // Look for lines containing our address + if (line.find(fmt::format("{:x}", address)) != std::string::npos) + { + // Extract breakpoint ID (first number in the line) + std::istringstream iss(line); + std::string id_str; + if (iss >> id_str) + { + try + { + int bp_id = std::stoi(id_str); + // Remove the breakpoint using bc (breakpoint clear) command + auto clear_result = InvokeBackendCommand(fmt::format("bc {}", bp_id)); + return clear_result.find("error") == std::string::npos && + clear_result.find("Error") == std::string::npos; + } + catch (...) + { + // Continue searching if this line doesn't contain a valid ID + continue; + } + } + } + } + + return false; +} + + +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 88b4fd24..2a06492a 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 6fc7cac4..43b54bf7 100644 --- a/core/adapters/esrevenadapter.cpp +++ b/core/adapters/esrevenadapter.cpp @@ -412,6 +412,15 @@ DebugBreakpoint EsrevenAdapter::AddBreakpoint(const std::uintptr_t address, unsi DebugBreakpoint(address)) != this->m_debugBreakpoints.end()) return {}; + // Handle hardware breakpoint types + if (breakpoint_type != SoftwareBreakpoint) + { + if (AddHardwareBreakpoint(address, (DebugBreakpointType)breakpoint_type)) + return DebugBreakpoint(address, 0, true, (DebugBreakpointType)breakpoint_type); + else + return DebugBreakpoint{}; + } + /* TODO: replace %d with the actual breakpoint size as it differs per architecture */ size_t kind = 1; if (m_remoteArch == "aarch64") @@ -422,7 +431,7 @@ DebugBreakpoint EsrevenAdapter::AddBreakpoint(const std::uintptr_t address, unsi if (this->m_rspConnector->TransmitAndReceive(RspData("Z0,{:x},{}", address, kind)).AsString() != "OK" ) return DebugBreakpoint{}; - const auto new_breakpoint = DebugBreakpoint(address, this->m_internalBreakpointId++, true); + const auto new_breakpoint = DebugBreakpoint(address, this->m_internalBreakpointId++, true, SoftwareBreakpoint); this->m_debugBreakpoints.push_back(new_breakpoint); return new_breakpoint; @@ -1199,20 +1208,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; + } - return this->m_rspConnector->TransmitAndReceive(RspData("Z2,{:x},{}", address, 1)).AsString() == "OK"; + std::string command; + switch (type) + { + case HardwareExecuteBreakpoint: + // Z1 = hardware execution breakpoint + command = fmt::format("Z1,{:x},{}", address, size); + break; + case HardwareReadBreakpoint: + // Z3 = hardware read watchpoint + command = fmt::format("Z3,{:x},{}", address, size); + break; + case HardwareWriteBreakpoint: + // Z2 = hardware write watchpoint + command = fmt::format("Z2,{:x},{}", address, size); + break; + case HardwareAccessBreakpoint: + // Z4 = hardware access watchpoint (read/write) + command = fmt::format("Z4,{:x},{}", address, size); + break; + default: + return false; + } + + return m_rspConnector->TransmitAndReceive(RspData(command)).AsString() == "OK"; } -bool EsrevenAdapter::RemoveHardwareWriteBreakpoint(uint64_t address) + +bool EsrevenAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (m_isTargetRunning || !m_rspConnector) + { + // 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 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; + } +} + - return this->m_rspConnector->TransmitAndReceive(RspData("z2,{:x},{}", address, 1)).AsString() == "OK"; +bool EsrevenAdapter::AddHardwareWriteBreakpoint(uint64_t address) +{ + // Delegate to new standardized method + return AddHardwareBreakpoint(address, HardwareWriteBreakpoint, 1); +} + +bool EsrevenAdapter::RemoveHardwareWriteBreakpoint(uint64_t address) +{ + // Delegate to new standardized method + return RemoveHardwareBreakpoint(address, HardwareWriteBreakpoint, 1); } bool EsrevenAdapter::StepReturnReverse() @@ -1720,6 +1857,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 +1873,19 @@ void EsrevenAdapter::CheckApplyPendingBreakpoints() } it++; } + + // Apply pending hardware breakpoints + for (auto it = m_pendingHardwareBreakpoints.begin(); it != m_pendingHardwareBreakpoints.end(); ) + { + if (AddHardwareBreakpoint(it->address, it->type, it->size)) + { + it = m_pendingHardwareBreakpoints.erase(it); + } + else + { + it++; + } + } } diff --git a/core/adapters/esrevenadapter.h b/core/adapters/esrevenadapter.h index 04a5d314..4946d939 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 2dd6c7e1..e1ef5f53 100644 --- a/core/adapters/gdbadapter.cpp +++ b/core/adapters/gdbadapter.cpp @@ -410,6 +410,15 @@ DebugBreakpoint GdbAdapter::AddBreakpoint(const std::uintptr_t address, unsigned DebugBreakpoint(address)) != this->m_debugBreakpoints.end()) return {}; + // Handle hardware breakpoint types + if (breakpoint_type != SoftwareBreakpoint) + { + if (AddHardwareBreakpoint(address, (DebugBreakpointType)breakpoint_type)) + return DebugBreakpoint(address, 0, true, (DebugBreakpointType)breakpoint_type); + else + return DebugBreakpoint{}; + } + /* TODO: replace %d with the actual breakpoint size as it differs per architecture */ size_t kind = 1; if (m_remoteArch == "aarch64") @@ -420,7 +429,7 @@ DebugBreakpoint GdbAdapter::AddBreakpoint(const std::uintptr_t address, unsigned if (this->m_rspConnector->TransmitAndReceive(RspData("Z0,{:x},{}", address, kind)).AsString() != "OK" ) return DebugBreakpoint{}; - const auto new_breakpoint = DebugBreakpoint(address, this->m_internalBreakpointId++, true); + const auto new_breakpoint = DebugBreakpoint(address, this->m_internalBreakpointId++, true, SoftwareBreakpoint); this->m_debugBreakpoints.push_back(new_breakpoint); return new_breakpoint; @@ -1120,20 +1129,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; + } - return this->m_rspConnector->TransmitAndReceive(RspData("Z2,{:x},{}", address, 1)).AsString() != "OK"; + std::string command; + switch (type) + { + case HardwareExecuteBreakpoint: + // Z1 = hardware execution breakpoint + command = fmt::format("Z1,{:x},{}", address, size); + break; + case HardwareReadBreakpoint: + // Z3 = hardware read watchpoint + command = fmt::format("Z3,{:x},{}", address, size); + break; + case HardwareWriteBreakpoint: + // Z2 = hardware write watchpoint + command = fmt::format("Z2,{:x},{}", address, size); + break; + case HardwareAccessBreakpoint: + // Z4 = hardware access watchpoint (read/write) + command = fmt::format("Z4,{:x},{}", address, size); + break; + default: + return false; + } + + return m_rspConnector->TransmitAndReceive(RspData(command)).AsString() == "OK"; } -bool GdbAdapter::RemoveHardwareWriteBreakpoint(uint64_t address) + +bool GdbAdapter::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size) { if (m_isTargetRunning || !m_rspConnector) + { + // 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); +} + +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; + } +} + - return this->m_rspConnector->TransmitAndReceive(RspData("Z2,{:x},{}", address, 1)).AsString() != "OK"; +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 +1550,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 +1566,19 @@ void GdbAdapter::CheckApplyPendingBreakpoints() } it++; } + + // Apply pending hardware breakpoints + for (auto it = m_pendingHardwareBreakpoints.begin(); it != m_pendingHardwareBreakpoints.end(); ) + { + if (AddHardwareBreakpoint(it->address, it->type, it->size)) + { + it = m_pendingHardwareBreakpoints.erase(it); + } + else + { + it++; + } + } } diff --git a/core/adapters/gdbadapter.h b/core/adapters/gdbadapter.h index ed25167a..a97b9073 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 e7362020..68a3ff38 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,190 @@ 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 + std::vector pendingCopy = m_pendingHardwareBreakpoints; + m_pendingHardwareBreakpoints.clear(); + + for (const auto& pending : pendingCopy) + { + if (pending.isRelative) + { + // Module+offset based hardware breakpoint + AddHardwareBreakpoint(pending.location, pending.type, pending.size); + } + else + { + // Absolute address hardware breakpoint + AddHardwareBreakpoint(pending.address, pending.type, pending.size); + } + } +} + // --- Adapter Type Registration --- GdbMiAdapterType::GdbMiAdapterType() : DebugAdapterType("GDB MI") {} @@ -1162,11 +1361,15 @@ Ref GdbMiAdapterType::RegisterAdapterSettings() void GdbMiAdapter::ApplyBreakpoints() { - for (const auto& bp : m_pendingBreakpoints) + // Make a copy and clear the original list - AddBreakpoint will re-add + // any breakpoints that can't be resolved yet + std::vector pendingCopy = m_pendingBreakpoints; + m_pendingBreakpoints.clear(); + + for (const auto& bp : pendingCopy) { AddBreakpoint(bp, 0); } - m_pendingBreakpoints.clear(); } 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 86573bb7..ab8bc4d3 100644 --- a/core/adapters/lldbadapter.cpp +++ b/core/adapters/lldbadapter.cpp @@ -326,6 +326,7 @@ void BinaryNinjaDebugger::InitLldbAdapterType() void LldbAdapter::ApplyBreakpoints() { + // Apply pending software breakpoints immediately - these work fine before process starts for (const auto& bp : m_pendingBreakpoints) { AddBreakpoint(bp); @@ -333,6 +334,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; + } } @@ -917,11 +949,21 @@ 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); } @@ -993,6 +1035,214 @@ 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: + { + // 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 - use LLDB's module-aware syntax + uint64_t addr = location.offset + m_originalImageBase; + std::string command; + + switch (type) + { + case HardwareExecuteBreakpoint: + { + // Use breakpoint set with module and address, plus -H for hardware + command = fmt::format("breakpoint set --shlib \"{}\" --address 0x{:x} -H", location.module, addr); + auto result = InvokeBackendCommand(command); + return result.find("Breakpoint") != std::string::npos; + } + case HardwareReadBreakpoint: + case HardwareWriteBreakpoint: + case HardwareAccessBreakpoint: + { + // For watchpoints, we need to resolve to absolute address first + // LLDB watchpoints don't have direct module+offset syntax + // So we delegate to the absolute address version which will resolve at runtime + return AddHardwareBreakpoint(addr, type, size); + } + default: + return false; + } + } +} + + +bool LldbAdapter::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + if (!m_targetActive) + { + // Target not active - remove from pending list using module+offset + PendingHardwareBreakpoint pending(location, type, size); + + auto it = std::find(m_pendingHardwareBreakpoints.begin(), m_pendingHardwareBreakpoints.end(), pending); + if (it != m_pendingHardwareBreakpoints.end()) + { + m_pendingHardwareBreakpoints.erase(it); + return true; + } + + // Also check deferred list + auto deferredIt = std::find(m_deferredHardwareBreakpoints.begin(), m_deferredHardwareBreakpoints.end(), pending); + if (deferredIt != m_deferredHardwareBreakpoints.end()) + { + m_deferredHardwareBreakpoints.erase(deferredIt); + if (m_deferredHardwareBreakpoints.empty()) + { + m_needsHardwareBreakpointReapplication = false; + } + return true; + } + + return false; + } + else + { + // Target is active - resolve to absolute address and remove + uint64_t address = location.offset + m_originalImageBase; + return RemoveHardwareBreakpoint(address, type, size); + } +} + + static intx::uint512 SBValueToUint512(lldb::SBValue& reg_val) { using namespace lldb; using namespace intx; @@ -1798,6 +2048,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. @@ -1880,22 +2152,14 @@ void LldbAdapter::EventListener() if (module.IsValid()) { SBAddress headerAddress = module.GetObjectFileHeaderAddress(); - uint64_t moduleBase = headerAddress.GetLoadAddress(m_target); - uint64_t bpAddress = location.GetAddress().GetLoadAddress(m_target); - auto fileSpec = module.GetFileSpec(); - char path[1024]; - size_t bytes = fileSpec.GetPath(path, sizeof(path)); DebuggerEvent evt; - evt.type = RelativeBreakpointAddedEvent; - evt.data.relativeAddress.module = std::string(path, bytes); - evt.data.relativeAddress.offset = bpAddress - moduleBase; + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } else { DebuggerEvent evt; - evt.type = AbsoluteBreakpointAddedEvent; - evt.data.absoluteAddress = location.GetAddress().GetLoadAddress(m_target); + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } } @@ -1906,23 +2170,14 @@ void LldbAdapter::EventListener() auto module = address.GetModule(); if (module.IsValid()) { - SBAddress headerAddress = module.GetObjectFileHeaderAddress(); - uint64_t moduleBase = headerAddress.GetLoadAddress(m_target); - uint64_t bpAddress = location.GetAddress().GetLoadAddress(m_target); - auto fileSpec = module.GetFileSpec(); - char path[1024]; - size_t bytes = fileSpec.GetPath(path, sizeof(path)); DebuggerEvent evt; - evt.type = RelativeBreakpointRemovedEvent; - evt.data.relativeAddress.module = std::string(path, bytes); - evt.data.relativeAddress.offset = bpAddress - moduleBase; + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } else { DebuggerEvent evt; - evt.type = AbsoluteBreakpointRemovedEvent; - evt.data.absoluteAddress = location.GetAddress().GetLoadAddress(m_target); + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } } diff --git a/core/adapters/lldbadapter.h b/core/adapters/lldbadapter.h index b974cf57..cb867ced 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. @@ -96,6 +102,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 50d15f80..730cc0ce 100644 --- a/core/adapters/lldbcoredumpadapter.cpp +++ b/core/adapters/lldbcoredumpadapter.cpp @@ -1112,22 +1112,14 @@ void LldbCoreDumpAdapter::EventListener() if (module.IsValid()) { SBAddress headerAddress = module.GetObjectFileHeaderAddress(); - uint64_t moduleBase = headerAddress.GetLoadAddress(m_target); - uint64_t bpAddress = location.GetAddress().GetLoadAddress(m_target); - auto fileSpec = module.GetFileSpec(); - char path[1024]; - size_t bytes = fileSpec.GetPath(path, sizeof(path)); DebuggerEvent evt; - evt.type = RelativeBreakpointAddedEvent; - evt.data.relativeAddress.module = std::string(path, bytes); - evt.data.relativeAddress.offset = bpAddress - moduleBase; + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } else { DebuggerEvent evt; - evt.type = AbsoluteBreakpointAddedEvent; - evt.data.absoluteAddress = location.GetAddress().GetLoadAddress(m_target); + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } } @@ -1138,23 +1130,14 @@ void LldbCoreDumpAdapter::EventListener() auto module = address.GetModule(); if (module.IsValid()) { - SBAddress headerAddress = module.GetObjectFileHeaderAddress(); - uint64_t moduleBase = headerAddress.GetLoadAddress(m_target); - uint64_t bpAddress = location.GetAddress().GetLoadAddress(m_target); - auto fileSpec = module.GetFileSpec(); - char path[1024]; - size_t bytes = fileSpec.GetPath(path, sizeof(path)); DebuggerEvent evt; - evt.type = RelativeBreakpointRemovedEvent; - evt.data.relativeAddress.module = std::string(path, bytes); - evt.data.relativeAddress.offset = bpAddress - moduleBase; + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } else { DebuggerEvent evt; - evt.type = AbsoluteBreakpointRemovedEvent; - evt.data.absoluteAddress = location.GetAddress().GetLoadAddress(m_target); + evt.type = BreakpointChangedEvent; PostDebuggerEvent(evt); } } @@ -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 1989c13a..821b6af5 100644 --- a/core/adapters/lldbcoredumpadapter.h +++ b/core/adapters/lldbcoredumpadapter.h @@ -135,6 +135,12 @@ namespace BinaryNinjaDebugger { bool DisconnectDebugServer() override; + // Hardware breakpoint support - not supported for core dumps + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1) override; + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1) override; + void GenerateDefaultAdapterSettings(BinaryView* data); Ref GetAdapterSettings() override; }; diff --git a/core/debugadapter.cpp b/core/debugadapter.cpp index 1e27bffb..3858ee0d 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 268abb05..b730b0e6 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -118,12 +118,14 @@ namespace BinaryNinjaDebugger { std::uintptr_t m_address {}; unsigned long m_id {}; bool m_is_active {}; + DebugBreakpointType m_type = SoftwareBreakpoint; - DebugBreakpoint(std::uintptr_t address, unsigned long id, bool active) : - m_address(address), m_id(id), m_is_active(active) + DebugBreakpoint(std::uintptr_t address, unsigned long id, bool active, DebugBreakpointType type = SoftwareBreakpoint) : + m_address(address), m_id(id), m_is_active(active), m_type(type) {} - DebugBreakpoint(std::uintptr_t address) : m_address(address) {} + DebugBreakpoint(std::uintptr_t address, DebugBreakpointType type = SoftwareBreakpoint) : + m_address(address), m_type(type) {} DebugBreakpoint() {} @@ -132,6 +134,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 {}; @@ -272,6 +309,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 5e1e37df..e00d42da 100644 --- a/core/debuggercommon.h +++ b/core/debuggercommon.h @@ -175,7 +175,7 @@ namespace BinaryNinjaDebugger { uint64_t size; // Size of the module in bytes uint32_t checksum; // Checksum of the module uint32_t timestamp; // Timestamp of the module - + TTDModule() : address(0), size(0), checksum(0), timestamp(0) {} }; @@ -188,7 +188,7 @@ namespace BinaryNinjaDebugger { TTDPosition lifetimeEnd; // Lifetime end position TTDPosition activeTimeStart; // Active time start position TTDPosition activeTimeEnd; // Active time end position - + TTDThread() : uniqueId(0), id(0) {} }; @@ -208,7 +208,7 @@ namespace BinaryNinjaDebugger { uint32_t flags; // Exception flags uint64_t recordAddress; // Where in memory the exception record is found TTDPosition position; // Position where exception occurred - + TTDException() : type(TTDExceptionSoftware), programCounter(0), code(0), flags(0), recordAddress(0) {} }; @@ -217,13 +217,23 @@ namespace BinaryNinjaDebugger { { TTDEventType type; // Type of event TTDPosition position; // Position where event occurred - + // Optional child objects - existence depends on event type std::optional module; // For ModuleLoaded/ModuleUnloaded events std::optional thread; // For ThreadCreated/ThreadTerminated events std::optional exception; // For Exception events - + TTDEvent() : type(TTDEventThreadCreated) {} TTDEvent(TTDEventType eventType) : type(eventType) {} }; + + // Breakpoint types - used to specify the type of breakpoint to set + enum DebugBreakpointType + { + SoftwareBreakpoint = 0, // Default software breakpoint + HardwareExecuteBreakpoint = 1, // Hardware execution breakpoint + HardwareReadBreakpoint = 2, // Hardware read watchpoint + HardwareWriteBreakpoint = 3, // Hardware write watchpoint + HardwareAccessBreakpoint = 4 // Hardware read/write watchpoint + }; }; // namespace BinaryNinjaDebugger diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 1cadab60..8b0bfa6d 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -64,8 +64,7 @@ void DebuggerController::AddBreakpoint(uint64_t address) { m_state->AddBreakpoint(address); DebuggerEvent event; - event.type = AbsoluteBreakpointAddedEvent; - event.data.absoluteAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -74,8 +73,7 @@ void DebuggerController::AddBreakpoint(const ModuleNameAndOffset& address) { m_state->AddBreakpoint(address); DebuggerEvent event; - event.type = RelativeBreakpointAddedEvent; - event.data.relativeAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -84,8 +82,7 @@ void DebuggerController::DeleteBreakpoint(uint64_t address) { m_state->DeleteBreakpoint(address); DebuggerEvent event; - event.type = AbsoluteBreakpointRemovedEvent; - event.data.absoluteAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -94,8 +91,7 @@ void DebuggerController::DeleteBreakpoint(const ModuleNameAndOffset& address) { m_state->DeleteBreakpoint(address); DebuggerEvent event; - event.type = RelativeBreakpointRemovedEvent; - event.data.relativeAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -104,8 +100,7 @@ void DebuggerController::EnableBreakpoint(uint64_t address) { m_state->EnableBreakpoint(address); DebuggerEvent event; - event.type = AbsoluteBreakpointEnabledEvent; - event.data.absoluteAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -114,8 +109,7 @@ void DebuggerController::EnableBreakpoint(const ModuleNameAndOffset& address) { m_state->EnableBreakpoint(address); DebuggerEvent event; - event.type = RelativeBreakpointEnabledEvent; - event.data.relativeAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -124,8 +118,7 @@ void DebuggerController::DisableBreakpoint(uint64_t address) { m_state->DisableBreakpoint(address); DebuggerEvent event; - event.type = AbsoluteBreakpointDisabledEvent; - event.data.absoluteAddress = address; + event.type = BreakpointChangedEvent; PostDebuggerEvent(event); } @@ -134,12 +127,99 @@ 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::SetIP(uint64_t address) { std::string ipRegisterName; diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index 54d10813..25054273 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -231,8 +231,22 @@ namespace BinaryNinjaDebugger { void EnableBreakpoint(const ModuleNameAndOffset& address); void DisableBreakpoint(uint64_t address); void DisableBreakpoint(const ModuleNameAndOffset& address); + bool ContainsBreakpoint(const ModuleNameAndOffset& address); DebugBreakpoint GetAllBreakpoints(); + // hardware breakpoints + // 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 6153b9e5..d0285621 100644 --- a/core/debuggerstate.cpp +++ b/core/debuggerstate.cpp @@ -513,8 +513,14 @@ std::vector DebuggerModules::GetAllModules() DebuggerBreakpoints::DebuggerBreakpoints(DebuggerState* state, std::vector initial) : - m_state(state), m_breakpoints(std::move(initial)) -{} + m_state(state) +{ + // Convert initial software breakpoints to BreakpointInfo + for (const auto& bp : initial) + { + m_breakpoints.push_back(BreakpointInfo(bp)); + } +} bool DebuggerBreakpoints::AddAbsolute(uint64_t remoteAddress) @@ -533,8 +539,9 @@ bool DebuggerBreakpoints::AddAbsolute(uint64_t remoteAddress) if (!ContainsAbsolute(remoteAddress)) { ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(remoteAddress); - m_breakpoints.push_back(info); - m_enabledState[info] = true; // Enable by default + BreakpointInfo bp(info); + bp.address = remoteAddress; + m_breakpoints.push_back(bp); SerializeMetadata(); } @@ -546,8 +553,7 @@ bool DebuggerBreakpoints::AddOffset(const ModuleNameAndOffset& address) { if (!ContainsOffset(address)) { - m_breakpoints.push_back(address); - m_enabledState[address] = true; // Enable by default + m_breakpoints.push_back(BreakpointInfo(address)); SerializeMetadata(); // If the adapter is already created, we ask it to add the breakpoint. @@ -571,12 +577,12 @@ bool DebuggerBreakpoints::RemoveAbsolute(uint64_t remoteAddress) ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(remoteAddress); if (ContainsOffset(info)) { - auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), info); + BreakpointInfo toFind(info); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); if (iter != m_breakpoints.end()) { m_breakpoints.erase(iter); } - m_enabledState.erase(info); // Remove enabled state SerializeMetadata(); m_state->GetAdapter()->RemoveBreakpoint(remoteAddress); return true; @@ -589,10 +595,10 @@ bool DebuggerBreakpoints::RemoveOffset(const ModuleNameAndOffset& address) { if (ContainsOffset(address)) { - if (auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), address); iter != m_breakpoints.end()) + BreakpointInfo toFind(address); + if (auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); iter != m_breakpoints.end()) m_breakpoints.erase(iter); - m_enabledState.erase(address); // Remove enabled state SerializeMetadata(); if (m_state->GetAdapter() && m_state->IsConnected()) @@ -619,7 +625,13 @@ bool DebuggerBreakpoints::EnableOffset(const ModuleNameAndOffset& address) if (!ContainsOffset(address)) return false; - m_enabledState[address] = true; + // Find and enable the breakpoint + BreakpointInfo toFind(address); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + { + iter->enabled = true; + } SerializeMetadata(); // If connected, make sure the breakpoint is active in the target @@ -645,7 +657,13 @@ bool DebuggerBreakpoints::DisableOffset(const ModuleNameAndOffset& address) if (!ContainsOffset(address)) return false; - m_enabledState[address] = false; + // Find and disable the breakpoint + BreakpointInfo toFind(address); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + { + iter->enabled = false; + } SerializeMetadata(); // If connected, remove the breakpoint from the target but keep it in our list @@ -668,10 +686,11 @@ bool DebuggerBreakpoints::IsEnabledAbsolute(uint64_t address) bool DebuggerBreakpoints::IsEnabledOffset(const ModuleNameAndOffset& address) { - auto iter = m_enabledState.find(address); - if (iter != m_enabledState.end()) - return iter->second; - + BreakpointInfo toFind(address); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + return iter->enabled; + // Default to enabled if not explicitly set return true; } @@ -681,8 +700,9 @@ bool DebuggerBreakpoints::ContainsOffset(const ModuleNameAndOffset& address) { // If there is no backend, then only check if the breakpoint is in the list // This is useful when we deal with the breakpoint before the target is launched + BreakpointInfo toFind(address); if (!m_state->GetAdapter()) - return std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end(); + return std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind) != m_breakpoints.end(); // When the backend is live, convert the relative address to absolute address and check its existence uint64_t absolute = m_state->GetModules()->RelativeAddressToAbsolute(address); @@ -699,26 +719,329 @@ bool DebuggerBreakpoints::ContainsAbsolute(uint64_t address) // Because every ModuleAndOffset can be converted to an absolute address, but there is no guarantee that it works // backward // Well, that is because lldb does not report the size of the loaded libraries, so it is currently screwed up - for (const ModuleNameAndOffset& breakpoint : m_breakpoints) + for (const BreakpointInfo& breakpoint : m_breakpoints) { - uint64_t absolute = m_state->GetModules()->RelativeAddressToAbsolute(breakpoint); - if (absolute == address) + if (breakpoint.IsSoftware()) + { + uint64_t absolute = m_state->GetModules()->RelativeAddressToAbsolute(breakpoint.location); + if (absolute == address) + return true; + } + else if (breakpoint.address == address) + { return true; + } + } + return false; +} + + +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); + BreakpointInfo bp(info, type, size); + bp.address = address; + 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 BreakpointInfo& breakpoint : m_breakpoints) + { + if (breakpoint.IsHardware() && breakpoint.type == type && breakpoint.size == size) + { + if (breakpoint.isRelative) + { + // Convert module+offset to absolute address and compare + uint64_t absolute = m_state->GetModules()->RelativeAddressToAbsolute(breakpoint.location); + if (absolute == address) + return true; + } + else if (breakpoint.address == address) + { + return true; + } + } } return false; } +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) + BreakpointInfo bp(location, type, size); // Uses the constructor for module+offset + m_breakpoints.push_back(bp); + SerializeMetadata(); + + return true; +} + + +bool DebuggerBreakpoints::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + BreakpointInfo toFind(location, type, size); + + // Remove from our list + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + { + m_breakpoints.erase(iter); + SerializeMetadata(); + } + + // Remove from the adapter if connected + if (m_state->GetAdapter() && m_state->IsConnected()) + { + // Call adapter with module+offset directly - adapter will handle resolution + return m_state->GetAdapter()->RemoveHardwareBreakpoint(location, type, size); + } + + return true; +} + + +bool DebuggerBreakpoints::ContainsHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + BreakpointInfo toFind(location, type, size); + return std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind) != m_breakpoints.end(); +} + + +bool DebuggerBreakpoints::EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + if (!ContainsHardwareBreakpoint(location, type, size)) + return false; + + // Find and enable the hardware breakpoint + BreakpointInfo toFind(location, type, size); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + { + iter->enabled = true; + } + SerializeMetadata(); + + // If connected, make sure the breakpoint is active in the target + if (m_state->GetAdapter() && m_state->IsConnected()) + { + // Call adapter with module+offset directly - adapter will handle resolution + return m_state->GetAdapter()->AddHardwareBreakpoint(location, type, size); + } + return true; +} + + +bool DebuggerBreakpoints::DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size) +{ + if (!ContainsHardwareBreakpoint(location, type, size)) + return false; + + // Find and disable the hardware breakpoint + BreakpointInfo toFind(location, type, size); + auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), toFind); + if (iter != m_breakpoints.end()) + { + iter->enabled = false; + } + SerializeMetadata(); + + // If connected, remove the breakpoint from the target but keep it in our list + if (m_state->GetAdapter() && m_state->IsConnected()) + { + // Call adapter with module+offset directly - adapter will handle resolution + return m_state->GetAdapter()->RemoveHardwareBreakpoint(location, type, size); + } + return true; +} + + +std::vector DebuggerBreakpoints::GetSoftwareBreakpointList() const +{ + std::vector result; + for (const BreakpointInfo& bp : m_breakpoints) + { + if (bp.IsSoftware()) + { + result.push_back(bp.location); + } + } + return result; +} + + void DebuggerBreakpoints::SerializeMetadata() { // TODO: who should free these Metadata objects? + // Only serialize software breakpoints (hardware breakpoints are transient) std::vector> breakpoints; - for (const ModuleNameAndOffset& bp : m_breakpoints) + for (const BreakpointInfo& bp : m_breakpoints) { - std::map> info; - info["module"] = new Metadata(bp.module); - info["offset"] = new Metadata(bp.offset); - breakpoints.push_back(new Metadata(info)); + if (bp.IsSoftware()) + { + std::map> info; + info["module"] = new Metadata(bp.location.module); + info["offset"] = new Metadata(bp.location.offset); + breakpoints.push_back(new Metadata(info)); + } } m_state->GetController()->GetData()->StoreMetadata("debugger.breakpoints", new Metadata(breakpoints)); } @@ -731,7 +1054,7 @@ void DebuggerBreakpoints::UnserializedMetadata() return; vector> array = metadata->GetArray(); - std::vector newBreakpoints; + std::vector newBreakpoints; for (auto& element : array) { @@ -750,7 +1073,7 @@ void DebuggerBreakpoints::UnserializedMetadata() continue; address.offset = info["offset"]->GetUnsignedInteger(); - newBreakpoints.push_back(address); + newBreakpoints.push_back(BreakpointInfo(address)); } m_breakpoints = newBreakpoints; @@ -762,8 +1085,32 @@ void DebuggerBreakpoints::Apply() if (!m_state->GetAdapter()) return; - for (const ModuleNameAndOffset& address : m_breakpoints) - m_state->GetAdapter()->AddBreakpoint(address); + for (const BreakpointInfo& bp : m_breakpoints) + { + if (bp.IsSoftware()) + { + // Software breakpoints always use module+offset + m_state->GetAdapter()->AddBreakpoint(bp.location); + } + else + { + // Hardware breakpoints - only add if enabled + if (!bp.enabled) + continue; + + // Hardware breakpoints can use either module+offset or absolute address + if (bp.isRelative) + { + // Use module+offset - adapter will handle resolution + m_state->GetAdapter()->AddHardwareBreakpoint(bp.location, bp.type, bp.size); + } + else + { + // Use absolute address + m_state->GetAdapter()->AddHardwareBreakpoint(bp.address, bp.type, bp.size); + } + } + } } @@ -1078,6 +1425,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 065edf1d..df92cf67 100644 --- a/core/debuggerstate.h +++ b/core/debuggerstate.h @@ -78,12 +78,65 @@ namespace BinaryNinjaDebugger { }; + // Unified structure to track both software and hardware breakpoints + struct BreakpointInfo + { + ModuleNameAndOffset location; // Module + offset (for relative addressing) + uint64_t address; // Absolute address (for absolute addressing or resolved relative) + DebugBreakpointType type; // Breakpoint type (Software, HardwareExecute, etc.) + size_t size; // Size for hardware watchpoints + bool enabled; // Enabled state + bool isRelative; // True if using module+offset, false if using absolute address + + // Create a software breakpoint (always relative) + BreakpointInfo(const ModuleNameAndOffset& loc) + : location(loc), address(0), type(SoftwareBreakpoint), size(1), enabled(true), isRelative(true) {} + + // Create a hardware breakpoint with absolute address + BreakpointInfo(uint64_t addr, DebugBreakpointType bpType, size_t bpSize) + : location(), address(addr), type(bpType), size(bpSize), enabled(true), isRelative(false) {} + + // Create a hardware breakpoint with module+offset (ASLR-safe) + BreakpointInfo(const ModuleNameAndOffset& loc, DebugBreakpointType bpType, size_t bpSize) + : location(loc), address(0), type(bpType), size(bpSize), enabled(true), isRelative(true) {} + + bool IsSoftware() const { return type == SoftwareBreakpoint; } + bool IsHardware() const { return type != SoftwareBreakpoint; } + + bool operator==(const BreakpointInfo& other) const + { + if (type != other.type) return false; + if (size != other.size) return false; + if (isRelative != other.isRelative) return false; + + // For relative addressing, compare module+offset + if (isRelative) + return location == other.location; + // For absolute addressing, compare address + else + return address == other.address; + } + + bool operator<(const BreakpointInfo& other) const + { + if (type != other.type) return type < other.type; + if (size != other.size) return size < other.size; + if (isRelative != other.isRelative) return isRelative < other.isRelative; + + // For relative addressing, compare module+offset + if (isRelative) + return location < other.location; + // For absolute addressing, compare address + else + return address < other.address; + } + }; + class DebuggerBreakpoints { private: DebuggerState* m_state; - std::vector m_breakpoints; - std::map m_enabledState; + std::vector m_breakpoints; public: DebuggerBreakpoints(DebuggerState* state, std::vector initial = {}); @@ -102,7 +155,27 @@ namespace BinaryNinjaDebugger { void Apply(); void SerializeMetadata(); void UnserializedMetadata(); - std::vector GetBreakpointList() const { return m_breakpoints; } + + // Get all breakpoints (both software and hardware) + std::vector GetBreakpointList() const { return m_breakpoints; } + + // Get only software breakpoints (for backward compatibility) + std::vector GetSoftwareBreakpointList() const; + + // Hardware breakpoint methods + // Hardware breakpoint methods - absolute address + bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + bool ContainsHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size); + + // Hardware breakpoint methods - module+offset (ASLR-safe) + bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); + bool ContainsHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size); }; @@ -250,6 +323,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 cb3c082e..b8f8ec5a 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -802,7 +802,7 @@ void BNDebuggerSetCommandLineArguments(BNDebuggerController* controller, const c BNDebugBreakpoint* BNDebuggerGetBreakpoints(BNDebuggerController* controller, size_t* count) { DebuggerState* state = controller->object->GetState(); - std::vector breakpoints = state->GetBreakpoints()->GetBreakpointList(); + std::vector breakpoints = state->GetBreakpoints()->GetBreakpointList(); *count = breakpoints.size(); //std::vector remoteList; @@ -812,12 +812,23 @@ BNDebugBreakpoint* BNDebuggerGetBreakpoints(BNDebuggerController* controller, si BNDebugBreakpoint* result = new BNDebugBreakpoint[breakpoints.size()]; for (size_t i = 0; i < breakpoints.size(); i++) { - uint64_t remoteAddress = state->GetModules()->RelativeAddressToAbsolute(breakpoints[i]); - bool enabled = state->GetBreakpoints()->IsEnabledOffset(breakpoints[i]); - result[i].module = BNDebuggerAllocString(breakpoints[i].module.c_str()); - result[i].offset = breakpoints[i].offset; + uint64_t remoteAddress; + if (breakpoints[i].isRelative) + { + // For relative addressing (both software and hardware), convert module+offset to absolute address + remoteAddress = state->GetModules()->RelativeAddressToAbsolute(breakpoints[i].location); + } + else + { + // For absolute addressing, use the stored address directly + remoteAddress = breakpoints[i].address; + } + result[i].module = BNDebuggerAllocString(breakpoints[i].location.module.c_str()); + result[i].offset = breakpoints[i].location.offset; result[i].address = remoteAddress; - result[i].enabled = enabled; + result[i].enabled = breakpoints[i].enabled; + result[i].type = (BNDebugBreakpointType)breakpoints[i].type; + result[i].size = breakpoints[i].size; } return result; } @@ -915,15 +926,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)); +} - DebuggerBreakpoints* breakpoints = state->GetBreakpoints(); - if (!breakpoints) - return false; - return breakpoints->ContainsOffset(ModuleNameAndOffset(module, offset)); +bool BNDebuggerAddHardwareBreakpoint(BNDebuggerController* controller, uint64_t address, BNDebugBreakpointType type, size_t size) +{ + return controller->object->AddHardwareBreakpoint(address, (DebugBreakpointType)type, size); +} + + +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/examples/hardware_breakpoints.py b/docs/examples/hardware_breakpoints.py new file mode 100644 index 00000000..09c2148d --- /dev/null +++ b/docs/examples/hardware_breakpoints.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +""" +Example script demonstrating hardware breakpoint usage + +This script shows how to use the new hardware breakpoint functionality +introduced in the debugger. +""" + +try: + from binaryninja import load + from debugger import DebuggerController, DebugBreakpointType +except ImportError: + from binaryninja import load + from binaryninja.debugger import DebuggerController, DebugBreakpointType + + +def hardware_breakpoint_example(binary_path): + """ + Example showing how to use hardware breakpoints + """ + # Load the binary + bv = load(binary_path) + if not bv: + print(f"Failed to load binary: {binary_path}") + return + + # Get the debugger controller + controller = DebuggerController(bv) + + print("Setting up hardware breakpoints...") + + # Example 1: Hardware execution breakpoint at entry point + entry_point = bv.entry_point + print(f"Setting hardware execution breakpoint at entry point: 0x{entry_point:x}") + success = controller.add_hardware_breakpoint( + entry_point, + DebugBreakpointType.HardwareExecuteBreakpoint + ) + if success: + print("✓ Hardware execution breakpoint set successfully") + else: + print("✗ Failed to set hardware execution breakpoint") + + # Example 2: Hardware write watchpoint on a data address + # In a real scenario, you'd find a data address from your binary analysis + data_address = 0x1000 # Example address + print(f"Setting hardware write watchpoint at 0x{data_address:x} (4 bytes)") + success = controller.add_hardware_breakpoint( + data_address, + DebugBreakpointType.HardwareWriteBreakpoint, + 4 # Watch 4 bytes + ) + if success: + print("✓ Hardware write watchpoint set successfully") + else: + print("✗ Failed to set hardware write watchpoint") + + # Example 3: Hardware read watchpoint + print(f"Setting hardware read watchpoint at 0x{data_address + 8:x} (8 bytes)") + success = controller.add_hardware_breakpoint( + data_address + 8, + DebugBreakpointType.HardwareReadBreakpoint, + 8 # Watch 8 bytes + ) + if success: + print("✓ Hardware read watchpoint set successfully") + else: + print("✗ Failed to set hardware read watchpoint") + + # Example 4: Hardware access (read/write) watchpoint + print(f"Setting hardware access watchpoint at 0x{data_address + 16:x} (1 byte)") + success = controller.add_hardware_breakpoint( + data_address + 16, + DebugBreakpointType.HardwareAccessBreakpoint, + 1 # Watch 1 byte + ) + if success: + print("✓ Hardware access watchpoint set successfully") + else: + print("✗ Failed to set hardware access watchpoint") + + print("\nLaunching target...") + stop_reason = controller.launch_and_wait() + print(f"Target stopped with reason: {stop_reason}") + + # Continue execution to test breakpoints + print("Continuing execution...") + stop_reason = controller.go_and_wait() + print(f"Target stopped with reason: {stop_reason}") + + # Clean up - remove hardware breakpoints + print("\nCleaning up hardware breakpoints...") + + controller.remove_hardware_breakpoint( + entry_point, + DebugBreakpointType.HardwareExecuteBreakpoint + ) + + controller.remove_hardware_breakpoint( + data_address, + DebugBreakpointType.HardwareWriteBreakpoint, + 4 + ) + + controller.remove_hardware_breakpoint( + data_address + 8, + DebugBreakpointType.HardwareReadBreakpoint, + 8 + ) + + controller.remove_hardware_breakpoint( + data_address + 16, + DebugBreakpointType.HardwareAccessBreakpoint, + 1 + ) + + print("Hardware breakpoints removed") + + # Quit the debugger + controller.quit_and_wait() + print("Debugging session ended") + + +def backend_command_example(binary_path): + """ + Example showing how to use hardware breakpoints via backend commands + (useful for advanced scenarios or when the API is not sufficient) + """ + bv = load(binary_path) + controller = DebuggerController(bv) + + print("Using backend commands for hardware breakpoints...") + + # Launch the target first + controller.launch_and_wait() + + # For LLDB adapter: + if controller.get_adapter_type() == "LLDB": + print("Using LLDB commands:") + + # Hardware execution breakpoint + result = controller.send_command("breakpoint set --address 0x100000000 -H") + print(f"LLDB hardware execution breakpoint: {result}") + + # Hardware write watchpoint + result = controller.send_command("watchpoint set expression -w write -s 4 -- 0x100001000") + print(f"LLDB hardware write watchpoint: {result}") + + # List breakpoints and watchpoints + result = controller.send_command("breakpoint list") + print(f"LLDB breakpoints: {result}") + + result = controller.send_command("watchpoint list") + print(f"LLDB watchpoints: {result}") + + # For GDB RSP adapter: + elif "GDB" in controller.get_adapter_type(): + print("Using GDB RSP commands:") + + # Hardware execution breakpoint (Z1) + result = controller.send_command("Z1,100000000,1") + print(f"GDB hardware execution breakpoint: {result}") + + # Hardware write watchpoint (Z2) + result = controller.send_command("Z2,100001000,4") + print(f"GDB hardware write watchpoint: {result}") + + controller.quit_and_wait() + + +if __name__ == "__main__": + import sys + + if len(sys.argv) != 2: + print("Usage: python hardware_breakpoints.py ") + sys.exit(1) + + binary_path = sys.argv[1] + + print("=== Hardware Breakpoint API Example ===") + hardware_breakpoint_example(binary_path) + + print("\n=== Backend Command Example ===") + backend_command_example(binary_path) \ No newline at end of file diff --git a/docs/guide/index.md b/docs/guide/index.md index 7d8f4d6f..25d498af 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -512,9 +512,44 @@ dbg.execute_backend_command('image list') ### Hardware Breakpoints/Watchpoints -Hardware breakpoints and watchpoints are very useful and we plan to add better support for it soon. It is tracked by -this [issue](https://github.com/Vector35/debugger/issues/53). For now, we can run a backend command directly to set -hardware breakpoints/watchpoints. +Hardware breakpoints and watchpoints are now supported through both the debugger API and direct backend commands. + +#### Using the Debugger API + +Hardware breakpoints can be set using the following methods in Python: + +```python +from debugger import DebuggerController, DebugBreakpointType + +# Get the controller for your binary view +controller = DebuggerController(bv) + +# Set hardware execution breakpoint +controller.add_hardware_breakpoint(0x12345678, DebugBreakpointType.HardwareExecuteBreakpoint) + +# Set hardware read watchpoint (1 byte) +controller.add_hardware_breakpoint(0x12345678, DebugBreakpointType.HardwareReadBreakpoint, 1) + +# Set hardware write watchpoint (4 bytes) +controller.add_hardware_breakpoint(0x12345678, DebugBreakpointType.HardwareWriteBreakpoint, 4) + +# Set hardware access (read/write) watchpoint (8 bytes) +controller.add_hardware_breakpoint(0x12345678, DebugBreakpointType.HardwareAccessBreakpoint, 8) + +# Remove hardware breakpoint +controller.remove_hardware_breakpoint(0x12345678, DebugBreakpointType.HardwareExecuteBreakpoint) +``` + +The supported breakpoint types are: +- `SoftwareBreakpoint`: Regular software breakpoint (default) +- `HardwareExecuteBreakpoint`: Hardware execution breakpoint +- `HardwareReadBreakpoint`: Hardware read watchpoint +- `HardwareWriteBreakpoint`: Hardware write watchpoint +- `HardwareAccessBreakpoint`: Hardware read/write watchpoint + +#### Using Backend Commands + +For cases where you need more control or the API is not available, you can use backend commands directly. #### WinDbg/DbgEng diff --git a/hardware_breakpoint_dialog.png b/hardware_breakpoint_dialog.png new file mode 100644 index 00000000..89694aad Binary files /dev/null and b/hardware_breakpoint_dialog.png differ diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index 83c6b8d8..f0de3850 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -19,11 +19,16 @@ limitations under the License. #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" @@ -32,11 +37,31 @@ using namespace BinaryNinjaDebuggerAPI; using namespace BinaryNinja; using namespace std; -BreakpointItem::BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t address) : - m_enabled(enabled), m_location(location), m_address(address) +BreakpointItem::BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t address, DebugBreakpointType type, size_t size) : + m_enabled(enabled), m_location(location), m_address(address), 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 { return (m_enabled == other.enabled()) && (m_location == other.location()) && (m_address == other.address()); @@ -100,13 +125,17 @@ QVariant DebugBreakpointsListModel::data(const QModelIndex& index, int role) con if (!item) return QVariant(); - if ((role != Qt::DisplayRole) && (role != Qt::SizeHintRole)) + if ((role != Qt::DisplayRole) && (role != Qt::SizeHintRole) && (role != Qt::ToolTipRole)) return QVariant(); switch (index.column()) { case DebugBreakpointsListModel::EnabledColumn: { + if (role == Qt::ToolTipRole) + { + return item->enabled() ? "Breakpoint is enabled" : "Breakpoint is disabled"; + } QString text = item->enabled() ? "☑" : "☐"; return QVariant(text); } @@ -138,6 +167,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); + } } return QVariant(); } @@ -154,11 +210,13 @@ 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::TypeColumn: + return "Type"; } return QVariant(); } @@ -199,8 +257,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()); @@ -262,7 +342,10 @@ DebugBreakpointsWidget::DebugBreakpointsWidget(ViewFrame* view, BinaryViewRef da resizeColumnsToContents(); resizeRowsToContents(); - horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + + // Make the enabled column minimal width, and stretch the location column instead + horizontalHeader()->setSectionResizeMode(DebugBreakpointsListModel::EnabledColumn, QHeaderView::ResizeToContents); + horizontalHeader()->setSectionResizeMode(DebugBreakpointsListModel::LocationColumn, QHeaderView::Stretch); m_actionHandler.setupActionHandler(this); m_contextMenuManager = new ContextMenuManager(this); @@ -290,7 +373,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")); @@ -362,11 +450,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 } @@ -425,6 +536,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) @@ -462,85 +608,213 @@ 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; // Get the selected breakpoint location BreakpointItem selectedBp = m_model->getRow(sel[0].row()); - + // Disable all breakpoints first std::vector breakpoints = m_controller->GetBreakpoints(); for (const DebugBreakpoint& bp : breakpoints) { - ModuleNameAndOffset info; - info.module = bp.module; - info.offset = bp.offset; - m_controller->DisableBreakpoint(info); + if (bp.type == SoftwareBreakpoint) + { + ModuleNameAndOffset info; + info.module = bp.module; + info.offset = bp.offset; + m_controller->DisableBreakpoint(info); + } + else + { + // 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()); + } } 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()); + } + } } @@ -554,7 +828,7 @@ void DebugBreakpointsWidget::updateContent() ModuleNameAndOffset info; info.module = bp.module; info.offset = bp.offset; - bps.emplace_back(bp.enabled, info, bp.address); + bps.emplace_back(bp.enabled, info, bp.address, bp.type, bp.size); } m_model->updateRows(bps); diff --git a/ui/breakpointswidget.h b/ui/breakpointswidget.h index 2df230a8..4b3cec5e 100644 --- a/ui/breakpointswidget.h +++ b/ui/breakpointswidget.h @@ -39,12 +39,17 @@ class BreakpointItem bool m_enabled; ModuleNameAndOffset m_location; uint64_t m_address; + DebugBreakpointType m_type; + size_t m_size; // Size in bytes for hardware breakpoints/watchpoints public: - BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t remoteAddress); + BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t remoteAddress, DebugBreakpointType type = SoftwareBreakpoint, size_t size = 1); bool enabled() const { return m_enabled; } ModuleNameAndOffset location() const { return m_location; } uint64_t address() const { return m_address; } + DebugBreakpointType type() const { return m_type; } + size_t size() const { return m_size; } + std::string typeString() const; bool operator==(const BreakpointItem& other) const; bool operator!=(const BreakpointItem& other) const; bool operator<(const BreakpointItem& other) const; @@ -68,6 +73,7 @@ class DebugBreakpointsListModel : public QAbstractTableModel EnabledColumn, LocationColumn, AddressColumn, + TypeColumn, }; DebugBreakpointsListModel(QWidget* parent, ViewFrame* view); @@ -83,7 +89,7 @@ class DebugBreakpointsListModel : public QAbstractTableModel virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override { (void)parent; - return 3; + return 4; } BreakpointItem getRow(int row) const; virtual QVariant data(const QModelIndex& i, int role) const override; @@ -149,6 +155,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 f2cf4d3e..1c90439b 100644 --- a/ui/debuggerwidget.cpp +++ b/ui/debuggerwidget.cpp @@ -127,14 +127,7 @@ void DebuggerWidget::uiEventHandler(const DebuggerEvent& event) case RegisterChangedEvent: updateContent(); break; - case RelativeBreakpointAddedEvent: - case AbsoluteBreakpointAddedEvent: - case RelativeBreakpointRemovedEvent: - case AbsoluteBreakpointRemovedEvent: - case AbsoluteBreakpointEnabledEvent: - case RelativeBreakpointEnabledEvent: - case AbsoluteBreakpointDisabledEvent: - case RelativeBreakpointDisabledEvent: + case BreakpointChangedEvent: m_breakpointsWidget->updateContent(); break; default: diff --git a/ui/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 8abe3114..ecc26f12 100644 --- a/ui/renderlayer.cpp +++ b/ui/renderlayer.cpp @@ -38,12 +38,14 @@ void DebuggerRenderLayer::ApplyToBlock(Ref block, std::vectorIP(); bool paused = controller->GetTargetStatus() == DebugAdapterPausedStatus; - // Get all breakpoints with their enabled state + // Get all breakpoints with their enabled state and type std::vector breakpoints = controller->GetBreakpoints(); std::map breakpointEnabledMap; + std::map breakpointTypeMap; for (const auto& bp : breakpoints) { breakpointEnabledMap[bp.address] = bp.enabled; + breakpointTypeMap[bp.address] = bp.type; } for (auto& line : lines) @@ -55,9 +57,17 @@ void DebuggerRenderLayer::ApplyToBlock(Ref block, std::vector 0) { + // Check if it's a hardware breakpoint + if (breakpointTypeMap.count(line.addr) > 0) + { + DebugBreakpointType type = breakpointTypeMap[line.addr]; + isHardwareBreakpoint = (type != SoftwareBreakpoint); + } + if (breakpointEnabledMap[line.addr]) hasEnabledBreakpoint = true; else @@ -66,19 +76,21 @@ void DebuggerRenderLayer::ApplyToBlock(Ref block, std::vector block, std::vector block, std::vector block, std::vector function, std::ve uint64_t ipAddr = controller->IP(); bool paused = controller->GetTargetStatus() == DebugAdapterPausedStatus; - // Get all breakpoints with their enabled state + // Get all breakpoints with their enabled state and type std::vector breakpoints = controller->GetBreakpoints(); std::map breakpointEnabledMap; + std::map breakpointTypeMap; for (const auto& bp : breakpoints) { breakpointEnabledMap[bp.address] = bp.enabled; + breakpointTypeMap[bp.address] = bp.type; } for (auto& linearLine : lines) @@ -221,9 +241,17 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve bool hasPC = (line.addr == ipAddr) && paused; bool hasEnabledBreakpoint = false; bool hasDisabledBreakpoint = false; - + bool isHardwareBreakpoint = false; + if (breakpointEnabledMap.count(line.addr) > 0) { + // Check if it's a hardware breakpoint + if (breakpointTypeMap.count(line.addr) > 0) + { + DebugBreakpointType type = breakpointTypeMap[line.addr]; + isHardwareBreakpoint = (type != SoftwareBreakpoint); + } + if (breakpointEnabledMap[line.addr]) hasEnabledBreakpoint = true; else @@ -232,19 +260,21 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve if (hasPC && hasEnabledBreakpoint) { + // Use different icons for software vs hardware breakpoints + std::string icon = isHardwareBreakpoint ? "🔶➞" : "🛑➞"; bool appliedTag = false; for (size_t i = 0; i < line.tokens.size(); i++) { if (line.tokens[i].type == TagToken) { - line.tokens[i].text = "🛑➞"; + line.tokens[i].text = icon; appliedTag = true; break; } } if (!appliedTag) { - InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, "🛑➞"); + InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, icon); line.tokens.insert(line.tokens.begin(), indicator); } @@ -259,20 +289,21 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve } else if (hasPC && hasDisabledBreakpoint) { - // PC at a disabled breakpoint - show both indicators, no breakpoint highlighting + // PC at a disabled breakpoint - show both indicators using different icons for software vs hardware + std::string icon = isHardwareBreakpoint ? "◇➞" : "⭕➞"; bool appliedTag = false; for (size_t i = 0; i < line.tokens.size(); i++) { if (line.tokens[i].type == TagToken) { - line.tokens[i].text = "⭘➞"; + line.tokens[i].text = icon; appliedTag = true; break; } } if (!appliedTag) { - InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, "⭘➞"); + InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, icon); line.tokens.insert(line.tokens.begin(), indicator); } @@ -314,19 +345,22 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve } else if (hasEnabledBreakpoint) { + // Use different icons for software vs hardware breakpoints + std::string icon = isHardwareBreakpoint ? "🔶" : "🛑"; + std::string iconWithEllipsis = isHardwareBreakpoint ? "…🔶" : "…🛑"; bool appliedTag = false; for (size_t i = 0; i < line.tokens.size(); i++) { if (line.tokens[i].type == TagToken) { - line.tokens[i].text = "…🛑"; + line.tokens[i].text = iconWithEllipsis; appliedTag = true; break; } } if (!appliedTag) { - InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, "🛑"); + InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, icon); line.tokens.insert(line.tokens.begin(), indicator); } @@ -341,20 +375,22 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve } else if (hasDisabledBreakpoint) { - // Disabled breakpoint - show tag but no line highlighting + // Disabled breakpoint - use different icons for software vs hardware, no line highlighting + std::string icon = isHardwareBreakpoint ? "◇" : "⭕"; + std::string iconWithEllipsis = isHardwareBreakpoint ? "…◇" : "…⭕"; bool appliedTag = false; for (size_t i = 0; i < line.tokens.size(); i++) { if (line.tokens[i].type == TagToken) { - line.tokens[i].text = "…⭘"; + line.tokens[i].text = iconWithEllipsis; appliedTag = true; break; } } if (!appliedTag) { - InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, "⭘"); + InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, icon); line.tokens.insert(line.tokens.begin(), indicator); } // No line highlighting for disabled breakpoints diff --git a/ui/ui.cpp b/ui/ui.cpp index e3423145..e38e8bdd 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -17,6 +17,7 @@ limitations under the License. #include "ui.h" #include "binaryninjaapi.h" #include "breakpointswidget.h" +#include "hardwarebreakpointdialog.h" #include "moduleswidget.h" #include "renderlayer.h" #include "uinotification.h" @@ -927,6 +928,24 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) })); debuggerMenu->addAction("Solo Breakpoint", "Breakpoint"); + // Register "Add Hardware Breakpoint" action + UIAction::registerAction("Add Hardware Breakpoint...", 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( @@ -1398,14 +1417,9 @@ DebuggerUI::DebuggerUI(UIContext* context, DebuggerControllerRef controller) : // Since the Controller is constructed earlier than the UI, any breakpoints added before the construction of the UI, // e.g. the entry point breakpoint, will be missing the visual indicator. // Here, we forcibly add them. - for (auto bp : m_controller->GetBreakpoints()) - { - DebuggerEvent event; - event.type = RelativeBreakpointAddedEvent; - event.data.relativeAddress.module = bp.module; - event.data.relativeAddress.offset = bp.offset; - updateUI(event); - } + DebuggerEvent event; + event.type = BreakpointChangedEvent; + updateUI(event); m_uiCallbacks = new DebuggerUICallbacks; m_uiCallbacks->rebaseBinaryViewImpl = [&](uint64_t address) @@ -1732,14 +1746,7 @@ void DebuggerUI::updateUI(const DebuggerEvent& event) break; } - case RelativeBreakpointAddedEvent: - case AbsoluteBreakpointAddedEvent: - case RelativeBreakpointRemovedEvent: - case AbsoluteBreakpointRemovedEvent: - case RelativeBreakpointEnabledEvent: - case AbsoluteBreakpointEnabledEvent: - case RelativeBreakpointDisabledEvent: - case AbsoluteBreakpointDisabledEvent: + case BreakpointChangedEvent: { m_context->refreshCurrentViewContents(); break; diff --git a/ui/uinotification.cpp b/ui/uinotification.cpp index 79f4d7cb..1ab704c9 100644 --- a/ui/uinotification.cpp +++ b/ui/uinotification.cpp @@ -178,6 +178,7 @@ void NotificationListener::OnContextMenuCreated(UIContext *context, View* view, menu.addAction("Debugger", "Toggle Breakpoint", "Breakpoint"); menu.addAction("Debugger", "Enable Breakpoint", "Breakpoint"); menu.addAction("Debugger", "Solo Breakpoint", "Breakpoint"); + menu.addAction("Debugger", "Add Hardware Breakpoint...", "Breakpoint"); menu.addAction("Debugger", "Launch", "Control"); menu.addAction("Debugger", "Pause", "Control"); menu.addAction("Debugger", "Restart", "Control");