Skip to content

Commit 51a0800

Browse files
Copilotxusheng6
andcommitted
Implement breakpoint enable/disable improvements
- Consolidate context menu: replace Enable/Disable/Toggle with single Toggle action - Add Enable All, Disable All, and Solo Breakpoint context menu actions - Update render layer to only show enabled breakpoints - Add context menu actions in debugger for enable/disable with dynamic visibility - Make Python API DebugBreakpoint.enabled field read-only - Add enable_breakpoint() and disable_breakpoint() to Python API Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com>
1 parent 0c6ab83 commit 51a0800

File tree

5 files changed

+216
-33
lines changed

5 files changed

+216
-33
lines changed

api/python/debuggercontroller.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ class DebugBreakpoint:
260260
* ``module``: the name of the module for which the breakpoint is in
261261
* ``offset``: the offset of the breakpoint to the start of the module
262262
* ``address``: the absolute address of the breakpoint
263-
* ``enabled``: not used
263+
* ``enabled``: whether the breakpoint is enabled (read-only)
264264
265265
"""
266266
def __init__(self, module, offset, address, enabled):
@@ -281,7 +281,7 @@ def __ne__(self, other):
281281
return not (self == other)
282282

283283
def __hash__(self):
284-
return hash((self.module, self.offset, self.address. self.enabled))
284+
return hash((self.module, self.offset, self.address, self.enabled))
285285

286286
def __setattr__(self, name, value):
287287
try:
@@ -290,7 +290,8 @@ def __setattr__(self, name, value):
290290
raise AttributeError(f"attribute '{name}' is read only")
291291

292292
def __repr__(self):
293-
return f"<DebugBreakpoint: {self.module}:{self.offset:#x}, {self.address:#x}>"
293+
status = "enabled" if self.enabled else "disabled"
294+
return f"<DebugBreakpoint: {self.module}:{self.offset:#x}, {self.address:#x}, {status}>"
294295

295296

296297
class ModuleNameAndOffset:
@@ -1958,6 +1959,38 @@ def has_breakpoint(self, address) -> bool:
19581959
else:
19591960
raise NotImplementedError
19601961

1962+
def enable_breakpoint(self, address):
1963+
"""
1964+
Enable a breakpoint
1965+
1966+
The input can be either an absolute address, or a ModuleNameAndOffset, which specifies a relative address to the
1967+
start of a module. The latter is useful for ASLR.
1968+
1969+
:param address: the address of breakpoint to enable
1970+
"""
1971+
if isinstance(address, int):
1972+
dbgcore.BNDebuggerEnableAbsoluteBreakpoint(self.handle, address)
1973+
elif isinstance(address, ModuleNameAndOffset):
1974+
dbgcore.BNDebuggerEnableRelativeBreakpoint(self.handle, address.module, address.offset)
1975+
else:
1976+
raise NotImplementedError
1977+
1978+
def disable_breakpoint(self, address):
1979+
"""
1980+
Disable a breakpoint
1981+
1982+
The input can be either an absolute address, or a ModuleNameAndOffset, which specifies a relative address to the
1983+
start of a module. The latter is useful for ASLR.
1984+
1985+
:param address: the address of breakpoint to disable
1986+
"""
1987+
if isinstance(address, int):
1988+
dbgcore.BNDebuggerDisableAbsoluteBreakpoint(self.handle, address)
1989+
elif isinstance(address, ModuleNameAndOffset):
1990+
dbgcore.BNDebuggerDisableRelativeBreakpoint(self.handle, address.module, address.offset)
1991+
else:
1992+
raise NotImplementedError
1993+
19611994
@property
19621995
def ip(self) -> int:
19631996
"""

ui/breakpointswidget.cpp

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -292,23 +292,29 @@ DebugBreakpointsWidget::DebugBreakpointsWidget(ViewFrame* view, BinaryViewRef da
292292
m_actionHandler.bindAction(
293293
addBreakpointActionName, UIAction([&]() { add(); }));
294294

295-
QString enableBreakpointActionName = QString::fromStdString("Enable Breakpoint");
296-
UIAction::registerAction(enableBreakpointActionName);
297-
m_menu->addAction(enableBreakpointActionName, "Options", MENU_ORDER_NORMAL);
295+
QString toggleBreakpointActionName = QString::fromStdString("Toggle Breakpoint");
296+
UIAction::registerAction(toggleBreakpointActionName, QKeySequence("Ctrl+Shift+B"));
297+
m_menu->addAction(toggleBreakpointActionName, "Options", MENU_ORDER_NORMAL);
298298
m_actionHandler.bindAction(
299-
enableBreakpointActionName, UIAction([&]() { enableSelected(); }, [&]() { return selectionNotEmpty(); }));
299+
toggleBreakpointActionName, UIAction([&]() { toggleSelected(); }, [&]() { return selectionNotEmpty(); }));
300300

