From 647137e1aea34b5263c20210931b2f15a2ebb302 Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Sun, 18 Jan 2026 19:41:33 -0800 Subject: [PATCH 01/14] Add Time Range Filter option for ttd code coverage analysis --- api/debuggerapi.h | 2 +- api/debuggercontroller.cpp | 11 +++-- api/ffi.h | 2 +- core/debuggercontroller.cpp | 22 ++++++++-- core/debuggercontroller.h | 2 +- core/ffi.cpp | 6 ++- ui/ttdanalysisdialog.cpp | 83 +++++++++++++++++++++++++++++++---- ui/ttdanalysisdialog.h | 8 +++- ui/ttdcoveragerenderlayer.cpp | 4 +- 9 files changed, 117 insertions(+), 23 deletions(-) diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 67d653fa..84b06c72 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -808,7 +808,7 @@ namespace BinaryNinjaDebuggerAPI { // TTD Code Coverage Analysis Methods bool IsInstructionExecuted(uint64_t address); - bool RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress); + bool RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress, TTDPosition startTime, TTDPosition endTime); size_t GetExecutedInstructionCount() const; bool SaveCodeCoverageToFile(const std::string& filePath) const; bool LoadCodeCoverageFromFile(const std::string& filePath); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index d4c5173b..809adcf6 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -1325,9 +1325,14 @@ bool DebuggerController::IsInstructionExecuted(uint64_t address) } -bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress) -{ - return BNDebuggerRunCodeCoverageAnalysisRange(m_object, startAddress, endAddress); +bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress, TTDPosition startTime, TTDPosition endTime) +{ + BNDebuggerTTDPosition startPos, endPos; + startPos.sequence = startTime.sequence; + startPos.step = startTime.step; + endPos.sequence = endTime.sequence; + endPos.step = endTime.step; + return BNDebuggerRunCodeCoverageAnalysisRange(m_object, startAddress, endAddress, startPos, endPos); } diff --git a/api/ffi.h b/api/ffi.h index 22c74bc0..ceef06b5 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -678,7 +678,7 @@ extern "C" // 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); + DEBUGGER_FFI_API bool BNDebuggerRunCodeCoverageAnalysisRange(BNDebuggerController* controller, uint64_t startAddress, uint64_t endAddress, BNDebuggerTTDPosition startTime, BNDebuggerTTDPosition endTime); DEBUGGER_FFI_API size_t BNDebuggerGetExecutedInstructionCount(BNDebuggerController* controller); DEBUGGER_FFI_API bool BNDebuggerSaveCodeCoverageToFile(BNDebuggerController* controller, const char* filePath); DEBUGGER_FFI_API bool BNDebuggerLoadCodeCoverageFromFile(BNDebuggerController* controller, const char* filePath); diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index a51e0d3a..7558e99d 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -3227,7 +3227,7 @@ bool DebuggerController::IsInstructionExecuted(uint64_t address) } -bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress) +bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress, TTDPosition startTime, TTDPosition endTime) { if (!m_state->IsConnected() || !IsTTD()) { @@ -3245,7 +3245,18 @@ bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t m_executedInstructions.clear(); m_codeCoverageAnalysisRun = false; - LogInfo("Starting TTD code coverage analysis for range 0x%" PRIX64 " - 0x%" PRIX64 "...", startAddress, endAddress); + LogInfo("Starting TTD code coverage analysis."); + LogInfo("\tAddress range: 0x%" PRIX64 " - 0x%" PRIX64, startAddress, endAddress); + //log time range + bool endTimeIsMax = endTime.sequence== std::numeric_limits::max() && endTime.step == std::numeric_limits::max(); + if(endTimeIsMax) + { + LogInfo("\tTime range: %" PRIX64 ":%" PRIX64 " - end of trace", startTime.sequence, startTime.step); + } + else{ + LogInfo("\tTime range: %" PRIX64 ":%" PRIX64 " - %" PRIX64 ":%" PRIX64, startTime.sequence, startTime.step, + endTime.sequence, endTime.step); + } // Query TTD for execute access covering the specified range auto events = GetTTDMemoryAccessForAddress(startAddress, endAddress, TTDMemoryExecute); @@ -3257,13 +3268,16 @@ bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t // Add all executed instruction addresses within the range if (event.instructionAddress >= startAddress && event.instructionAddress <= endAddress) { - m_executedInstructions.insert(event.instructionAddress); + if ((event.timeStart.step >= startTime.step || event.timeStart.sequence >= startTime.sequence) && (event.timeStart.sequence <= endTime.sequence || event.timeStart.step <= endTime.step)) + { + m_executedInstructions.insert(event.instructionAddress); + } } } } m_codeCoverageAnalysisRun = true; - LogInfo("TTD code coverage analysis completed for range. Found 0x%" PRIu64 "executed instructions.", + LogInfo("TTD code coverage analysis completed for ranges. Found %" PRIu64 " executed instructions.", (uint64_t)m_executedInstructions.size()); return true; diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index 1014bb97..6a44bb1f 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -397,7 +397,7 @@ namespace BinaryNinjaDebugger { // TTD Code Coverage Analysis Methods bool IsInstructionExecuted(uint64_t address); - bool RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress); + bool RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress, TTDPosition startTime, TTDPosition endTime); size_t GetExecutedInstructionCount() const; bool SaveCodeCoverageToFile(const std::string& filePath) const; bool LoadCodeCoverageFromFile(const std::string& filePath); diff --git a/core/ffi.cpp b/core/ffi.cpp index 43db6e8c..62e2486a 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -1246,9 +1246,11 @@ bool BNDebuggerIsInstructionExecuted(BNDebuggerController* controller, uint64_t return controller->object->IsInstructionExecuted(address); } -bool BNDebuggerRunCodeCoverageAnalysisRange(BNDebuggerController* controller, uint64_t startAddress, uint64_t endAddress) +bool BNDebuggerRunCodeCoverageAnalysisRange(BNDebuggerController* controller, uint64_t startAddress, uint64_t endAddress, BNDebuggerTTDPosition startTime, BNDebuggerTTDPosition endTime) { - return controller->object->RunCodeCoverageAnalysis(startAddress, endAddress); + TTDPosition startPos(startTime.sequence, startTime.step); + TTDPosition endPos(endTime.sequence, endTime.step); + return controller->object->RunCodeCoverageAnalysis(startAddress, endAddress, startPos, endPos); } size_t BNDebuggerGetExecutedInstructionCount(BNDebuggerController* controller) diff --git a/ui/ttdanalysisdialog.cpp b/ui/ttdanalysisdialog.cpp index 2aa84b44..8a198893 100644 --- a/ui/ttdanalysisdialog.cpp +++ b/ui/ttdanalysisdialog.cpp @@ -27,8 +27,8 @@ TTDAnalysisWorker::TTDAnalysisWorker(DbgRef controller, TTDA { } -TTDAnalysisWorker::TTDAnalysisWorker(DbgRef controller, TTDAnalysisType type, uint64_t startAddress, uint64_t endAddress, QObject* parent) - : QThread(parent), m_controller(controller), m_analysisType(type), m_useRange(true), m_startAddress(startAddress), m_endAddress(endAddress) +TTDAnalysisWorker::TTDAnalysisWorker(DbgRef controller, TTDAnalysisType type, uint64_t startAddress, uint64_t endAddress, TTDPosition startTime, TTDPosition endTime, QObject* parent) + : QThread(parent), m_controller(controller), m_analysisType(type), m_useRange(true), m_startAddress(startAddress), m_endAddress(endAddress), m_startTime(startTime), m_endTime(endTime) { } @@ -58,14 +58,16 @@ void TTDAnalysisWorker::run() emit analysisProgress(30, "Running code coverage analysis..."); if (m_useRange) { - success = m_controller->RunCodeCoverageAnalysis(m_startAddress, m_endAddress); + success = m_controller->RunCodeCoverageAnalysis(m_startAddress, m_endAddress, m_startTime, m_endTime); if (success) { resultCount = m_controller->GetExecutedInstructionCount(); - message = QString("Range-based code coverage analysis completed successfully. Found %1 executed instructions in range 0x%2 - 0x%3.") + message = QString("Range-based code coverage analysis completed successfully. Found %1 executed instructions in address range 0x%2 - 0x%3 and time range (%4,%5) - (%6,%7).") .arg(resultCount) .arg(m_startAddress, 0, 16) - .arg(m_endAddress, 0, 16); + .arg(m_endAddress, 0, 16) + .arg(m_startTime.sequence).arg(m_startTime.step) + .arg(m_endTime.sequence).arg(m_endTime.step); } else { @@ -197,9 +199,26 @@ void TTDAnalysisDialog::setupUI() m_endAddressEdit->setText(QString("0x") + QString::number(m_data->GetImageBase() + 0x7000, 16)); m_endAddressEdit->setEnabled(true); rangeControlsLayout->addWidget(m_endAddressEdit); - rangeLayout->addLayout(rangeControlsLayout); + // Time settings + QGroupBox* timeGroup = new QGroupBox("Time Range (Optional)"); + QVBoxLayout* timeLayout = new QVBoxLayout(timeGroup); + + QHBoxLayout* timeControlsLayout = new QHBoxLayout(); + timeControlsLayout->addWidget(new QLabel("Start Time:")); + m_startTimeEdit = new QLineEdit(); + //set text to starting position + m_startTimeEdit->setEnabled(true); + timeControlsLayout->addWidget(m_startTimeEdit); + + timeControlsLayout->addWidget(new QLabel("End Time:")); + m_endTimeEdit = new QLineEdit(); + //set text to ending position + m_endTimeEdit->setEnabled(true); + timeControlsLayout->addWidget(m_endTimeEdit); + timeLayout->addLayout(timeControlsLayout); + // Connect range checkbox to enable/disable range controls connect(m_useRangeCheckBox, &QCheckBox::toggled, [this](bool checked) { m_startAddressEdit->setEnabled(checked); @@ -207,6 +226,7 @@ void TTDAnalysisDialog::setupUI() }); mainLayout->addWidget(rangeGroup); + mainLayout->addWidget(timeGroup); // Cache settings QGroupBox* cacheGroup = new QGroupBox("Cache Settings"); @@ -379,9 +399,12 @@ void TTDAnalysisDialog::onRunAnalysis() if (m_useRangeCheckBox->isChecked()) { // Validate range inputs - bool startOk, endOk; + bool startOk, endOk, startTimeOk, endTimeOk; QString startText = m_startAddressEdit->text().trimmed(); QString endText = m_endAddressEdit->text().trimmed(); + QString startTimeText = m_startTimeEdit->text().trimmed(); + QString endTimeText = m_endTimeEdit->text().trimmed(); + TTDPosition startTime, endTime; if (startText.isEmpty() || endText.isEmpty()) { @@ -389,6 +412,50 @@ void TTDAnalysisDialog::onRunAnalysis() return; } + if (startTimeText.isEmpty()) + { + startTime = TTDPosition(0, 0); + }else{ + QStringList startTimeParts = startTimeText.split(u':'); + if (startTimeParts.size() != 2) + { + QMessageBox::warning(this, "Invalid Time", "Start time must be in the format 'sequence:step'"); + return; + } + bool seqOk, stepOk; + uint64_t sequence = startTimeParts[0].toULongLong(&seqOk, 16); + uint64_t step = startTimeParts[1].toULongLong(&stepOk, 16); + if (!seqOk || !stepOk) + { + QMessageBox::warning(this, "Invalid Time", "Start time contains invalid numbers"); + return; + } + startTime = TTDPosition(sequence, step); + } + + if (endTimeText.isEmpty()) + { + endTime = TTDPosition(std::numeric_limits::max(),std::numeric_limits::max()); + } + else + { + QStringList endTimeParts = endTimeText.split(u':'); + if (endTimeParts.size() != 2) + { + QMessageBox::warning(this, "Invalid Time", "End time must be in the format 'sequence:step'"); + return; + } + bool seqOk, stepOk; + uint64_t sequence = endTimeParts[0].toULongLong(&seqOk, 16); + uint64_t step = endTimeParts[1].toULongLong(&stepOk, 16); + if (!seqOk || !stepOk) + { + QMessageBox::warning(this, "Invalid Time", "End time contains invalid numbers"); + return; + } + endTime = TTDPosition(sequence, step); + } + uint64_t startAddress = startText.toULongLong(&startOk, 0); // Auto-detect base (0x for hex) uint64_t endAddress = endText.toULongLong(&endOk, 0); @@ -405,7 +472,7 @@ void TTDAnalysisDialog::onRunAnalysis() } // Start range-based analysis in worker thread - m_currentWorker = new TTDAnalysisWorker(m_controller, analysisType, startAddress, endAddress, this); + m_currentWorker = new TTDAnalysisWorker(m_controller, analysisType, startAddress, endAddress, startTime, endTime, this); } else { diff --git a/ui/ttdanalysisdialog.h b/ui/ttdanalysisdialog.h index ff43e052..2d6347dc 100644 --- a/ui/ttdanalysisdialog.h +++ b/ui/ttdanalysisdialog.h @@ -70,7 +70,7 @@ class TTDAnalysisWorker : public QThread public: TTDAnalysisWorker(DbgRef controller, TTDAnalysisType type, QObject* parent = nullptr); - TTDAnalysisWorker(DbgRef controller, TTDAnalysisType type, uint64_t startAddress, uint64_t endAddress, QObject* parent = nullptr); + TTDAnalysisWorker(DbgRef controller, TTDAnalysisType type, uint64_t startAddress, uint64_t endAddress, TTDPosition startTime, TTDPosition endTime, QObject* parent = nullptr); protected: void run() override; @@ -85,6 +85,8 @@ class TTDAnalysisWorker : public QThread bool m_useRange; uint64_t m_startAddress; uint64_t m_endAddress; + TTDPosition m_startTime; + TTDPosition m_endTime; }; class TTDAnalysisDialog : public QDialog @@ -136,6 +138,10 @@ private slots: QLineEdit* m_startAddressEdit; QLineEdit* m_endAddressEdit; + // Time controls + QLineEdit* m_startTimeEdit; + QLineEdit* m_endTimeEdit; + // Analysis data QList m_analysisResults; TTDAnalysisWorker* m_currentWorker; diff --git a/ui/ttdcoveragerenderlayer.cpp b/ui/ttdcoveragerenderlayer.cpp index ad45fef1..055dbebe 100644 --- a/ui/ttdcoveragerenderlayer.cpp +++ b/ui/ttdcoveragerenderlayer.cpp @@ -56,7 +56,7 @@ void TTDCoverageRenderLayer::ApplyToBlock(Ref block, std::vector function, std: line.highlight.r = 0; line.highlight.g = 0; line.highlight.b = 0; - line.highlight.alpha = 64; // Light highlight + line.highlight.alpha = 170; // Light highlight } } } \ No newline at end of file From 03cc9ad9394a6ddd1bc5edcfca82a274057225d8 Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Sun, 18 Jan 2026 20:19:07 -0800 Subject: [PATCH 02/14] Fix Info message showing in error stream --- core/debuggercontroller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 7558e99d..d5f2e0bf 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -3323,7 +3323,7 @@ bool DebuggerController::SaveCodeCoverageToFile(const std::string& filePath) con } file.close(); - LogError("%s", fmt::format("Saved {} executed instruction addresses to {}", count, filePath.c_str()).c_str()); + LogInfo("%s", fmt::format("Saved {} executed instruction addresses to {}", count, filePath.c_str()).c_str()); return true; } From 77b48b5249b307eaff07e4e6ca1c6b778b2bba25 Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:17:55 -0800 Subject: [PATCH 03/14] add method to query memory accress for a time range in ttd --- api/debuggerapi.h | 1 + api/debuggercontroller.cpp | 37 ++++++++++++++++++++ api/ffi.h | 3 ++ core/adapters/dbgengttdadapter.cpp | 56 ++++++++++++++++++++++++++++++ core/adapters/dbgengttdadapter.h | 4 +++ core/debugadapter.cpp | 6 ++++ core/debugadapter.h | 1 + core/debuggercontroller.cpp | 18 ++++++++++ core/debuggercontroller.h | 1 + core/ffi.cpp | 38 ++++++++++++++++++++ 10 files changed, 165 insertions(+) diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 84b06c72..ccc330b5 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -800,6 +800,7 @@ namespace BinaryNinjaDebuggerAPI { // TTD Memory Analysis Methods std::vector GetTTDMemoryAccessForAddress(uint64_t address, uint64_t endAddress, TTDMemoryAccessType accessType = TTDMemoryRead); + std::vector GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime); std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0); std::vector GetTTDEvents(TTDEventType eventType); std::vector GetAllTTDEvents(); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index 809adcf6..0623b463 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -1078,6 +1078,43 @@ bool DebuggerController::IsTTD() return BNDebuggerIsTTD(m_object); } +std::vector DebuggerController::GetTTDMemoryAccessForPositionRange(uint64_t address, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime) +{ + std::vector result; + + BNDebuggerTTDMemoryAccessType type = static_cast(accessType); + BNDebuggerTTDPosition bnStartTime = {startTime.sequence, startTime.step}; + BNDebuggerTTDPosition bnEndTime = {endTime.sequence, endTime.step}; + + size_t count = 0; + BNDebuggerTTDMemoryEvent* events = BNDebuggerGetTTDMemoryAccessForPositionRange(m_object, address, endAddress, type, bnStartTime, bnEndTime, &count); + + if (events && count > 0) + { + result.reserve(count); + for (size_t i = 0; i < count; i++) + { + TTDMemoryEvent event; + event.eventType = events[i].eventType ? std::string(events[i].eventType) : ""; + event.threadId = events[i].threadId; + event.uniqueThreadId = events[i].uniqueThreadId; + event.timeStart.sequence = events[i].timeStart.sequence; + event.timeStart.step = events[i].timeStart.step; + event.timeEnd.sequence = events[i].timeEnd.sequence; + event.timeEnd.step = events[i].timeEnd.step; + event.accessType = static_cast(events[i].accessType); + event.address = events[i].address; + event.size = events[i].size; + event.memoryAddress = events[i].memoryAddress; + event.instructionAddress = events[i].instructionAddress; + event.value = events[i].value; + result.push_back(event); + } + BNDebuggerFreeTTDMemoryEvents(events, count); + } + + return result; +} std::vector DebuggerController::GetTTDMemoryAccessForAddress(uint64_t address, uint64_t endAddress, TTDMemoryAccessType accessType) { diff --git a/api/ffi.h b/api/ffi.h index ceef06b5..3f507018 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -665,6 +665,9 @@ extern "C" // TTD Memory Analysis Functions DEBUGGER_FFI_API BNDebuggerTTDMemoryEvent* BNDebuggerGetTTDMemoryAccessForAddress(BNDebuggerController* controller, uint64_t address, uint64_t endAddress, BNDebuggerTTDMemoryAccessType accessType, size_t* count); + DEBUGGER_FFI_API BNDebuggerTTDMemoryEvent* BNDebuggerGetTTDMemoryAccessForPositionRange(BNDebuggerController* controller, + uint64_t address, uint64_t endAddress, BNDebuggerTTDMemoryAccessType accessType ,BNDebuggerTTDPosition startPosition, BNDebuggerTTDPosition endPosition, + size_t* count); DEBUGGER_FFI_API BNDebuggerTTDCallEvent* BNDebuggerGetTTDCallsForSymbols(BNDebuggerController* controller, const char* symbols, uint64_t startReturnAddress, uint64_t endReturnAddress, size_t* count); DEBUGGER_FFI_API BNDebuggerTTDEvent* BNDebuggerGetTTDEvents(BNDebuggerController* controller, diff --git a/core/adapters/dbgengttdadapter.cpp b/core/adapters/dbgengttdadapter.cpp index 317855cf..b48bb5c9 100644 --- a/core/adapters/dbgengttdadapter.cpp +++ b/core/adapters/dbgengttdadapter.cpp @@ -403,6 +403,18 @@ std::vector DbgEngTTDAdapter::GetTTDMemoryAccessForAddress(uint6 return events; } +std::vector DbgEngTTDAdapter::GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime) +{ + std::vector events; + + if (!QueryMemoryAccessByAddressAndPositionRange(startAddress, endAddress, accessType, startTime, endTime, events)) + { + LogError("Failed to query TTD memory access events for address range 0x%llx-0x%llx", startAddress, endAddress); + } + + return events; +} + TTDPosition DbgEngTTDAdapter::GetCurrentTTDPosition() { TTDPosition position; @@ -563,6 +575,50 @@ bool DbgEngTTDAdapter::QueryMemoryAccessByAddress(uint64_t startAddress, uint64_ } } +bool DbgEngTTDAdapter::QueryMemoryAccessByAddressAndPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, TTDPosition startTime, TTDPosition endTime, std::vector& events) +{ + if (!m_debugControl) + { + LogError("Debug control interface not available"); + return false; + } + + try + { + // Build the access type string for TTD memory queries - combine flags as needed + std::string accessTypeStr; + if (accessType & TTDMemoryRead) accessTypeStr += "r"; + if (accessType & TTDMemoryWrite) accessTypeStr += "w"; + if (accessType & TTDMemoryExecute) accessTypeStr += "e"; + + if (accessTypeStr.empty()) + { + LogError("Invalid access type specified"); + return false; + } + + // Create the actual TTD memory query expression + std::string expression = fmt::format("@$cursession.TTD.MemoryForPositionRange(0x{:x},0x{:x},\"{}\",\"{:x}:{:x}\",\"{:x}:{:x}\")", startAddress, endAddress, accessTypeStr, startTime.sequence,startTime.step, endTime.sequence, endTime.step); + + LogInfo("Executing TTD memory query: %s", expression.c_str()); + + // Execute the query and parse results + if (!ParseTTDMemoryObjects(expression, accessType, events)) + { + LogError("Failed to parse TTD memory objects from query"); + return false; + } + + LogInfo("Successfully retrieved %zu TTD memory events", events.size()); + return true; + } + catch (const std::exception& e) + { + LogError("Exception in QueryMemoryAccessByAddress: %s", e.what()); + return false; + } +} + void DbgEngTTDAdapter::GenerateDefaultAdapterSettings(BinaryView* data) { diff --git a/core/adapters/dbgengttdadapter.h b/core/adapters/dbgengttdadapter.h index c740d473..0921f4ab 100644 --- a/core/adapters/dbgengttdadapter.h +++ b/core/adapters/dbgengttdadapter.h @@ -48,6 +48,9 @@ namespace BinaryNinjaDebugger { // TTD Memory Analysis Methods std::vector GetTTDMemoryAccessForAddress(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType = TTDMemoryRead) override; + std::vector GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, TTDPosition startTime, TTDPosition endTime) override; + + // TTD Position Methods TTDPosition GetCurrentTTDPosition() override; bool SetTTDPosition(const TTDPosition& position) override; @@ -64,6 +67,7 @@ namespace BinaryNinjaDebugger { private: // Helper methods for TTD memory analysis bool QueryMemoryAccessByAddress(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, std::vector& events); + bool QueryMemoryAccessByAddressAndPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, TTDPosition startTime, TTDPosition endTime, std::vector& events); // Helper methods for TTD calls analysis bool QueryCallsForSymbols(const std::vector& symbols, uint64_t startReturnAddress, uint64_t endReturnAddress, std::vector& events); diff --git a/core/debugadapter.cpp b/core/debugadapter.cpp index 048e402d..4740c7e8 100644 --- a/core/debugadapter.cpp +++ b/core/debugadapter.cpp @@ -219,6 +219,12 @@ std::vector DebugAdapter::GetTTDMemoryAccessForAddress(uint64_t return {}; } +std::vector DebugAdapter::GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime) +{ + // Default implementation returns empty results for adapters that don't support TTD + return {}; +} + std::vector DebugAdapter::GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress, uint64_t endReturnAddress) { diff --git a/core/debugadapter.h b/core/debugadapter.h index f733014f..b59a0d25 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -390,6 +390,7 @@ namespace BinaryNinjaDebugger { // TTD (Time Travel Debugging) methods - default implementations return empty results virtual std::vector GetTTDMemoryAccessForAddress(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType = TTDMemoryRead); + virtual std::vector GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime); virtual std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0); virtual std::vector GetTTDEvents(TTDEventType eventType); virtual std::vector GetAllTTDEvents(); diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index d5f2e0bf..4c0ac1fa 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -3134,6 +3134,24 @@ std::vector DebuggerController::GetTTDMemoryAccessForAddress(uin return events; } +std::vector DebuggerController::GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime) +{ + std::vector events; + + if (!m_state->IsConnected() || !IsTTD()) + { + LogError("Current adapter does not support TTD"); + return events; + } + + if (m_adapter) + { + events = m_adapter->GetTTDMemoryAccessForPositionRange(startAddress, endAddress, accessType, startTime, endTime); + } + + return events; +} + std::vector DebuggerController::GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress, uint64_t endReturnAddress) { std::vector events; diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index 6a44bb1f..108cc6f8 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -389,6 +389,7 @@ namespace BinaryNinjaDebugger { // TTD Memory Analysis Methods std::vector GetTTDMemoryAccessForAddress(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType = TTDMemoryRead); + std::vector GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime); std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0); std::vector GetTTDEvents(TTDEventType eventType); std::vector GetAllTTDEvents(); diff --git a/core/ffi.cpp b/core/ffi.cpp index 62e2486a..cd8b51c6 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -1226,6 +1226,44 @@ BNDebuggerTTDMemoryEvent* BNDebuggerGetTTDMemoryAccessForAddress(BNDebuggerContr return result; } +BNDebuggerTTDMemoryEvent* BNDebuggerGetTTDMemoryAccessForPositionRange(BNDebuggerController* controller, + uint64_t address, uint64_t endAddress, BNDebuggerTTDMemoryAccessType accessType, BNDebuggerTTDPosition startTime, BNDebuggerTTDPosition endTime, size_t* count) +{ + if (!count) + return nullptr; + + *count = 0; + + TTDMemoryAccessType type = static_cast(accessType); + TTDPosition startPos(startTime.sequence, startTime.step); + TTDPosition endPos(endTime.sequence, endTime.step); + auto events = controller->object->GetTTDMemoryAccessForPositionRange(address, endAddress, type, startPos, endPos); + if (events.empty()) + return nullptr; + + *count = events.size(); + auto result = new BNDebuggerTTDMemoryEvent[events.size()]; + + for (size_t i = 0; i < events.size(); i++) + { + result[i].eventType = BNAllocString(events[i].eventType.c_str()); + result[i].threadId = events[i].threadId; + result[i].uniqueThreadId = events[i].uniqueThreadId; + result[i].timeStart.sequence = events[i].timeStart.sequence; + result[i].timeStart.step = events[i].timeStart.step; + result[i].timeEnd.sequence = events[i].timeEnd.sequence; + result[i].timeEnd.step = events[i].timeEnd.step; + result[i].address = events[i].address; + result[i].size = events[i].size; + result[i].memoryAddress = events[i].memoryAddress; + result[i].instructionAddress = events[i].instructionAddress; + result[i].value = events[i].value; + result[i].accessType = static_cast(events[i].accessType); + } + + return result; +} + BNDebuggerTTDPosition BNDebuggerGetCurrentTTDPosition(BNDebuggerController* controller) { auto position = controller->object->GetCurrentTTDPosition(); From 13ec3c3667b323158a8aecf9192e55961615f4b3 Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:18:50 -0800 Subject: [PATCH 04/14] use time based memory access query instead of filtering --- core/debuggercontroller.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 4c0ac1fa..2ae479ac 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -3277,8 +3277,8 @@ bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t } // Query TTD for execute access covering the specified range - auto events = GetTTDMemoryAccessForAddress(startAddress, endAddress, TTDMemoryExecute); - + auto events = GetTTDMemoryAccessForPositionRange(startAddress, endAddress, TTDMemoryExecute, startTime, endTime); + for (const auto& event : events) { if (event.accessType == TTDMemoryExecute) @@ -3286,10 +3286,8 @@ bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t // Add all executed instruction addresses within the range if (event.instructionAddress >= startAddress && event.instructionAddress <= endAddress) { - if ((event.timeStart.step >= startTime.step || event.timeStart.sequence >= startTime.sequence) && (event.timeStart.sequence <= endTime.sequence || event.timeStart.step <= endTime.step)) - { - m_executedInstructions.insert(event.instructionAddress); - } + // Check if the event is within the specified time range + m_executedInstructions.insert(event.instructionAddress); } } } From 7776e2673e8a6243dce132a7ea92fb764277ed90 Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Wed, 21 Jan 2026 13:33:30 -0800 Subject: [PATCH 05/14] use new time based api in TTD memory widget --- ui/ttdmemorywidget.cpp | 93 ++++++++++++++++++++++++++++++++++++++++-- ui/ttdmemorywidget.h | 3 ++ 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/ui/ttdmemorywidget.cpp b/ui/ttdmemorywidget.cpp index 65a640a3..3659c554 100644 --- a/ui/ttdmemorywidget.cpp +++ b/ui/ttdmemorywidget.cpp @@ -159,8 +159,30 @@ void TTDMemoryQueryWidget::setupUI() m_endAddressEdit->setPlaceholderText("0xFFFFFFFF"); } - inputLayout->addRow("Start Address:", m_startAddressEdit); - inputLayout->addRow("End Address:", m_endAddressEdit); + // Put both address fields on the same line + QHBoxLayout* addressLayout = new QHBoxLayout(); + addressLayout->addWidget(new QLabel("Start:")); + addressLayout->addWidget(m_startAddressEdit); + addressLayout->addWidget(new QLabel("End:")); + addressLayout->addWidget(m_endAddressEdit); + + inputLayout->addRow("Address Range:", addressLayout); + + m_startTimeEdit = new QLineEdit(); + m_startTimeEdit->setToolTip("Start time in format 'sequence:step' (hexadecimal), leave blank for start of recording"); + m_startTimeEdit->setPlaceholderText("e.g. 0:0"); + + m_endTimeEdit = new QLineEdit(); + m_endTimeEdit->setToolTip("End time in format 'sequence:step' (hexadecimal), leave blank for end of recording"); + m_endTimeEdit->setPlaceholderText("e.g. 23f:a7"); + + QHBoxLayout* timeLayout = new QHBoxLayout(); + timeLayout->addWidget(new QLabel("Start Time:")); + timeLayout->addWidget(m_startTimeEdit); + timeLayout->addWidget(new QLabel("End Time:")); + timeLayout->addWidget(m_endTimeEdit); + + inputLayout->addRow("Time Range (Optional):", timeLayout); // Memory access type checkboxes QHBoxLayout* accessLayout = new QHBoxLayout(); @@ -317,7 +339,43 @@ void TTDMemoryQueryWidget::performQuery() // Parse input parameters uint64_t startAddress = parseAddress(m_startAddressEdit->text()); uint64_t endAddress = parseAddress(m_endAddressEdit->text()); - + TTDPosition startTime ; + TTDPosition endTime ; + if (m_startTimeEdit->text().isEmpty()) + { + startTime = TTDPosition(0, 0); + } + else + { + try + { + startTime = parseTimePosition(m_startTimeEdit->text()); + } + catch (const std::invalid_argument&) + { + QMessageBox::warning(this, "Invalid Start Time", + "Start time must be in the format 'sequence:step' with valid hexadecimal numbers."); + return; + } + } + + if (m_endTimeEdit->text().isEmpty()) + { + endTime = TTDPosition(std::numeric_limits::max(), std::numeric_limits::max()); + } + else + { + try{ + endTime = parseTimePosition(m_endTimeEdit->text()); + } + catch (const std::invalid_argument&) + { + QMessageBox::warning(this, "Invalid End Time", + "End time must be in the format 'sequence:step' with valid hexadecimal numbers."); + return; + } + } + if (endAddress <= startAddress) { QMessageBox::warning(this, "Invalid Address Range", @@ -332,6 +390,13 @@ void TTDMemoryQueryWidget::performQuery() "Please select at least one memory access type (Read, Write, or Execute)."); return; } + + if (endTime < startTime) + { + QMessageBox::warning(this, "Invalid Time Range", + "End time must be greater than or equal to start time."); + return; + } // Clear previous results clearResults(); @@ -344,7 +409,7 @@ void TTDMemoryQueryWidget::performQuery() try { // Execute the TTD memory query - auto events = m_controller->GetTTDMemoryAccessForAddress(startAddress, endAddress, accessType); + auto events = m_controller->GetTTDMemoryAccessForPositionRange(startAddress, endAddress, accessType, startTime, endTime); // Populate the results table m_resultsTable->setRowCount((int)events.size()); @@ -637,6 +702,26 @@ uint64_t TTDMemoryQueryWidget::parseAddress(const QString& text) return ok ? address : 0; } +TTDPosition TTDMemoryQueryWidget::parseTimePosition(const QString& text) +{ + QString cleanText = text.trimmed(); + if (cleanText.isEmpty()) + return TTDPosition(0, 0); // Default to start + + QStringList parts = cleanText.split(':'); + if (parts.size() != 2) + throw std::invalid_argument("Invalid time position format"); + + bool ok1, ok2; + uint64_t sequence = parts[0].toULongLong(&ok1, 16); + uint64_t step = parts[1].toULongLong(&ok2, 16); + + if (ok1 && ok2) + return TTDPosition(sequence, step); + else + throw std::invalid_argument("Invalid time position format"); +} + TTDMemoryAccessType TTDMemoryQueryWidget::getSelectedAccessTypes() { TTDMemoryAccessType accessType = static_cast(0); diff --git a/ui/ttdmemorywidget.h b/ui/ttdmemorywidget.h index 4c5f3dfa..af31e0cd 100644 --- a/ui/ttdmemorywidget.h +++ b/ui/ttdmemorywidget.h @@ -89,6 +89,8 @@ class TTDMemoryQueryWidget : public QWidget // Input controls QLineEdit* m_startAddressEdit; QLineEdit* m_endAddressEdit; + QLineEdit* m_startTimeEdit; + QLineEdit* m_endTimeEdit; QCheckBox* m_readAccessCheck; QCheckBox* m_writeAccessCheck; QCheckBox* m_executeAccessCheck; @@ -114,6 +116,7 @@ class TTDMemoryQueryWidget : public QWidget void setupTable(); void updateStatus(const QString& message); uint64_t parseAddress(const QString& text); + TTDPosition parseTimePosition(const QString& text); TTDMemoryAccessType getSelectedAccessTypes(); void setupContextMenu(); void setupUIActions(); From 47f22819e2f6ea372b91da1bfc799e0ebf8f123a Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Sun, 18 Jan 2026 20:14:04 -0800 Subject: [PATCH 06/14] Added visual studio build related files to gitignore revert gitignore changes because i built in the wrong dir, wasnt an issue From 4e0045cb528d9869172d9cb6a0fcd0b329366f9c Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Thu, 22 Jan 2026 09:38:04 -0800 Subject: [PATCH 07/14] Add in TTD api for GetTTDMemoryAccessForPositionRange and associated structs. In order to fix ttdmemorywidget results display --- api/debuggerapi.h | 19 +- api/debuggercontroller.cpp | 22 +-- api/ffi.h | 16 +- core/adapters/dbgengttdadapter.cpp | 307 ++++++++++++++++++++++++++++- core/adapters/dbgengttdadapter.h | 5 +- core/debugadapter.cpp | 2 +- core/debugadapter.h | 2 +- core/debuggercommon.h | 17 ++ core/debuggercontroller.cpp | 6 +- core/debuggercontroller.h | 2 +- core/ffi.cpp | 24 ++- ui/ttdmemorywidget.cpp | 70 +++---- 12 files changed, 414 insertions(+), 78 deletions(-) diff --git a/api/debuggerapi.h b/api/debuggerapi.h index ccc330b5..053275b3 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -520,6 +520,23 @@ namespace BinaryNinjaDebuggerAPI { TTDMemoryEvent() : threadId(0), uniqueThreadId(0), accessType(TTDMemoryRead), address(0), size(0), memoryAddress(0), instructionAddress(0), value(0) {} }; + struct TTDPositionRangeIndexedMemoryEvent{ + TTDPosition position; // Position of the memory event + uint32_t threadId; // Thread ID that performed the access + uint32_t uniqueThreadId; // Unique thread ID that performed the access + uint64_t address; // Memory address accessed + uint64_t instructionAddress; // Instruction pointer at time of access + uint64_t size; // Size of memory access + TTDMemoryAccessType accessType; // Type of memory access (parsed from object) + uint64_t value; // Value that was read/written/executed + uint8_t data[8]; // The next 8 bytes of data at the memory address + + TTDPositionRangeIndexedMemoryEvent() : threadId(0), uniqueThreadId(0), address(0), size(0), accessType(TTDMemoryRead), value(0) + { + memset(data, 0, sizeof(data)); + } + }; + struct TTDCallEvent { std::string eventType; // Event type (always "Call" for TTD.Calls objects) @@ -800,7 +817,7 @@ namespace BinaryNinjaDebuggerAPI { // TTD Memory Analysis Methods std::vector GetTTDMemoryAccessForAddress(uint64_t address, uint64_t endAddress, TTDMemoryAccessType accessType = TTDMemoryRead); - std::vector GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime); + std::vector GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime); std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0); std::vector GetTTDEvents(TTDEventType eventType); std::vector GetAllTTDEvents(); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index 0623b463..5b708896 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -1078,39 +1078,39 @@ bool DebuggerController::IsTTD() return BNDebuggerIsTTD(m_object); } -std::vector DebuggerController::GetTTDMemoryAccessForPositionRange(uint64_t address, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime) +std::vector DebuggerController::GetTTDMemoryAccessForPositionRange(uint64_t address, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime) { - std::vector result; + std::vector result; BNDebuggerTTDMemoryAccessType type = static_cast(accessType); BNDebuggerTTDPosition bnStartTime = {startTime.sequence, startTime.step}; BNDebuggerTTDPosition bnEndTime = {endTime.sequence, endTime.step}; size_t count = 0; - BNDebuggerTTDMemoryEvent* events = BNDebuggerGetTTDMemoryAccessForPositionRange(m_object, address, endAddress, type, bnStartTime, bnEndTime, &count); + BNDebuggerTTDPositionRangeIndexedMemoryEvent* events = BNDebuggerGetTTDMemoryAccessForPositionRange(m_object, address, endAddress, type, bnStartTime, bnEndTime, &count); if (events && count > 0) { result.reserve(count); for (size_t i = 0; i < count; i++) { - TTDMemoryEvent event; - event.eventType = events[i].eventType ? std::string(events[i].eventType) : ""; + TTDPositionRangeIndexedMemoryEvent event; event.threadId = events[i].threadId; event.uniqueThreadId = events[i].uniqueThreadId; - event.timeStart.sequence = events[i].timeStart.sequence; - event.timeStart.step = events[i].timeStart.step; - event.timeEnd.sequence = events[i].timeEnd.sequence; - event.timeEnd.step = events[i].timeEnd.step; + event.position.sequence = events[i].position.sequence; + event.position.step = events[i].position.step; event.accessType = static_cast(events[i].accessType); event.address = events[i].address; event.size = events[i].size; - event.memoryAddress = events[i].memoryAddress; event.instructionAddress = events[i].instructionAddress; event.value = events[i].value; + for (size_t j = 0; j < 8; j++) + { + event.data[j] = events[i].data[j]; + } result.push_back(event); } - BNDebuggerFreeTTDMemoryEvents(events, count); + BNDebuggerFreeTTDPositionRangeIndexedMemoryEvents(events, count); } return result; diff --git a/api/ffi.h b/api/ffi.h index 3f507018..afd008b0 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -335,6 +335,19 @@ extern "C" BNDebuggerTTDMemoryAccessType accessType; } BNDebuggerTTDMemoryEvent; + typedef struct BNDebuggerTTDPositionRangeIndexedMemoryEvent + { + BNDebuggerTTDPosition position; + uint32_t threadId; + uint32_t uniqueThreadId; + uint64_t address; + uint64_t instructionAddress; + uint64_t size; + BNDebuggerTTDMemoryAccessType accessType; + uint64_t value; + uint8_t data[8]; + } BNDebuggerTTDPositionRangeIndexedMemoryEvent; + typedef struct BNDebuggerTTDCallEvent { char* eventType; // Event type (always "Call" for TTD.Calls objects) @@ -665,7 +678,7 @@ extern "C" // TTD Memory Analysis Functions DEBUGGER_FFI_API BNDebuggerTTDMemoryEvent* BNDebuggerGetTTDMemoryAccessForAddress(BNDebuggerController* controller, uint64_t address, uint64_t endAddress, BNDebuggerTTDMemoryAccessType accessType, size_t* count); - DEBUGGER_FFI_API BNDebuggerTTDMemoryEvent* BNDebuggerGetTTDMemoryAccessForPositionRange(BNDebuggerController* controller, + DEBUGGER_FFI_API BNDebuggerTTDPositionRangeIndexedMemoryEvent* BNDebuggerGetTTDMemoryAccessForPositionRange(BNDebuggerController* controller, uint64_t address, uint64_t endAddress, BNDebuggerTTDMemoryAccessType accessType ,BNDebuggerTTDPosition startPosition, BNDebuggerTTDPosition endPosition, size_t* count); DEBUGGER_FFI_API BNDebuggerTTDCallEvent* BNDebuggerGetTTDCallsForSymbols(BNDebuggerController* controller, @@ -676,6 +689,7 @@ extern "C" DEBUGGER_FFI_API BNDebuggerTTDPosition BNDebuggerGetCurrentTTDPosition(BNDebuggerController* controller); DEBUGGER_FFI_API bool BNDebuggerSetTTDPosition(BNDebuggerController* controller, BNDebuggerTTDPosition position); DEBUGGER_FFI_API void BNDebuggerFreeTTDMemoryEvents(BNDebuggerTTDMemoryEvent* events, size_t count); + DEBUGGER_FFI_API void BNDebuggerFreeTTDPositionRangeIndexedMemoryEvents(BNDebuggerTTDPositionRangeIndexedMemoryEvent* events, size_t count); DEBUGGER_FFI_API void BNDebuggerFreeTTDCallEvents(BNDebuggerTTDCallEvent* events, size_t count); DEBUGGER_FFI_API void BNDebuggerFreeTTDEvents(BNDebuggerTTDEvent* events, size_t count); diff --git a/core/adapters/dbgengttdadapter.cpp b/core/adapters/dbgengttdadapter.cpp index b48bb5c9..b37c81be 100644 --- a/core/adapters/dbgengttdadapter.cpp +++ b/core/adapters/dbgengttdadapter.cpp @@ -403,9 +403,9 @@ std::vector DbgEngTTDAdapter::GetTTDMemoryAccessForAddress(uint6 return events; } -std::vector DbgEngTTDAdapter::GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime) +std::vector DbgEngTTDAdapter::GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime) { - std::vector events; + std::vector events; if (!QueryMemoryAccessByAddressAndPositionRange(startAddress, endAddress, accessType, startTime, endTime, events)) { @@ -575,7 +575,7 @@ bool DbgEngTTDAdapter::QueryMemoryAccessByAddress(uint64_t startAddress, uint64_ } } -bool DbgEngTTDAdapter::QueryMemoryAccessByAddressAndPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, TTDPosition startTime, TTDPosition endTime, std::vector& events) +bool DbgEngTTDAdapter::QueryMemoryAccessByAddressAndPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, TTDPosition startTime, TTDPosition endTime, std::vector& events) { if (!m_debugControl) { @@ -603,7 +603,7 @@ bool DbgEngTTDAdapter::QueryMemoryAccessByAddressAndPositionRange(uint64_t start LogInfo("Executing TTD memory query: %s", expression.c_str()); // Execute the query and parse results - if (!ParseTTDMemoryObjects(expression, accessType, events)) + if (!ParseTTDPositionRangeIndexedMemoryObjects(expression, accessType, events)) { LogError("Failed to parse TTD memory objects from query"); return false; @@ -614,7 +614,7 @@ bool DbgEngTTDAdapter::QueryMemoryAccessByAddressAndPositionRange(uint64_t start } catch (const std::exception& e) { - LogError("Exception in QueryMemoryAccessByAddress: %s", e.what()); + LogError("Exception in QueryMemoryAccessByAddressAndPositionRange: %s", e.what()); return false; } } @@ -1021,6 +1021,303 @@ bool DbgEngTTDAdapter::ParseTTDMemoryObjects(const std::string& expression, TTDM } } +// Implementation of TTD position range indexed memory objects parsing +// This sadly appears to be an undocumented return type structure from Microsoft +// By calling TTD.Memory you would get a TTD Memory Object, documented here: https://learn.microsoft.com/en-us/windows-hardware/drivers/debuggercmds/time-travel-debugging-memory-objects +// However, TTD.MemoryForPositionRange appears to return a different structure that is not documented +// The key differences appear to be the following: +// - Instead of `timeStart` and `timeEnd` being fields, they are replaced with `position` field that contains a TTD position +// - Another distinction is that `execute` queries 'value` field can differ between calls to TTD.Memory and TTD.MemoryForPositionRange, see the example below to understand +/* +>>> dx -r1 @$cursession.TTD.Memory(0x7ff74e769e70, 0x7ff74e769e80, "e").First() +@$cursession.TTD.Memory(0x7ff74e769e70, 0x7ff74e769e80, "e").First() + EventType : 0x1 + ThreadId : 0x9bd8 + UniqueThreadId : 0x1e + TimeStart : 2B65:21B + TimeEnd : 2B65:21B + AccessType : Execute + IP : 0x7ff74e769e70 + Address : 0x7ff74e769e70 + Size : 0x2 + Value : 0x5540 + SystemTimeStart : Saturday, January 17, 2026 05:58:34.140 + SystemTimeEnd : Saturday, January 17, 2026 05:58:34.140 + +>>> dx -r1 @$cursession.TTD.MemoryForPositionRange(0x7ff74e769e70, 0x7ff74e769e80, "e" , "0:0","adf1:745").First() +@$cursession.TTD.MemoryForPositionRange(0x7ff74e769e70, 0x7ff74e769e80, "e" , "0:0","adf1:745").First() + Position : 2B65:21B + ThreadId : 0x9bd8 + UniqueThreadId : 0x1e + Address : 0x7ff74e769e70 + IP : 0x7ff74e769e70 + Size : 0x2 + AccessType : Execute + Value : 0x4154415756535540 + Data + +>>> dx @$cursession.TTD.MemoryForPositionRange(0x7ff74e769e70, 0x7ff74e769e80, "e", "0:0", "adf1:745").First().Data +@$cursession.TTD.MemoryForPositionRange(0x7ff74e769e70, 0x7ff74e769e80, "e", "0:0", "adf1:745").First().Data + [0x0] : 0x40 + [0x1] : 0x55 + [0x2] : 0x53 + [0x3] : 0x56 + [0x4] : 0x57 + [0x5] : 0x41 + [0x6] : 0x54 + [0x7] : 0x41 +*/ +// The data field here contains raw instruction bytes read from memory. +// `MemoryForPositionRange` does not truncate the value field to match the actual size of the memory access, but contains all the parts to piece it together. +bool DbgEngTTDAdapter::ParseTTDPositionRangeIndexedMemoryObjects(const std::string& expression, TTDMemoryAccessType accessType, std::vector& events) +{ + if (!m_hostEvaluator) + { + LogError("Data model evaluator not available"); + return false; + } + + try + { + // Convert expression to wide string + std::wstring wExpression(expression.begin(), expression.end()); + + // Create context for evaluation + ComPtr hostContext; + if (FAILED(m_debugHost->GetCurrentContext(hostContext.GetAddressOf()))) + { + LogError("Failed to get current debug host context"); + return false; + } + + // Evaluate the TTD memory collection expression + ComPtr result; + ComPtr metadata; + HRESULT hr = m_hostEvaluator->EvaluateExtendedExpression( + hostContext.Get(), + wExpression.c_str(), + nullptr, // No binding context + result.GetAddressOf(), + metadata.GetAddressOf() + ); + + if (FAILED(hr)) + { + LogError("Failed to evaluate TTD memory expression '%s': 0x%08x", expression.c_str(), hr); + return false; + } + + // Check if result is iterable (collection) + ComPtr iterableConcept; + if (FAILED(result->GetConcept(__uuidof(IIterableConcept), &iterableConcept, nullptr))) + { + LogError("TTD memory result is not iterable"); + return false; + } + + // Get iterator + ComPtr iterator; + if (FAILED(iterableConcept->GetIterator(result.Get(), &iterator))) + { + LogError("Failed to get iterator for TTD memory objects"); + return false; + } + + // Iterate through memory objects + ComPtr memoryObject; + ComPtr metadataKeyStore; + + // Get the max results setting + auto adapterSettings = GetAdapterSettings(); + BNSettingsScope scope = SettingsResourceScope; + auto maxResults = adapterSettings->Get("ttd.maxMemoryQueryResults", GetData(), &scope); + + uint64_t resultCounter = 0; + bool wasLimited = false; + + while (SUCCEEDED(iterator->GetNext(&memoryObject, 0, nullptr, &metadataKeyStore))) + { + if (!memoryObject) + break; + + // Check if we've reached the limit (0 means no limit) + if (maxResults > 0 && resultCounter >= maxResults) + { + wasLimited = true; + break; + } + + TTDPositionRangeIndexedMemoryEvent event; + + // Extract all fields from the memory object based on Microsoft documentation + + // Get ThreadId + ComPtr threadIdObj; + if (SUCCEEDED(memoryObject->GetKeyValue(L"ThreadId", &threadIdObj, nullptr))) + { + VARIANT vtThreadId; + VariantInit(&vtThreadId); + if (SUCCEEDED(threadIdObj->GetIntrinsicValueAs(VT_UI4, &vtThreadId))) + { + event.threadId = vtThreadId.ulVal; + } + VariantClear(&vtThreadId); + } + + // Get UniqueThreadId + ComPtr uniqueThreadIdObj; + if (SUCCEEDED(memoryObject->GetKeyValue(L"UniqueThreadId", &uniqueThreadIdObj, nullptr))) + { + VARIANT vtUniqueThreadId; + VariantInit(&vtUniqueThreadId); + if (SUCCEEDED(uniqueThreadIdObj->GetIntrinsicValueAs(VT_UI4, &vtUniqueThreadId))) + { + event.uniqueThreadId = vtUniqueThreadId.ulVal; + } + VariantClear(&vtUniqueThreadId); + } + + // Get TimeStart for position + ComPtr positionObj; + if (SUCCEEDED(memoryObject->GetKeyValue(L"Position", &positionObj, nullptr))) + { + // TimeStart is typically a TTD position object with Sequence and Steps + ComPtr sequenceObj, stepsObj; + if (SUCCEEDED(positionObj->GetKeyValue(L"Sequence", &sequenceObj, nullptr))) + { + VARIANT vtSequence; + VariantInit(&vtSequence); + if (SUCCEEDED(sequenceObj->GetIntrinsicValueAs(VT_UI8, &vtSequence))) + { + event.position.sequence = vtSequence.ullVal; + } + VariantClear(&vtSequence); + } + + if (SUCCEEDED(positionObj->GetKeyValue(L"Steps", &stepsObj, nullptr))) + { + VARIANT vtSteps; + VariantInit(&vtSteps); + if (SUCCEEDED(stepsObj->GetIntrinsicValueAs(VT_UI8, &vtSteps))) + { + event.position.step = vtSteps.ullVal; + } + VariantClear(&vtSteps); + } + } + + // Get Address + ComPtr addressObj; + if (SUCCEEDED(memoryObject->GetKeyValue(L"Address", &addressObj, nullptr))) + { + VARIANT vtAddress; + VariantInit(&vtAddress); + if (SUCCEEDED(addressObj->GetIntrinsicValueAs(VT_UI8, &vtAddress))) + { + event.address = vtAddress.ullVal; + } + VariantClear(&vtAddress); + } + + // Get Size + ComPtr sizeObj; + if (SUCCEEDED(memoryObject->GetKeyValue(L"Size", &sizeObj, nullptr))) + { + VARIANT vtSize; + VariantInit(&vtSize); + if (SUCCEEDED(sizeObj->GetIntrinsicValueAs(VT_UI8, &vtSize))) + { + event.size = vtSize.ullVal; + } + VariantClear(&vtSize); + } + + // Get IP (Instruction Pointer) + ComPtr ipObj; + if (SUCCEEDED(memoryObject->GetKeyValue(L"IP", &ipObj, nullptr))) + { + VARIANT vtIP; + VariantInit(&vtIP); + if (SUCCEEDED(ipObj->GetIntrinsicValueAs(VT_UI8, &vtIP))) + { + event.instructionAddress = vtIP.ullVal; + } + VariantClear(&vtIP); + } + + // Get Value (the value that was read/written/executed) + ComPtr valueObj; + if (SUCCEEDED(memoryObject->GetKeyValue(L"Value", &valueObj, nullptr))) + { + VARIANT vtValue; + VariantInit(&vtValue); + if (SUCCEEDED(valueObj->GetIntrinsicValueAs(VT_UI8, &vtValue))) + { + event.value = vtValue.ullVal; + } + VariantClear(&vtValue); + } + + // Get AccessType from the object itself + ComPtr accessTypeObj; + if (SUCCEEDED(memoryObject->GetKeyValue(L"AccessType", &accessTypeObj, nullptr))) + { + VARIANT vtAccessType; + VariantInit(&vtAccessType); + if (SUCCEEDED(accessTypeObj->GetIntrinsicValueAs(VT_BSTR, &vtAccessType))) + { + _bstr_t bstr(vtAccessType.bstrVal); + std::string accessTypeStr = std::string(bstr); + + // Parse access type string to bitfield + TTDMemoryAccessType parsedAccessType = static_cast(0); + if (accessTypeStr.find("Read") != std::string::npos) + parsedAccessType = static_cast(parsedAccessType | TTDMemoryRead); + if (accessTypeStr.find("Write") != std::string::npos) + parsedAccessType = static_cast(parsedAccessType | TTDMemoryWrite); + if (accessTypeStr.find("Execute") != std::string::npos) + parsedAccessType = static_cast(parsedAccessType | TTDMemoryExecute); + + event.accessType = parsedAccessType; + } + else + { + // Fallback to query parameter if parsing fails + event.accessType = accessType; + } + VariantClear(&vtAccessType); + } + else + { + // Fallback to query parameter if field is not available + event.accessType = accessType; + } + + events.push_back(event); + resultCounter++; + + // Reset objects for next iteration + memoryObject.Reset(); + metadataKeyStore.Reset(); + } + + if (wasLimited) + { + LogWarnF("Successfully parsed {} TTD memory events from data model (limited by max results setting of {})", events.size(), maxResults); + } + else + { + LogInfo("Successfully parsed %zu TTD memory events from data model", events.size()); + } + return true; + } + catch (const std::exception& e) + { + LogError("Exception in ParseTTDPositionRangeIndexedMemoryObjects: %s", e.what()); + return false; + } +} + std::vector DbgEngTTDAdapter::GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress, uint64_t endReturnAddress) { diff --git a/core/adapters/dbgengttdadapter.h b/core/adapters/dbgengttdadapter.h index 0921f4ab..b8b57e42 100644 --- a/core/adapters/dbgengttdadapter.h +++ b/core/adapters/dbgengttdadapter.h @@ -48,7 +48,7 @@ namespace BinaryNinjaDebugger { // TTD Memory Analysis Methods std::vector GetTTDMemoryAccessForAddress(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType = TTDMemoryRead) override; - std::vector GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, TTDPosition startTime, TTDPosition endTime) override; + std::vector GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, TTDPosition startTime, TTDPosition endTime) override; // TTD Position Methods TTDPosition GetCurrentTTDPosition() override; @@ -67,7 +67,7 @@ namespace BinaryNinjaDebugger { private: // Helper methods for TTD memory analysis bool QueryMemoryAccessByAddress(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, std::vector& events); - bool QueryMemoryAccessByAddressAndPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, TTDPosition startTime, TTDPosition endTime, std::vector& events); + bool QueryMemoryAccessByAddressAndPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, TTDPosition startTime, TTDPosition endTime, std::vector& events); // Helper methods for TTD calls analysis bool QueryCallsForSymbols(const std::vector& symbols, uint64_t startReturnAddress, uint64_t endReturnAddress, std::vector& events); @@ -91,6 +91,7 @@ namespace BinaryNinjaDebugger { // Data model helper methods std::string EvaluateDataModelExpression(const std::string& expression); bool ParseTTDMemoryObjects(const std::string& expression, TTDMemoryAccessType accessType, std::vector& events); + bool ParseTTDPositionRangeIndexedMemoryObjects(const std::string& expression, TTDMemoryAccessType accessType, std::vector& events); // Data model interfaces for TTD IHostDataModelAccess* m_dataModelManager; diff --git a/core/debugadapter.cpp b/core/debugadapter.cpp index 4740c7e8..3e8b5392 100644 --- a/core/debugadapter.cpp +++ b/core/debugadapter.cpp @@ -219,7 +219,7 @@ std::vector DebugAdapter::GetTTDMemoryAccessForAddress(uint64_t return {}; } -std::vector DebugAdapter::GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime) +std::vector DebugAdapter::GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime) { // Default implementation returns empty results for adapters that don't support TTD return {}; diff --git a/core/debugadapter.h b/core/debugadapter.h index b59a0d25..92932eca 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -390,7 +390,7 @@ namespace BinaryNinjaDebugger { // TTD (Time Travel Debugging) methods - default implementations return empty results virtual std::vector GetTTDMemoryAccessForAddress(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType = TTDMemoryRead); - virtual std::vector GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime); + virtual std::vector GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime); virtual std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0); virtual std::vector GetTTDEvents(TTDEventType eventType); virtual std::vector GetAllTTDEvents(); diff --git a/core/debuggercommon.h b/core/debuggercommon.h index 9c3b2e53..276c7945 100644 --- a/core/debuggercommon.h +++ b/core/debuggercommon.h @@ -137,6 +137,23 @@ namespace BinaryNinjaDebugger { TTDMemoryEvent() : threadId(0), uniqueThreadId(0), address(0), size(0), memoryAddress(0), instructionAddress(0), value(0), accessType(TTDMemoryRead) {} }; + struct TTDPositionRangeIndexedMemoryEvent{ + TTDPosition position; // Position of the memory event + uint32_t threadId; // Thread ID that performed the access + uint32_t uniqueThreadId; // Unique thread ID that performed the access + uint64_t address; // Memory address accessed + uint64_t instructionAddress; // Instruction pointer at time of access + uint64_t size; // Size of memory access + TTDMemoryAccessType accessType; // Type of memory access (parsed from object) + uint64_t value; // Value that was read/written/executed + uint8_t data[8]; // The next 8 bytes of data at the memory address + + TTDPositionRangeIndexedMemoryEvent() : threadId(0), uniqueThreadId(0), address(0), size(0), accessType(TTDMemoryRead), value(0) + { + memset(data, 0, sizeof(data)); + } + }; + // TTD Call Event - complete set of fields from Microsoft documentation for TTD.Calls struct TTDCallEvent { diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 2ae479ac..a8bdc390 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -3134,9 +3134,9 @@ std::vector DebuggerController::GetTTDMemoryAccessForAddress(uin return events; } -std::vector DebuggerController::GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime) +std::vector DebuggerController::GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime) { - std::vector events; + std::vector events; if (!m_state->IsConnected() || !IsTTD()) { @@ -3287,7 +3287,7 @@ bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t if (event.instructionAddress >= startAddress && event.instructionAddress <= endAddress) { // Check if the event is within the specified time range - m_executedInstructions.insert(event.instructionAddress); + m_executedInstructions.insert(event.address); } } } diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index 108cc6f8..067dbf47 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -389,7 +389,7 @@ namespace BinaryNinjaDebugger { // TTD Memory Analysis Methods std::vector GetTTDMemoryAccessForAddress(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType = TTDMemoryRead); - std::vector GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime); + std::vector GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime); std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0); std::vector GetTTDEvents(TTDEventType eventType); std::vector GetAllTTDEvents(); diff --git a/core/ffi.cpp b/core/ffi.cpp index cd8b51c6..41393025 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -1226,7 +1226,7 @@ BNDebuggerTTDMemoryEvent* BNDebuggerGetTTDMemoryAccessForAddress(BNDebuggerContr return result; } -BNDebuggerTTDMemoryEvent* BNDebuggerGetTTDMemoryAccessForPositionRange(BNDebuggerController* controller, +BNDebuggerTTDPositionRangeIndexedMemoryEvent* BNDebuggerGetTTDMemoryAccessForPositionRange(BNDebuggerController* controller, uint64_t address, uint64_t endAddress, BNDebuggerTTDMemoryAccessType accessType, BNDebuggerTTDPosition startTime, BNDebuggerTTDPosition endTime, size_t* count) { if (!count) @@ -1242,23 +1242,23 @@ BNDebuggerTTDMemoryEvent* BNDebuggerGetTTDMemoryAccessForPositionRange(BNDebugge return nullptr; *count = events.size(); - auto result = new BNDebuggerTTDMemoryEvent[events.size()]; + auto result = new BNDebuggerTTDPositionRangeIndexedMemoryEvent[events.size()]; for (size_t i = 0; i < events.size(); i++) { - result[i].eventType = BNAllocString(events[i].eventType.c_str()); result[i].threadId = events[i].threadId; result[i].uniqueThreadId = events[i].uniqueThreadId; - result[i].timeStart.sequence = events[i].timeStart.sequence; - result[i].timeStart.step = events[i].timeStart.step; - result[i].timeEnd.sequence = events[i].timeEnd.sequence; - result[i].timeEnd.step = events[i].timeEnd.step; + result[i].position.sequence = events[i].position.sequence; + result[i].position.step = events[i].position.step; result[i].address = events[i].address; result[i].size = events[i].size; - result[i].memoryAddress = events[i].memoryAddress; result[i].instructionAddress = events[i].instructionAddress; result[i].value = events[i].value; result[i].accessType = static_cast(events[i].accessType); + for (size_t j = 0; j < 8; j++) + { + result[i].data[j]=events[i].data[j]; + } } return result; @@ -1322,6 +1322,14 @@ void BNDebuggerFreeTTDMemoryEvents(BNDebuggerTTDMemoryEvent* events, size_t coun } } +void BNDebuggerFreeTTDPositionRangeIndexedMemoryEvents(BNDebuggerTTDPositionRangeIndexedMemoryEvent* events, size_t count) +{ + if (events && count > 0) + { + delete[] events; + } +} + BNDebuggerTTDCallEvent* BNDebuggerGetTTDCallsForSymbols(BNDebuggerController* controller, const char* symbols, uint64_t startReturnAddress, uint64_t endReturnAddress, size_t* count) diff --git a/ui/ttdmemorywidget.cpp b/ui/ttdmemorywidget.cpp index 3659c554..069c60cf 100644 --- a/ui/ttdmemorywidget.cpp +++ b/ui/ttdmemorywidget.cpp @@ -63,9 +63,7 @@ ColumnVisibilityDialog::ColumnVisibilityDialog(QWidget* parent, const QStringLis // Reset to default visibility (hide Event Type, Time End, Unique Thread ID) QList defaultVisibility; defaultVisibility << true // Index - << false // Event Type (hidden by default) - << true // Time Start - << false // Time End (hidden by default) + << true // Position << true // Access Type << true // Address << true // Size @@ -102,14 +100,12 @@ TTDMemoryQueryWidget::TTDMemoryQueryWidget(QWidget* parent, BinaryViewRef data) m_controller = DebuggerController::GetController(m_data); // Initialize column names and visibility - m_columnNames << "Index" << "Event Type" << "Time Start" << "Time End" << "Access Type" + m_columnNames << "Index" << "Position" << "Access Type" << "Address" << "Size" << "Value" << "Thread ID" << "Unique Thread ID" << "IP"; // Set default visibility (hide Event Type, Time End, Unique Thread ID) m_columnVisibility << true // Index - << false // Event Type (hidden by default) - << true // Time Start - << false // Time End (hidden by default) + << true // Position << true // Access Type << true // Address << true // Size @@ -267,15 +263,13 @@ void TTDMemoryQueryWidget::setupTable() QHeaderView* header = m_resultsTable->horizontalHeader(); header->setStretchLastSection(true); m_resultsTable->setColumnWidth(0, 80); // Index - m_resultsTable->setColumnWidth(1, 100); // Event Type - m_resultsTable->setColumnWidth(2, 120); // Time Start - m_resultsTable->setColumnWidth(3, 120); // Time End - m_resultsTable->setColumnWidth(4, 100); // Access Type - m_resultsTable->setColumnWidth(5, 120); // Address - m_resultsTable->setColumnWidth(6, 80); // Size - m_resultsTable->setColumnWidth(7, 120); // Value - m_resultsTable->setColumnWidth(8, 80); // Thread ID - m_resultsTable->setColumnWidth(9, 100); // Unique Thread ID + m_resultsTable->setColumnWidth(1, 100); // Position + m_resultsTable->setColumnWidth(2, 100); // Access Type + m_resultsTable->setColumnWidth(3, 120); // Address + m_resultsTable->setColumnWidth(4, 80); // Size + m_resultsTable->setColumnWidth(5, 120); // Value + m_resultsTable->setColumnWidth(6, 80); // Thread ID + m_resultsTable->setColumnWidth(7, 100); // Unique Thread ID // IP column will stretch // Apply initial column visibility @@ -421,50 +415,40 @@ void TTDMemoryQueryWidget::performQuery() // Index m_resultsTable->setItem(i, 0, new NumericalTableWidgetItem(QString("0x%1").arg(i, 0, 16), i)); - // Event Type - m_resultsTable->setItem(i, 1, new QTableWidgetItem(QString::fromStdString(event.eventType))); - - // Time Start - QString timeStartStr = QString("%1:%2") - .arg(event.timeStart.sequence, 0, 16) - .arg(event.timeStart.step, 0, 16); - uint64_t timeStartSortValue = (event.timeStart.sequence << 32) | (event.timeStart.step & 0xFFFFFFFF); - m_resultsTable->setItem(i, 2, new NumericalTableWidgetItem(timeStartStr, timeStartSortValue)); - - // Time End - QString timeEndStr = QString("%1:%2") - .arg(event.timeEnd.sequence, 0, 16) - .arg(event.timeEnd.step, 0, 16); - uint64_t timeEndSortValue = (event.timeEnd.sequence << 32) | (event.timeEnd.step & 0xFFFFFFFF); - m_resultsTable->setItem(i, 3, new NumericalTableWidgetItem(timeEndStr, timeEndSortValue)); + // Position + QString PositionStr = QString("%1:%2") + .arg(event.position.sequence, 0, 16) + .arg(event.position.step, 0, 16); + uint64_t positionSortValue = (event.position.sequence << 32) | (event.position.step & 0xFFFFFFFF); + m_resultsTable->setItem(i, 1, new NumericalTableWidgetItem(PositionStr, positionSortValue)); // Access Type QString accessTypeStr; if (event.accessType & TTDMemoryRead) accessTypeStr += "R"; if (event.accessType & TTDMemoryWrite) accessTypeStr += "W"; if (event.accessType & TTDMemoryExecute) accessTypeStr += "E"; - m_resultsTable->setItem(i, 4, new QTableWidgetItem(accessTypeStr)); + m_resultsTable->setItem(i, 2, new QTableWidgetItem(accessTypeStr)); // Address QString addressStr = QString("0x%1").arg(event.address, 0, 16); - m_resultsTable->setItem(i, 5, new NumericalTableWidgetItem(addressStr, event.address)); + m_resultsTable->setItem(i, 3, new NumericalTableWidgetItem(addressStr, event.address)); // Size - m_resultsTable->setItem(i, 6, new NumericalTableWidgetItem(QString::number(event.size), event.size)); + m_resultsTable->setItem(i, 4, new NumericalTableWidgetItem(QString::number(event.size), event.size)); - // Value - QString valueStr = QString("0x%1").arg(event.value, 0, 16); - m_resultsTable->setItem(i, 7, new NumericalTableWidgetItem(valueStr, event.value)); + // Value truncated to the number of bytes specified by size + QString valueStr = QString("0x%1").arg(event.value & ((1ULL << (event.size * 8)) - 1), 0, 16); + m_resultsTable->setItem(i, 5, new NumericalTableWidgetItem(valueStr, event.value)); // Thread ID - m_resultsTable->setItem(i, 8, new NumericalTableWidgetItem(QString::number(event.threadId), event.threadId)); + m_resultsTable->setItem(i, 6, new NumericalTableWidgetItem(QString::number(event.threadId), event.threadId)); // Unique Thread ID - m_resultsTable->setItem(i, 9, new NumericalTableWidgetItem(QString::number(event.uniqueThreadId), event.uniqueThreadId)); + m_resultsTable->setItem(i, 7, new NumericalTableWidgetItem(QString::number(event.uniqueThreadId), event.uniqueThreadId)); // IP (Instruction Address) QString instrAddrStr = QString("0x%1").arg(event.instructionAddress, 0, 16); - m_resultsTable->setItem(i, 10, new NumericalTableWidgetItem(instrAddrStr, event.instructionAddress)); + m_resultsTable->setItem(i, 8, new NumericalTableWidgetItem(instrAddrStr, event.instructionAddress)); } updateStatus(QString("Found %1 memory access events").arg(events.size())); @@ -668,9 +652,7 @@ void TTDMemoryQueryWidget::resetColumnsToDefault() // Reset to default visibility (hide Event Type, Time End, Unique Thread ID) m_columnVisibility.clear(); m_columnVisibility << true // Index - << false // Event Type (hidden by default) - << true // Time Start - << false // Time End (hidden by default) + << true // Position << true // Access Type << true // Address << true // Size From a27421decbdf74f732056c5013af602dc16c6263 Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:07:39 -0800 Subject: [PATCH 08/14] increase alpha to 100% for code coverage highlight --- ui/ttdcoveragerenderlayer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/ttdcoveragerenderlayer.cpp b/ui/ttdcoveragerenderlayer.cpp index 055dbebe..6566b727 100644 --- a/ui/ttdcoveragerenderlayer.cpp +++ b/ui/ttdcoveragerenderlayer.cpp @@ -56,7 +56,7 @@ void TTDCoverageRenderLayer::ApplyToBlock(Ref block, std::vector function, std: line.highlight.r = 0; line.highlight.g = 0; line.highlight.b = 0; - line.highlight.alpha = 170; // Light highlight + line.highlight.alpha = 255; } } } \ No newline at end of file From c41226ca3ce200404105dddffd9076b34135c267 Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:28:03 -0800 Subject: [PATCH 09/14] remove note taking comment made before concrete type for PositionRangeIndexedMemoryEvent --- core/adapters/dbgengttdadapter.cpp | 47 ------------------------------ 1 file changed, 47 deletions(-) diff --git a/core/adapters/dbgengttdadapter.cpp b/core/adapters/dbgengttdadapter.cpp index b37c81be..08f80cc5 100644 --- a/core/adapters/dbgengttdadapter.cpp +++ b/core/adapters/dbgengttdadapter.cpp @@ -1021,53 +1021,6 @@ bool DbgEngTTDAdapter::ParseTTDMemoryObjects(const std::string& expression, TTDM } } -// Implementation of TTD position range indexed memory objects parsing -// This sadly appears to be an undocumented return type structure from Microsoft -// By calling TTD.Memory you would get a TTD Memory Object, documented here: https://learn.microsoft.com/en-us/windows-hardware/drivers/debuggercmds/time-travel-debugging-memory-objects -// However, TTD.MemoryForPositionRange appears to return a different structure that is not documented -// The key differences appear to be the following: -// - Instead of `timeStart` and `timeEnd` being fields, they are replaced with `position` field that contains a TTD position -// - Another distinction is that `execute` queries 'value` field can differ between calls to TTD.Memory and TTD.MemoryForPositionRange, see the example below to understand -/* ->>> dx -r1 @$cursession.TTD.Memory(0x7ff74e769e70, 0x7ff74e769e80, "e").First() -@$cursession.TTD.Memory(0x7ff74e769e70, 0x7ff74e769e80, "e").First() - EventType : 0x1 - ThreadId : 0x9bd8 - UniqueThreadId : 0x1e - TimeStart : 2B65:21B - TimeEnd : 2B65:21B - AccessType : Execute - IP : 0x7ff74e769e70 - Address : 0x7ff74e769e70 - Size : 0x2 - Value : 0x5540 - SystemTimeStart : Saturday, January 17, 2026 05:58:34.140 - SystemTimeEnd : Saturday, January 17, 2026 05:58:34.140 - ->>> dx -r1 @$cursession.TTD.MemoryForPositionRange(0x7ff74e769e70, 0x7ff74e769e80, "e" , "0:0","adf1:745").First() -@$cursession.TTD.MemoryForPositionRange(0x7ff74e769e70, 0x7ff74e769e80, "e" , "0:0","adf1:745").First() - Position : 2B65:21B - ThreadId : 0x9bd8 - UniqueThreadId : 0x1e - Address : 0x7ff74e769e70 - IP : 0x7ff74e769e70 - Size : 0x2 - AccessType : Execute - Value : 0x4154415756535540 - Data - ->>> dx @$cursession.TTD.MemoryForPositionRange(0x7ff74e769e70, 0x7ff74e769e80, "e", "0:0", "adf1:745").First().Data -@$cursession.TTD.MemoryForPositionRange(0x7ff74e769e70, 0x7ff74e769e80, "e", "0:0", "adf1:745").First().Data - [0x0] : 0x40 - [0x1] : 0x55 - [0x2] : 0x53 - [0x3] : 0x56 - [0x4] : 0x57 - [0x5] : 0x41 - [0x6] : 0x54 - [0x7] : 0x41 -*/ -// The data field here contains raw instruction bytes read from memory. // `MemoryForPositionRange` does not truncate the value field to match the actual size of the memory access, but contains all the parts to piece it together. bool DbgEngTTDAdapter::ParseTTDPositionRangeIndexedMemoryObjects(const std::string& expression, TTDMemoryAccessType accessType, std::vector& events) { From aa72a853d95acea5e0f348d2cf36b1f36d9d1f40 Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:52:25 -0800 Subject: [PATCH 10/14] fix results table logical column enum. And tt on double click --- ui/ttdmemorywidget.cpp | 2 +- ui/ttdmemorywidget.h | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/ttdmemorywidget.cpp b/ui/ttdmemorywidget.cpp index 069c60cf..09e2b2f3 100644 --- a/ui/ttdmemorywidget.cpp +++ b/ui/ttdmemorywidget.cpp @@ -476,7 +476,7 @@ void TTDMemoryQueryWidget::onCellDoubleClicked(int row, int column) if (row < 0 || row >= m_resultsTable->rowCount()) return; - if (column == TimeStartColumn || column == TimeEndColumn) + if (column == PositionColumn) { // Parse position and navigate to it QTableWidgetItem* posItem = m_resultsTable->item(row, column); diff --git a/ui/ttdmemorywidget.h b/ui/ttdmemorywidget.h index af31e0cd..e8644dee 100644 --- a/ui/ttdmemorywidget.h +++ b/ui/ttdmemorywidget.h @@ -70,9 +70,7 @@ class TTDMemoryQueryWidget : public QWidget // Enum for logical column identification enum LogicalColumn { IndexColumn = 0, - EventTypeColumn, - TimeStartColumn, - TimeEndColumn, + PositionColumn, AccessTypeColumn, AddressColumn, SizeColumn, From 215edefb6f975e9585472289075e2d3953c2616b Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Fri, 23 Jan 2026 20:48:56 -0800 Subject: [PATCH 11/14] add in address parsing that matches windbg default console output format --- ui/ttdmemorywidget.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/ttdmemorywidget.cpp b/ui/ttdmemorywidget.cpp index 09e2b2f3..13573b42 100644 --- a/ui/ttdmemorywidget.cpp +++ b/ui/ttdmemorywidget.cpp @@ -676,8 +676,14 @@ uint64_t TTDMemoryQueryWidget::parseAddress(const QString& text) return 0; // Remove 0x prefix if present - if (cleanText.startsWith("0x", Qt::CaseInsensitive)) + if (cleanText.startsWith("0x", Qt::CaseInsensitive)){ cleanText = cleanText.mid(2); + } + // Remove ' character if present (default address display format in windbg console) + else if (cleanText.contains("`")){ + cleanText = cleanText.replace("`", ""); + } + bool ok; uint64_t address = cleanText.toULongLong(&ok, 16); From 7b404a51aa264a6bdb23b5b1e8e420909f586108 Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Fri, 23 Jan 2026 21:01:13 -0800 Subject: [PATCH 12/14] Add in explicit declaration of base and manual trim to 2 acceptable formats, 0x and windbg hex output for addresses --- ui/ttdanalysisdialog.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/ttdanalysisdialog.cpp b/ui/ttdanalysisdialog.cpp index 8a198893..8949701a 100644 --- a/ui/ttdanalysisdialog.cpp +++ b/ui/ttdanalysisdialog.cpp @@ -402,6 +402,8 @@ void TTDAnalysisDialog::onRunAnalysis() bool startOk, endOk, startTimeOk, endTimeOk; QString startText = m_startAddressEdit->text().trimmed(); QString endText = m_endAddressEdit->text().trimmed(); + endText=endText.replace("`", "").replace("0x", ""); // remove extra address formatting + startText=startText.replace("`", "").replace("0x", ""); // windbg format looks like: 000000dd`7e7fed80 QString startTimeText = m_startTimeEdit->text().trimmed(); QString endTimeText = m_endTimeEdit->text().trimmed(); TTDPosition startTime, endTime; @@ -456,8 +458,8 @@ void TTDAnalysisDialog::onRunAnalysis() endTime = TTDPosition(sequence, step); } - uint64_t startAddress = startText.toULongLong(&startOk, 0); // Auto-detect base (0x for hex) - uint64_t endAddress = endText.toULongLong(&endOk, 0); + uint64_t startAddress = startText.toULongLong(&startOk, 16); + uint64_t endAddress = endText.toULongLong(&endOk, 16); if (!startOk || !endOk) { From 7f90676d24a8f1dbc7f7ed41f415d91ac8092441 Mon Sep 17 00:00:00 2001 From: Nicole <41876584+NicoleFaye@users.noreply.github.com> Date: Sat, 24 Jan 2026 23:07:46 -0800 Subject: [PATCH 13/14] remove unused vars --- ui/ttdanalysisdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/ttdanalysisdialog.cpp b/ui/ttdanalysisdialog.cpp index 8949701a..1d3ba1b2 100644 --- a/ui/ttdanalysisdialog.cpp +++ b/ui/ttdanalysisdialog.cpp @@ -399,7 +399,7 @@ void TTDAnalysisDialog::onRunAnalysis() if (m_useRangeCheckBox->isChecked()) { // Validate range inputs - bool startOk, endOk, startTimeOk, endTimeOk; + bool startOk, endOk; QString startText = m_startAddressEdit->text().trimmed(); QString endText = m_endAddressEdit->text().trimmed(); endText=endText.replace("`", "").replace("0x", ""); // remove extra address formatting From f3a667857ea04a539f18b09ca5cee49a34c5e471 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Mon, 26 Jan 2026 14:56:03 +0800 Subject: [PATCH 14/14] Various fixes --- api/debuggerapi.h | 2 +- api/python/debuggercontroller.py | 209 +++++++++++++++++++++++++++++++ core/debuggercommon.h | 2 +- ui/debuggeruicommon.cpp | 71 +++++++++++ ui/debuggeruicommon.h | 13 ++ ui/ttdanalysisdialog.cpp | 36 ++++-- ui/ttdmemorywidget.cpp | 21 +--- 7 files changed, 322 insertions(+), 32 deletions(-) create mode 100644 ui/debuggeruicommon.cpp diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 053275b3..787a0fc7 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -531,7 +531,7 @@ namespace BinaryNinjaDebuggerAPI { uint64_t value; // Value that was read/written/executed uint8_t data[8]; // The next 8 bytes of data at the memory address - TTDPositionRangeIndexedMemoryEvent() : threadId(0), uniqueThreadId(0), address(0), size(0), accessType(TTDMemoryRead), value(0) + TTDPositionRangeIndexedMemoryEvent() : threadId(0), uniqueThreadId(0), address(0), size(0), instructionAddress(0), accessType(TTDMemoryRead), value(0) { memset(data, 0, sizeof(data)); } diff --git a/api/python/debuggercontroller.py b/api/python/debuggercontroller.py index 194a97c6..73d9aa1c 100644 --- a/api/python/debuggercontroller.py +++ b/api/python/debuggercontroller.py @@ -804,6 +804,68 @@ def __repr__(self): return f"" +class TTDPositionRangeIndexedMemoryEvent: + """ + TTDPositionRangeIndexedMemoryEvent represents a memory access event in a TTD trace with position information. + This is used for memory queries filtered by both address range and time range. + + * ``position``: TTD position when the memory access occurred + * ``thread_id``: OS thread ID that performed the memory access + * ``unique_thread_id``: unique thread ID across the trace + * ``address``: memory address that was accessed + * ``instruction_address``: address of the instruction that performed the access + * ``size``: size of the memory access in bytes + * ``access_type``: type of access (read/write/execute) + * ``value``: value that was read/written/executed (truncated to size) + * ``data``: the next 8 bytes of data at the memory address (as a bytes object) + """ + + def __init__(self, position: TTDPosition, thread_id: int, unique_thread_id: int, + address: int, instruction_address: int, size: int, + access_type: int, value: int, data: bytes): + self.position = position + self.thread_id = thread_id + self.unique_thread_id = unique_thread_id + self.address = address + self.instruction_address = instruction_address + self.size = size + self.access_type = access_type + self.value = value + self.data = data + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return (self.position == other.position and + self.thread_id == other.thread_id and + self.unique_thread_id == other.unique_thread_id and + self.address == other.address and + self.instruction_address == other.instruction_address and + self.size == other.size and + self.access_type == other.access_type and + self.value == other.value and + self.data == other.data) + + def __ne__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return not (self == other) + + def __hash__(self): + return hash((self.position, self.thread_id, self.unique_thread_id, + self.address, self.instruction_address, self.size, + self.access_type, self.value, self.data)) + + def __setattr__(self, name, value): + try: + object.__setattr__(self, name, value) + except AttributeError: + raise AttributeError(f"attribute '{name}' is read only") + + def __repr__(self): + return f"" + + class TTDCallEvent: """ TTDCallEvent represents a function call event in a TTD trace. It has the following fields: @@ -2499,6 +2561,75 @@ def get_ttd_memory_access_for_address(self, address: int, end_address: int, acce dbgcore.BNDebuggerFreeTTDMemoryEvents(events, count.value) return result + def get_ttd_memory_access_for_position_range(self, start_address: int, end_address: int, access_type, + start_time: TTDPosition = None, end_time: TTDPosition = None) -> List[TTDPositionRangeIndexedMemoryEvent]: + """ + Get TTD memory access events for a specific address range and time range. + + This method is only available when debugging with TTD (Time Travel Debugging). + Use the is_ttd property to check if TTD is available before calling this method. + + :param start_address: starting memory address to query + :param end_address: ending memory address to query + :param access_type: type of memory access to query - can be: + - DebuggerTTDMemoryAccessType enum values + - String specification like "r", "w", "e", "rw", "rwe", etc. + - Integer values (for backward compatibility) + :param start_time: starting TTD position (default: start of trace) + :param end_time: ending TTD position (default: end of trace) + :return: list of TTDPositionRangeIndexedMemoryEvent objects + :raises: May raise an exception if TTD is not available + """ + # Parse access type if it's a string + parsed_access_type = parse_ttd_access_type(access_type) + + # Set default time range if not provided + if start_time is None: + start_time = TTDPosition(0, 0) + if end_time is None: + end_time = TTDPosition(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF) + + # Create ctypes structures for positions + bn_start_pos = dbgcore.BNDebuggerTTDPosition() + bn_start_pos.sequence = start_time.sequence + bn_start_pos.step = start_time.step + + bn_end_pos = dbgcore.BNDebuggerTTDPosition() + bn_end_pos.sequence = end_time.sequence + bn_end_pos.step = end_time.step + + count = ctypes.c_ulonglong() + events = dbgcore.BNDebuggerGetTTDMemoryAccessForPositionRange( + self.handle, start_address, end_address, parsed_access_type, + bn_start_pos, bn_end_pos, count) + + if not events: + return [] + + result = [] + for i in range(count.value): + event = events[i] + position = TTDPosition(event.position.sequence, event.position.step) + + # Convert data array to bytes + data = bytes(event.data[j] for j in range(8)) + + memory_event = TTDPositionRangeIndexedMemoryEvent( + position=position, + thread_id=event.threadId, + unique_thread_id=event.uniqueThreadId, + address=event.address, + instruction_address=event.instructionAddress, + size=event.size, + access_type=event.accessType, + value=event.value, + data=data + ) + result.append(memory_event) + + dbgcore.BNDebuggerFreeTTDPositionRangeIndexedMemoryEvents(events, count.value) + return result + def get_ttd_calls_for_symbols(self, symbols: str, start_return_address: int = 0, end_return_address: int = 0) -> List[TTDCallEvent]: """ Get TTD call events for specific symbols/functions. @@ -2707,6 +2838,84 @@ def get_all_ttd_events(self) -> List[TTDEvent]: dbgcore.BNDebuggerFreeTTDEvents(events, count.value) return result + def run_code_coverage_analysis(self, start_address: int, end_address: int, + start_time: TTDPosition = None, end_time: TTDPosition = None) -> bool: + """ + Run code coverage analysis on a specific address range and time range. + + This method is only available when debugging with TTD (Time Travel Debugging). + Use the is_ttd property to check if TTD is available before calling this method. + + The code coverage analysis identifies which instructions within the specified address range + were executed during the specified time range. Results can be queried using the + is_instruction_executed() method. + + :param start_address: starting address of the range to analyze + :param end_address: ending address of the range to analyze + :param start_time: starting TTD position (default: start of trace) + :param end_time: ending TTD position (default: end of trace) + :return: True if analysis succeeded, False otherwise + :raises: May raise an exception if TTD is not available + """ + # Set default time range if not provided + if start_time is None: + start_time = TTDPosition(0, 0) + if end_time is None: + end_time = TTDPosition(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF) + + # Create ctypes structures for positions + bn_start_pos = dbgcore.BNDebuggerTTDPosition() + bn_start_pos.sequence = start_time.sequence + bn_start_pos.step = start_time.step + + bn_end_pos = dbgcore.BNDebuggerTTDPosition() + bn_end_pos.sequence = end_time.sequence + bn_end_pos.step = end_time.step + + return dbgcore.BNDebuggerRunCodeCoverageAnalysisRange( + self.handle, start_address, end_address, bn_start_pos, bn_end_pos) + + def is_instruction_executed(self, address: int) -> bool: + """ + Check if an instruction at a specific address was executed during code coverage analysis. + + This method requires that run_code_coverage_analysis() has been called first. + + :param address: address of the instruction to check + :return: True if the instruction was executed, False otherwise + """ + return dbgcore.BNDebuggerIsInstructionExecuted(self.handle, address) + + def get_executed_instruction_count(self) -> int: + """ + Get the count of executed instructions from the last code coverage analysis. + + This method requires that run_code_coverage_analysis() has been called first. + + :return: number of unique executed instructions + """ + return dbgcore.BNDebuggerGetExecutedInstructionCount(self.handle) + + def save_code_coverage_to_file(self, file_path: str) -> bool: + """ + Save code coverage results to a file. + + This method requires that run_code_coverage_analysis() has been called first. + + :param file_path: path to the file where results should be saved + :return: True if save succeeded, False otherwise + """ + return dbgcore.BNDebuggerSaveCodeCoverageToFile(self.handle, file_path.encode('utf-8')) + + def load_code_coverage_from_file(self, file_path: str) -> bool: + """ + Load code coverage results from a file. + + :param file_path: path to the file containing code coverage results + :return: True if load succeeded, False otherwise + """ + return dbgcore.BNDebuggerLoadCodeCoverageFromFile(self.handle, file_path.encode('utf-8')) + def __del__(self): if dbgcore is not None: dbgcore.BNDebuggerFreeController(self.handle) diff --git a/core/debuggercommon.h b/core/debuggercommon.h index 276c7945..82c21aef 100644 --- a/core/debuggercommon.h +++ b/core/debuggercommon.h @@ -148,7 +148,7 @@ namespace BinaryNinjaDebugger { uint64_t value; // Value that was read/written/executed uint8_t data[8]; // The next 8 bytes of data at the memory address - TTDPositionRangeIndexedMemoryEvent() : threadId(0), uniqueThreadId(0), address(0), size(0), accessType(TTDMemoryRead), value(0) + TTDPositionRangeIndexedMemoryEvent() : threadId(0), uniqueThreadId(0), address(0), size(0), instructionAddress(0), accessType(TTDMemoryRead), value(0) { memset(data, 0, sizeof(data)); } diff --git a/ui/debuggeruicommon.cpp b/ui/debuggeruicommon.cpp new file mode 100644 index 00000000..39408c59 --- /dev/null +++ b/ui/debuggeruicommon.cpp @@ -0,0 +1,71 @@ +/* +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 "debuggeruicommon.h" + +using namespace BinaryNinja; + +bool ParseAddress(const QString& text, BinaryNinja::Ref data, uint64_t& result, std::string* errorMessage) +{ + QString cleanText = text.trimmed(); + if (cleanText.isEmpty()) + { + if (errorMessage) + *errorMessage = "Address string is empty"; + return false; + } + + // First try WinDbg format - remove backticks (e.g., 000000dd`7e7fed80) + QString cleanedText = cleanText; + cleanedText.replace("`", ""); + + bool ok = false; + uint64_t address = 0; + + // Try parsing as hexadecimal first + if (cleanedText.startsWith("0x", Qt::CaseInsensitive)) + { + // Has 0x prefix, parse as hex + address = cleanedText.mid(2).toULongLong(&ok, 16); + } + else + { + // Try parsing as hex without prefix + address = cleanedText.toULongLong(&ok, 16); + } + + // If simple parsing succeeded, return the result + if (ok) + { + result = address; + return true; + } + + // If simple parsing failed, use Binary Ninja's expression parser + // This supports symbols, expressions like "main+0x10", etc. + std::string parseError; + if (BinaryView::ParseExpression(data, text.toStdString(), address, 0, parseError)) + { + result = address; + return true; + } + + // All parsing methods failed + if (errorMessage) + *errorMessage = parseError.empty() ? "Failed to parse address" : parseError; + + return false; +} diff --git a/ui/debuggeruicommon.h b/ui/debuggeruicommon.h index 20c2c132..1835c583 100644 --- a/ui/debuggeruicommon.h +++ b/ui/debuggeruicommon.h @@ -18,6 +18,9 @@ limitations under the License. #include #include +#include +#include +#include "binaryninjaapi.h" // Custom table widget item that supports numerical sorting class NumericalTableWidgetItem : public QTableWidgetItem @@ -38,3 +41,13 @@ class NumericalTableWidgetItem : public QTableWidgetItem private: [[maybe_unused]] uint64_t m_numericValue; }; + +// Parse an address from a QString, supporting multiple formats: +// - WinDbg format with backticks (e.g., "000000dd`7e7fed80") +// - Hexadecimal with 0x prefix (e.g., "0x1000") +// - Plain hexadecimal (e.g., "1000") +// - Binary Ninja expressions (e.g., "main+0x10", "ImageBase+0x1000") +// +// Returns true if parsing succeeded and sets 'result' to the parsed address. +// Returns false if parsing failed. +bool ParseAddress(const QString& text, BinaryNinja::Ref data, uint64_t& result, std::string* errorMessage = nullptr); diff --git a/ui/ttdanalysisdialog.cpp b/ui/ttdanalysisdialog.cpp index 1d3ba1b2..0fc70e69 100644 --- a/ui/ttdanalysisdialog.cpp +++ b/ui/ttdanalysisdialog.cpp @@ -15,6 +15,7 @@ limitations under the License. */ #include "ttdanalysisdialog.h" +#include "debuggeruicommon.h" #include #include #include @@ -399,11 +400,8 @@ void TTDAnalysisDialog::onRunAnalysis() if (m_useRangeCheckBox->isChecked()) { // Validate range inputs - bool startOk, endOk; QString startText = m_startAddressEdit->text().trimmed(); QString endText = m_endAddressEdit->text().trimmed(); - endText=endText.replace("`", "").replace("0x", ""); // remove extra address formatting - startText=startText.replace("`", "").replace("0x", ""); // windbg format looks like: 000000dd`7e7fed80 QString startTimeText = m_startTimeEdit->text().trimmed(); QString endTimeText = m_endTimeEdit->text().trimmed(); TTDPosition startTime, endTime; @@ -414,10 +412,31 @@ void TTDAnalysisDialog::onRunAnalysis() return; } + // Parse addresses using the common address parser + uint64_t startAddress = 0; + std::string startError; + if (!ParseAddress(startText, m_data, startAddress, &startError)) + { + QMessageBox::warning(this, "Invalid Address", + QString("Failed to parse start address '%1': %2").arg(startText).arg(QString::fromStdString(startError))); + return; + } + + uint64_t endAddress = 0; + std::string endError; + if (!ParseAddress(endText, m_data, endAddress, &endError)) + { + QMessageBox::warning(this, "Invalid Address", + QString("Failed to parse end address '%1': %2").arg(endText).arg(QString::fromStdString(endError))); + return; + } + if (startTimeText.isEmpty()) { startTime = TTDPosition(0, 0); - }else{ + } + else + { QStringList startTimeParts = startTimeText.split(u':'); if (startTimeParts.size() != 2) { @@ -458,15 +477,6 @@ void TTDAnalysisDialog::onRunAnalysis() endTime = TTDPosition(sequence, step); } - uint64_t startAddress = startText.toULongLong(&startOk, 16); - uint64_t endAddress = endText.toULongLong(&endOk, 16); - - if (!startOk || !endOk) - { - QMessageBox::warning(this, "Invalid Range", "Invalid address format. Use decimal or hexadecimal (0x...) notation"); - return; - } - if (startAddress >= endAddress) { QMessageBox::warning(this, "Invalid Range", "Start address must be less than end address"); diff --git a/ui/ttdmemorywidget.cpp b/ui/ttdmemorywidget.cpp index 13573b42..e33d27bc 100644 --- a/ui/ttdmemorywidget.cpp +++ b/ui/ttdmemorywidget.cpp @@ -15,6 +15,7 @@ limitations under the License. */ #include "ttdmemorywidget.h" +#include "debuggeruicommon.h" #include "ui.h" #include #include @@ -671,23 +672,9 @@ void TTDMemoryQueryWidget::updateStatus(const QString& message) uint64_t TTDMemoryQueryWidget::parseAddress(const QString& text) { - QString cleanText = text.trimmed(); - if (cleanText.isEmpty()) - return 0; - - // Remove 0x prefix if present - if (cleanText.startsWith("0x", Qt::CaseInsensitive)){ - cleanText = cleanText.mid(2); - } - // Remove ' character if present (default address display format in windbg console) - else if (cleanText.contains("`")){ - cleanText = cleanText.replace("`", ""); - } - - - bool ok; - uint64_t address = cleanText.toULongLong(&ok, 16); - return ok ? address : 0; + uint64_t address = 0; + ParseAddress(text, m_data, address); + return address; } TTDPosition TTDMemoryQueryWidget::parseTimePosition(const QString& text)