Skip to content

Commit 3822d2f

Browse files
xusheng6claude
andauthored
Add TTD timestamp history navigation (back/forward) (#1013)
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) <noreply@anthropic.com>
1 parent 5bb9c4a commit 3822d2f

10 files changed

Lines changed: 307 additions & 6 deletions

api/debuggerapi.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,13 @@ namespace BinaryNinjaDebuggerAPI {
843843
std::pair<bool, TTDMemoryEvent> GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType);
844844
std::pair<bool, TTDMemoryEvent> GetTTDPrevMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType);
845845

846+
// TTD Position History Navigation
847+
bool TTDNavigateBack();
848+
bool TTDNavigateForward();
849+
bool CanTTDNavigateBack();
850+
bool CanTTDNavigateForward();
851+
void ClearTTDPositionHistory();
852+
846853
// TTD Bookmark Methods
847854
std::vector<TTDBookmark> GetTTDBookmarks();
848855
bool AddTTDBookmark(const TTDPosition& position, const std::string& note = "", uint64_t viewAddress = 0);

api/debuggercontroller.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,37 @@ bool DebuggerController::SetTTDPosition(const TTDPosition& position)
11821182
return BNDebuggerSetTTDPosition(m_object, pos);
11831183
}
11841184

1185+
1186+
bool DebuggerController::TTDNavigateBack()
1187+
{
1188+
return BNDebuggerTTDNavigateBack(m_object);
1189+
}
1190+
1191+
1192+
bool DebuggerController::TTDNavigateForward()
1193+
{
1194+
return BNDebuggerTTDNavigateForward(m_object);
1195+
}
1196+
1197+
1198+
bool DebuggerController::CanTTDNavigateBack()
1199+
{
1200+
return BNDebuggerCanTTDNavigateBack(m_object);
1201+
}
1202+
1203+
1204+
bool DebuggerController::CanTTDNavigateForward()
1205+
{
1206+
return BNDebuggerCanTTDNavigateForward(m_object);
1207+
}
1208+
1209+
1210+
void DebuggerController::ClearTTDPositionHistory()
1211+
{
1212+
BNDebuggerClearTTDPositionHistory(m_object);
1213+
}
1214+
1215+
11851216
std::pair<bool, TTDMemoryEvent> DebuggerController::GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType)
11861217
{
11871218
BNDebuggerTTDMemoryEvent bnEvent = {};

api/ffi.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,13 @@ extern "C"
718718
DEBUGGER_FFI_API void BNDebuggerClearTTDBookmarks(BNDebuggerController* controller);
719719
DEBUGGER_FFI_API void BNDebuggerFreeTTDBookmarks(BNDebuggerTTDBookmark* bookmarks, size_t count);
720720

721+
// TTD Position History Navigation
722+
DEBUGGER_FFI_API bool BNDebuggerTTDNavigateBack(BNDebuggerController* controller);
723+
DEBUGGER_FFI_API bool BNDebuggerTTDNavigateForward(BNDebuggerController* controller);
724+
DEBUGGER_FFI_API bool BNDebuggerCanTTDNavigateBack(BNDebuggerController* controller);
725+
DEBUGGER_FFI_API bool BNDebuggerCanTTDNavigateForward(BNDebuggerController* controller);
726+
DEBUGGER_FFI_API void BNDebuggerClearTTDPositionHistory(BNDebuggerController* controller);
727+
721728
// TTD Code Coverage Analysis Functions
722729
DEBUGGER_FFI_API bool BNDebuggerIsInstructionExecuted(BNDebuggerController* controller, uint64_t address);
723730
DEBUGGER_FFI_API bool BNDebuggerRunCodeCoverageAnalysisRange(BNDebuggerController* controller, uint64_t startAddress, uint64_t endAddress, BNDebuggerTTDPosition startTime, BNDebuggerTTDPosition endTime);

api/python/debuggercontroller.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2682,6 +2682,50 @@ def clear_ttd_bookmarks(self):
26822682
"""
26832683
dbgcore.BNDebuggerClearTTDBookmarks(self.handle)
26842684

2685+
def ttd_navigate_back(self):
2686+
"""
2687+
Navigate to the previous TTD timestamp in the position history.
2688+
2689+
Returns:
2690+
bool: True if navigation succeeded, False if there is no previous position
2691+
"""
2692+
return dbgcore.BNDebuggerTTDNavigateBack(self.handle)
2693+
2694+
def ttd_navigate_forward(self):
2695+
"""
2696+
Navigate to the next TTD timestamp in the position history.
2697+
2698+
Returns:
2699+
bool: True if navigation succeeded, False if there is no next position
2700+
"""
2701+
return dbgcore.BNDebuggerTTDNavigateForward(self.handle)
2702+
2703+
@property
2704+
def can_ttd_navigate_back(self):
2705+
"""
2706+
Check if there is a previous TTD position to navigate to.
2707+
2708+
Returns:
2709+
bool: True if back navigation is possible
2710+
"""
2711+
return dbgcore.BNDebuggerCanTTDNavigateBack(self.handle)
2712+
2713+
@property
2714+
def can_ttd_navigate_forward(self):
2715+
"""
2716+
Check if there is a next TTD position to navigate to.
2717+
2718+
Returns:
2719+
bool: True if forward navigation is possible
2720+
"""
2721+
return dbgcore.BNDebuggerCanTTDNavigateForward(self.handle)
2722+
2723+
def clear_ttd_position_history(self):
2724+
"""
2725+
Clear the TTD position navigation history.
2726+
"""
2727+
dbgcore.BNDebuggerClearTTDPositionHistory(self.handle)
2728+
26852729
def get_ttd_next_memory_access(self, address: int, size: int, access_type = DebuggerTTDMemoryAccessType.DebuggerTTDMemoryRead):
26862730
"""
26872731
Get the next memory access to a specific address from the current TTD position.

core/debuggercontroller.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,6 +1864,7 @@ void DebuggerController::EventHandler(const DebuggerEvent& event)
18641864
m_state->MarkDirty();
18651865
m_inputFileLoaded = false;
18661866
m_initialBreakpointSeen = false;
1867+
ClearTTDPositionHistory();
18671868
RemoveDebuggerMemoryRegion();
18681869
if (m_oldAnalysisState != HoldState)
18691870
{
@@ -1893,6 +1894,7 @@ void DebuggerController::EventHandler(const DebuggerEvent& event)
18931894
UpdateStackVariables();
18941895
AddRegisterValuesToExpressionParser();
18951896
AddModuleValuesToExpressionParser();
1897+
RecordTTDPosition();
18961898
break;
18971899
}
18981900
case ActiveThreadChangedEvent:
@@ -3143,6 +3145,79 @@ std::vector<TTDEvent> DebuggerController::GetAllTTDEvents()
31433145
}
31443146

