Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
25 changes: 25 additions & 0 deletions api/debuggerapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -358,13 +358,26 @@ 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;
std::string condition;
DebugBreakpointType type = SoftwareBreakpoint;
size_t size = 1; // Size in bytes for hardware breakpoints/watchpoints (1, 2, 4, 8)
};


Expand Down Expand Up @@ -735,6 +748,18 @@ namespace BinaryNinjaDebuggerAPI {
std::string GetBreakpointCondition(uint64_t address);
std::string GetBreakpointCondition(const ModuleNameAndOffset& address);

// Hardware breakpoint and watchpoint support - absolute address
bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1);
bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1);
bool EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1);
bool DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size = 1);

// Hardware breakpoint and watchpoint support - module+offset (ASLR-safe)
bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1);
bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1);
bool EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1);
bool DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size = 1);

uint64_t IP();
uint64_t GetLastIP();
bool SetIP(uint64_t address);
Expand Down
52 changes: 52 additions & 0 deletions api/debuggercontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,8 @@ std::vector<DebugBreakpoint> DebuggerController::GetBreakpoints()
bp.address = breakpoints[i].address;
bp.enabled = breakpoints[i].enabled;
bp.condition = breakpoints[i].condition ? breakpoints[i].condition : "";
bp.type = (DebugBreakpointType)breakpoints[i].type;
bp.size = breakpoints[i].size;
result[i] = bp;
}

Expand Down Expand Up @@ -831,6 +833,56 @@ std::string DebuggerController::GetBreakpointCondition(const ModuleNameAndOffset
}


bool DebuggerController::AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size)
{
return BNDebuggerAddHardwareBreakpoint(m_object, address, (BNDebugBreakpointType)type, size);
}


bool DebuggerController::RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size)
{
return BNDebuggerRemoveHardwareBreakpoint(m_object, address, (BNDebugBreakpointType)type, size);
}


bool DebuggerController::EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size)
{
return BNDebuggerEnableHardwareBreakpoint(m_object, address, (BNDebugBreakpointType)type, size);
}


bool DebuggerController::DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size)
{
return BNDebuggerDisableHardwareBreakpoint(m_object, address, (BNDebugBreakpointType)type, size);
}


// Hardware breakpoint methods - module+offset (ASLR-safe)

bool DebuggerController::AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size)
{
return BNDebuggerAddRelativeHardwareBreakpoint(m_object, location.module.c_str(), location.offset, (BNDebugBreakpointType)type, size);
}


bool DebuggerController::RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size)
{
return BNDebuggerRemoveRelativeHardwareBreakpoint(m_object, location.module.c_str(), location.offset, (BNDebugBreakpointType)type, size);
}


bool DebuggerController::EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size)
{
return BNDebuggerEnableRelativeHardwareBreakpoint(m_object, location.module.c_str(), location.offset, (BNDebugBreakpointType)type, size);
}


bool DebuggerController::DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size)
{
return BNDebuggerDisableRelativeHardwareBreakpoint(m_object, location.module.c_str(), location.offset, (BNDebugBreakpointType)type, size);
}


uint64_t DebuggerController::RelativeAddressToAbsolute(const ModuleNameAndOffset& address)
{
return BNDebuggerRelativeAddressToAbsolute(m_object, address.module.c_str(), address.offset);
Expand Down
44 changes: 34 additions & 10 deletions api/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ extern "C"
} BNDebugRegister;


typedef enum BNDebugBreakpointType
{
BNSoftwareBreakpoint = 0, // Default software breakpoint
BNHardwareExecuteBreakpoint = 1, // Hardware execution breakpoint
BNHardwareReadBreakpoint = 2, // Hardware read watchpoint
BNHardwareWriteBreakpoint = 3, // Hardware write watchpoint
BNHardwareAccessBreakpoint = 4 // Hardware read/write watchpoint
} BNDebugBreakpointType;


