Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/debuggerapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ namespace BinaryNinjaDebuggerAPI {
uint64_t offset;
uint64_t address;
bool enabled;
std::string condition;
};


Expand Down Expand Up @@ -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();
Expand Down
31 changes: 31 additions & 0 deletions api/debuggercontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,7 @@ std::vector<DebugBreakpoint> 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;
}

Expand Down Expand Up @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions api/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ extern "C"
uint64_t offset;
uint64_t address;
bool enabled;
char* condition; // NULL if no condition
} BNDebugBreakpoint;


Expand Down Expand Up @@ -256,6 +257,8 @@ extern "C"
RelativeBreakpointEnabledEvent,
AbsoluteBreakpointDisabledEvent,
RelativeBreakpointDisabledEvent,
AbsoluteBreakpointConditionChangedEvent,
RelativeBreakpointConditionChangedEvent,

ActiveThreadChangedEvent,

Expand Down Expand Up @@ -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);
Expand Down
48 changes: 46 additions & 2 deletions api/python/debuggercontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
"""
Expand Down
86 changes: 86 additions & 0 deletions core/debuggercontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions core/debuggercontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ namespace BinaryNinjaDebugger {
void UpdateStackVariables();
void AddRegisterValuesToExpressionParser();
void AddModuleValuesToExpressionParser();
bool EvaluateBreakpointCondition(uint64_t address);
bool CreateDebuggerBinaryView();

DebugStopReason StepIntoIL(BNFunctionGraphType il);
Expand Down Expand Up @@ -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);
Expand Down
Loading