301-
QString disableBreakpointActionName = QString::fromStdString("Disable Breakpoint");
302-
UIAction::registerAction(disableBreakpointActionName);
303-
m_menu->addAction(disableBreakpointActionName, "Options", MENU_ORDER_NORMAL);
301+
QString enableAllActionName = QString::fromStdString("Enable All Breakpoints");
302+
UIAction::registerAction(enableAllActionName);
303+
m_menu->addAction(enableAllActionName, "Options", MENU_ORDER_NORMAL);
304304
m_actionHandler.bindAction(
305-
disableBreakpointActionName, UIAction([&]() { disableSelected(); }, [&]() { return selectionNotEmpty(); }));
305+
enableAllActionName, UIAction([&]() { enableAll(); }));
306306

307-
QString toggleBreakpointActionName = QString::fromStdString("Toggle Breakpoint Enable/Disable");
308-
UIAction::registerAction(toggleBreakpointActionName, QKeySequence("Ctrl+Shift+B"));
309-
m_menu->addAction(toggleBreakpointActionName, "Options", MENU_ORDER_NORMAL);
307+
QString disableAllActionName = QString::fromStdString("Disable All Breakpoints");
308+
UIAction::registerAction(disableAllActionName);
309+
m_menu->addAction(disableAllActionName, "Options", MENU_ORDER_NORMAL);
310310
m_actionHandler.bindAction(
311-
toggleBreakpointActionName, UIAction([&]() { toggleSelected(); }, [&]() { return selectionNotEmpty(); }));
311+
disableAllActionName, UIAction([&]() { disableAll(); }));
312+
313+
QString soloBreakpointActionName = QString::fromStdString("Solo Breakpoint");
314+
UIAction::registerAction(soloBreakpointActionName);
315+
m_menu->addAction(soloBreakpointActionName, "Options", MENU_ORDER_NORMAL);
316+
m_actionHandler.bindAction(
317+
soloBreakpointActionName, UIAction([&]() { soloSelected(); }, [&]() { return selectionNotEmpty(); }));
312318

313319
connect(this, &QTableView::doubleClicked, this, &DebugBreakpointsWidget::onDoubleClicked);
314320

@@ -456,39 +462,67 @@ void DebugBreakpointsWidget::add()
456462
}
457463

458464

459-
void DebugBreakpointsWidget::enableSelected()
465+
void DebugBreakpointsWidget::toggleSelected()
460466
{
461467
QModelIndexList sel = selectionModel()->selectedRows();
462468
for (const QModelIndex& index : sel)
463469
{
464470
BreakpointItem bp = m_model->getRow(index.row());
465-
m_controller->EnableBreakpoint(bp.location());
471+
if (bp.enabled())
472+
m_controller->DisableBreakpoint(bp.location());
473+
else
474+
m_controller->EnableBreakpoint(bp.location());
466475
}
467476
}
468477

469478

470-
void DebugBreakpointsWidget::disableSelected()
479+
void DebugBreakpointsWidget::enableAll()
471480
{
472-
QModelIndexList sel = selectionModel()->selectedRows();
473-
for (const QModelIndex& index : sel)
481+
std::vector<DebugBreakpoint> breakpoints = m_controller->GetBreakpoints();
482+
for (const DebugBreakpoint& bp : breakpoints)
474483
{
475-
BreakpointItem bp = m_model->getRow(index.row());
476-
m_controller->DisableBreakpoint(bp.location());
484+
ModuleNameAndOffset info;
485+
info.module = bp.module;
486+
info.offset = bp.offset;
487+
m_controller->EnableBreakpoint(info);
477488
}
478489
}
479490

480491