typedef struct BNDebugBreakpoint
{
// TODO: we should add an absolute address to this, along with a boolean telling whether it is valid
Expand All @@ -134,6 +144,8 @@ extern "C"
uint64_t address;
bool enabled;
char* condition; // NULL if no condition
BNDebugBreakpointType type;
size_t size; // Size in bytes for hardware breakpoints/watchpoints (1, 2, 4, 8)
} BNDebugBreakpoint;


Expand Down Expand Up @@ -250,16 +262,8 @@ extern "C"
TargetExitedEventType,
DetachedEventType,

AbsoluteBreakpointAddedEvent,
RelativeBreakpointAddedEvent,
AbsoluteBreakpointRemovedEvent,
RelativeBreakpointRemovedEvent,
AbsoluteBreakpointEnabledEvent,
RelativeBreakpointEnabledEvent,
AbsoluteBreakpointDisabledEvent,
RelativeBreakpointDisabledEvent,
AbsoluteBreakpointConditionChangedEvent,
RelativeBreakpointConditionChangedEvent,
// Unified breakpoint change event - use this for all breakpoint changes (add/remove/enable/disable)
BreakpointChangedEvent,

ActiveThreadChangedEvent,

Expand Down Expand Up @@ -610,6 +614,26 @@ extern "C"
DEBUGGER_FFI_API char* BNDebuggerGetBreakpointConditionRelative(
BNDebuggerController* controller, const char* module, uint64_t offset);

// Hardware breakpoint and watchpoint support
DEBUGGER_FFI_API bool BNDebuggerAddHardwareBreakpoint(BNDebuggerController* controller, uint64_t address,
BNDebugBreakpointType type, size_t size);
DEBUGGER_FFI_API bool BNDebuggerRemoveHardwareBreakpoint(BNDebuggerController* controller, uint64_t address,
BNDebugBreakpointType type, size_t size);
DEBUGGER_FFI_API bool BNDebuggerEnableHardwareBreakpoint(BNDebuggerController* controller, uint64_t address,
BNDebugBreakpointType type, size_t size);
DEBUGGER_FFI_API bool BNDebuggerDisableHardwareBreakpoint(BNDebuggerController* controller, uint64_t address,
BNDebugBreakpointType type, size_t size);

// Hardware breakpoint methods - module+offset (ASLR-safe)
DEBUGGER_FFI_API bool BNDebuggerAddRelativeHardwareBreakpoint(BNDebuggerController* controller, const char* module,
uint64_t offset, BNDebugBreakpointType type, size_t size);
DEBUGGER_FFI_API bool BNDebuggerRemoveRelativeHardwareBreakpoint(BNDebuggerController* controller, const char* module,
uint64_t offset, BNDebugBreakpointType type, size_t size);
DEBUGGER_FFI_API bool BNDebuggerEnableRelativeHardwareBreakpoint(BNDebuggerController* controller, const char* module,
uint64_t offset, BNDebugBreakpointType type, size_t size);
DEBUGGER_FFI_API bool BNDebuggerDisableRelativeHardwareBreakpoint(BNDebuggerController* controller, const char* module,
uint64_t offset, BNDebugBreakpointType type, size_t size);

DEBUGGER_FFI_API uint64_t BNDebuggerGetIP(BNDebuggerController* controller);
DEBUGGER_FFI_API uint64_t BNDebuggerGetLastIP(BNDebuggerController* controller);
DEBUGGER_FFI_API bool BNDebuggerSetIP(BNDebuggerController* controller, uint64_t address);
Expand Down
71 changes: 66 additions & 5 deletions api/python/debuggercontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,28 +371,33 @@ class DebugBreakpoint:
* ``address``: the absolute address of the breakpoint
* ``enabled``: whether the breakpoint is enabled (read-only)
* ``condition``: the condition expression for the breakpoint (empty if no condition)
* ``type``: the type of breakpoint (Software, HardwareExecute, HardwareRead, HardwareWrite, HardwareAccess)
* ``size``: the size in bytes for hardware breakpoints/watchpoints (1, 2, 4, or 8)

