From 6e20911e9cd00fd408e534ab9b96133ef4d98b1c Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 24 Jul 2026 14:15:15 -0400 Subject: [PATCH] Reject a TTD launch with no trace specified (Fixes #910) The TTD adapter replays a recorded trace, but nothing checked that one was actually configured, so launching without a trace path failed deep inside the engine with an opaque "OpenDumpFile failed: 0x..." HRESULT. Validate the trace path in AdapterSettingsDialog::apply() and keep the dialog open with an explanation instead of accepting it. The settings view writes each value as it is edited, so the current value can simply be read back; no changes to SettingsView are needed. The check is limited to the launch group with the DBGENG_TTD adapter selected so the other flows are unaffected. Also check the trace path in DbgEngTTDAdapter::ExecuteWithArgsInternal before starting the engine, which covers the headless and scripted paths as well as the launches where the settings dialog is skipped (#1133). Co-Authored-By: Claude Opus 5 (1M context) --- core/adapters/dbgengttdadapter.cpp | 24 +++++++++++++++++ ui/adaptersettings.cpp | 42 +++++++++++++++++++++++++++++- ui/adaptersettings.h | 2 ++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/core/adapters/dbgengttdadapter.cpp b/core/adapters/dbgengttdadapter.cpp index f68e1690..ef87bcdf 100644 --- a/core/adapters/dbgengttdadapter.cpp +++ b/core/adapters/dbgengttdadapter.cpp @@ -27,6 +27,30 @@ bool DbgEngTTDAdapter::ExecuteWithArgsInternal(const std::string& path, const st scope = SettingsResourceScope; auto inputFile = adapterSettings->Get("common.inputFile", data, &scope); + // A trace is not optional for replay, but a missing one only surfaces later as an opaque OpenDumpFile + // HRESULT, so check it up front. The UI blocks this in the adapter settings dialog; this covers the + // headless and scripted paths, plus the launches where that dialog is skipped. + std::error_code ec; + if (tracePath.empty() || !std::filesystem::exists(tracePath, ec)) + { + DebuggerEvent event; + event.type = LaunchFailureEventType; + if (tracePath.empty()) + { + event.data.errorData.error = "No TTD trace specified. Set the trace path in the debug adapter " + "settings, or record a new trace first."; + event.data.errorData.shortError = "No TTD trace specified"; + } + else + { + event.data.errorData.error = fmt::format("The TTD trace \"{}\" does not exist. Set the trace path in " + "the debug adapter settings.", tracePath); + event.data.errorData.shortError = "TTD trace not found"; + } + PostDebuggerEvent(event); + return false; + } + if (this->m_dbgengInitialized) { this->Reset(); } diff --git a/ui/adaptersettings.cpp b/ui/adaptersettings.cpp index fc79d6a6..38b1dab0 100644 --- a/ui/adaptersettings.cpp +++ b/ui/adaptersettings.cpp @@ -18,13 +18,15 @@ limitations under the License. #include "uicontext.h" #include "qfiledialog.h" #include "settingsview.h" +#include +#include using namespace BinaryNinjaDebuggerAPI; using namespace BinaryNinja; using namespace std; AdapterSettingsDialog::AdapterSettingsDialog(QWidget* parent, DbgRef controller, const std::string& highlightGroup) : - QDialog(), m_controller(controller) + QDialog(), m_controller(controller), m_highlightGroup(highlightGroup) { setWindowTitle("Debug Adapter Settings"); setAttribute(Qt::WA_DeleteOnClose); @@ -158,8 +160,46 @@ QWidget* AdapterSettingsDialog::getWidgetForAdapter(const QString& adapter) } +// The settings view writes each value as it is edited, so the current values can be read straight back from the +// adapter settings here. Only the settings whose absence is guaranteed to fail the operation are checked, so that +// the dialog does not get in the way of anything the adapter itself is willing to accept. +bool AdapterSettingsDialog::validateSettings() +{ + if ((m_highlightGroup != "launch") || (m_adapterEntry->currentText() != "DBGENG_TTD")) + return true; + + auto adapterSettings = m_controller->GetAdapterSettings(); + if (!adapterSettings) + return true; + + BNSettingsScope scope = SettingsResourceScope; + auto tracePath = adapterSettings->Get("launch.trace_path", m_controller->GetData(), &scope); + if (tracePath.empty()) + { + QMessageBox::warning(this, "No Trace Specified", + "The TTD adapter replays a previously recorded trace, so a trace path is required.\n\n" + "Set \"Trace Path\" in the launch settings, or record a new trace first."); + return false; + } + + std::error_code ec; + if (!std::filesystem::exists(tracePath, ec)) + { + QMessageBox::warning(this, "Trace Not Found", + QString("The trace file\n\n%1\n\ndoes not exist. Set \"Trace Path\" in the launch settings to an " + "existing trace.").arg(QString::fromStdString(tracePath))); + return false; + } + + return true; +} + + void AdapterSettingsDialog::apply() { + if (!validateSettings()) + return; + if (m_useSameSettingsCheckbox) m_controller->SetShowAdapterSettingsNextTime(!m_useSameSettingsCheckbox->isChecked()); accept(); diff --git a/ui/adaptersettings.h b/ui/adaptersettings.h index 0aa74d9f..ff5085c6 100644 --- a/ui/adaptersettings.h +++ b/ui/adaptersettings.h @@ -43,8 +43,10 @@ class AdapterSettingsDialog : public QDialog QMap m_viewMap; QLabel* m_noSettingsLabel; QCheckBox* m_useSameSettingsCheckbox = nullptr; + std::string m_highlightGroup; QWidget* getWidgetForAdapter(const QString& adapter); + bool validateSettings(); public: AdapterSettingsDialog(QWidget* parent, DbgRef controller, const std::string& highlightGroup = "");