481-
void DebugBreakpointsWidget::toggleSelected()
492+
void DebugBreakpointsWidget::disableAll()
493+
{
494+
std::vector<DebugBreakpoint> breakpoints = m_controller->GetBreakpoints();
495+
for (const DebugBreakpoint& bp : breakpoints)
496+
{
497+
ModuleNameAndOffset info;
498+
info.module = bp.module;
499+
info.offset = bp.offset;
500+
m_controller->DisableBreakpoint(info);
501+
}
502+
}
503+
504+
505+
void DebugBreakpointsWidget::soloSelected()
482506
{
483507
QModelIndexList sel = selectionModel()->selectedRows();
484-
for (const QModelIndex& index : sel)
508+
if (sel.empty())
509+
return;
510+
511+
// Get the selected breakpoint location
512+
BreakpointItem selectedBp = m_model->getRow(sel[0].row());
513+
514+
// Disable all breakpoints first
515+
std::vector<DebugBreakpoint> breakpoints = m_controller->GetBreakpoints();
516+
for (const DebugBreakpoint& bp : breakpoints)
485517
{
486-
BreakpointItem bp = m_model->getRow(index.row());
487-
if (bp.enabled())
488-
m_controller->DisableBreakpoint(bp.location());
489-
else
490-
m_controller->EnableBreakpoint(bp.location());
518+
ModuleNameAndOffset info;
519+
info.module = bp.module;
520+
info.offset = bp.offset;
521+
m_controller->DisableBreakpoint(info);
491522
}
523+
524+
// Enable the selected breakpoint
525+
m_controller->EnableBreakpoint(selectedBp.location());
492526
}
493527

494528

ui/breakpointswidget.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,10 @@ private slots:
149149
void remove();
150150
void onDoubleClicked();
151151
void add();
152-
void enableSelected();
153-
void disableSelected();
154152
void toggleSelected();
153+
void enableAll();
154+
void disableAll();
155+
void soloSelected();
155156

156157
public slots:
157158
void updateContent();

ui/renderlayer.cpp

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
#include "renderlayer.h"
1818
#include "ttdcoveragerenderlayer.h"
1919
#include "debuggerapi.h"
20+
#include <map>
2021

