Skip to content

Commit 1ab8515

Browse files
3rditAdamR05
andcommitted
Added conditional breakpoints to the debugger GUI (breakpoints widget + context menu additions)
Co-authored-by: AdamR05 <215697996+AdamR05@users.noreply.github.com>
1 parent ed55c20 commit 1ab8515

5 files changed

Lines changed: 121 additions & 8 deletions

File tree

api/debuggercontroller.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@ std::vector<DebugBreakpoint> DebuggerController::GetBreakpoints()
731731
bp.offset = breakpoints[i].offset;
732732
bp.address = breakpoints[i].address;
733733
bp.enabled = breakpoints[i].enabled;
734+
bp.condition = breakpoints[i].condition ? breakpoints[i].condition : "";
734735
result[i] = bp;
735736
}
736737

ui/breakpointswidget.cpp

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ limitations under the License.
2323
#include <QStringList>
2424
#include <algorithm>
2525
#include <QMouseEvent>
26+
#include <QInputDialog>
2627
#include "breakpointswidget.h"
2728
#include "ui.h"
2829
#include "menus.h"
@@ -32,13 +33,14 @@ using namespace BinaryNinjaDebuggerAPI;
3233
using namespace BinaryNinja;
3334
using namespace std;
3435

35-
BreakpointItem::BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t address) :
36-
m_enabled(enabled), m_location(location), m_address(address)
36+
BreakpointItem::BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t address, const std::string& condition) :
37+
m_enabled(enabled), m_location(location), m_address(address), m_condition(condition)
3738
{}
3839

3940

4041
bool BreakpointItem::operator==(const BreakpointItem& other) const
4142
{
43+
// Condition is not part of identity - same location = same breakpoint
4244
return (m_enabled == other.enabled()) && (m_location == other.location()) && (m_address == other.address());
4345
}
4446

@@ -100,7 +102,7 @@ QVariant DebugBreakpointsListModel::data(const QModelIndex& index, int role) con
100102
if (!item)
101103
return QVariant();
102104

103-
if ((role != Qt::DisplayRole) && (role != Qt::SizeHintRole))
105+
if ((role != Qt::DisplayRole) && (role != Qt::SizeHintRole) && (role != Qt::ToolTipRole))
104106
return QVariant();
105107

106108
switch (index.column())
@@ -138,6 +140,18 @@ QVariant DebugBreakpointsListModel::data(const QModelIndex& index, int role) con
138140

139141
return QVariant(text);
140142
}
143+
case DebugBreakpointsListModel::ConditionColumn:
144+
{
145+
QString condition = QString::fromStdString(item->condition());
146+
147+
if (role == Qt::ToolTipRole && !condition.isEmpty())
148+
return QVariant(condition);
149+
150+
if (role == Qt::SizeHintRole)
151+
return QVariant((qulonglong)condition.size());
152+
153+
return QVariant(condition);
154+
}
141155
}
142156
return QVariant();
143157
}
@@ -159,6 +173,8 @@ QVariant DebugBreakpointsListModel::headerData(int column, Qt::Orientation orien
159173
return "Location";
160174
case DebugBreakpointsListModel::AddressColumn:
161175
return "Remote Address";
176+
case DebugBreakpointsListModel::ConditionColumn:
177+
return "Condition";
162178
}
163179
return QVariant();
164180
}
@@ -207,6 +223,17 @@ void DebugBreakpointsItemDelegate::paint(
207223
painter->drawText(textRect, data.toString());
208224
break;
209225
}
226+
case DebugBreakpointsListModel::ConditionColumn:
227+
{
228+
painter->setFont(m_font);
229+
painter->setPen(option.palette.color(QPalette::WindowText).rgba());
230+
231+
QString text = data.toString();
232+
QFontMetrics metrics(m_font);
233+
QString elidedText = metrics.elidedText(text, Qt::ElideRight, textRect.width());
234+
painter->drawText(textRect, elidedText);
235+
break;
236+
}
210237
default:
211238
break;
212239
}
@@ -298,6 +325,15 @@ DebugBreakpointsWidget::DebugBreakpointsWidget(ViewFrame* view, BinaryViewRef da
298325
m_actionHandler.bindAction(
299326
toggleEnabledActionName, UIAction([&]() { toggleSelected(); }, [&]() { return selectionNotEmpty(); }));
300327

328+
QString editConditionActionName = QString::fromStdString("Edit Condition...");
329+
UIAction::registerAction(editConditionActionName);
330+
m_menu->addAction(editConditionActionName, "Options", MENU_ORDER_NORMAL);
331+
m_actionHandler.bindAction(
332+
editConditionActionName, UIAction([&]() { editCondition(); }, [&]() {
333+
QModelIndexList sel = selectionModel()->selectedRows();
334+
return sel.size() == 1;
335+
}));
336+
301337
QString enableAllActionName = QString::fromStdString("Enable All Breakpoints");
302338
UIAction::registerAction(enableAllActionName);
303339
m_menu->addAction(enableAllActionName, "Options", MENU_ORDER_NORMAL);
@@ -510,7 +546,7 @@ void DebugBreakpointsWidget::soloSelected()
510546

511547
// Get the selected breakpoint location
512548
BreakpointItem selectedBp = m_model->getRow(sel[0].row());
513-
549+
514550
// Disable all breakpoints first
515551
std::vector<DebugBreakpoint> breakpoints = m_controller->GetBreakpoints();
516552
for (const DebugBreakpoint& bp : breakpoints)
@@ -520,12 +556,34 @@ void DebugBreakpointsWidget::soloSelected()
520556
info.offset = bp.offset;
521557
m_controller->DisableBreakpoint(info);
522558
}
523-
559+
524560
// Enable the selected breakpoint
525561
m_controller->EnableBreakpoint(selectedBp.location());
526562
}
527563

