diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 48e33f31..84bd78a7 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -360,6 +360,7 @@ namespace BinaryNinjaDebuggerAPI { uint64_t offset; uint64_t address; bool enabled; + std::string condition; }; @@ -725,6 +726,10 @@ namespace BinaryNinjaDebuggerAPI { void DisableBreakpoint(const ModuleNameAndOffset& breakpoint); bool ContainsBreakpoint(uint64_t address); bool ContainsBreakpoint(const ModuleNameAndOffset& breakpoint); + bool SetBreakpointCondition(uint64_t address, const std::string& condition); + bool SetBreakpointCondition(const ModuleNameAndOffset& address, const std::string& condition); + std::string GetBreakpointCondition(uint64_t address); + std::string GetBreakpointCondition(const ModuleNameAndOffset& address); uint64_t IP(); uint64_t GetLastIP(); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index 944c8c1e..08c6ab62 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -731,6 +731,7 @@ std::vector DebuggerController::GetBreakpoints() bp.offset = breakpoints[i].offset; bp.address = breakpoints[i].address; bp.enabled = breakpoints[i].enabled; + bp.condition = breakpoints[i].condition ? breakpoints[i].condition : ""; result[i] = bp; } @@ -799,6 +800,36 @@ bool DebuggerController::ContainsBreakpoint(const ModuleNameAndOffset& breakpoin } +bool DebuggerController::SetBreakpointCondition(uint64_t address, const std::string& condition) +{ + return BNDebuggerSetBreakpointConditionAbsolute(m_object, address, condition.c_str()); +} + + +bool DebuggerController::SetBreakpointCondition(const ModuleNameAndOffset& address, const std::string& condition) +{ + return BNDebuggerSetBreakpointConditionRelative(m_object, address.module.c_str(), address.offset, condition.c_str()); +} + + +std::string DebuggerController::GetBreakpointCondition(uint64_t address) +{ + char* condition = BNDebuggerGetBreakpointConditionAbsolute(m_object, address); + std::string result = condition ? condition : ""; + BNDebuggerFreeString(condition); + return result; +} + + +std::string DebuggerController::GetBreakpointCondition(const ModuleNameAndOffset& address) +{ + char* condition = BNDebuggerGetBreakpointConditionRelative(m_object, address.module.c_str(), address.offset); + std::string result = condition ? condition : ""; + BNDebuggerFreeString(condition); + return result; +} + + 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..b3145e51 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -132,6 +132,7 @@ extern "C" uint64_t offset; uint64_t address; bool enabled; + char* condition; // NULL if no condition } BNDebugBreakpoint; @@ -256,6 +257,8 @@ extern "C" RelativeBreakpointEnabledEvent, AbsoluteBreakpointDisabledEvent, RelativeBreakpointDisabledEvent, + AbsoluteBreakpointConditionChangedEvent, + RelativeBreakpointConditionChangedEvent, ActiveThreadChangedEvent, @@ -597,6 +600,15 @@ extern "C" DEBUGGER_FFI_API bool BNDebuggerContainsRelativeBreakpoint( BNDebuggerController* controller, const char* module, uint64_t offset); + DEBUGGER_FFI_API bool BNDebuggerSetBreakpointConditionAbsolute( + BNDebuggerController* controller, uint64_t address, const char* condition); + DEBUGGER_FFI_API bool BNDebuggerSetBreakpointConditionRelative( + BNDebuggerController* controller, const char* module, uint64_t offset, const char* condition); + DEBUGGER_FFI_API char* BNDebuggerGetBreakpointConditionAbsolute( + BNDebuggerController* controller, uint64_t address); + DEBUGGER_FFI_API char* BNDebuggerGetBreakpointConditionRelative( + BNDebuggerController* controller, const char* module, uint64_t offset); + 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..586f2fd5 100644 --- a/api/python/debuggercontroller.py +++ b/api/python/debuggercontroller.py @@ -368,13 +368,15 @@ 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) + * ``condition``: the condition expression for the breakpoint (empty if no condition) """ - def __init__(self, module, offset, address, enabled): + def __init__(self, module, offset, address, enabled, condition=""): self.module = module self.offset = offset self.address = address self.enabled = enabled + self.condition = condition def __eq__(self, other): if not isinstance(other, self.__class__): @@ -2053,7 +2055,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) + condition = breakpoints[i].condition if breakpoints[i].condition else "" + bp = DebugBreakpoint(breakpoints[i].module, breakpoints[i].offset, breakpoints[i].address, breakpoints[i].enabled, condition) result.append(bp) dbgcore.BNDebuggerFreeBreakpoints(breakpoints, count.value) @@ -2139,6 +2142,47 @@ def disable_breakpoint(self, address): else: raise NotImplementedError + def set_breakpoint_condition(self, address, condition: str) -> bool: + """ + Set a condition for a breakpoint + + The condition is an expression that will be evaluated using Binary Ninja's expression parser + when the breakpoint is hit. If the condition evaluates to non-zero (true), the debugger stops. + If it evaluates to zero (false), the debugger silently continues execution. + + Example conditions: + - ``"$rax == 0x1234"`` - break when RAX equals 0x1234 + - ``"$rsp + 0x20"`` - break when the expression is non-zero + + Pass an empty string to clear the condition. + + :param address: the address of the breakpoint (int or ModuleNameAndOffset) + :param condition: the condition expression, or empty string to clear + :return: True if successful, False otherwise + """ + if isinstance(address, int): + return dbgcore.BNDebuggerSetBreakpointConditionAbsolute(self.handle, address, condition) + elif isinstance(address, ModuleNameAndOffset): + return dbgcore.BNDebuggerSetBreakpointConditionRelative(self.handle, address.module, address.offset, condition) + else: + raise NotImplementedError + + def get_breakpoint_condition(self, address) -> str: + """ + Get the condition for a breakpoint + + :param address: the address of the breakpoint (int or ModuleNameAndOffset) + :return: the condition expression, or empty string if no condition + """ + if isinstance(address, int): + result = dbgcore.BNDebuggerGetBreakpointConditionAbsolute(self.handle, address) + elif isinstance(address, ModuleNameAndOffset): + result = dbgcore.BNDebuggerGetBreakpointConditionRelative(self.handle, address.module, address.offset) + else: + raise NotImplementedError + + return result if result else "" + @property def ip(self) -> int: """ diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 6d4a3cfa..bc1ccd5a 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -140,6 +140,46 @@ void DebuggerController::DisableBreakpoint(const ModuleNameAndOffset& address) } +bool DebuggerController::SetBreakpointCondition(uint64_t address, const std::string& condition) +{ + bool result = m_state->GetBreakpoints()->SetConditionAbsolute(address, condition); + if (result) + { + DebuggerEvent event; + event.type = AbsoluteBreakpointConditionChangedEvent; + event.data.absoluteAddress = address; + PostDebuggerEvent(event); + } + return result; +} + + +bool DebuggerController::SetBreakpointCondition(const ModuleNameAndOffset& address, const std::string& condition) +{ + bool result = m_state->GetBreakpoints()->SetConditionOffset(address, condition); + if (result) + { + DebuggerEvent event; + event.type = RelativeBreakpointConditionChangedEvent; + event.data.relativeAddress = address; + PostDebuggerEvent(event); + } + return result; +} + + +std::string DebuggerController::GetBreakpointCondition(uint64_t address) +{ + return m_state->GetBreakpoints()->GetConditionAbsolute(address); +} + + +std::string DebuggerController::GetBreakpointCondition(const ModuleNameAndOffset& address) +{ + return m_state->GetBreakpoints()->GetConditionOffset(address); +} + + bool DebuggerController::SetIP(uint64_t address) { std::string ipRegisterName; @@ -1963,6 +2003,31 @@ void DebuggerController::DebuggerMainThread() if (event.type == AdapterStoppedEventType) m_lastAdapterStopEventConsumed = false; + if (event.type == AdapterStoppedEventType && + event.data.targetStoppedData.reason == Breakpoint) + { + // update the caches so registers are available for condition evaluation + m_state->SetConnectionStatus(DebugAdapterConnectedStatus); + m_state->SetExecutionStatus(DebugAdapterPausedStatus); + m_state->MarkDirty(); + m_state->UpdateCaches(); + AddRegisterValuesToExpressionParser(); + AddModuleValuesToExpressionParser(); + + if (uint64_t ip = m_state->IP(); m_state->GetBreakpoints()->ContainsAbsolute(ip)) + { + if (!EvaluateBreakpointCondition(ip)) + { + m_lastAdapterStopEventConsumed = true; + current->done.set_value(); + // using m_adapter->Go() directly instead of Go() to avoid mutex deadlock + // since we're already inside ExecuteAdapterAndWait's event processing + m_adapter->Go(); + continue; + } + } + } + DebuggerEvent eventToSend = event; if ((eventToSend.type == TargetStoppedEventType) && !m_initialBreakpointSeen) { @@ -2401,6 +2466,27 @@ void DebuggerController::AddModuleValuesToExpressionParser() } +bool DebuggerController::EvaluateBreakpointCondition(uint64_t address) +{ + const std::string condition = m_state->GetBreakpoints()->GetConditionAbsolute(address); + if (condition.empty()) + return true; // no condition means always break + + uint64_t result = 0; + std::string errorString; + + if (const bool parseSuccess = BinaryView::ParseExpression(GetData(), condition, result, address, errorString); + !parseSuccess) + { + LogWarn("Failed to parse breakpoint condition '%s' at 0x%" PRIx64 ": %s", + condition.c_str(), address, errorString.c_str()); + return true; // parse failure means break (don't silently continue) + } + + return result != 0; // non-zero means condition is true +} + + std::string DebuggerController::GetStopReasonString(DebugStopReason reason) { switch (reason) diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index 4ac6229d..6f7a2b24 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -131,6 +131,7 @@ namespace BinaryNinjaDebugger { void UpdateStackVariables(); void AddRegisterValuesToExpressionParser(); void AddModuleValuesToExpressionParser(); + bool EvaluateBreakpointCondition(uint64_t address); bool CreateDebuggerBinaryView(); DebugStopReason StepIntoIL(BNFunctionGraphType il); @@ -233,6 +234,10 @@ namespace BinaryNinjaDebugger { void DisableBreakpoint(uint64_t address); void DisableBreakpoint(const ModuleNameAndOffset& address); DebugBreakpoint GetAllBreakpoints(); + bool SetBreakpointCondition(uint64_t address, const std::string& condition); + bool SetBreakpointCondition(const ModuleNameAndOffset& address, const std::string& condition); + std::string GetBreakpointCondition(uint64_t address); + std::string GetBreakpointCondition(const ModuleNameAndOffset& address); // registers intx::uint512 GetRegisterValue(const std::string& name); diff --git a/core/debuggerstate.cpp b/core/debuggerstate.cpp index 6153b9e5..0b69d0ab 100644 --- a/core/debuggerstate.cpp +++ b/core/debuggerstate.cpp @@ -513,8 +513,40 @@ std::vector DebuggerModules::GetAllModules() DebuggerBreakpoints::DebuggerBreakpoints(DebuggerState* state, std::vector initial) : - m_state(state), m_breakpoints(std::move(initial)) -{} + m_state(state) +{ + for (const auto& addr : initial) + m_breakpoints.push_back({addr, true, ""}); +} + + +std::vector::iterator DebuggerBreakpoints::FindBreakpoint(const ModuleNameAndOffset& address) +{ + if (m_state->GetAdapter()) + { + const uint64_t targetAbsolute = m_state->GetModules()->RelativeAddressToAbsolute(address); + for (auto it = m_breakpoints.begin(); it != m_breakpoints.end(); ++it) + { + if (m_state->GetModules()->RelativeAddressToAbsolute(it->address) == targetAbsolute) + return it; + } + } + else + { + for (auto it = m_breakpoints.begin(); it != m_breakpoints.end(); ++it) + { + if (it->address == address) + return it; + } + } + return m_breakpoints.end(); +} + + +std::vector::const_iterator DebuggerBreakpoints::FindBreakpoint(const ModuleNameAndOffset& address) const +{ + return const_cast(this)->FindBreakpoint(address); +} bool DebuggerBreakpoints::AddAbsolute(uint64_t remoteAddress) @@ -523,7 +555,6 @@ bool DebuggerBreakpoints::AddAbsolute(uint64_t remoteAddress) return false; bool result = false; - // Always add the breakpoint as long as the adapter is connected, even if it may be already present if (m_state->IsConnected()) { m_state->GetAdapter()->AddBreakpoint(remoteAddress); @@ -533,8 +564,7 @@ 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 + m_breakpoints.push_back({info, true, ""}); SerializeMetadata(); } @@ -546,17 +576,12 @@ 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({address, true, ""}); SerializeMetadata(); - // If the adapter is already created, we ask it to add the breakpoint. - // Otherwise, all breakpoints will be added to the adapter when the adapter is created. if (m_state->GetAdapter() && m_state->IsConnected()) - { m_state->GetAdapter()->AddBreakpoint(address); - return true; - } + return true; } return false; @@ -569,41 +594,33 @@ bool DebuggerBreakpoints::RemoveAbsolute(uint64_t remoteAddress) return false; ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(remoteAddress); - if (ContainsOffset(info)) - { - auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), info); - if (iter != m_breakpoints.end()) - { - m_breakpoints.erase(iter); - } - m_enabledState.erase(info); // Remove enabled state - SerializeMetadata(); - m_state->GetAdapter()->RemoveBreakpoint(remoteAddress); - return true; - } - return false; + auto it = FindBreakpoint(info); + if (it == m_breakpoints.end()) + return false; + + m_breakpoints.erase(it); + SerializeMetadata(); + m_state->GetAdapter()->RemoveBreakpoint(remoteAddress); + return true; } 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()) - m_breakpoints.erase(iter); + auto it = FindBreakpoint(address); + if (it == m_breakpoints.end()) + return false; - m_enabledState.erase(address); // Remove enabled state - SerializeMetadata(); + ModuleNameAndOffset actualAddr = it->address; + m_breakpoints.erase(it); + SerializeMetadata(); - if (m_state->GetAdapter() && m_state->IsConnected()) - { - uint64_t remoteAddress = m_state->GetModules()->RelativeAddressToAbsolute(address); - m_state->GetAdapter()->RemoveBreakpoint(remoteAddress); - return true; - } - return true; + if (m_state->GetAdapter() && m_state->IsConnected()) + { + uint64_t remoteAddress = m_state->GetModules()->RelativeAddressToAbsolute(actualAddr); + m_state->GetAdapter()->RemoveBreakpoint(remoteAddress); } - return false; + return true; } @@ -616,18 +633,17 @@ bool DebuggerBreakpoints::EnableAbsolute(uint64_t remoteAddress) bool DebuggerBreakpoints::EnableOffset(const ModuleNameAndOffset& address) { - if (!ContainsOffset(address)) + auto it = FindBreakpoint(address); + if (it == m_breakpoints.end()) return false; - m_enabledState[address] = true; + it->enabled = true; SerializeMetadata(); - // If connected, make sure the breakpoint is active in the target if (m_state->GetAdapter() && m_state->IsConnected()) { - uint64_t remoteAddress = m_state->GetModules()->RelativeAddressToAbsolute(address); + uint64_t remoteAddress = m_state->GetModules()->RelativeAddressToAbsolute(it->address); m_state->GetAdapter()->AddBreakpoint(remoteAddress); - return true; } return true; } @@ -642,18 +658,17 @@ bool DebuggerBreakpoints::DisableAbsolute(uint64_t remoteAddress) bool DebuggerBreakpoints::DisableOffset(const ModuleNameAndOffset& address) { - if (!ContainsOffset(address)) + auto it = FindBreakpoint(address); + if (it == m_breakpoints.end()) return false; - m_enabledState[address] = false; + it->enabled = false; SerializeMetadata(); - // If connected, remove the breakpoint from the target but keep it in our list if (m_state->GetAdapter() && m_state->IsConnected()) { - uint64_t remoteAddress = m_state->GetModules()->RelativeAddressToAbsolute(address); + uint64_t remoteAddress = m_state->GetModules()->RelativeAddressToAbsolute(it->address); m_state->GetAdapter()->RemoveBreakpoint(remoteAddress); - return true; } return true; } @@ -668,25 +683,17 @@ 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; - - // Default to enabled if not explicitly set - return true; + auto it = FindBreakpoint(address); + if (it == m_breakpoints.end()) + return true; // Default to enabled if breakpoint not found + + return it->enabled; } 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 - if (!m_state->GetAdapter()) - return std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != 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); - return ContainsAbsolute(absolute); + return FindBreakpoint(address) != m_breakpoints.end(); } @@ -695,29 +702,83 @@ bool DebuggerBreakpoints::ContainsAbsolute(uint64_t address) if (!m_state->GetAdapter()) return false; - // We need to convert every ModuleAndOffset to absolute address and compare with the input 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 auto& bp : m_breakpoints) { - uint64_t absolute = m_state->GetModules()->RelativeAddressToAbsolute(breakpoint); - if (absolute == address) + if (m_state->GetModules()->RelativeAddressToAbsolute(bp.address) == address) return true; } return false; } +bool DebuggerBreakpoints::SetConditionAbsolute(const uint64_t remoteAddress, const std::string& condition) +{ + const ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(remoteAddress); + return SetConditionOffset(info, condition); +} + + +bool DebuggerBreakpoints::SetConditionOffset(const ModuleNameAndOffset& address, const std::string& condition) +{ + auto it = FindBreakpoint(address); + if (it == m_breakpoints.end()) + return false; + + it->condition = condition; + SerializeMetadata(); + return true; +} + + +std::string DebuggerBreakpoints::GetConditionAbsolute(const uint64_t address) +{ + for (const auto& bp : m_breakpoints) + { + if (m_state->GetModules()->RelativeAddressToAbsolute(bp.address) == address) + return bp.condition; + } + return ""; +} + + +std::string DebuggerBreakpoints::GetConditionOffset(const ModuleNameAndOffset& address) +{ + auto it = FindBreakpoint(address); + return it != m_breakpoints.end() ? it->condition : ""; +} + + +bool DebuggerBreakpoints::HasConditionAbsolute(const uint64_t address) +{ + for (const auto& bp : m_breakpoints) + { + if (m_state->GetModules()->RelativeAddressToAbsolute(bp.address) == address) + return !bp.condition.empty(); + } + return false; +} + + +bool DebuggerBreakpoints::HasConditionOffset(const ModuleNameAndOffset& address) +{ + auto it = FindBreakpoint(address); + return it != m_breakpoints.end() && !it->condition.empty(); +} + + void DebuggerBreakpoints::SerializeMetadata() { - // TODO: who should free these Metadata objects? std::vector> breakpoints; - for (const ModuleNameAndOffset& bp : m_breakpoints) + for (const auto& bp : m_breakpoints) { std::map> info; - info["module"] = new Metadata(bp.module); - info["offset"] = new Metadata(bp.offset); + info["module"] = new Metadata(bp.address.module); + info["offset"] = new Metadata(bp.address.offset); + info["enabled"] = new Metadata(bp.enabled); + + if (!bp.condition.empty()) + info["condition"] = new Metadata(bp.condition); + breakpoints.push_back(new Metadata(info)); } m_state->GetController()->GetData()->StoreMetadata("debugger.breakpoints", new Metadata(breakpoints)); @@ -727,33 +788,31 @@ void DebuggerBreakpoints::SerializeMetadata() void DebuggerBreakpoints::UnserializedMetadata() { Ref metadata = m_state->GetController()->GetData()->QueryMetadata("debugger.breakpoints"); - if (!metadata || (!metadata->IsArray())) + if (!metadata || !metadata->IsArray()) return; - vector> array = metadata->GetArray(); - std::vector newBreakpoints; + m_breakpoints.clear(); - for (auto& element : array) + for (auto& element : metadata->GetArray()) { - if (!element || (!element->IsKeyValueStore())) + if (!element || !element->IsKeyValueStore()) continue; std::map> info = element->GetKeyValueStore(); - ModuleNameAndOffset address; if (!(info["module"] && info["module"]->IsString())) continue; - - address.module = info["module"]->GetString(); - if (!(info["offset"] && info["offset"]->IsUnsignedInteger())) continue; - address.offset = info["offset"]->GetUnsignedInteger(); - newBreakpoints.push_back(address); - } + BreakpointEntry bp; + bp.address.module = info["module"]->GetString(); + bp.address.offset = info["offset"]->GetUnsignedInteger(); + bp.enabled = (info["enabled"] && info["enabled"]->IsBoolean()) ? info["enabled"]->GetBoolean() : true; + bp.condition = (info["condition"] && info["condition"]->IsString()) ? info["condition"]->GetString() : ""; - m_breakpoints = newBreakpoints; + m_breakpoints.push_back(bp); + } } @@ -762,8 +821,11 @@ void DebuggerBreakpoints::Apply() if (!m_state->GetAdapter()) return; - for (const ModuleNameAndOffset& address : m_breakpoints) - m_state->GetAdapter()->AddBreakpoint(address); + for (const auto& bp : m_breakpoints) + { + if (bp.enabled) + m_state->GetAdapter()->AddBreakpoint(bp.address); + } } diff --git a/core/debuggerstate.h b/core/debuggerstate.h index 065edf1d..ffd6efd6 100644 --- a/core/debuggerstate.h +++ b/core/debuggerstate.h @@ -78,12 +78,18 @@ namespace BinaryNinjaDebugger { }; + struct BreakpointEntry + { + ModuleNameAndOffset address; + bool enabled = true; + std::string condition; + }; + 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 +108,19 @@ namespace BinaryNinjaDebugger { void Apply(); void SerializeMetadata(); void UnserializedMetadata(); - std::vector GetBreakpointList() const { return m_breakpoints; } + std::vector GetBreakpointList() const { return m_breakpoints; } + + bool SetConditionAbsolute(uint64_t remoteAddress, const std::string& condition); + bool SetConditionOffset(const ModuleNameAndOffset& address, const std::string& condition); + std::string GetConditionAbsolute(uint64_t address); + std::string GetConditionOffset(const ModuleNameAndOffset& address); + bool HasConditionAbsolute(uint64_t address); + bool HasConditionOffset(const ModuleNameAndOffset& address); + + private: + // Find breakpoint by address, handling module name differences via absolute address comparison + std::vector::iterator FindBreakpoint(const ModuleNameAndOffset& address); + std::vector::const_iterator FindBreakpoint(const ModuleNameAndOffset& address) const; }; diff --git a/core/ffi.cpp b/core/ffi.cpp index cb3c082e..5dec5730 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -797,27 +797,21 @@ void BNDebuggerSetCommandLineArguments(BNDebuggerController* controller, const c } -// TODO: the structures to hold information about the breakpoints are different in the API and the core, so we need to -// convert it here. Better unify them later. BNDebugBreakpoint* BNDebuggerGetBreakpoints(BNDebuggerController* controller, size_t* count) { DebuggerState* state = controller->object->GetState(); - std::vector breakpoints = state->GetBreakpoints()->GetBreakpointList(); + const auto& breakpoints = state->GetBreakpoints()->GetBreakpointList(); *count = breakpoints.size(); - //std::vector remoteList; - //if (state->IsConnected() && state->GetAdapter()) - // remoteList = state->GetAdapter()->GetBreakpointList(); - 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; - result[i].address = remoteAddress; - result[i].enabled = enabled; + const auto& bp = breakpoints[i]; + result[i].module = BNDebuggerAllocString(bp.address.module.c_str()); + result[i].offset = bp.address.offset; + result[i].address = state->GetModules()->RelativeAddressToAbsolute(bp.address); + result[i].enabled = bp.enabled; + result[i].condition = bp.condition.empty() ? nullptr : BNDebuggerAllocString(bp.condition.c_str()); } return result; } @@ -828,6 +822,8 @@ void BNDebuggerFreeBreakpoints(BNDebugBreakpoint* breakpoints, size_t count) for (size_t i = 0; i < count; i++) { BNDebuggerFreeString(breakpoints[i].module); + if (breakpoints[i].condition) + BNDebuggerFreeString(breakpoints[i].condition); } delete[] breakpoints; } @@ -927,6 +923,30 @@ bool BNDebuggerContainsRelativeBreakpoint(BNDebuggerController* controller, cons } +bool BNDebuggerSetBreakpointConditionAbsolute(BNDebuggerController* controller, uint64_t address, const char* condition) +{ + return controller->object->SetBreakpointCondition(address, condition ? condition : ""); +} + + +bool BNDebuggerSetBreakpointConditionRelative(BNDebuggerController* controller, const char* module, uint64_t offset, const char* condition) +{ + return controller->object->SetBreakpointCondition(ModuleNameAndOffset(module, offset), condition ? condition : ""); +} + + +char* BNDebuggerGetBreakpointConditionAbsolute(BNDebuggerController* controller, uint64_t address) +{ + return BNDebuggerAllocString(controller->object->GetBreakpointCondition(address).c_str()); +} + + +char* BNDebuggerGetBreakpointConditionRelative(BNDebuggerController* controller, const char* module, uint64_t offset) +{ + return BNDebuggerAllocString(controller->object->GetBreakpointCondition(ModuleNameAndOffset(module, offset)).c_str()); +} + + uint64_t BNDebuggerRelativeAddressToAbsolute(BNDebuggerController* controller, const char* module, uint64_t offset) { DebuggerState* state = controller->object->GetState(); diff --git a/test/debugger_test.py b/test/debugger_test.py index d962ac0d..44b08e3a 100644 --- a/test/debugger_test.py +++ b/test/debugger_test.py @@ -192,6 +192,38 @@ def test_breakpoint(self): self.assertEqual(dbg.ip, entry) dbg.quit_and_wait() + def test_breakpoint_condition(self): + fpath = name_to_fpath('helloworld', self.arch) + bv = load(fpath) + dbg = DebuggerController(bv) + self.assertNotIn(dbg.launch_and_wait(), [DebugStopReason.ProcessExited, DebugStopReason.InternalError]) + + entry = dbg.data.entry_point + dbg.add_breakpoint(entry) + + arch_name = bv.arch.name + if arch_name == 'x86': + reg1, reg2 = '$eax', '$ebx' + elif arch_name == 'x86_64': + reg1, reg2 = '$rax', '$rbx' + else: + reg1, reg2 = '$x0', '$x1' + + cond1 = f"{reg1} == 0x1234" + self.assertTrue(dbg.set_breakpoint_condition(entry, cond1)) + self.assertEqual(dbg.get_breakpoint_condition(entry), cond1) + + cond2 = f"{reg2} != 0" + self.assertTrue(dbg.set_breakpoint_condition(entry, cond2)) + self.assertEqual(dbg.get_breakpoint_condition(entry), cond2) + + self.assertTrue(dbg.set_breakpoint_condition(entry, "")) + self.assertEqual(dbg.get_breakpoint_condition(entry), "") + + self.assertFalse(dbg.set_breakpoint_condition(0x12345678, f"{reg1} == 0")) + self.assertEqual(dbg.get_breakpoint_condition(0x12345678), "") + dbg.quit_and_wait() + def test_register_read_write(self): fpath = name_to_fpath('helloworld', self.arch) bv = load(fpath) diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index 83c6b8d8..2277f158 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -23,6 +23,7 @@ limitations under the License. #include #include #include +#include #include "breakpointswidget.h" #include "ui.h" #include "menus.h" @@ -32,13 +33,14 @@ 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, const std::string& condition) : + m_enabled(enabled), m_location(location), m_address(address), m_condition(condition) {} bool BreakpointItem::operator==(const BreakpointItem& other) const { + // Condition is not part of identity - same location = same breakpoint return (m_enabled == other.enabled()) && (m_location == other.location()) && (m_address == other.address()); } @@ -100,7 +102,7 @@ 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()) @@ -138,6 +140,18 @@ QVariant DebugBreakpointsListModel::data(const QModelIndex& index, int role) con return QVariant(text); } + case DebugBreakpointsListModel::ConditionColumn: + { + QString condition = QString::fromStdString(item->condition()); + + if (role == Qt::ToolTipRole && !condition.isEmpty()) + return QVariant(condition); + + if (role == Qt::SizeHintRole) + return QVariant((qulonglong)condition.size()); + + return QVariant(condition); + } } return QVariant(); } @@ -159,6 +173,8 @@ QVariant DebugBreakpointsListModel::headerData(int column, Qt::Orientation orien return "Location"; case DebugBreakpointsListModel::AddressColumn: return "Remote Address"; + case DebugBreakpointsListModel::ConditionColumn: + return "Condition"; } return QVariant(); } @@ -207,6 +223,17 @@ void DebugBreakpointsItemDelegate::paint( painter->drawText(textRect, data.toString()); break; } + case DebugBreakpointsListModel::ConditionColumn: + { + painter->setFont(m_font); + painter->setPen(option.palette.color(QPalette::WindowText).rgba()); + + QString text = data.toString(); + QFontMetrics metrics(m_font); + QString elidedText = metrics.elidedText(text, Qt::ElideRight, textRect.width()); + painter->drawText(textRect, elidedText); + break; + } default: break; } @@ -298,6 +325,15 @@ DebugBreakpointsWidget::DebugBreakpointsWidget(ViewFrame* view, BinaryViewRef da m_actionHandler.bindAction( toggleEnabledActionName, UIAction([&]() { toggleSelected(); }, [&]() { return selectionNotEmpty(); })); + QString editConditionActionName = QString::fromStdString("Edit Condition..."); + UIAction::registerAction(editConditionActionName); + m_menu->addAction(editConditionActionName, "Options", MENU_ORDER_NORMAL); + m_actionHandler.bindAction( + editConditionActionName, UIAction([&]() { editCondition(); }, [&]() { + QModelIndexList sel = selectionModel()->selectedRows(); + return sel.size() == 1; + })); + QString enableAllActionName = QString::fromStdString("Enable All Breakpoints"); UIAction::registerAction(enableAllActionName); m_menu->addAction(enableAllActionName, "Options", MENU_ORDER_NORMAL); @@ -510,7 +546,7 @@ void DebugBreakpointsWidget::soloSelected() // Get the selected breakpoint location BreakpointItem selectedBp = m_model->getRow(sel[0].row()); - + // Disable all breakpoints first std::vector breakpoints = m_controller->GetBreakpoints(); for (const DebugBreakpoint& bp : breakpoints) @@ -520,12 +556,31 @@ void DebugBreakpointsWidget::soloSelected() info.offset = bp.offset; m_controller->DisableBreakpoint(info); } - + // Enable the selected breakpoint m_controller->EnableBreakpoint(selectedBp.location()); } +void DebugBreakpointsWidget::editCondition() +{ + QModelIndexList sel = selectionModel()->selectedRows(); + if (sel.size() != 1) + return; + + BreakpointItem bp = m_model->getRow(sel[0].row()); + std::string currentCondition = m_controller->GetBreakpointCondition(bp.location()); + + bool ok; + QString newCondition = QInputDialog::getText( + this, "Edit Condition", "Condition (e.g., $rax == 0x1234):", + QLineEdit::Normal, QString::fromStdString(currentCondition), &ok); + + if (ok) + m_controller->SetBreakpointCondition(bp.location(), newCondition.trimmed().toStdString()); +} + + void DebugBreakpointsWidget::remove() { QModelIndexList sel = selectionModel()->selectedRows(); @@ -554,7 +609,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.condition); } m_model->updateRows(bps); @@ -562,4 +617,5 @@ void DebugBreakpointsWidget::updateContent() resizeColumnToContents(DebugBreakpointsListModel::EnabledColumn); resizeColumnToContents(DebugBreakpointsListModel::LocationColumn); resizeColumnToContents(DebugBreakpointsListModel::AddressColumn); + resizeColumnToContents(DebugBreakpointsListModel::ConditionColumn); } diff --git a/ui/breakpointswidget.h b/ui/breakpointswidget.h index 2df230a8..5c84b8ac 100644 --- a/ui/breakpointswidget.h +++ b/ui/breakpointswidget.h @@ -39,12 +39,14 @@ class BreakpointItem bool m_enabled; ModuleNameAndOffset m_location; uint64_t m_address; + std::string m_condition; public: - BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t remoteAddress); + BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t remoteAddress, const std::string& condition = ""); bool enabled() const { return m_enabled; } ModuleNameAndOffset location() const { return m_location; } uint64_t address() const { return m_address; } + std::string condition() const { return m_condition; } bool operator==(const BreakpointItem& other) const; bool operator!=(const BreakpointItem& other) const; bool operator<(const BreakpointItem& other) const; @@ -68,6 +70,7 @@ class DebugBreakpointsListModel : public QAbstractTableModel EnabledColumn, LocationColumn, AddressColumn, + ConditionColumn, }; DebugBreakpointsListModel(QWidget* parent, ViewFrame* view); @@ -83,7 +86,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; @@ -141,7 +144,6 @@ class DebugBreakpointsWidget : public QTableView DebugBreakpointsWidget(ViewFrame* view, BinaryViewRef data, Menu* menu); ~DebugBreakpointsWidget(); - void uiEventHandler(const DebuggerEvent& event); void updateFonts(); private slots: @@ -153,6 +155,7 @@ private slots: void enableAll(); void disableAll(); void soloSelected(); + void editCondition(); public slots: void updateContent(); diff --git a/ui/debuggerwidget.cpp b/ui/debuggerwidget.cpp index f2cf4d3e..d9633d5e 100644 --- a/ui/debuggerwidget.cpp +++ b/ui/debuggerwidget.cpp @@ -135,6 +135,8 @@ void DebuggerWidget::uiEventHandler(const DebuggerEvent& event) case RelativeBreakpointEnabledEvent: case AbsoluteBreakpointDisabledEvent: case RelativeBreakpointDisabledEvent: + case AbsoluteBreakpointConditionChangedEvent: + case RelativeBreakpointConditionChangedEvent: m_breakpointsWidget->updateContent(); break; default: diff --git a/ui/ui.cpp b/ui/ui.cpp index e3423145..e0f44aad 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -118,6 +118,36 @@ static void BreakpointToggleCallback(BinaryView* view, uint64_t addr) } } +static void BreakpointEditConditionCallback(BinaryView* view, uint64_t addr, UIContext* context) +{ + auto controller = DebuggerController::GetController(view); + if (!controller) + return; + + const bool isAbsoluteAddress = controller->IsConnected(); + const ModuleNameAndOffset relativeAddr = { + controller->GetInputFile(), + addr - controller->GetViewFileSegmentsStart() + }; + + const std::string currentCondition = isAbsoluteAddress + ? controller->GetBreakpointCondition(addr) + : controller->GetBreakpointCondition(relativeAddr); + + bool ok; + QString newCondition = QInputDialog::getText( + context->mainWindow(), "Edit Condition", "Condition (e.g., $rax == 0x1234):", + QLineEdit::Normal, QString::fromStdString(currentCondition), &ok); + + if (ok) + { + if (isAbsoluteAddress) + controller->SetBreakpointCondition(addr, newCondition.trimmed().toStdString()); + else + controller->SetBreakpointCondition(relativeAddr, newCondition.trimmed().toStdString()); + } +} + static void JumpToIPCallback(BinaryView* view, UIContext* context) { auto controller = DebuggerController::GetController(view); @@ -927,6 +957,24 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) })); debuggerMenu->addAction("Solo Breakpoint", "Breakpoint"); + UIAction::registerAction("Edit Condition..."); + context->globalActions()->bindAction("Edit Condition...", + UIAction( + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView || !ctxt.context) + return; + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller) + return; + + BreakpointEditConditionCallback(ctxt.binaryView, ctxt.address, ctxt.context); + }, + [=](const UIActionContext& ctxt) { + auto [hasBreakpoint, isEnabled] = getBreakpointEnabledState(ctxt.binaryView, ctxt.address); + return ctxt.binaryView && hasBreakpoint; + })); + debuggerMenu->addAction("Edit Condition...", "Breakpoint"); + UIAction::registerAction("Connect to Debug Server"); context->globalActions()->bindAction("Connect to Debug Server", UIAction( @@ -1740,6 +1788,8 @@ void DebuggerUI::updateUI(const DebuggerEvent& event) case AbsoluteBreakpointEnabledEvent: case RelativeBreakpointDisabledEvent: case AbsoluteBreakpointDisabledEvent: + case AbsoluteBreakpointConditionChangedEvent: + case RelativeBreakpointConditionChangedEvent: { m_context->refreshCurrentViewContents(); break; diff --git a/ui/uinotification.cpp b/ui/uinotification.cpp index 79f4d7cb..92f94fd8 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", "Edit Condition...", "Breakpoint"); menu.addAction("Debugger", "Launch", "Control"); menu.addAction("Debugger", "Pause", "Control"); menu.addAction("Debugger", "Restart", "Control");