diff --git a/api/debuggerapi.h b/api/debuggerapi.h index fc23d425..c334b513 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -719,6 +719,10 @@ namespace BinaryNinjaDebuggerAPI { void DeleteBreakpoint(const ModuleNameAndOffset& breakpoint); void AddBreakpoint(uint64_t address); void AddBreakpoint(const ModuleNameAndOffset& breakpoint); + void EnableBreakpoint(uint64_t address); + void EnableBreakpoint(const ModuleNameAndOffset& breakpoint); + void DisableBreakpoint(uint64_t address); + void DisableBreakpoint(const ModuleNameAndOffset& breakpoint); bool ContainsBreakpoint(uint64_t address); bool ContainsBreakpoint(const ModuleNameAndOffset& breakpoint); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index 644565bf..d58985b1 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -760,6 +760,30 @@ void DebuggerController::AddBreakpoint(const ModuleNameAndOffset& breakpoint) } +void DebuggerController::EnableBreakpoint(uint64_t address) +{ + BNDebuggerEnableAbsoluteBreakpoint(m_object, address); +} + + +void DebuggerController::EnableBreakpoint(const ModuleNameAndOffset& breakpoint) +{ + BNDebuggerEnableRelativeBreakpoint(m_object, breakpoint.module.c_str(), breakpoint.offset); +} + + +void DebuggerController::DisableBreakpoint(uint64_t address) +{ + BNDebuggerDisableAbsoluteBreakpoint(m_object, address); +} + + +void DebuggerController::DisableBreakpoint(const ModuleNameAndOffset& breakpoint) +{ + BNDebuggerDisableRelativeBreakpoint(m_object, breakpoint.module.c_str(), breakpoint.offset); +} + + bool DebuggerController::ContainsBreakpoint(uint64_t address) { return BNDebuggerContainsAbsoluteBreakpoint(m_object, address); diff --git a/api/ffi.h b/api/ffi.h index 1f20dc86..a771bd48 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -252,6 +252,10 @@ extern "C" RelativeBreakpointAddedEvent, AbsoluteBreakpointRemovedEvent, RelativeBreakpointRemovedEvent, + AbsoluteBreakpointEnabledEvent, + RelativeBreakpointEnabledEvent, + AbsoluteBreakpointDisabledEvent, + RelativeBreakpointDisabledEvent, ActiveThreadChangedEvent, @@ -583,6 +587,12 @@ extern "C" DEBUGGER_FFI_API void BNDebuggerAddAbsoluteBreakpoint(BNDebuggerController* controller, uint64_t address); DEBUGGER_FFI_API void BNDebuggerAddRelativeBreakpoint( BNDebuggerController* controller, const char* module, uint64_t offset); + DEBUGGER_FFI_API void BNDebuggerEnableAbsoluteBreakpoint(BNDebuggerController* controller, uint64_t address); + DEBUGGER_FFI_API void BNDebuggerEnableRelativeBreakpoint( + BNDebuggerController* controller, const char* module, uint64_t offset); + DEBUGGER_FFI_API void BNDebuggerDisableAbsoluteBreakpoint(BNDebuggerController* controller, uint64_t address); + DEBUGGER_FFI_API void BNDebuggerDisableRelativeBreakpoint( + BNDebuggerController* controller, const char* module, uint64_t offset); DEBUGGER_FFI_API bool BNDebuggerContainsAbsoluteBreakpoint(BNDebuggerController* controller, uint64_t address); DEBUGGER_FFI_API bool BNDebuggerContainsRelativeBreakpoint( BNDebuggerController* controller, const char* module, uint64_t offset); diff --git a/api/python/debuggercontroller.py b/api/python/debuggercontroller.py index 7058144b..7c92c216 100644 --- a/api/python/debuggercontroller.py +++ b/api/python/debuggercontroller.py @@ -260,7 +260,7 @@ class DebugBreakpoint: * ``module``: the name of the module for which the breakpoint is in * ``offset``: the offset of the breakpoint to the start of the module * ``address``: the absolute address of the breakpoint - * ``enabled``: not used + * ``enabled``: whether the breakpoint is enabled (read-only) """ def __init__(self, module, offset, address, enabled): @@ -281,7 +281,7 @@ def __ne__(self, other): 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)) def __setattr__(self, name, value): try: @@ -290,7 +290,8 @@ def __setattr__(self, name, value): raise AttributeError(f"attribute '{name}' is read only") def __repr__(self): - return f"" + status = "enabled" if self.enabled else "disabled" + return f"" class ModuleNameAndOffset: @@ -1958,6 +1959,38 @@ def has_breakpoint(self, address) -> bool: else: raise NotImplementedError + def enable_breakpoint(self, address): + """ + Enable a 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 enable + """ + if isinstance(address, int): + dbgcore.BNDebuggerEnableAbsoluteBreakpoint(self.handle, address) + elif isinstance(address, ModuleNameAndOffset): + dbgcore.BNDebuggerEnableRelativeBreakpoint(self.handle, address.module, address.offset) + else: + raise NotImplementedError + + def disable_breakpoint(self, address): + """ + Disable a 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 disable + """ + if isinstance(address, int): + dbgcore.BNDebuggerDisableAbsoluteBreakpoint(self.handle, address) + elif isinstance(address, ModuleNameAndOffset): + dbgcore.BNDebuggerDisableRelativeBreakpoint(self.handle, address.module, address.offset) + else: + raise NotImplementedError + @property def ip(self) -> int: """ diff --git a/core/debugadapter.h b/core/debugadapter.h index 04055c21..575fa368 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -264,6 +264,14 @@ namespace BinaryNinjaDebugger { virtual bool RemoveBreakpoint(const ModuleNameAndOffset& address) { return false; } + virtual bool EnableBreakpoint(const std::uintptr_t address) { return false; } + + virtual bool EnableBreakpoint(const ModuleNameAndOffset& address) { return false; } + + virtual bool DisableBreakpoint(const std::uintptr_t address) { return false; } + + virtual bool DisableBreakpoint(const ModuleNameAndOffset& address) { return false; } + virtual std::vector GetBreakpointList() const = 0; virtual std::unordered_map ReadAllRegisters() = 0; diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 6e74b663..dc81f198 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -100,6 +100,46 @@ void DebuggerController::DeleteBreakpoint(const ModuleNameAndOffset& address) } +void DebuggerController::EnableBreakpoint(uint64_t address) +{ + m_state->EnableBreakpoint(address); + DebuggerEvent event; + event.type = AbsoluteBreakpointEnabledEvent; + event.data.absoluteAddress = address; + PostDebuggerEvent(event); +} + + +void DebuggerController::EnableBreakpoint(const ModuleNameAndOffset& address) +{ + m_state->EnableBreakpoint(address); + DebuggerEvent event; + event.type = RelativeBreakpointEnabledEvent; + event.data.relativeAddress = address; + PostDebuggerEvent(event); +} + + +void DebuggerController::DisableBreakpoint(uint64_t address) +{ + m_state->DisableBreakpoint(address); + DebuggerEvent event; + event.type = AbsoluteBreakpointDisabledEvent; + event.data.absoluteAddress = address; + PostDebuggerEvent(event); +} + + +void DebuggerController::DisableBreakpoint(const ModuleNameAndOffset& address) +{ + m_state->DisableBreakpoint(address); + DebuggerEvent event; + event.type = RelativeBreakpointDisabledEvent; + event.data.relativeAddress = address; + PostDebuggerEvent(event); +} + + bool DebuggerController::SetIP(uint64_t address) { std::string ipRegisterName; diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index d2b31867..67d78285 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -227,6 +227,10 @@ namespace BinaryNinjaDebugger { void AddBreakpoint(const ModuleNameAndOffset& address); void DeleteBreakpoint(uint64_t address); void DeleteBreakpoint(const ModuleNameAndOffset& address); + void EnableBreakpoint(uint64_t address); + void EnableBreakpoint(const ModuleNameAndOffset& address); + void DisableBreakpoint(uint64_t address); + void DisableBreakpoint(const ModuleNameAndOffset& address); DebugBreakpoint GetAllBreakpoints(); // registers diff --git a/core/debuggerstate.cpp b/core/debuggerstate.cpp index 0de03083..58f7af8e 100644 --- a/core/debuggerstate.cpp +++ b/core/debuggerstate.cpp @@ -534,6 +534,7 @@ bool DebuggerBreakpoints::AddAbsolute(uint64_t remoteAddress) { ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(remoteAddress); m_breakpoints.push_back(info); + m_enabledState[info] = true; // Enable by default SerializeMetadata(); } @@ -546,6 +547,7 @@ bool DebuggerBreakpoints::AddOffset(const ModuleNameAndOffset& address) if (!ContainsOffset(address)) { m_breakpoints.push_back(address); + m_enabledState[address] = true; // Enable by default SerializeMetadata(); // If the adapter is already created, we ask it to add the breakpoint. @@ -574,6 +576,7 @@ bool DebuggerBreakpoints::RemoveAbsolute(uint64_t remoteAddress) { m_breakpoints.erase(iter); } + m_enabledState.erase(info); // Remove enabled state SerializeMetadata(); m_state->GetAdapter()->RemoveBreakpoint(remoteAddress); return true; @@ -589,6 +592,7 @@ bool DebuggerBreakpoints::RemoveOffset(const ModuleNameAndOffset& address) if (auto iter = std::find(m_breakpoints.begin(), m_breakpoints.end(), address); iter != m_breakpoints.end()) m_breakpoints.erase(iter); + m_enabledState.erase(address); // Remove enabled state SerializeMetadata(); if (m_state->GetAdapter() && m_state->IsConnected()) @@ -603,6 +607,76 @@ bool DebuggerBreakpoints::RemoveOffset(const ModuleNameAndOffset& address) } +bool DebuggerBreakpoints::EnableAbsolute(uint64_t remoteAddress) +{ + ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(remoteAddress); + return EnableOffset(info); +} + + +bool DebuggerBreakpoints::EnableOffset(const ModuleNameAndOffset& address) +{ + if (!ContainsOffset(address)) + return false; + + m_enabledState[address] = 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); + m_state->GetAdapter()->AddBreakpoint(remoteAddress); + return true; + } + return true; +} + + +bool DebuggerBreakpoints::DisableAbsolute(uint64_t remoteAddress) +{ + ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(remoteAddress); + return DisableOffset(info); +} + + +bool DebuggerBreakpoints::DisableOffset(const ModuleNameAndOffset& address) +{ + if (!ContainsOffset(address)) + return false; + + m_enabledState[address] = 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); + m_state->GetAdapter()->RemoveBreakpoint(remoteAddress); + return true; + } + return true; +} + + +bool DebuggerBreakpoints::IsEnabledAbsolute(uint64_t address) +{ + ModuleNameAndOffset info = m_state->GetModules()->AbsoluteAddressToRelative(address); + return IsEnabledOffset(info); +} + + +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; +} + + bool DebuggerBreakpoints::ContainsOffset(const ModuleNameAndOffset& address) { // If there is no backend, then only check if the breakpoint is in the list @@ -980,6 +1054,30 @@ void DebuggerState::DeleteBreakpoint(const ModuleNameAndOffset& address) } +void DebuggerState::EnableBreakpoint(uint64_t address) +{ + m_breakpoints->EnableAbsolute(address); +} + + +void DebuggerState::EnableBreakpoint(const ModuleNameAndOffset& address) +{ + m_breakpoints->EnableOffset(address); +} + + +void DebuggerState::DisableBreakpoint(uint64_t address) +{ + m_breakpoints->DisableAbsolute(address); +} + + +void DebuggerState::DisableBreakpoint(const ModuleNameAndOffset& address) +{ + m_breakpoints->DisableOffset(address); +} + + uint64_t DebuggerState::IP() { if (!IsConnected()) diff --git a/core/debuggerstate.h b/core/debuggerstate.h index 82719cd1..065edf1d 100644 --- a/core/debuggerstate.h +++ b/core/debuggerstate.h @@ -16,6 +16,7 @@ limitations under the License. #pragma once +#include #include "binaryninjaapi.h" #include "ui/uitypes.h" #include "debugadaptertype.h" @@ -82,6 +83,7 @@ namespace BinaryNinjaDebugger { private: DebuggerState* m_state; std::vector m_breakpoints; + std::map m_enabledState; public: DebuggerBreakpoints(DebuggerState* state, std::vector initial = {}); @@ -89,8 +91,14 @@ namespace BinaryNinjaDebugger { bool AddOffset(const ModuleNameAndOffset& address); bool RemoveAbsolute(uint64_t remoteAddress); bool RemoveOffset(const ModuleNameAndOffset& address); + bool EnableAbsolute(uint64_t remoteAddress); + bool EnableOffset(const ModuleNameAndOffset& address); + bool DisableAbsolute(uint64_t remoteAddress); + bool DisableOffset(const ModuleNameAndOffset& address); bool ContainsAbsolute(uint64_t address); bool ContainsOffset(const ModuleNameAndOffset& address); + bool IsEnabledAbsolute(uint64_t address); + bool IsEnabledOffset(const ModuleNameAndOffset& address); void Apply(); void SerializeMetadata(); void UnserializedMetadata(); @@ -237,6 +245,10 @@ namespace BinaryNinjaDebugger { void AddBreakpoint(const ModuleNameAndOffset& address); void DeleteBreakpoint(uint64_t address); void DeleteBreakpoint(const ModuleNameAndOffset& address); + void EnableBreakpoint(uint64_t address); + void EnableBreakpoint(const ModuleNameAndOffset& address); + void DisableBreakpoint(uint64_t address); + void DisableBreakpoint(const ModuleNameAndOffset& address); uint64_t IP(); uint64_t StackPointer(); diff --git a/core/ffi.cpp b/core/ffi.cpp index 0b59285a..583edc6c 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -813,7 +813,7 @@ BNDebugBreakpoint* BNDebuggerGetBreakpoints(BNDebuggerController* controller, si for (size_t i = 0; i < breakpoints.size(); i++) { uint64_t remoteAddress = state->GetModules()->RelativeAddressToAbsolute(breakpoints[i]); - bool enabled = false; + 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; @@ -857,6 +857,30 @@ void BNDebuggerAddRelativeBreakpoint(BNDebuggerController* controller, const cha } +void BNDebuggerEnableAbsoluteBreakpoint(BNDebuggerController* controller, uint64_t address) +{ + controller->object->EnableBreakpoint(address); +} + + +void BNDebuggerEnableRelativeBreakpoint(BNDebuggerController* controller, const char* module, uint64_t offset) +{ + controller->object->EnableBreakpoint(ModuleNameAndOffset(module, offset)); +} + + +void BNDebuggerDisableAbsoluteBreakpoint(BNDebuggerController* controller, uint64_t address) +{ + controller->object->DisableBreakpoint(address); +} + + +void BNDebuggerDisableRelativeBreakpoint(BNDebuggerController* controller, const char* module, uint64_t offset) +{ + controller->object->DisableBreakpoint(ModuleNameAndOffset(module, offset)); +} + + uint64_t BNDebuggerGetIP(BNDebuggerController* controller) { return controller->object->GetCurrentIP(); diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index 666b0c2a..83c6b8d8 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -22,6 +22,7 @@ limitations under the License. #include #include #include +#include #include "breakpointswidget.h" #include "ui.h" #include "menus.h" @@ -104,11 +105,11 @@ QVariant DebugBreakpointsListModel::data(const QModelIndex& index, int role) con switch (index.column()) { -// case DebugBreakpointsListModel::EnabledColumn: -// { -// QString text = item->enabled() ? "true" : "false"; -// return QVariant(text); -// } + case DebugBreakpointsListModel::EnabledColumn: + { + QString text = item->enabled() ? "☑" : "☐"; + return QVariant(text); + } case DebugBreakpointsListModel::LocationColumn: { QString text; @@ -152,8 +153,8 @@ QVariant DebugBreakpointsListModel::headerData(int column, Qt::Orientation orien switch (column) { -// case DebugBreakpointsListModel::EnabledColumn: -// return "Enabled"; + case DebugBreakpointsListModel::EnabledColumn: + return ""; case DebugBreakpointsListModel::LocationColumn: return "Location"; case DebugBreakpointsListModel::AddressColumn: @@ -197,7 +198,7 @@ void DebugBreakpointsItemDelegate::paint( auto data = idx.data(Qt::DisplayRole); switch (idx.column()) { -// case DebugBreakpointsListModel::EnabledColumn: + case DebugBreakpointsListModel::EnabledColumn: case DebugBreakpointsListModel::LocationColumn: case DebugBreakpointsListModel::AddressColumn: { @@ -291,6 +292,30 @@ DebugBreakpointsWidget::DebugBreakpointsWidget(ViewFrame* view, BinaryViewRef da m_actionHandler.bindAction( addBreakpointActionName, UIAction([&]() { add(); })); + QString toggleEnabledActionName = QString::fromStdString("Toggle Enabled"); + UIAction::registerAction(toggleEnabledActionName, QKeySequence("Ctrl+Shift+B")); + m_menu->addAction(toggleEnabledActionName, "Options", MENU_ORDER_NORMAL); + m_actionHandler.bindAction( + toggleEnabledActionName, UIAction([&]() { toggleSelected(); }, [&]() { return selectionNotEmpty(); })); + + QString enableAllActionName = QString::fromStdString("Enable All Breakpoints"); + UIAction::registerAction(enableAllActionName); + m_menu->addAction(enableAllActionName, "Options", MENU_ORDER_NORMAL); + m_actionHandler.bindAction( + enableAllActionName, UIAction([&]() { enableAll(); })); + + QString disableAllActionName = QString::fromStdString("Disable All Breakpoints"); + UIAction::registerAction(disableAllActionName); + m_menu->addAction(disableAllActionName, "Options", MENU_ORDER_NORMAL); + m_actionHandler.bindAction( + disableAllActionName, UIAction([&]() { disableAll(); })); + + QString soloBreakpointActionName = QString::fromStdString("Solo Breakpoint"); + UIAction::registerAction(soloBreakpointActionName); + m_menu->addAction(soloBreakpointActionName, "Options", MENU_ORDER_NORMAL); + m_actionHandler.bindAction( + soloBreakpointActionName, UIAction([&]() { soloSelected(); }, [&]() { return selectionNotEmpty(); })); + connect(this, &QTableView::doubleClicked, this, &DebugBreakpointsWidget::onDoubleClicked); updateContent(); @@ -331,6 +356,25 @@ void DebugBreakpointsWidget::keyPressEvent(QKeyEvent* event) } +void DebugBreakpointsWidget::mousePressEvent(QMouseEvent* event) +{ + QModelIndex index = indexAt(event->pos()); + if (index.isValid() && index.column() == DebugBreakpointsListModel::EnabledColumn) + { + // Toggle breakpoint enabled state when clicking on enabled column + BreakpointItem bp = m_model->getRow(index.row()); + if (bp.enabled()) + m_controller->DisableBreakpoint(bp.location()); + else + m_controller->EnableBreakpoint(bp.location()); + return; // Don't call parent to avoid selection change + } + + // Call parent for normal behavior + QTableView::mousePressEvent(event); +} + + bool DebugBreakpointsWidget::selectionNotEmpty() { QModelIndexList sel = selectionModel()->selectedIndexes(); @@ -418,6 +462,70 @@ void DebugBreakpointsWidget::add() } +void DebugBreakpointsWidget::toggleSelected() +{ + QModelIndexList sel = selectionModel()->selectedRows(); + for (const QModelIndex& index : sel) + { + BreakpointItem bp = m_model->getRow(index.row()); + if (bp.enabled()) + m_controller->DisableBreakpoint(bp.location()); + else + m_controller->EnableBreakpoint(bp.location()); + } +} + + +void DebugBreakpointsWidget::enableAll() +{ + std::vector breakpoints = m_controller->GetBreakpoints(); + for (const DebugBreakpoint& bp : breakpoints) + { + ModuleNameAndOffset info; + info.module = bp.module; + info.offset = bp.offset; + m_controller->EnableBreakpoint(info); + } +} + + +void DebugBreakpointsWidget::disableAll() +{ + std::vector breakpoints = m_controller->GetBreakpoints(); + for (const DebugBreakpoint& bp : breakpoints) + { + ModuleNameAndOffset info; + info.module = bp.module; + info.offset = bp.offset; + m_controller->DisableBreakpoint(info); + } +} + + +void DebugBreakpointsWidget::soloSelected() +{ + QModelIndexList sel = selectionModel()->selectedRows(); + if (sel.empty()) + return; + + // 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) + { + ModuleNameAndOffset info; + info.module = bp.module; + info.offset = bp.offset; + m_controller->DisableBreakpoint(info); + } + + // Enable the selected breakpoint + m_controller->EnableBreakpoint(selectedBp.location()); +} + + void DebugBreakpointsWidget::remove() { QModelIndexList sel = selectionModel()->selectedRows(); @@ -450,4 +558,8 @@ void DebugBreakpointsWidget::updateContent() } m_model->updateRows(bps); + + resizeColumnToContents(DebugBreakpointsListModel::EnabledColumn); + resizeColumnToContents(DebugBreakpointsListModel::LocationColumn); + resizeColumnToContents(DebugBreakpointsListModel::AddressColumn); } diff --git a/ui/breakpointswidget.h b/ui/breakpointswidget.h index 3a5a3f4f..2df230a8 100644 --- a/ui/breakpointswidget.h +++ b/ui/breakpointswidget.h @@ -65,7 +65,7 @@ class DebugBreakpointsListModel : public QAbstractTableModel public: enum ColumnHeaders { - //EnabledColumn, + EnabledColumn, LocationColumn, AddressColumn, }; @@ -83,7 +83,7 @@ class DebugBreakpointsListModel : public QAbstractTableModel virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override { (void)parent; - return 2; + return 3; } BreakpointItem getRow(int row) const; virtual QVariant data(const QModelIndex& i, int role) const override; @@ -135,6 +135,7 @@ class DebugBreakpointsWidget : public QTableView virtual void contextMenuEvent(QContextMenuEvent* event) override; virtual void keyPressEvent(QKeyEvent* event) override; + virtual void mousePressEvent(QMouseEvent* event) override; public: DebugBreakpointsWidget(ViewFrame* view, BinaryViewRef data, Menu* menu); @@ -148,6 +149,10 @@ private slots: void remove(); void onDoubleClicked(); void add(); + void toggleSelected(); + void enableAll(); + void disableAll(); + void soloSelected(); public slots: void updateContent(); diff --git a/ui/debuggerwidget.cpp b/ui/debuggerwidget.cpp index 2629cd15..9824c6e7 100644 --- a/ui/debuggerwidget.cpp +++ b/ui/debuggerwidget.cpp @@ -92,6 +92,10 @@ void DebuggerWidget::uiEventHandler(const DebuggerEvent& event) case AbsoluteBreakpointAddedEvent: case RelativeBreakpointRemovedEvent: case AbsoluteBreakpointRemovedEvent: + case AbsoluteBreakpointEnabledEvent: + case RelativeBreakpointEnabledEvent: + case AbsoluteBreakpointDisabledEvent: + case RelativeBreakpointDisabledEvent: m_breakpointsWidget->updateContent(); break; default: diff --git a/ui/renderlayer.cpp b/ui/renderlayer.cpp index 7d91185f..8abe3114 100644 --- a/ui/renderlayer.cpp +++ b/ui/renderlayer.cpp @@ -17,6 +17,7 @@ limitations under the License. #include "renderlayer.h" #include "ttdcoveragerenderlayer.h" #include "debuggerapi.h" +#include using namespace BinaryNinja; using namespace BinaryNinjaDebuggerAPI; @@ -37,6 +38,14 @@ void DebuggerRenderLayer::ApplyToBlock(Ref block, std::vectorIP(); bool paused = controller->GetTargetStatus() == DebugAdapterPausedStatus; + // Get all breakpoints with their enabled state + std::vector breakpoints = controller->GetBreakpoints(); + std::map breakpointEnabledMap; + for (const auto& bp : breakpoints) + { + breakpointEnabledMap[bp.address] = bp.enabled; + } + for (auto& line : lines) { // Do not draw the tags on an empty line, e.g., those separating the basic blocks in the linear view @@ -44,9 +53,18 @@ void DebuggerRenderLayer::ApplyToBlock(Ref block, std::vectorContainsBreakpoint(line.addr); + bool hasEnabledBreakpoint = false; + bool hasDisabledBreakpoint = false; + + if (breakpointEnabledMap.count(line.addr) > 0) + { + if (breakpointEnabledMap[line.addr]) + hasEnabledBreakpoint = true; + else + hasDisabledBreakpoint = true; + } - if (hasPC && hasBreakpoint) + if (hasPC && hasEnabledBreakpoint) { bool appliedTag = false; for (size_t i = 0; i < line.tokens.size(); i++) @@ -73,6 +91,34 @@ void DebuggerRenderLayer::ApplyToBlock(Ref block, std::vector block, std::vector block, std::vector function, std::ve uint64_t ipAddr = controller->IP(); bool paused = controller->GetTargetStatus() == DebugAdapterPausedStatus; + // Get all breakpoints with their enabled state + std::vector breakpoints = controller->GetBreakpoints(); + std::map breakpointEnabledMap; + for (const auto& bp : breakpoints) + { + breakpointEnabledMap[bp.address] = bp.enabled; + } + for (auto& linearLine : lines) { DisassemblyTextLine& line = linearLine.contents; bool hasPC = (line.addr == ipAddr) && paused; - bool hasBreakpoint = controller->ContainsBreakpoint(line.addr); + bool hasEnabledBreakpoint = false; + bool hasDisabledBreakpoint = false; + + if (breakpointEnabledMap.count(line.addr) > 0) + { + if (breakpointEnabledMap[line.addr]) + hasEnabledBreakpoint = true; + else + hasDisabledBreakpoint = true; + } - if (hasPC && hasBreakpoint) + if (hasPC && hasEnabledBreakpoint) { bool appliedTag = false; for (size_t i = 0; i < line.tokens.size(); i++) @@ -174,6 +257,34 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve line.highlight.b = 0; line.highlight.alpha = 255; } + else if (hasPC && hasDisabledBreakpoint) + { + // PC at a disabled breakpoint - show both indicators, no breakpoint highlighting + bool appliedTag = false; + for (size_t i = 0; i < line.tokens.size(); i++) + { + if (line.tokens[i].type == TagToken) + { + line.tokens[i].text = "⭘➞"; + appliedTag = true; + break; + } + } + if (!appliedTag) + { + InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, "⭘➞"); + line.tokens.insert(line.tokens.begin(), indicator); + } + + line.highlight.style = StandardHighlightColor; + line.highlight.color = BlueHighlightColor; + line.highlight.mixColor = NoHighlightColor; + line.highlight.mix = 0; + line.highlight.r = 0; + line.highlight.g = 0; + line.highlight.b = 0; + line.highlight.alpha = 255; + } else if (hasPC) { bool appliedTag = false; @@ -201,7 +312,7 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve line.highlight.b = 0; line.highlight.alpha = 255; } - else if (hasBreakpoint) + else if (hasEnabledBreakpoint) { bool appliedTag = false; for (size_t i = 0; i < line.tokens.size(); i++) @@ -228,6 +339,26 @@ void DebuggerRenderLayer::ApplyToHighLevelILBody(Ref function, std::ve line.highlight.b = 0; line.highlight.alpha = 255; } + else if (hasDisabledBreakpoint) + { + // Disabled breakpoint - show tag but no line highlighting + bool appliedTag = false; + for (size_t i = 0; i < line.tokens.size(); i++) + { + if (line.tokens[i].type == TagToken) + { + line.tokens[i].text = "…⭘"; + appliedTag = true; + break; + } + } + if (!appliedTag) + { + InstructionTextToken indicator(BNInstructionTextTokenType::TagToken, "⭘"); + line.tokens.insert(line.tokens.begin(), indicator); + } + // No line highlighting for disabled breakpoints + } } } diff --git a/ui/ui.cpp b/ui/ui.cpp index 16f284ec..5be5304d 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -811,6 +811,120 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) requireBinaryView)); debuggerMenu->addAction("Toggle Breakpoint", "Breakpoint"); + // Helper function to check if there's a breakpoint at the current address and return its enabled state + auto getBreakpointEnabledState = [](BinaryView* view, uint64_t addr) -> std::pair { + auto controller = DebuggerController::GetController(view); + if (!controller) + return {false, false}; // {hasBreakpoint, isEnabled} + + std::vector breakpoints = controller->GetBreakpoints(); + for (const auto& bp : breakpoints) + { + if (bp.address == addr) + return {true, bp.enabled}; + } + return {false, false}; + }; + + // Register dynamic "Enable/Disable Breakpoint" action + UIAction::registerAction("Enable Breakpoint"); + + context->globalActions()->bindAction("Enable Breakpoint", + UIAction( + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return; + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller) + return; + + auto [hasBreakpoint, isEnabled] = getBreakpointEnabledState(ctxt.binaryView, ctxt.address); + bool isAbsoluteAddress = controller->IsConnected(); + + if (isAbsoluteAddress) + { + if (isEnabled) + controller->DisableBreakpoint(ctxt.address); + else + controller->EnableBreakpoint(ctxt.address); + } + else + { + std::string filename = controller->GetInputFile(); + uint64_t offset = ctxt.address - controller->GetViewFileSegmentsStart(); + ModuleNameAndOffset info = {filename, offset}; + if (isEnabled) + controller->DisableBreakpoint(info); + else + controller->EnableBreakpoint(info); + } + }, + [=](const UIActionContext& ctxt) { + auto [hasBreakpoint, isEnabled] = getBreakpointEnabledState(ctxt.binaryView, ctxt.address); + return ctxt.binaryView && hasBreakpoint; + })); + + // Dynamically change the action name based on the current breakpoint state + UIAction::setActionDisplayName("Enable Breakpoint", [=](const UIActionContext& ctxt) -> QString { + if (!ctxt.binaryView) + return "Enable Breakpoint"; + + auto [hasBreakpoint, isEnabled] = getBreakpointEnabledState(ctxt.binaryView, ctxt.address); + if (hasBreakpoint && isEnabled) + return "Disable Breakpoint"; + + return "Enable Breakpoint"; + }); + + debuggerMenu->addAction("Enable Breakpoint", "Breakpoint"); + + // Register "Solo Breakpoint" action + UIAction::registerAction("Solo Breakpoint"); + context->globalActions()->bindAction("Solo Breakpoint", + UIAction( + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return; + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller) + return; + + // Get the current address breakpoint location + bool isAbsoluteAddress = controller->IsConnected(); + ModuleNameAndOffset currentInfo; + if (!isAbsoluteAddress) + { + std::string filename = controller->GetInputFile(); + uint64_t offset = ctxt.address - controller->GetViewFileSegmentsStart(); + currentInfo = {filename, offset}; + } + + // Disable all breakpoints + std::vector breakpoints = controller->GetBreakpoints(); + for (const auto& bp : breakpoints) + { + ModuleNameAndOffset info; + info.module = bp.module; + info.offset = bp.offset; + controller->DisableBreakpoint(info); + } + + // Enable the current breakpoint + if (isAbsoluteAddress) + { + controller->EnableBreakpoint(ctxt.address); + } + else + { + controller->EnableBreakpoint(currentInfo); + } + }, + [=](const UIActionContext& ctxt) { + auto [hasBreakpoint, isEnabled] = getBreakpointEnabledState(ctxt.binaryView, ctxt.address); + return ctxt.binaryView && hasBreakpoint; + })); + debuggerMenu->addAction("Solo Breakpoint", "Breakpoint"); + UIAction::registerAction("Connect to Debug Server"); context->globalActions()->bindAction("Connect to Debug Server", UIAction( @@ -1595,70 +1709,14 @@ void DebuggerUI::updateUI(const DebuggerEvent& event) } case RelativeBreakpointAddedEvent: - { - uint64_t address = m_controller->RelativeAddressToAbsolute(event.data.relativeAddress); - - std::vector> dataAndAddress; - if (m_controller->GetData()) - dataAndAddress.emplace_back(m_controller->GetData(), address); - - if (DebugModule::IsSameBaseModule(event.data.relativeAddress.module, m_controller->GetInputFile())) - { - dataAndAddress.emplace_back(m_controller->GetData(), m_controller->GetViewFileSegmentsStart() + event.data.relativeAddress.offset); - } - - m_context->refreshCurrentViewContents(); - break; - } case AbsoluteBreakpointAddedEvent: - { - uint64_t address = event.data.absoluteAddress; - - std::vector> dataAndAddress; - BinaryViewRef data = m_controller->GetData(); - if (data) - dataAndAddress.emplace_back(data, address); - - ModuleNameAndOffset relative = m_controller->AbsoluteAddressToRelative(address); - if (DebugModule::IsSameBaseModule(relative.module, m_controller->GetInputFile())) - { - dataAndAddress.emplace_back(m_controller->GetData(), m_controller->GetViewFileSegmentsStart() + relative.offset); - } - - m_context->refreshCurrentViewContents(); - break; - } case RelativeBreakpointRemovedEvent: - { - uint64_t address = m_controller->RelativeAddressToAbsolute(event.data.relativeAddress); - - std::vector> dataAndAddress; - if (m_controller->GetData()) - dataAndAddress.emplace_back(m_controller->GetData(), address); - - if (DebugModule::IsSameBaseModule(event.data.relativeAddress.module, m_controller->GetInputFile())) - { - dataAndAddress.emplace_back(m_controller->GetData(), m_controller->GetViewFileSegmentsStart() + event.data.relativeAddress.offset); - } - - m_context->refreshCurrentViewContents(); - break; - } case AbsoluteBreakpointRemovedEvent: + case RelativeBreakpointEnabledEvent: + case AbsoluteBreakpointEnabledEvent: + case RelativeBreakpointDisabledEvent: + case AbsoluteBreakpointDisabledEvent: { - uint64_t address = event.data.absoluteAddress; - - std::vector> dataAndAddress; - BinaryViewRef data = m_controller->GetData(); - if (data) - dataAndAddress.emplace_back(data, address); - - ModuleNameAndOffset relative = m_controller->AbsoluteAddressToRelative(address); - if (DebugModule::IsSameBaseModule(relative.module, m_controller->GetInputFile())) - { - dataAndAddress.emplace_back(m_controller->GetData(), m_controller->GetViewFileSegmentsStart() + relative.offset); - } - m_context->refreshCurrentViewContents(); break; } diff --git a/ui/uinotification.cpp b/ui/uinotification.cpp index 87e1bc09..79f4d7cb 100644 --- a/ui/uinotification.cpp +++ b/ui/uinotification.cpp @@ -176,6 +176,8 @@ void NotificationListener::OnContextMenuCreated(UIContext *context, View* view, return; menu.addAction("Debugger", "Toggle Breakpoint", "Breakpoint"); + menu.addAction("Debugger", "Enable Breakpoint", "Breakpoint"); + menu.addAction("Debugger", "Solo Breakpoint", "Breakpoint"); menu.addAction("Debugger", "Launch", "Control"); menu.addAction("Debugger", "Pause", "Control"); menu.addAction("Debugger", "Restart", "Control");