528564

565+
void DebugBreakpointsWidget::editCondition()
566+
{
567+
QModelIndexList sel = selectionModel()->selectedRows();
568+
if (sel.size() != 1)
569+
return;
570+
571+
BreakpointItem bp = m_model->getRow(sel[0].row());
572+
std::string currentCondition = m_controller->GetBreakpointCondition(bp.location());
573+
574+
bool ok;
575+
QString newCondition = QInputDialog::getText(
576+
this, "Edit Condition", "Condition (e.g., $rax == 0x1234):",
577+
QLineEdit::Normal, QString::fromStdString(currentCondition), &ok);
578+
579+
if (ok)
580+
{
581+
m_controller->SetBreakpointCondition(bp.location(), newCondition.trimmed().toStdString());
582+
updateContent();
583+
}
584+
}
585+
586+
529587
void DebugBreakpointsWidget::remove()
530588
{
531589
QModelIndexList sel = selectionModel()->selectedRows();
@@ -554,12 +612,13 @@ void DebugBreakpointsWidget::updateContent()
554612
ModuleNameAndOffset info;
555613
info.module = bp.module;
556614
info.offset = bp.offset;
557-
bps.emplace_back(bp.enabled, info, bp.address);
615+
bps.emplace_back(bp.enabled, info, bp.address, bp.condition);
558616
}
559617

560618
m_model->updateRows(bps);
561619

562620
resizeColumnToContents(DebugBreakpointsListModel::EnabledColumn);
563621
resizeColumnToContents(DebugBreakpointsListModel::LocationColumn);
564622
resizeColumnToContents(DebugBreakpointsListModel::AddressColumn);
623+
resizeColumnToContents(DebugBreakpointsListModel::ConditionColumn);
565624
}

ui/breakpointswidget.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ class BreakpointItem
3939
bool m_enabled;
4040
ModuleNameAndOffset m_location;
4141
uint64_t m_address;
42+
std::string m_condition;
4243

4344
public:
44-
BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t remoteAddress);
45+
BreakpointItem(bool enabled, const ModuleNameAndOffset location, uint64_t remoteAddress, const std::string& condition = "");
4546
bool enabled() const { return m_enabled; }
4647
ModuleNameAndOffset location() const { return m_location; }
4748
uint64_t address() const { return m_address; }
49+
std::string condition() const { return m_condition; }
4850
bool operator==(const BreakpointItem& other) const;
4951
bool operator!=(const BreakpointItem& other) const;
5052
bool operator<(const BreakpointItem& other) const;
@@ -68,6 +70,7 @@ class DebugBreakpointsListModel : public QAbstractTableModel
6870
EnabledColumn,
6971
LocationColumn,
7072
AddressColumn,
73+
ConditionColumn,
7174
};
7275

