From 9bfbe3897d0075e730eac18cf131b576d76c348f Mon Sep 17 00:00:00 2001 From: Xusheng Date: Tue, 17 Mar 2026 18:39:46 +0800 Subject: [PATCH] Add TTD timestamp history navigation (back/forward) Record the TTD position every time the target stops and allow navigating back and forth through the history with Shift+ESC / Ctrl+Shift+ESC. Adds toolbar buttons, menu items, and full API support (C++, FFI, Python). Fixes #932. TODO: Need dedicated left/right arrow icons for the toolbar buttons. Co-Authored-By: Claude Opus 4.6 (1M context) --- api/debuggerapi.h | 7 +++ api/debuggercontroller.cpp | 31 +++++++++++++ api/ffi.h | 7 +++ api/python/debuggercontroller.py | 44 +++++++++++++++++++ core/debuggercontroller.cpp | 75 ++++++++++++++++++++++++++++++++ core/debuggercontroller.h | 13 ++++++ core/ffi.cpp | 26 +++++++++++ ui/controlswidget.cpp | 56 +++++++++++++++++++++--- ui/controlswidget.h | 4 ++ ui/ui.cpp | 50 +++++++++++++++++++++ 10 files changed, 307 insertions(+), 6 deletions(-) diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 69d7b3db..6fbaf67b 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -843,6 +843,13 @@ namespace BinaryNinjaDebuggerAPI { std::pair GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType); std::pair GetTTDPrevMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType); + // TTD Position History Navigation + bool TTDNavigateBack(); + bool TTDNavigateForward(); + bool CanTTDNavigateBack(); + bool CanTTDNavigateForward(); + void ClearTTDPositionHistory(); + // TTD Bookmark Methods std::vector GetTTDBookmarks(); bool AddTTDBookmark(const TTDPosition& position, const std::string& note = "", uint64_t viewAddress = 0); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index 8db8cb89..08415362 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -1182,6 +1182,37 @@ bool DebuggerController::SetTTDPosition(const TTDPosition& position) return BNDebuggerSetTTDPosition(m_object, pos); } + +bool DebuggerController::TTDNavigateBack() +{ + return BNDebuggerTTDNavigateBack(m_object); +} + + +bool DebuggerController::TTDNavigateForward() +{ + return BNDebuggerTTDNavigateForward(m_object); +} + + +bool DebuggerController::CanTTDNavigateBack() +{ + return BNDebuggerCanTTDNavigateBack(m_object); +} + + +bool DebuggerController::CanTTDNavigateForward() +{ + return BNDebuggerCanTTDNavigateForward(m_object); +} + + +void DebuggerController::ClearTTDPositionHistory() +{ + BNDebuggerClearTTDPositionHistory(m_object); +} + + std::pair DebuggerController::GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType) { BNDebuggerTTDMemoryEvent bnEvent = {}; diff --git a/api/ffi.h b/api/ffi.h index 1c035fbd..783d98eb 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -718,6 +718,13 @@ extern "C" DEBUGGER_FFI_API void BNDebuggerClearTTDBookmarks(BNDebuggerController* controller); DEBUGGER_FFI_API void BNDebuggerFreeTTDBookmarks(BNDebuggerTTDBookmark* bookmarks, size_t count); + // TTD Position History Navigation + DEBUGGER_FFI_API bool BNDebuggerTTDNavigateBack(BNDebuggerController* controller); + DEBUGGER_FFI_API bool BNDebuggerTTDNavigateForward(BNDebuggerController* controller); + DEBUGGER_FFI_API bool BNDebuggerCanTTDNavigateBack(BNDebuggerController* controller); + DEBUGGER_FFI_API bool BNDebuggerCanTTDNavigateForward(BNDebuggerController* controller); + DEBUGGER_FFI_API void BNDebuggerClearTTDPositionHistory(BNDebuggerController* controller); + // TTD Code Coverage Analysis Functions DEBUGGER_FFI_API bool BNDebuggerIsInstructionExecuted(BNDebuggerController* controller, uint64_t address); DEBUGGER_FFI_API bool BNDebuggerRunCodeCoverageAnalysisRange(BNDebuggerController* controller, uint64_t startAddress, uint64_t endAddress, BNDebuggerTTDPosition startTime, BNDebuggerTTDPosition endTime); diff --git a/api/python/debuggercontroller.py b/api/python/debuggercontroller.py index e50f0b45..e922144a 100644 --- a/api/python/debuggercontroller.py +++ b/api/python/debuggercontroller.py @@ -2682,6 +2682,50 @@ def clear_ttd_bookmarks(self): """ dbgcore.BNDebuggerClearTTDBookmarks(self.handle) + def ttd_navigate_back(self): + """ + Navigate to the previous TTD timestamp in the position history. + + Returns: + bool: True if navigation succeeded, False if there is no previous position + """ + return dbgcore.BNDebuggerTTDNavigateBack(self.handle) + + def ttd_navigate_forward(self): + """ + Navigate to the next TTD timestamp in the position history. + + Returns: + bool: True if navigation succeeded, False if there is no next position + """ + return dbgcore.BNDebuggerTTDNavigateForward(self.handle) + + @property + def can_ttd_navigate_back(self): + """ + Check if there is a previous TTD position to navigate to. + + Returns: + bool: True if back navigation is possible + """ + return dbgcore.BNDebuggerCanTTDNavigateBack(self.handle) + + @property + def can_ttd_navigate_forward(self): + """ + Check if there is a next TTD position to navigate to. + + Returns: + bool: True if forward navigation is possible + """ + return dbgcore.BNDebuggerCanTTDNavigateForward(self.handle) + + def clear_ttd_position_history(self): + """ + Clear the TTD position navigation history. + """ + dbgcore.BNDebuggerClearTTDPositionHistory(self.handle) + def get_ttd_next_memory_access(self, address: int, size: int, access_type = DebuggerTTDMemoryAccessType.DebuggerTTDMemoryRead): """ Get the next memory access to a specific address from the current TTD position. diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index b2ad5e91..0ef03829 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -1872,6 +1872,7 @@ void DebuggerController::EventHandler(const DebuggerEvent& event) m_state->MarkDirty(); m_inputFileLoaded = false; m_initialBreakpointSeen = false; + ClearTTDPositionHistory(); RemoveDebuggerMemoryRegion(); if (m_oldAnalysisState != HoldState) { @@ -1901,6 +1902,7 @@ void DebuggerController::EventHandler(const DebuggerEvent& event) UpdateStackVariables(); AddRegisterValuesToExpressionParser(); AddModuleValuesToExpressionParser(); + RecordTTDPosition(); break; } case ActiveThreadChangedEvent: @@ -3151,6 +3153,79 @@ std::vector DebuggerController::GetAllTTDEvents() } +void DebuggerController::RecordTTDPosition() +{ + if (!m_adapterSupportsTTD || !m_adapter || m_suppressTTDPositionRecording) + return; + + TTDPosition position = m_adapter->GetCurrentTTDPosition(); + if (position.sequence == 0 && position.step == 0) + return; + + // If we're not at the end of the history (i.e., the user navigated back and then did something new), + // truncate the forward history + if (m_ttdPositionHistoryIndex >= 0 + && m_ttdPositionHistoryIndex < static_cast(m_ttdPositionHistory.size()) - 1) + { + m_ttdPositionHistory.resize(m_ttdPositionHistoryIndex + 1); + } + + // Don't record duplicates + if (!m_ttdPositionHistory.empty() && m_ttdPositionHistory.back() == position) + return; + + m_ttdPositionHistory.push_back(position); + m_ttdPositionHistoryIndex = static_cast(m_ttdPositionHistory.size()) - 1; +} + + +bool DebuggerController::TTDNavigateBack() +{ + if (!CanTTDNavigateBack()) + return false; + + m_ttdPositionHistoryIndex--; + m_suppressTTDPositionRecording = true; + bool result = SetTTDPosition(m_ttdPositionHistory[m_ttdPositionHistoryIndex]); + m_suppressTTDPositionRecording = false; + return result; +} + + +bool DebuggerController::TTDNavigateForward() +{ + if (!CanTTDNavigateForward()) + return false; + + m_ttdPositionHistoryIndex++; + m_suppressTTDPositionRecording = true; + bool result = SetTTDPosition(m_ttdPositionHistory[m_ttdPositionHistoryIndex]); + m_suppressTTDPositionRecording = false; + return result; +} + + +bool DebuggerController::CanTTDNavigateBack() const +{ + return m_adapterSupportsTTD && m_ttdPositionHistoryIndex > 0; +} + + +bool DebuggerController::CanTTDNavigateForward() const +{ + return m_adapterSupportsTTD + && m_ttdPositionHistoryIndex >= 0 + && m_ttdPositionHistoryIndex < static_cast(m_ttdPositionHistory.size()) - 1; +} + + +void DebuggerController::ClearTTDPositionHistory() +{ + m_ttdPositionHistory.clear(); + m_ttdPositionHistoryIndex = -1; +} + + TTDPosition DebuggerController::GetCurrentTTDPosition() { TTDPosition position; diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index 5947fb50..4a74cc24 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -210,6 +210,12 @@ namespace BinaryNinjaDebugger { std::unordered_map m_executedInstructionCounts; bool m_codeCoverageAnalysisRun = false; + // TTD Position History for back/forward navigation + std::vector m_ttdPositionHistory; + int m_ttdPositionHistoryIndex = -1; + bool m_suppressTTDPositionRecording = false; + void RecordTTDPosition(); + public: DebuggerController(BinaryViewRef data); static DbgRef GetController(BinaryViewRef data); @@ -405,6 +411,13 @@ namespace BinaryNinjaDebugger { std::pair GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType); std::pair GetTTDPrevMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType); + // TTD Position History Navigation + bool TTDNavigateBack(); + bool TTDNavigateForward(); + bool CanTTDNavigateBack() const; + bool CanTTDNavigateForward() const; + void ClearTTDPositionHistory(); + // TTD Bookmark Methods std::vector GetTTDBookmarks(); bool AddTTDBookmark(const TTDPosition& position, const std::string& note = "", uint64_t viewAddress = 0); diff --git a/core/ffi.cpp b/core/ffi.cpp index b595de6c..5b2a7dfd 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -1393,6 +1393,32 @@ void BNDebuggerFreeTTDBookmarks(BNDebuggerTTDBookmark* bookmarks, size_t count) } +bool BNDebuggerTTDNavigateBack(BNDebuggerController* controller) +{ + return controller->object->TTDNavigateBack(); +} + +bool BNDebuggerTTDNavigateForward(BNDebuggerController* controller) +{ + return controller->object->TTDNavigateForward(); +} + +bool BNDebuggerCanTTDNavigateBack(BNDebuggerController* controller) +{ + return controller->object->CanTTDNavigateBack(); +} + +bool BNDebuggerCanTTDNavigateForward(BNDebuggerController* controller) +{ + return controller->object->CanTTDNavigateForward(); +} + +void BNDebuggerClearTTDPositionHistory(BNDebuggerController* controller) +{ + controller->object->ClearTTDPositionHistory(); +} + + bool BNDebuggerIsInstructionExecuted(BNDebuggerController* controller, uint64_t address) { return controller->object->IsInstructionExecuted(address); diff --git a/ui/controlswidget.cpp b/ui/controlswidget.cpp index 70c61fc9..21892c72 100644 --- a/ui/controlswidget.cpp +++ b/ui/controlswidget.cpp @@ -143,6 +143,17 @@ DebugControlsWidget::DebugControlsWidget(QWidget* parent, const std::string name performTimestampNavigation(); }); m_actionTimestampNavigation->setToolTip(getToolTip("Navigate to TTD Timestamp...")); + + m_actionTTDNavigateBack = addAction(getColoredIcon(":/debugger/resume-reverse", cyan), "TTD Navigate Back", [this]() { + performTTDNavigateBack(); + }); + m_actionTTDNavigateBack->setToolTip(getToolTip("TTD Navigate Back")); + + m_actionTTDNavigateForward = addAction(getColoredIcon(":/debugger/resume", cyan), "TTD Navigate Forward", [this]() { + performTTDNavigateForward(); + }); + m_actionTTDNavigateForward->setToolTip(getToolTip("TTD Navigate Forward")); + updateButtons(); } @@ -617,6 +628,8 @@ void DebugControlsWidget::updateButtons() DebugAdapterConnectionStatus connection = m_controller->GetConnectionStatus(); DebugAdapterTargetStatus status = m_controller->GetTargetStatus(); + bool isTTD = m_controller->IsTTD(); + if (connection == DebugAdapterNotConnectedStatus) { setStartingEnabled(true); @@ -631,6 +644,9 @@ void DebugControlsWidget::updateButtons() m_actionPause->setVisible(false); m_actionResume->setVisible(false); m_actionGoBack->setVisible(false); + + m_actionTTDNavigateBack->setVisible(false); + m_actionTTDNavigateForward->setVisible(false); } else if (status == DebugAdapterRunningStatus) { @@ -638,9 +654,9 @@ void DebugControlsWidget::updateButtons() setStoppingEnabled(true); setSteppingEnabled(false); setReverseSteppingEnabled(false); - m_actionStepIntoBack->setVisible(m_controller->IsTTD()); - m_actionStepOverBack->setVisible(m_controller->IsTTD()); - + m_actionStepIntoBack->setVisible(isTTD); + m_actionStepOverBack->setVisible(isTTD); + m_actionPause->setEnabled(true); m_actionResume->setEnabled(false); m_actionGoBack->setEnabled(false); @@ -649,21 +665,31 @@ void DebugControlsWidget::updateButtons() m_actionPause->setVisible(true); m_actionResume->setVisible(false); m_actionGoBack->setVisible(false); + + m_actionTTDNavigateBack->setVisible(isTTD); + m_actionTTDNavigateBack->setEnabled(false); + m_actionTTDNavigateForward->setVisible(isTTD); + m_actionTTDNavigateForward->setEnabled(false); } else // status == DebugAdapterPausedStatus { setStartingEnabled(false); setStoppingEnabled(true); setSteppingEnabled(true); - setReverseSteppingEnabled(m_controller->IsTTD()); + setReverseSteppingEnabled(isTTD); m_actionPause->setEnabled(false); m_actionResume->setEnabled(true); - m_actionGoBack->setEnabled(m_controller->IsTTD()); + m_actionGoBack->setEnabled(isTTD); m_actionRun->setVisible(false); m_actionPause->setVisible(false); m_actionResume->setVisible(true); - m_actionGoBack->setVisible(m_controller->IsTTD()); + m_actionGoBack->setVisible(isTTD); + + m_actionTTDNavigateBack->setVisible(isTTD); + m_actionTTDNavigateBack->setEnabled(m_controller->CanTTDNavigateBack()); + m_actionTTDNavigateForward->setVisible(isTTD); + m_actionTTDNavigateForward->setEnabled(m_controller->CanTTDNavigateForward()); } } @@ -679,3 +705,21 @@ void DebugControlsWidget::performTimestampNavigation() auto* dialog = new TimestampNavigationDialog(this, m_controller); dialog->show(); } + + +void DebugControlsWidget::performTTDNavigateBack() +{ + if (!m_controller->CanTTDNavigateBack()) + return; + + m_controller->TTDNavigateBack(); +} + + +void DebugControlsWidget::performTTDNavigateForward() +{ + if (!m_controller->CanTTDNavigateForward()) + return; + + m_controller->TTDNavigateForward(); +} diff --git a/ui/controlswidget.h b/ui/controlswidget.h index 580904bd..e1f3a74e 100644 --- a/ui/controlswidget.h +++ b/ui/controlswidget.h @@ -54,6 +54,8 @@ class DebugControlsWidget : public QToolBar QAction* m_actionSettings; QAction* m_actionToggleBreakpoint; QAction* m_actionTimestampNavigation; + QAction* m_actionTTDNavigateBack; + QAction* m_actionTTDNavigateForward; bool canExec(); bool canConnect(); @@ -93,4 +95,6 @@ public Q_SLOTS: void performSettings(); void toggleBreakpoint(); void performTimestampNavigation(); + void performTTDNavigateBack(); + void performTTDNavigateForward(); }; diff --git a/ui/ui.cpp b/ui/ui.cpp index 2105b729..df1873ca 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -1518,6 +1518,56 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) connectedToTTD)); debuggerMenu->addAction("TTD Analysis...", "TTD"); + UIAction::registerAction("TTD Navigate Back", QKeySequence(Qt::ShiftModifier | Qt::Key_Escape)); + context->globalActions()->bindAction("TTD Navigate Back", + UIAction( + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return; + + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller || !controller->IsTTD()) + return; + + controller->TTDNavigateBack(); + }, + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return false; + + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller) + return false; + + return controller->CanTTDNavigateBack(); + })); + debuggerMenu->addAction("TTD Navigate Back", "TTD"); + + UIAction::registerAction("TTD Navigate Forward", QKeySequence(Qt::ShiftModifier | Qt::ControlModifier | Qt::Key_Escape)); + context->globalActions()->bindAction("TTD Navigate Forward", + UIAction( + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return; + + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller || !controller->IsTTD()) + return; + + controller->TTDNavigateForward(); + }, + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return false; + + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller) + return false; + + return controller->CanTTDNavigateForward(); + })); + debuggerMenu->addAction("TTD Navigate Forward", "TTD"); + #endif }