From 3bee0b1fa40b1c11b5067b82ca7ca16f4b637231 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 24 Jul 2026 16:40:38 -0400 Subject: [PATCH] Add a TTD navigation widget under the debugger controls (Fixes #1149) --- docs/guide/dbgeng-ttd.md | 17 ++ ui/debuggerwidget.cpp | 26 +- ui/debuggerwidget.h | 3 + ui/ttdnavigationwidget.cpp | 500 +++++++++++++++++++++++++++++++++++++ ui/ttdnavigationwidget.h | 107 ++++++++ 5 files changed, 651 insertions(+), 2 deletions(-) create mode 100644 ui/ttdnavigationwidget.cpp create mode 100644 ui/ttdnavigationwidget.h diff --git a/docs/guide/dbgeng-ttd.md b/docs/guide/dbgeng-ttd.md index ec832a1b..65d3f7b8 100644 --- a/docs/guide/dbgeng-ttd.md +++ b/docs/guide/dbgeng-ttd.md @@ -167,6 +167,23 @@ https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel- Binary Ninja's TTD integration includes powerful analysis widgets that leverage WinDbg's TTD.Calls and TTD.Memory queries to help you understand program behavior during the recorded trace. +### TTD Navigation Widget + +Stepping to the next or previous access of a register or a piece of memory is by far the most common TTD operation, so it is available directly underneath the control buttons in the debugger sidebar. The widget only appears during a TTD session and is hidden otherwise. + +- The `Target` field is what the navigation walks through. Unless pinned, it tracks the code view: + - Put the cursor on a register token (e.g. `rax`) and the target becomes that register, which takes priority over the address the cursor is on + - Select a range of bytes and the target becomes that address range + - Otherwise the target is the address the cursor is on +- You can also type a target in yourself. Address ranges are written as `start..end` (`start - end` works too), and a single address covers one byte. Both ends accept the same expressions as the rest of Binary Ninja, so symbol names work. +- `R`, `W` and `X` choose which kinds of memory access to stop on. They do not apply to a register target, since TTD reports register value changes without an access type, and are disabled in that case. +- The arrow buttons time-travel to the closest matching access in that direction, and a status line appears underneath reporting where the trace landed. +- The button carrying the TTD Memory sidebar's icon lists *every* access to the target instead of stepping to one, by handing the query to the [TTD Memory widget](#ttd-memory-widget) and opening it. This is the same as the `TTD Memory Access` context menu items, and is likewise memory-only. + +`Pin` freezes the target where it is. Time travelling moves the code view, so an unpinned target becomes whatever the trace landed on; pinning is what lets repeated navigation keep walking the same register or address range. Typing your own target pins it for you, since otherwise the next click in the code view would overwrite what you typed. + +This is the same functionality as the `TTD Memory Access (Next/Prev)` and `Go to Next/Previous Register Write` context menu items described below, so use whichever is more convenient. + ### TTD Calls Widget The TTD Calls widget allows you to query and analyze function call events from your TTD trace. This is equivalent to WinDbg's `dx @$cursession.TTD.Calls()` functionality but integrated directly into Binary Ninja. diff --git a/ui/debuggerwidget.cpp b/ui/debuggerwidget.cpp index a25472bd..2e6fa5b5 100644 --- a/ui/debuggerwidget.cpp +++ b/ui/debuggerwidget.cpp @@ -71,6 +71,19 @@ DebuggerWidget::DebuggerWidget(const QString& name, ViewFrame* view, BinaryViewR m_splitter->setChildrenCollapsible(true); m_controlsWidget = new DebugControlsWidget(this, "Controls", data); + m_ttdNavigationWidget = new TTDNavigationWidget(this, data); + + // The controls and the TTD navigation widget form a single pane of the splitter so that the + // navigation widget always sits directly underneath the control buttons. The pane hugs its + // contents the way the bare toolbar used to, rather than growing into the space the tabs + // below should be getting. + QWidget* controlsContainer = new QWidget(this); + controlsContainer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + QVBoxLayout* controlsLayout = new QVBoxLayout(controlsContainer); + controlsLayout->setContentsMargins(0, 0, 0, 0); + controlsLayout->setSpacing(0); + controlsLayout->addWidget(m_controlsWidget); + controlsLayout->addWidget(m_ttdNavigationWidget); m_tabs = new QTabWidget(this); @@ -80,8 +93,10 @@ DebuggerWidget::DebuggerWidget(const QString& name, ViewFrame* view, BinaryViewR m_tabs->addTab(m_registersWidget, "Registers"); m_tabs->addTab(m_breakpointsWidget, "Breakpoints"); - m_splitter->addWidget(m_controlsWidget); + m_splitter->addWidget(controlsContainer); m_splitter->addWidget(m_tabs); + m_splitter->setStretchFactor(0, 0); + m_splitter->setStretchFactor(1, 1); layout->addWidget(m_splitter); setLayout(layout); @@ -101,6 +116,12 @@ void DebuggerWidget::notifyFontChanged() } +void DebuggerWidget::notifyViewLocationChanged(View* view, const ViewLocation& viewLocation) +{ + m_ttdNavigationWidget->updateTargetFromView(view); +} + + void DebuggerWidget::updateContent() { m_registersWidget->updateContent(); @@ -110,7 +131,8 @@ void DebuggerWidget::updateContent() void DebuggerWidget::uiEventHandler(const DebuggerEvent& event) { m_controlsWidget->updateButtons(); - + m_ttdNavigationWidget->updateState(); + // Enable adapter selector only when not connected DebugAdapterConnectionStatus connection = m_controller->GetConnectionStatus(); if (m_adapterSelector->count() > 0 && m_adapterSelector->currentText() != "(No available debug adapter)") diff --git a/ui/debuggerwidget.h b/ui/debuggerwidget.h index 46e9f2a7..1fcf61b9 100644 --- a/ui/debuggerwidget.h +++ b/ui/debuggerwidget.h @@ -33,6 +33,7 @@ limitations under the License. #include "registerswidget.h" #include "moduleswidget.h" #include "controlswidget.h" +#include "ttdnavigationwidget.h" #include "ui.h" #include "debuggerapi.h" @@ -50,6 +51,7 @@ class DebuggerWidget : public SidebarWidget QTabWidget* m_tabs; DebugControlsWidget* m_controlsWidget; + TTDNavigationWidget* m_ttdNavigationWidget; DebugRegistersContainer* m_registersWidget; DebugBreakpointsWidget* m_breakpointsWidget; @@ -58,6 +60,7 @@ class DebuggerWidget : public SidebarWidget //void shouldBeVisible() virtual void notifyFontChanged() override; + virtual void notifyViewLocationChanged(View* view, const ViewLocation& viewLocation) override; private slots: void uiEventHandler(const DebuggerEvent& event); diff --git a/ui/ttdnavigationwidget.cpp b/ui/ttdnavigationwidget.cpp new file mode 100644 index 00000000..9a0d9076 --- /dev/null +++ b/ui/ttdnavigationwidget.cpp @@ -0,0 +1,500 @@ +/* +Copyright 2020-2026 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ttdnavigationwidget.h" +#include "debuggeruicommon.h" +#include "ui.h" +#include "theme.h" +#include "viewframe.h" +#include +#include +#include +#include +#include + +using namespace BinaryNinjaDebuggerAPI; +using namespace BinaryNinja; + + +// Recolors one of the debugger's black and white icons to a theme color, the same way the +// control buttons above this widget do. +static QIcon getColoredIcon(const QString& iconPath, const QColor& color) +{ + auto pixmap = QPixmap(iconPath); + auto mask = pixmap.createMaskFromColor(QColor(0, 0, 0), Qt::MaskInColor); + pixmap.fill(color); + pixmap.setMask(mask); + return QIcon(pixmap); +} + + +TTDNavigationWidget::TTDNavigationWidget(QWidget* parent, BinaryViewRef data) : QWidget(parent), m_data(data) +{ + m_controller = DebuggerController::GetController(data); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(4, 2, 4, 2); + layout->setSpacing(2); + + QHBoxLayout* targetLayout = new QHBoxLayout(); + targetLayout->setSpacing(4); + targetLayout->addWidget(new QLabel("Target:")); + + m_targetEdit = new QLineEdit(); + m_targetEdit->setPlaceholderText("register or address range"); + m_targetEdit->setToolTip( + "The register or memory range to navigate through. Follows the code view unless pinned. " + "Ranges are written as \"start..end\"."); + connect(m_targetEdit, &QLineEdit::textEdited, this, &TTDNavigationWidget::targetEdited); + connect(m_targetEdit, &QLineEdit::returnPressed, this, &TTDNavigationWidget::goToNext); + targetLayout->addWidget(m_targetEdit); + + m_pinCheck = new QCheckBox("Pin"); + m_pinCheck->setFocusPolicy(Qt::NoFocus); + m_pinCheck->setToolTip( + "Pin the target so that it stays put.\n\n" + "Unpinned, the target is whatever you have selected in the code view, so it moves along " + "with the view as you time travel. Pin it to keep pressing Prev/Next on the same register " + "or address range."); + connect(m_pinCheck, &QCheckBox::toggled, this, &TTDNavigationWidget::pinToggled); + targetLayout->addWidget(m_pinCheck); + layout->addLayout(targetLayout); + + QHBoxLayout* actionLayout = new QHBoxLayout(); + actionLayout->setSpacing(4); + + m_readAccessCheck = new QCheckBox("R"); + m_readAccessCheck->setChecked(true); + m_readAccessCheck->setFocusPolicy(Qt::NoFocus); + m_readAccessCheck->setToolTip("Stop on reads of the memory range"); + m_writeAccessCheck = new QCheckBox("W"); + m_writeAccessCheck->setChecked(true); + m_writeAccessCheck->setFocusPolicy(Qt::NoFocus); + m_writeAccessCheck->setToolTip("Stop on writes to the memory range"); + m_executeAccessCheck = new QCheckBox("X"); + m_executeAccessCheck->setChecked(true); + m_executeAccessCheck->setFocusPolicy(Qt::NoFocus); + m_executeAccessCheck->setToolTip("Stop on execution of the memory range"); + actionLayout->addWidget(m_readAccessCheck); + actionLayout->addWidget(m_writeAccessCheck); + actionLayout->addWidget(m_executeAccessCheck); + actionLayout->addStretch(); + + // Wearing the TTD Memory sidebar's own icon, since pressing it is what opens that sidebar. + m_showAllButton = new QToolButton(); + m_showAllButton->setIcon(getColoredIcon(":/debugger/ttd-memory", getThemeColor(CyanStandardHighlightColor))); + m_showAllButton->setFocusPolicy(Qt::NoFocus); + m_showAllButton->setToolTip("List every access to the target in the TTD Memory sidebar"); + connect(m_showAllButton, &QToolButton::clicked, this, &TTDNavigationWidget::showAllAccesses); + actionLayout->addWidget(m_showAllButton); + + // Tool buttons rather than push buttons: they size themselves to the arrow instead of being + // padded out to the style's minimum push button width. + m_prevButton = new QToolButton(); + m_prevButton->setText("\xe2\x97\x80"); + m_prevButton->setFocusPolicy(Qt::NoFocus); + m_prevButton->setToolTip("Time travel to the previous access of the target"); + connect(m_prevButton, &QToolButton::clicked, this, &TTDNavigationWidget::goToPrev); + m_nextButton = new QToolButton(); + m_nextButton->setText("\xe2\x96\xb6"); + m_nextButton->setFocusPolicy(Qt::NoFocus); + m_nextButton->setToolTip("Time travel to the next access of the target"); + connect(m_nextButton, &QToolButton::clicked, this, &TTDNavigationWidget::goToNext); + actionLayout->addWidget(m_prevButton); + actionLayout->addWidget(m_nextButton); + layout->addLayout(actionLayout); + + m_statusLabel = new QLabel(); + m_statusLabel->setEnabled(false); + m_statusLabel->hide(); + layout->addWidget(m_statusLabel); + + setLayout(layout); + + setTargetIsRegister(false); + updateState(); + + UIContext::registerNotification(this); +} + + +TTDNavigationWidget::~TTDNavigationWidget() +{ + UIContext::unregisterNotification(this); +} + + +void TTDNavigationWidget::OnNewSelectionForXref( + UIContext* context, ViewFrame* frame, View* view, const SelectionInfoForXref& selection) +{ + if (context != UIContext::contextForWidget(this)) + return; + + updateTargetFromView(view); +} + + +void TTDNavigationWidget::updateState() +{ + bool isTTD = m_controller && m_controller->IsConnected() && m_controller->IsTTD(); + bool startingSession = isTTD && isHidden(); + setVisible(isTTD); + + if (!isTTD) + return; + + if (startingSession) + { + // A fresh session starts over from whatever the user has selected. No notification is + // coming to seed the target with at this point, so go and ask. + m_pinCheck->setChecked(false); + setStatus(QString()); + refreshTargetFromSelection(); + } + + updateNavigationButtons(); +} + + +void TTDNavigationWidget::setTargetIsRegister(bool isRegister) +{ + m_targetIsRegister = isRegister; + + // The access type only applies to memory accesses; TTD reports register value changes + // without any notion of read/write/execute. Listing every access is memory-only for the + // same reason -- the TTD Memory sidebar has nothing to say about a register. + m_readAccessCheck->setEnabled(!isRegister); + m_writeAccessCheck->setEnabled(!isRegister); + m_executeAccessCheck->setEnabled(!isRegister); + m_showAllButton->setEnabled(!isRegister); +} + + +void TTDNavigationWidget::updateRegisterNames() +{ + if (!m_data) + return; + + auto arch = m_data->GetDefaultArchitecture(); + if (!arch || (arch == m_registerNamesArch)) + return; + + m_registerNames.clear(); + for (uint32_t reg : arch->GetAllRegisters()) + { + std::string name = arch->GetRegisterName(reg); + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + m_registerNames.insert(name); + } + m_registerNamesArch = arch; +} + + +bool TTDNavigationWidget::isRegisterName(const std::string& text) +{ + updateRegisterNames(); + + std::string lowered = text; + std::transform(lowered.begin(), lowered.end(), lowered.begin(), ::tolower); + return m_registerNames.find(lowered) != m_registerNames.end(); +} + + +void TTDNavigationWidget::pinToggled(bool pinned) +{ + if (!pinned) + refreshTargetFromSelection(); +} + + +void TTDNavigationWidget::refreshTargetFromSelection() +{ + UIContext* context = UIContext::contextForWidget(this); + if (!context) + return; + + updateTargetFromView(context->getCurrentView()); +} + + +void TTDNavigationWidget::updateTargetFromView(View* view) +{ + if (!view || isHidden()) + return; + + // Do not fight the user over the target they picked, either by pinning it or by clicking + // into the field to edit it. + if (m_pinCheck->isChecked() || m_targetEdit->hasFocus()) + return; + + QString target; + bool isRegister = false; + + auto token = view->getHighlightTokenState(); + if (token.valid && (token.type == RegisterToken) && !token.token.text.empty()) + { + target = QString::fromStdString(token.token.text); + isRegister = true; + } + else + { + // Same rule the "TTD Memory Access" context menu actions use: a non-empty selection is + // the range, otherwise the single byte at the current address. + auto selection = view->getSelectionOffsets(); + if (selection.start != selection.end) + target = QString("0x%1..0x%2").arg(selection.start, 0, 16).arg(selection.end, 0, 16); + else + target = QString("0x%1").arg(view->getCurrentOffset(), 0, 16); + } + + if ((target == m_targetEdit->text()) && (isRegister == m_targetIsRegister)) + return; + + m_targetEdit->setText(target); + setTargetIsRegister(isRegister); +} + + +void TTDNavigationWidget::targetEdited(const QString& text) +{ + // The selection would otherwise overwrite what is being typed on the next click in the view. + m_pinCheck->setChecked(true); + setTargetIsRegister(isRegisterName(text.trimmed().toStdString())); +} + + +TTDMemoryAccessType TTDNavigationWidget::getSelectedAccessTypes() +{ + int accessType = 0; + if (m_readAccessCheck->isChecked()) + accessType |= TTDMemoryRead; + if (m_writeAccessCheck->isChecked()) + accessType |= TTDMemoryWrite; + if (m_executeAccessCheck->isChecked()) + accessType |= TTDMemoryExecute; + return static_cast(accessType); +} + + +bool TTDNavigationWidget::parseMemoryTarget(uint64_t& address, uint64_t& size) +{ + QString text = m_targetEdit->text().trimmed(); + if (text.isEmpty()) + return false; + + // Ranges are written as "start..end"; " - " is accepted too since it reads more naturally. + // Neither separator can appear in a single address expression, where "-" is subtraction. + QString startText = text; + QString endText; + for (const QString& separator : {QString(".."), QString(" - ")}) + { + int index = text.indexOf(separator); + if (index < 0) + continue; + + startText = text.left(index).trimmed(); + endText = text.mid(index + separator.size()).trimmed(); + break; + } + + uint64_t start = 0; + std::string error; + if (!ParseAddress(startText, m_data, start, &error)) + { + setStatus(QString("Invalid target: %1").arg(QString::fromStdString(error))); + return false; + } + + uint64_t end = start + 1; + if (!endText.isEmpty() && !ParseAddress(endText, m_data, end, &error)) + { + setStatus(QString("Invalid target: %1").arg(QString::fromStdString(error))); + return false; + } + + address = start; + size = (end > start) ? (end - start) : 1; + return true; +} + + +void TTDNavigationWidget::navigate(bool forward) +{ + if (!m_controller || !m_controller->IsConnected() || !m_controller->IsTTD()) + { + setStatus("TTD is not available."); + return; + } + + std::string target = m_targetEdit->text().trimmed().toStdString(); + if (target.empty()) + { + setStatus("Select a register or an address range to navigate."); + return; + } + + // The query runs on this thread and pumps the event loop while it waits, so keep the user + // from starting a second one on top of it. + m_prevButton->setEnabled(false); + m_nextButton->setEnabled(false); + + if (m_targetIsRegister) + navigateToRegisterWrite(target, forward); + else + navigateToMemoryAccess(forward); + + updateNavigationButtons(); +} + + +void TTDNavigationWidget::setStatus(const QString& text) +{ + m_statusLabel->setText(text); + m_statusLabel->setVisible(!text.isEmpty()); +} + + +void TTDNavigationWidget::updateNavigationButtons() +{ + bool stopped = m_controller && m_controller->GetTargetStatus() != DebugAdapterRunningStatus; + m_prevButton->setEnabled(stopped); + m_nextButton->setEnabled(stopped); + m_showAllButton->setEnabled(stopped && !m_targetIsRegister); +} + + +void TTDNavigationWidget::navigateToRegisterWrite(const std::string& reg, bool forward) +{ + setStatus(QString("Finding %1 write to %2...") + .arg(forward ? "next" : "previous") + .arg(QString::fromStdString(reg))); + QApplication::processEvents(); + + auto event = forward ? m_controller->GetTTDNextRegisterWrite(reg) : m_controller->GetTTDPrevRegisterWrite(reg); + + // A failed query (or a zero position) means there is no such write in the trace. Note that + // TTD reports register value *changes*, so a write of the same value is not detected. + if (!event || (event->position.sequence == 0 && event->position.step == 0)) + { + setStatus(QString("No %1 write to %2 found.") + .arg(forward ? "next" : "previous") + .arg(QString::fromStdString(reg))); + return; + } + + if (!m_controller->SetTTDPosition(event->position)) + { + setStatus("Found the write but failed to time travel to it."); + return; + } + + setStatus(QString("%1 = 0x%2 at %3:%4") + .arg(QString::fromStdString(reg)) + .arg(event->value, 0, 16) + .arg(event->position.sequence, 0, 16) + .arg(event->position.step, 0, 16)); +} + + +void TTDNavigationWidget::navigateToMemoryAccess(bool forward) +{ + TTDMemoryAccessType accessType = getSelectedAccessTypes(); + if (accessType == 0) + { + setStatus("Select at least one of R/W/X."); + return; + } + + uint64_t address = 0; + uint64_t size = 0; + if (!parseMemoryTarget(address, size)) + return; + + setStatus(QString("Finding %1 access...").arg(forward ? "next" : "previous")); + QApplication::processEvents(); + + auto [success, event] = forward ? m_controller->GetTTDNextMemoryAccess(address, size, accessType) : + m_controller->GetTTDPrevMemoryAccess(address, size, accessType); + if (!success || (event.timeStart.sequence == 0 && event.timeStart.step == 0)) + { + setStatus( + QString("No %1 access to 0x%2 found.").arg(forward ? "next" : "previous").arg(address, 0, 16)); + return; + } + + if (!m_controller->SetTTDPosition(event.timeStart)) + { + setStatus("Found the access but failed to time travel to it."); + return; + } + + setStatus(QString("0x%1 accessed at %2:%3") + .arg(event.address, 0, 16) + .arg(event.timeStart.sequence, 0, 16) + .arg(event.timeStart.step, 0, 16)); +} + + +void TTDNavigationWidget::showAllAccesses() +{ + if (!m_controller || !m_controller->IsConnected() || !m_controller->IsTTD()) + { + setStatus("TTD is not available."); + return; + } + + TTDMemoryAccessType accessType = getSelectedAccessTypes(); + if (accessType == 0) + { + setStatus("Select at least one of R/W/X."); + return; + } + + uint64_t address = 0; + uint64_t size = 0; + if (!parseMemoryTarget(address, size)) + return; + + UIContext* context = UIContext::contextForWidget(this); + if (!context) + return; + + auto* globalUI = GlobalDebuggerUI::GetForContext(context); + if (!globalUI) + return; + + // QueryTTDMemoryAccess only reads the context and the binary view off the action context, + // so there is no code view selection to stand in for here. + UIActionContext ctxt; + ctxt.context = context; + ctxt.binaryView = m_data; + + setStatus(QString()); + globalUI->QueryTTDMemoryAccess( + ctxt, address, address + size, static_cast(accessType)); +} + + +void TTDNavigationWidget::goToPrev() +{ + navigate(false); +} + + +void TTDNavigationWidget::goToNext() +{ + navigate(true); +} diff --git a/ui/ttdnavigationwidget.h b/ui/ttdnavigationwidget.h new file mode 100644 index 00000000..7d197f8c --- /dev/null +++ b/ui/ttdnavigationwidget.h @@ -0,0 +1,107 @@ +/* +Copyright 2020-2026 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "binaryninjaapi.h" +#include "uicontext.h" +#include "viewframe.h" +#include "debuggerapi.h" + +using namespace BinaryNinjaDebuggerAPI; + + +// A compact always-available version of the "TTD Memory Access (Next/Prev)" dialog and the +// "Go to Next/Previous Register Write" context menu items. It tracks whatever the user has +// selected in the code view -- a register token, or an address range -- and lets them walk +// the trace backwards/forwards from there. It only makes sense during a TTD session, so the +// whole widget is hidden otherwise. +class TTDNavigationWidget : public QWidget, public UIContextNotification +{ + Q_OBJECT + + BinaryViewRef m_data; + DbgRef m_controller; + + QLineEdit* m_targetEdit; + QCheckBox* m_pinCheck; + QCheckBox* m_readAccessCheck; + QCheckBox* m_writeAccessCheck; + QCheckBox* m_executeAccessCheck; + QToolButton* m_showAllButton; + QToolButton* m_prevButton; + QToolButton* m_nextButton; + QLabel* m_statusLabel; + + // True when the target field holds a register name rather than an address range. Kept in + // sync with the field itself: refreshed both when we auto-fill it and when the user types. + bool m_targetIsRegister = false; + + // The register names of the view's architecture, lower-cased. Used to tell a hand-typed + // register name (including sub-registers like "eax") from an address expression. + std::set m_registerNames; + ArchitectureRef m_registerNamesArch; + + void updateRegisterNames(); + bool isRegisterName(const std::string& text); + void setTargetIsRegister(bool isRegister); + + void updateNavigationButtons(); + // Sets the status line, hiding it entirely while there is nothing to report so that it does + // not sit there as an empty row between the widget and the tabs below it. + void setStatus(const QString& text); + TTDMemoryAccessType getSelectedAccessTypes(); + void navigate(bool forward); + void navigateToRegisterWrite(const std::string& reg, bool forward); + void navigateToMemoryAccess(bool forward); + bool parseMemoryTarget(uint64_t& address, uint64_t& size); + +public: + TTDNavigationWidget(QWidget* parent, BinaryViewRef data); + ~TTDNavigationWidget(); + + // Show/hide the widget based on whether a TTD session is active, and enable/disable the + // navigation buttons based on whether the target is currently stopped. + void updateState(); + + // Pick up the token/selection the user is on. + void updateTargetFromView(View* view); + + // Clicking a different token on the instruction the cursor is already on does not move the + // current offset, and Sidebar::updateViewLocation drops the view location notification when + // the location compares equal -- ViewLocation carries no token. The cross reference + // selection notification is not filtered that way, so it is what catches register tokens. + void OnNewSelectionForXref( + UIContext* context, ViewFrame* frame, View* view, const SelectionInfoForXref& selection) override; + +private Q_SLOTS: + // Same as updateTargetFromView(), against whichever view the context currently has open. + void refreshTargetFromSelection(); + void pinToggled(bool pinned); + void targetEdited(const QString& text); + void goToPrev(); + void goToNext(); + // Hand the target off to the TTD Memory sidebar widget, exactly as the "TTD Memory Access" + // context menu items do, to list every access rather than stepping to one. + void showAllAccesses(); +};