7376
DebugBreakpointsListModel(QWidget* parent, ViewFrame* view);
@@ -83,7 +86,7 @@ class DebugBreakpointsListModel : public QAbstractTableModel
8386
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override
8487
{
8588
(void)parent;
86-
return 3;
89+
return 4;
8790
}
8891
BreakpointItem getRow(int row) const;
8992
virtual QVariant data(const QModelIndex& i, int role) const override;
@@ -153,6 +156,7 @@ private slots:
153156
void enableAll();
154157
void disableAll();
155158
void soloSelected();
159+
void editCondition();
156160

157161
public slots:
158162
void updateContent();

ui/ui.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,36 @@ static void BreakpointToggleCallback(BinaryView* view, uint64_t addr)
118118
}
119119
}
120120

121+
static void BreakpointEditConditionCallback(BinaryView* view, uint64_t addr, UIContext* context)
122+
{
123+
auto controller = DebuggerController::GetController(view);
124+
if (!controller)
125+
return;
126+
127+
const bool isAbsoluteAddress = controller->IsConnected();
128+
const ModuleNameAndOffset relativeAddr = {
129+
controller->GetInputFile(),
130+
addr - controller->GetViewFileSegmentsStart()
131+
};
132+
133+
const std::string currentCondition = isAbsoluteAddress
134+
? controller->GetBreakpointCondition(addr)
135+
: controller->GetBreakpointCondition(relativeAddr);
136+
137+
bool ok;
138+
QString newCondition = QInputDialog::getText(
139+
context->mainWindow(), "Edit Condition", "Condition (e.g., $rax == 0x1234):",
140+
QLineEdit::Normal, QString::fromStdString(currentCondition), &ok);
141+
142+
if (ok)
143+
{
144+
if (isAbsoluteAddress)
145+
controller->SetBreakpointCondition(addr, newCondition.trimmed().toStdString());
146+
else
147+
controller->SetBreakpointCondition(relativeAddr, newCondition.trimmed().toStdString());
148+
}
149+
}
150+
121151
static void JumpToIPCallback(BinaryView* view, UIContext* context)
122152
{
123153
auto controller = DebuggerController::GetController(view);
@@ -927,6 +957,24 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context)
927957
}));
928958
debuggerMenu->addAction("Solo Breakpoint", "Breakpoint");
929959

960+
UIAction::registerAction("Edit Condition...");
961+
context->globalActions()->bindAction("Edit Condition...",
962+
UIAction(
963+
[=](const UIActionContext& ctxt) {
964+
if (!ctxt.binaryView || !ctxt.context)
965+
return;
966+
auto controller = DebuggerController::GetController(ctxt.binaryView);
967+
if (!controller)
968+
return;
969+
970+
BreakpointEditConditionCallback(ctxt.binaryView, ctxt.address, ctxt.context);
971+
},
972+
[=](const UIActionContext& ctxt) {
973+
auto [hasBreakpoint, isEnabled] = getBreakpointEnabledState(ctxt.binaryView, ctxt.address);
974+
return ctxt.binaryView && hasBreakpoint;
975+
}));
976+
debuggerMenu->addAction("Edit Condition...", "Breakpoint");
977+
930978
UIAction::registerAction("Connect to Debug Server");
931979
context->globalActions()->bindAction("Connect to Debug Server",
932980
UIAction(

ui/uinotification.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ void NotificationListener::OnContextMenuCreated(UIContext *context, View* view,
178178
menu.addAction("Debugger", "Toggle Breakpoint", "Breakpoint");
179179
menu.addAction("Debugger", "Enable Breakpoint", "Breakpoint");
180180
menu.addAction("Debugger", "Solo Breakpoint", "Breakpoint");
181+
menu.addAction("Debugger", "Edit Condition...", "Breakpoint");
181182
menu.addAction("Debugger", "Launch", "Control");
182183
menu.addAction("Debugger", "Pause", "Control");
183184
menu.addAction("Debugger", "Restart", "Control");

0 commit comments

Comments
 (0)