31453147

3148+
void DebuggerController::RecordTTDPosition()
3149+
{
3150+
if (!m_adapterSupportsTTD || !m_adapter || m_suppressTTDPositionRecording)
3151+
return;
3152+
3153+
TTDPosition position = m_adapter->GetCurrentTTDPosition();
3154+
if (position.sequence == 0 && position.step == 0)
3155+
return;
3156+
3157+
// If we're not at the end of the history (i.e., the user navigated back and then did something new),
3158+
// truncate the forward history
3159+
if (m_ttdPositionHistoryIndex >= 0
3160+
&& m_ttdPositionHistoryIndex < static_cast<int>(m_ttdPositionHistory.size()) - 1)
3161+
{
3162+
m_ttdPositionHistory.resize(m_ttdPositionHistoryIndex + 1);
3163+
}
3164+
3165+
// Don't record duplicates
3166+
if (!m_ttdPositionHistory.empty() && m_ttdPositionHistory.back() == position)
3167+
return;
3168+
3169+
m_ttdPositionHistory.push_back(position);
3170+
m_ttdPositionHistoryIndex = static_cast<int>(m_ttdPositionHistory.size()) - 1;
3171+
}
3172+
3173+
3174+
bool DebuggerController::TTDNavigateBack()
3175+
{
3176+
if (!CanTTDNavigateBack())
3177+
return false;
3178+
3179+
m_ttdPositionHistoryIndex--;
3180+
m_suppressTTDPositionRecording = true;
3181+
bool result = SetTTDPosition(m_ttdPositionHistory[m_ttdPositionHistoryIndex]);
3182+
m_suppressTTDPositionRecording = false;
3183+
return result;
3184+
}
3185+
3186+
3187+
bool DebuggerController::TTDNavigateForward()
3188+
{
3189+
if (!CanTTDNavigateForward())
3190+
return false;
3191+
3192+
m_ttdPositionHistoryIndex++;
3193+
m_suppressTTDPositionRecording = true;
3194+
bool result = SetTTDPosition(m_ttdPositionHistory[m_ttdPositionHistoryIndex]);
3195+
m_suppressTTDPositionRecording = false;
3196+
return result;
3197+
}
3198+
3199+
3200+
bool DebuggerController::CanTTDNavigateBack() const
3201+
{
3202+
return m_adapterSupportsTTD && m_ttdPositionHistoryIndex > 0;
3203+
}
3204+
3205+
3206+
bool DebuggerController::CanTTDNavigateForward() const
3207+
{
3208+
return m_adapterSupportsTTD
3209+
&& m_ttdPositionHistoryIndex >= 0
3210+
&& m_ttdPositionHistoryIndex < static_cast<int>(m_ttdPositionHistory.size()) - 1;
3211+
}
3212+
3213+
3214+
void DebuggerController::ClearTTDPositionHistory()
3215+
{
3216+
m_ttdPositionHistory.clear();
3217+
m_ttdPositionHistoryIndex = -1;
3218+
}
3219+
3220+
31463221
TTDPosition DebuggerController::GetCurrentTTDPosition()
31473222
{
31483223
TTDPosition position;

core/debuggercontroller.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ namespace BinaryNinjaDebugger {
213213
std::unordered_map<uint64_t, uint32_t> m_executedInstructionCounts;
214214
bool m_codeCoverageAnalysisRun = false;
215215

216+
// TTD Position History for back/forward navigation
217+
std::vector<TTDPosition> m_ttdPositionHistory;
218+
int m_ttdPositionHistoryIndex = -1;
219+
bool m_suppressTTDPositionRecording = false;
220+
void RecordTTDPosition();
221+
216222
public:
217223
DebuggerController(BinaryViewRef data);
218224
static DbgRef<DebuggerController> GetController(BinaryViewRef data);
@@ -408,6 +414,13 @@ namespace BinaryNinjaDebugger {
408414
std::pair<bool, TTDMemoryEvent> GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType);
409415
std::pair<bool, TTDMemoryEvent> GetTTDPrevMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType);
410416

417+
// TTD Position History Navigation
418+
bool TTDNavigateBack();
419+
bool TTDNavigateForward();
420+
bool CanTTDNavigateBack() const;
421+
bool CanTTDNavigateForward() const;
422+
void ClearTTDPositionHistory();
423+
411424
// TTD Bookmark Methods
412425
std::vector<TTDBookmark> GetTTDBookmarks();
413426
bool AddTTDBookmark(const TTDPosition& position, const std::string& note = "", uint64_t viewAddress = 0);

core/ffi.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,32 @@ void BNDebuggerFreeTTDBookmarks(BNDebuggerTTDBookmark* bookmarks, size_t count)
13931393
}
13941394