"""
def __init__(self, module, offset, address, enabled, condition=""):
def __init__(self, module, offset, address, enabled, condition="", bp_type=DebugBreakpointType.BNSoftwareBreakpoint, size=1):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this should really be a dataclass. I think this would clean up this class a bunch.

self.module = module
self.offset = offset
self.address = address
self.enabled = enabled
self.condition = condition
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 \
and self.condition == other.condition

def __ne__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return not (self == other)

def __hash__(self):
return hash((self.module, self.offset, self.address, self.enabled))
return hash((self.module, self.offset, self.address, self.enabled, self.type, self.size, self.condition))

def __setattr__(self, name, value):
try:
Expand All @@ -403,7 +408,22 @@ def __setattr__(self, name, value):
def __repr__(self):
status = "enabled" if self.enabled else "disabled"
cond_str = f", condition='{self.condition}'" if self.condition else ""
return f"<DebugBreakpoint: {self.module}:{self.offset:#x}, {self.address:#x}, {status}{cond_str}>"

# 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"<DebugBreakpoint: {self.module}:{self.offset:#x}, {self.address:#x}, type={type_str}, {status}{cond_str}>"


class ModuleNameAndOffset:
Expand Down Expand Up @@ -2059,7 +2079,8 @@ def breakpoints(self) -> DebugBreakpoints:
result = []
for i in range(0, count.value):
condition = breakpoints[i].condition if breakpoints[i].condition else ""
bp = DebugBreakpoint(breakpoints[i].module, breakpoints[i].offset, breakpoints[i].address, breakpoints[i].enabled, condition)
bp = DebugBreakpoint(breakpoints[i].module, breakpoints[i].offset, breakpoints[i].address,
breakpoints[i].enabled, condition, breakpoints[i].type, breakpoints[i].size)
result.append(bp)

dbgcore.BNDebuggerFreeBreakpoints(breakpoints, count.value)
Expand Down Expand Up @@ -2097,6 +2118,46 @@ def add_breakpoint(self, address):
else:
raise NotImplementedError

def add_hardware_breakpoint(self, address, bp_type: DebugBreakpointType, size: int = 1) -> bool:
"""
Add a hardware breakpoint

The input can be either an absolute address, or a ModuleNameAndOffset, which specifies a relative address to the
start of a module. The latter is useful for ASLR.

:param address: the address of breakpoint to add
:param bp_type: the type of hardware breakpoint (DebugBreakpointType.BNHardwareExecuteBreakpoint,
BNHardwareReadBreakpoint, BNHardwareWriteBreakpoint, or BNHardwareAccessBreakpoint)
:param size: the size in bytes for the watchpoint (1, 2, 4, or 8)
:return: True if successful, False otherwise
"""
if isinstance(address, int):
return dbgcore.BNDebuggerAddHardwareBreakpoint(self.handle, address, bp_type, size)
elif isinstance(address, ModuleNameAndOffset):
return dbgcore.BNDebuggerAddRelativeHardwareBreakpoint(self.handle, address.module, address.offset, bp_type, size)
else:
raise NotImplementedError

def delete_hardware_breakpoint(self, address, bp_type: DebugBreakpointType, size: int = 1) -> bool:
"""
Delete a hardware breakpoint

The input can be either an absolute address, or a ModuleNameAndOffset, which specifies a relative address to the
start of a module. The latter is useful for ASLR.

:param address: the address of breakpoint to delete
:param bp_type: the type of hardware breakpoint (DebugBreakpointType.BNHardwareExecuteBreakpoint,
BNHardwareReadBreakpoint, BNHardwareWriteBreakpoint, or BNHardwareAccessBreakpoint)
:param size: the size in bytes for the watchpoint (1, 2, 4, or 8)
:return: True if successful, False otherwise
"""
if isinstance(address, int):
return dbgcore.BNDebuggerRemoveHardwareBreakpoint(self.handle, address, bp_type, size)
elif isinstance(address, ModuleNameAndOffset):
return dbgcore.BNDebuggerRemoveRelativeHardwareBreakpoint(self.handle, address.module, address.offset, bp_type, size)
else:
raise NotImplementedError

def has_breakpoint(self, address) -> bool:
"""
Checks whether a breakpoint exists at the specified address
Expand Down
26 changes: 25 additions & 1 deletion cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand Down
Loading