2122
using namespace BinaryNinja;
2223
using namespace BinaryNinjaDebuggerAPI;
@@ -37,14 +38,27 @@ void DebuggerRenderLayer::ApplyToBlock(Ref<BasicBlock> block, std::vector<Disass
3738
uint64_t ipAddr = controller->IP();
3839
bool paused = controller->GetTargetStatus() == DebugAdapterPausedStatus;
3940

41+
// Get all breakpoints with their enabled state
42+
std::vector<DebugBreakpoint> breakpoints = controller->GetBreakpoints();
43+
std::map<uint64_t, bool> breakpointEnabledMap;
44+
for (const auto& bp : breakpoints)
45+
{
46+
breakpointEnabledMap[bp.address] = bp.enabled;
47+
}
48+
4049
for (auto& line : lines)
4150
{
4251
// Do not draw the tags on an empty line, e.g., those separating the basic blocks in the linear view
4352
if (line.tokens.empty() || (line.tokens[0].type == CommentToken))
4453
continue;
4554

4655
bool hasPC = (line.addr == ipAddr) && paused;
47-
bool hasBreakpoint = controller->ContainsBreakpoint(line.addr);
56+
// Only render enabled breakpoints
57+
bool hasBreakpoint = false;
58+
if (breakpointEnabledMap.count(line.addr) > 0)
59+
{
60+
hasBreakpoint = breakpointEnabledMap[line.addr];
61+
}
4862

4963
if (hasPC && hasBreakpoint)
5064
{
@@ -141,11 +155,24 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref<Function> function, std::ve
141155
uint64_t ipAddr = controller->IP();
142156
bool paused = controller->GetTargetStatus() == DebugAdapterPausedStatus;
143157

158+
// Get all breakpoints with their enabled state
159+
std::vector<DebugBreakpoint> breakpoints = controller->GetBreakpoints();
160+
std::map<uint64_t, bool> breakpointEnabledMap;
161+
for (const auto& bp : breakpoints)
162+
{
163+
breakpointEnabledMap[bp.address] = bp.enabled;
164+
}
165+
144166
for (auto& linearLine : lines)
145167
{
146168
DisassemblyTextLine& line = linearLine.contents;
147169
bool hasPC = (line.addr == ipAddr) && paused;
148-
bool hasBreakpoint = controller->ContainsBreakpoint(line.addr);
170+
// Only render enabled breakpoints
171+
bool hasBreakpoint = false;
172+
if (breakpointEnabledMap.count(line.addr) > 0)
173+
{
174+
hasBreakpoint = breakpointEnabledMap[line.addr];
175+
}
149176

150177
if (hasPC && hasBreakpoint)
151178
{

ui/ui.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,94 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context)
811811
requireBinaryView));
812812
debuggerMenu->addAction("Toggle Breakpoint", "Breakpoint");
813813

814+
// Helper function to check if there's an enabled breakpoint at the current address
815+
auto hasEnabledBreakpoint = [](BinaryView* view, uint64_t addr) -> bool {
816+
auto controller = DebuggerController::GetController(view);
817+
if (!controller)
818+
return false;
819+
820+
std::vector<DebugBreakpoint> breakpoints = controller->GetBreakpoints();
821+
for (const auto& bp : breakpoints)
822+
{
823+
if (bp.address == addr)
824+
return bp.enabled;
825+
}
826+
return false;
827+
};
828+
829+
// Helper function to check if there's a disabled breakpoint at the current address
830+
auto hasDisabledBreakpoint = [](BinaryView* view, uint64_t addr) -> bool {
831+
auto controller = DebuggerController::GetController(view);
832+
if (!controller)
833+
return false;
834+
835+
std::vector<DebugBreakpoint> breakpoints = controller->GetBreakpoints();
836+
for (const auto& bp : breakpoints)
837+
{
838+
if (bp.address == addr)
839+
return !bp.enabled;
840+
}
841+
return false;
842+
};
843+
844+
// Register "Enable Breakpoint" action (shown when breakpoint is disabled)
845+
UIAction::registerAction("Enable Breakpoint");
846+
context->globalActions()->bindAction("Enable Breakpoint",
847+
UIAction(
848+
[=](const UIActionContext& ctxt) {
849+
if (!ctxt.binaryView)
850+
return;
851+
auto controller = DebuggerController::GetController(ctxt.binaryView);
852+
if (!controller)
853+
return;
854+
855+
bool isAbsoluteAddress = controller->IsConnected();
856+
if (isAbsoluteAddress)
857+
{
858+
controller->EnableBreakpoint(ctxt.address);
859+
}
860+
else
861+
{
862+
std::string filename = controller->GetInputFile();
863+
uint64_t offset = ctxt.address - controller->GetViewFileSegmentsStart();
864+
ModuleNameAndOffset info = {filename, offset};
865+
controller->EnableBreakpoint(info);
866+
}
867+
},
868+
[=](const UIActionContext& ctxt) {
869+
return ctxt.binaryView && hasDisabledBreakpoint(ctxt.binaryView, ctxt.address);
870+
}));
871+
debuggerMenu->addAction("Enable Breakpoint", "Breakpoint");
872+
873+
// Register "Disable Breakpoint" action (shown when breakpoint is enabled)
874+
UIAction::registerAction("Disable Breakpoint");
875+
context->globalActions()->bindAction("Disable Breakpoint",
876+
UIAction(
877+
[=](const UIActionContext& ctxt) {
878+
if (!ctxt.binaryView)
879+
return;
880+
auto controller = DebuggerController::GetController(ctxt.binaryView);
881+
if (!controller)
882+
return;
883+
884+
bool isAbsoluteAddress = controller->IsConnected();
885+
if (isAbsoluteAddress)
886+
{
887+
controller->DisableBreakpoint(ctxt.address);
888+
}
889+
else
890+
{
891+
std::string filename = controller->GetInputFile();
892+
uint64_t offset = ctxt.address - controller->GetViewFileSegmentsStart();
893+
ModuleNameAndOffset info = {filename, offset};
894+
controller->DisableBreakpoint(info);
895+
}
896+
},
897+
[=](const UIActionContext& ctxt) {
898+
return ctxt.binaryView && hasEnabledBreakpoint(ctxt.binaryView, ctxt.address);
899+
}));
900+
debuggerMenu->addAction("Disable Breakpoint", "Breakpoint");
901+
814902
UIAction::registerAction("Connect to Debug Server");
815903
context->globalActions()->bindAction("Connect to Debug Server",
816904
UIAction(

0 commit comments

Comments
 (0)