13951395

1396+
bool BNDebuggerTTDNavigateBack(BNDebuggerController* controller)
1397+
{
1398+
return controller->object->TTDNavigateBack();
1399+
}
1400+
1401+
bool BNDebuggerTTDNavigateForward(BNDebuggerController* controller)
1402+
{
1403+
return controller->object->TTDNavigateForward();
1404+
}
1405+
1406+
bool BNDebuggerCanTTDNavigateBack(BNDebuggerController* controller)
1407+
{
1408+
return controller->object->CanTTDNavigateBack();
1409+
}
1410+
1411+
bool BNDebuggerCanTTDNavigateForward(BNDebuggerController* controller)
1412+
{
1413+
return controller->object->CanTTDNavigateForward();
1414+
}
1415+
1416+
void BNDebuggerClearTTDPositionHistory(BNDebuggerController* controller)
1417+
{
1418+
controller->object->ClearTTDPositionHistory();
1419+
}
1420+
1421+
13961422
bool BNDebuggerIsInstructionExecuted(BNDebuggerController* controller, uint64_t address)
13971423
{
13981424
return controller->object->IsInstructionExecuted(address);

ui/controlswidget.cpp

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ DebugControlsWidget::DebugControlsWidget(QWidget* parent, const std::string name
143143
performTimestampNavigation();
144144
});
145145
m_actionTimestampNavigation->setToolTip(getToolTip("Navigate to TTD Timestamp..."));
146+
147+
m_actionTTDNavigateBack = addAction(getColoredIcon(":/debugger/resume-reverse", cyan), "TTD Navigate Back", [this]() {
148+
performTTDNavigateBack();
149+
});
150+
m_actionTTDNavigateBack->setToolTip(getToolTip("TTD Navigate Back"));
151+
152+
m_actionTTDNavigateForward = addAction(getColoredIcon(":/debugger/resume", cyan), "TTD Navigate Forward", [this]() {
153+
performTTDNavigateForward();
154+
});
155+
m_actionTTDNavigateForward->setToolTip(getToolTip("TTD Navigate Forward"));
156+
146157
updateButtons();
147158
}
148159

@@ -617,6 +628,8 @@ void DebugControlsWidget::updateButtons()
617628
DebugAdapterConnectionStatus connection = m_controller->GetConnectionStatus();
618629
DebugAdapterTargetStatus status = m_controller->GetTargetStatus();
619630

631+
bool isTTD = m_controller->IsTTD();
632+
620633
if (connection == DebugAdapterNotConnectedStatus)
621634
{
622635
setStartingEnabled(true);
@@ -631,16 +644,19 @@ void DebugControlsWidget::updateButtons()
631644
m_actionPause->setVisible(false);
632645
m_actionResume->setVisible(false);
633646
m_actionGoBack->setVisible(false);
647+
648+
m_actionTTDNavigateBack->setVisible(false);
649+
m_actionTTDNavigateForward->setVisible(false);
634650
}
635651
else if (status == DebugAdapterRunningStatus)
636652
{
637653
setStartingEnabled(false);
638654
setStoppingEnabled(true);
639655
setSteppingEnabled(false);
640656
setReverseSteppingEnabled(false);
641-
m_actionStepIntoBack->setVisible(m_controller->IsTTD());
642-
m_actionStepOverBack->setVisible(m_controller->IsTTD());
643-
657+
m_actionStepIntoBack->setVisible(isTTD);
658+
m_actionStepOverBack->setVisible(isTTD);
659+
644660
m_actionPause->setEnabled(true);
645661
m_actionResume->setEnabled(false);
646662
m_actionGoBack->setEnabled(false);
@@ -649,21 +665,31 @@ void DebugControlsWidget::updateButtons()
649665
m_actionPause->setVisible(true);
650666
m_actionResume->setVisible(false);
651667
m_actionGoBack->setVisible(false);
668+
669+
m_actionTTDNavigateBack->setVisible(isTTD);
670+
m_actionTTDNavigateBack->setEnabled(false);
671+
m_actionTTDNavigateForward->setVisible(isTTD);
672+
m_actionTTDNavigateForward->setEnabled(false);
652673
}
653674
else // status == DebugAdapterPausedStatus
654675
{
655676
setStartingEnabled(false);
656677
setStoppingEnabled(true);
657678
setSteppingEnabled(true);
658-
setReverseSteppingEnabled(m_controller->IsTTD());
679+
setReverseSteppingEnabled(isTTD);
659680
m_actionPause->setEnabled(false);
660681
m_actionResume->setEnabled(true);
661-
m_actionGoBack->setEnabled(m_controller->IsTTD());
682+
m_actionGoBack->setEnabled(isTTD);
662683

663684
m_actionRun->setVisible(false);
664685
m_actionPause->setVisible(false);
665686
m_actionResume->setVisible(true);
666-
m_actionGoBack->setVisible(m_controller->IsTTD());
687+
m_actionGoBack->setVisible(isTTD);
688+
689+
m_actionTTDNavigateBack->setVisible(isTTD);
690+
m_actionTTDNavigateBack->setEnabled(m_controller->CanTTDNavigateBack());
691+
m_actionTTDNavigateForward->setVisible(isTTD);
692+
m_actionTTDNavigateForward->setEnabled(m_controller->CanTTDNavigateForward());
667693
}
668694
}
669695

@@ -679,3 +705,21 @@ void DebugControlsWidget::performTimestampNavigation()
679705
auto* dialog = new TimestampNavigationDialog(this, m_controller);
680706
dialog->show();
681707
}
708+
709+
710+
void DebugControlsWidget::performTTDNavigateBack()
711+
{
712+
if (!m_controller->CanTTDNavigateBack())
713+
return;
714+
715+
m_controller->TTDNavigateBack();
716+
}
717+
718+
719+
void DebugControlsWidget::performTTDNavigateForward()
720+
{
721+
if (!m_controller->CanTTDNavigateForward())
722+
return;
723+
724+
m_controller->TTDNavigateForward();
725+
}

ui/controlswidget.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class DebugControlsWidget : public QToolBar
5454
QAction* m_actionSettings;
5555
QAction* m_actionToggleBreakpoint;
5656
QAction* m_actionTimestampNavigation;
57+
QAction* m_actionTTDNavigateBack;
58+
QAction* m_actionTTDNavigateForward;
5759

5860
bool canExec();
5961
bool canConnect();
@@ -93,4 +95,6 @@ public Q_SLOTS:
9395
void performSettings();
9496
void toggleBreakpoint();
9597
void performTimestampNavigation();
98+
void performTTDNavigateBack();
99+
void performTTDNavigateForward();
96100
};

0 commit comments

Comments
 (0)