From 4feea2380e7d05d8b373da603b557bc59fe830a9 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 24 Jul 2026 13:31:26 -0400 Subject: [PATCH] Warn when TTD is used without the WinDbg/TTD package (Fixes #1103) The debugger only loads the WinDbg/TTD copy that Binary Ninja downloads itself, but a self-installed WinDbg looks like it should work, so the mistake surfaced as an opaque "Failed to initialize DbgEng". Check for the replay engine and the recorder before launching a DBGENG_TTD session and before the TTD record/attach dialogs, and explain what is missing with a button to install it. The core-side errors carry the same explanation. Co-Authored-By: Claude Opus 4.8 (1M context) --- core/adapters/dbgeng/README.md | 2 +- core/adapters/dbgengadapter.cpp | 16 +- core/adapters/dbgengadapter.h | 4 + core/adapters/dbgengttdadapter.cpp | 10 +- docs/guide/dbgeng-ttd.md | 7 + ui/CMakeLists.txt | 2 + ui/controlswidget.cpp | 11 ++ ui/ttdinstall.cpp | 295 +++++++++++++++++++++++++++++ ui/ttdinstall.h | 68 +++++++ ui/ttdrecord.cpp | 67 ++----- ui/ttdrecord.h | 1 - ui/ui.cpp | 129 ++----------- ui/ui.h | 2 - 13 files changed, 439 insertions(+), 175 deletions(-) create mode 100644 ui/ttdinstall.cpp create mode 100644 ui/ttdinstall.h diff --git a/core/adapters/dbgeng/README.md b/core/adapters/dbgeng/README.md index 26834a4b..022b689f 100644 --- a/core/adapters/dbgeng/README.md +++ b/core/adapters/dbgeng/README.md @@ -40,7 +40,7 @@ The installer performs these steps: ## UI Integration -The installer is called from `ui/ui.cpp` in the `GlobalDebuggerUI::installTTD()` function, which: +The installer is called from `ui/ttdinstall.cpp` in the `TTDInstall::RunInstaller()` function, which: - Shows a progress dialog - Runs the installer asynchronously using QTimer - Displays success/failure messages diff --git a/core/adapters/dbgengadapter.cpp b/core/adapters/dbgengadapter.cpp index d5fd7dda..2faf3681 100644 --- a/core/adapters/dbgengadapter.cpp +++ b/core/adapters/dbgengadapter.cpp @@ -103,6 +103,19 @@ std::string DbgEngAdapter::GetDbgEngPath(const std::string& arch) } +std::string DbgEngAdapter::GetDbgEngInstallHint() +{ + if (!Settings::Instance()->Get("debugger.x64dbgEngPath").empty()) + return "debugger.x64dbgEngPath is set, but it does not point at a folder that holds the DbgEng DLLs. It " + "must be the amd64 folder of a WinDbg installation."; + + return "The WinDbg/TTD package has not been downloaded. Use \"Debugger\" -> \"Install WinDbg/TTD\" to let " + "Binary Ninja download it, then restart Binary Ninja. A WinDbg installed from the Microsoft Store or " + "through the standalone installer cannot be used, since it is packaged in a form the debugger cannot " + "load from."; +} + + static bool LoadOneDLL(const string& path, const string& name, bool strictCheckPath = true, bool forceUnload = true) { auto handle = GetModuleHandleA(name.c_str()); @@ -171,8 +184,7 @@ bool DbgEngAdapter::LoadDngEngLibraries() auto enginePath = GetDbgEngPath("amd64"); if (enginePath.empty()) { - LogWarn("The debugger cannot find the path for the DbgEng DLLs. " - "If you have set debugger.x64dbgEngPath, check if it valid"); + LogWarn("The debugger cannot find the path for the DbgEng DLLs. %s", GetDbgEngInstallHint().c_str()); return false; } LogDebug("DbgEng libraries in path %s", enginePath.c_str()); diff --git a/core/adapters/dbgengadapter.h b/core/adapters/dbgengadapter.h index 7f007033..743c06ea 100644 --- a/core/adapters/dbgengadapter.h +++ b/core/adapters/dbgengadapter.h @@ -255,6 +255,10 @@ namespace BinaryNinjaDebugger { static std::string GetDbgEngPath(const std::string& arch); + // Explains, for the user, where the DbgEng DLLs are supposed to come from. Meant to be appended to an + // error reported when they could not be found or loaded. + static std::string GetDbgEngInstallHint(); + static bool LoadDngEngLibraries(); std::string GenerateRandomPipeName(); diff --git a/core/adapters/dbgengttdadapter.cpp b/core/adapters/dbgengttdadapter.cpp index f68e1690..388e8692 100644 --- a/core/adapters/dbgengttdadapter.cpp +++ b/core/adapters/dbgengttdadapter.cpp @@ -33,10 +33,16 @@ bool DbgEngTTDAdapter::ExecuteWithArgsInternal(const std::string& path, const st if (!Start()) { this->Reset(); + std::string error = "Failed to initialize DbgEng"; + // By far the most common reason for this is that the WinDbg/TTD package was never downloaded, so say + // what to do about it rather than leaving the user with an opaque failure + if (GetModuleHandleA("dbgeng.dll") == nullptr) + error += ": the DbgEng DLLs are not loaded. " + DbgEngAdapter::GetDbgEngInstallHint(); + DebuggerEvent event; event.type = LaunchFailureEventType; - event.data.errorData.error = fmt::format("Failed to initialize DbgEng"); - event.data.errorData.shortError = fmt::format("Failed to initialize DbgEng"); + event.data.errorData.error = error; + event.data.errorData.shortError = "Failed to initialize DbgEng"; PostDebuggerEvent(event); return false; } diff --git a/docs/guide/dbgeng-ttd.md b/docs/guide/dbgeng-ttd.md index ec832a1b..73131f4c 100644 --- a/docs/guide/dbgeng-ttd.md +++ b/docs/guide/dbgeng-ttd.md @@ -16,6 +16,13 @@ If it does not work, for example if your machine cannot connect to the Internet, The WinDbg installation only needs to be done once. +> **⚠️ A WinDbg you installed yourself is not used** +> +> Binary Ninja cannot use a WinDbg installed from the Microsoft Store or through the standalone installer, because +> those are packaged in a form the debugger cannot load from. You must either let Binary Ninja download its own +> copy (the first method below), or extract the package yourself and point `debugger.x64dbgEngPath` at it (the +> second method). Without one of those, TTD will not work. + ### Install WinDbg Automatically - Open Binary Ninja diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 154404d4..03902e9b 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -12,6 +12,8 @@ list(FILTER SOURCES EXCLUDE REGEX qrc_.*) if (NOT WIN32) list(REMOVE_ITEM SOURCES ${PROJECT_SOURCE_DIR}/ttdrecord.h) list(REMOVE_ITEM SOURCES ${PROJECT_SOURCE_DIR}/ttdrecord.cpp) + list(REMOVE_ITEM SOURCES ${PROJECT_SOURCE_DIR}/ttdinstall.h) + list(REMOVE_ITEM SOURCES ${PROJECT_SOURCE_DIR}/ttdinstall.cpp) list(REMOVE_ITEM SOURCES ${PROJECT_SOURCE_DIR}/windbgupdatedialog.h) list(REMOVE_ITEM SOURCES ${PROJECT_SOURCE_DIR}/windbgupdatedialog.cpp) endif () diff --git a/ui/controlswidget.cpp b/ui/controlswidget.cpp index 42e62188..5b6f62a0 100644 --- a/ui/controlswidget.cpp +++ b/ui/controlswidget.cpp @@ -31,6 +31,9 @@ limitations under the License. #include #include #include +#ifdef WIN32 + #include "ttdinstall.h" +#endif using namespace BinaryNinjaDebuggerAPI; using namespace BinaryNinja; @@ -274,6 +277,14 @@ void DebugControlsWidget::performLaunch() return; } +#ifdef WIN32 + // Replaying a trace needs the DbgEng DLLs that come with the WinDbg/TTD package. Say so now, instead of + // failing with an opaque "Failed to initialize DbgEng" once the launch is under way. + if ((m_controller->GetAdapterType() == "DBGENG_TTD") + && !TTDInstall::EnsureComponentAvailable(this, TTDInstall::ReplayEngine)) + return; +#endif + // TODO: we should have the adapter returns this property bool isLocalLaunch = true; auto adapter = m_controller->GetAdapterType(); diff --git a/ui/ttdinstall.cpp b/ui/ttdinstall.cpp new file mode 100644 index 00000000..0a8d464d --- /dev/null +++ b/ui/ttdinstall.cpp @@ -0,0 +1,295 @@ +/* +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. +*/ + +#ifdef WIN32 + + #include "ttdinstall.h" + #include "windbgupdatedialog.h" + #include "binaryninjaapi.h" + #include "debuggerapi.h" + #include + #include + #include + #include + #include + #include + #include + #include + #include + +using namespace BinaryNinja; +using namespace BinaryNinjaDebuggerAPI; +using namespace std; + +namespace TTDInstall { + +// The files each component is made of. The replay engine list matches the one DbgEngAdapter::GetDbgEngPath() +// validates, so that this check predicts whether the adapter will actually come up. +static vector ComponentFiles(Component component) +{ + if (component == Recorder) + return {"TTD.exe", "TTDRecord.dll"}; + + return {"dbgeng.dll", "dbghelp.dll", "dbgmodel.dll", "dbgcore.dll", "dbgsrv.exe"}; +} + + +static bool ContainsAllFiles(const filesystem::path& folder, const vector& files) +{ + if (folder.empty() || !filesystem::exists(folder)) + return false; + + for (const auto& file : files) + { + if (!filesystem::exists(folder / file)) + return false; + } + + return true; +} + + +std::string GetComponentPath(Component component) +{ + auto files = ComponentFiles(component); + // The recorder lives in a TTD subfolder of the DbgEng installation + auto resolve = [component](const filesystem::path& dbgEngRoot) { + return (component == Recorder) ? dbgEngRoot / "TTD" : dbgEngRoot; + }; + + std::string path = Settings::Instance()->Get("debugger.x64dbgEngPath"); + if (!path.empty()) + { + // If the user has specified the path in the setting, then check it for validity. If it is valid, then use + // it; if it is invalid, fail the operation -- do not fallback to the default one + auto userPath = resolve(path); + if (ContainsAllFiles(userPath, files)) + return userPath.string(); + return ""; + } + + std::string pluginRoot; + if (getenv("BN_STANDALONE_DEBUGGER") != nullptr) + pluginRoot = GetUserPluginDirectory(); + else + pluginRoot = GetBundledPluginDirectory(); + + auto bundledPath = resolve(filesystem::path(pluginRoot) / "dbgeng" / "amd64"); + if (ContainsAllFiles(bundledPath, files)) + return bundledPath.string(); + + return ""; +} + + +ComponentStatus GetComponentStatus(Component component) +{ + auto path = GetComponentPath(component); + if (path.empty()) + { + auto userPath = Settings::Instance()->Get("debugger.x64dbgEngPath"); + return userPath.empty() ? ComponentMissing : ComponentUserPathInvalid; + } + + // The DbgEng DLLs are loaded once, when the debugger plugin initializes. Installing them into a running + // Binary Ninja does not help until it is restarted. The recorder is a separate process launched on demand, + // so it does not have this problem. + if ((component == ReplayEngine) && (GetModuleHandleA("dbgeng.dll") == nullptr)) + return ComponentNeedsRestart; + + return ComponentReady; +} + + +QString DescribeUnavailableComponent(Component component) +{ + auto status = GetComponentStatus(component); + auto operation = (component == Recorder) ? QString("Recording a TTD trace") : QString("Time travel debugging"); + + switch (status) + { + case ComponentNeedsRestart: + return operation + + " requires WinDbg/TTD, which is installed but was not loaded when Binary Ninja started.\n\n" + "Restart Binary Ninja to use it.\n\nInstalled at: " + + QString::fromStdString(GetComponentPath(component)); + + case ComponentUserPathInvalid: + return operation + + " requires WinDbg/TTD, and the folder configured in debugger.x64dbgEngPath does not contain it.\n\n" + "The setting currently points at:\n\n" + + QString::fromStdString(Settings::Instance()->Get("debugger.x64dbgEngPath")) + + "\n\nIt must be the amd64 folder of a WinDbg installation, holding dbgeng.dll along with a TTD " + "subfolder. Note that the debugger cannot use a WinDbg installed from the Microsoft Store or " + "through the standalone installer, since those are packaged in a form Binary Ninja cannot load " + "from -- Binary Ninja has to download its own copy."; + + case ComponentMissing: + return operation + + " requires the WinDbg/TTD package that Binary Ninja downloads for itself.\n\n" + "Installing WinDbg from the Microsoft Store or through the standalone installer does not work, " + "since those are packaged in a form Binary Ninja cannot load from. Binary Ninja has to download " + "its own copy, which it installs into %APPDATA%\\Binary Ninja\\windbg."; + + default: + return QString(); + } +} + + +bool EnsureComponentAvailable(QWidget* parent, Component component) +{ + auto status = GetComponentStatus(component); + if (status == ComponentReady) + return true; + + auto description = DescribeUnavailableComponent(component); + + // A restart is the only thing that helps here, there is nothing to install + if (status == ComponentNeedsRestart) + { + QMessageBox::warning(parent, "Restart Required", description); + return false; + } + + QMessageBox box(parent); + box.setIcon(QMessageBox::Warning); + box.setWindowTitle("WinDbg/TTD Not Installed"); + box.setText(description); + box.setInformativeText("Download and install it now? Binary Ninja needs to be restarted once it finishes."); + + auto* installButton = box.addButton("Install WinDbg/TTD", QMessageBox::AcceptRole); + box.addButton(QMessageBox::Cancel); + box.setDefaultButton(installButton); + box.exec(); + + if (box.clickedButton() == installButton) + RunInstaller(parent); + + // Even when the install succeeds, Binary Ninja must be restarted before the operation can go ahead + return false; +} + + +void RunInstaller(QWidget* parent) +{ + QWidget* mainWindow = parent ? parent->window() : nullptr; + + // Determine install path + std::string userDir = GetUserDirectory(); + std::filesystem::path installTarget = std::filesystem::path(userDir) / "windbg"; + std::string installPath = installTarget.string(); + LogDebug("installTarget: %s", installPath.c_str()); + + // Check if WinDbg is already installed + if (std::filesystem::exists(installTarget) && IsWinDbgInstalled(installPath)) + { + // Get installed version + std::string installedVersion = GetWinDbgInstalledVersion(installPath); + if (installedVersion.empty()) { + installedVersion = "(unknown)"; + } + + // Show update dialog + WinDbgUpdateDialog dialog(mainWindow, installPath, installedVersion); + dialog.exec(); + return; + } + + // Not installed - proceed with fresh installation + // Show confirmation dialog first + QMessageBox::StandardButton reply = QMessageBox::question( + mainWindow, + "Install WinDbg/TTD", + "The WinDbg/TTD installer will be launched in a separate window.\n\n" + "You can continue using Binary Ninja while the installation proceeds.\n" + "You will be notified when the installation completes.\n\n" + "Do you want to continue?", + QMessageBox::Yes | QMessageBox::No, + QMessageBox::Yes + ); + + if (reply != QMessageBox::Yes) { + return; + } + + // Create and start background installation task + class InstallWorker : public QThread { + public: + InstallWorker(const std::string& path, QObject* parent = nullptr) + : QThread(parent), m_installPath(path) {} + + void run() override { + m_result = InstallWinDbg(m_installPath); + } + + const InstallResult& result() const { return m_result; } + const std::string& installPath() const { return m_installPath; } + + private: + std::string m_installPath; + InstallResult m_result; + }; + + InstallWorker* worker = new InstallWorker(installPath, mainWindow); + + // When installation completes, show result dialog and configure settings + QObject::connect(worker, &QThread::finished, mainWindow, [worker, installPath, mainWindow]() { + const InstallResult& result = worker->result(); + worker->deleteLater(); + + if (result.success && IsWinDbgInstalled(installPath)) { + // Configure debugger settings + std::string dbgEngPath = installPath + "\\amd64"; + BinaryNinja::Settings::Instance()->Set("debugger.x64dbgEngPath", dbgEngPath); + LogInfo("Configured debugger.x64dbgEngPath: %s", dbgEngPath.c_str()); + + // Offer to restart Binary Ninja + QMessageBox msgBox(mainWindow); + msgBox.setWindowTitle("Installation Successful"); + msgBox.setText("WinDbg/TTD has been installed successfully!"); + msgBox.setInformativeText("The debugger settings have been configured automatically.\n\n" + "Would you like to restart Binary Ninja now?"); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + msgBox.button(QMessageBox::Yes)->setText("Restart Now"); + msgBox.button(QMessageBox::No)->setText("Restart Later"); + + if (msgBox.exec() == QMessageBox::Yes) { + // Restart Binary Ninja by spawning a new instance before quitting + QStringList args = QCoreApplication::arguments(); + QString program = args.takeFirst(); + QProcess::startDetached(program, args); + QApplication::quit(); + } + } else { + // Show error message with specific failure reason + QString errorMsg = "WinDbg/TTD installation failed."; + if (!result.errorMessage.empty()) { + errorMsg += "\n\nError: " + QString::fromStdString(result.errorMessage); + } else { + errorMsg += "\n\nPlease check the installer console window for error details."; + } + QMessageBox::critical(mainWindow, "Installation Failed", errorMsg); + } + }); + + worker->start(); +} + +} // namespace TTDInstall + +#endif diff --git a/ui/ttdinstall.h b/ui/ttdinstall.h new file mode 100644 index 00000000..930688ab --- /dev/null +++ b/ui/ttdinstall.h @@ -0,0 +1,68 @@ +/* +Copyright 2020-2026 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once + +#ifdef WIN32 + + #include + #include + #include + +// Helpers for telling the user, before anything fails, that TTD needs the WinDbg/TTD package that Binary Ninja +// downloads for itself. A WinDbg installed from the Microsoft Store or the standalone installer is never used by +// the debugger, which is a very easy assumption to make. + +namespace TTDInstall { + // The pieces of the WinDbg/TTD package that a TTD operation can depend on + enum Component + { + // dbgeng.dll and friends, needed to replay a trace + ReplayEngine, + // TTD.exe and TTDRecord.dll, needed to record a trace + Recorder + }; + + enum ComponentStatus + { + // The component is present and usable right now + ComponentReady, + // Nothing usable was found, the package has most likely never been downloaded + ComponentMissing, + // debugger.x64dbgEngPath is set, but it does not point at a usable package + ComponentUserPathInvalid, + // The component is on disk, but it was not picked up when Binary Ninja started + ComponentNeedsRestart + }; + + // The folder the debugger will load the component from, or an empty string if there is no usable one. This + // mirrors the resolution done by DbgEngAdapter::GetDbgEngPath(). + std::string GetComponentPath(Component component); + + ComponentStatus GetComponentStatus(Component component); + + // Why the component cannot be used, phrased for the user, or an empty string when it is ready + QString DescribeUnavailableComponent(Component component); + + // Explains why TTD is not going to work and offers to download the package. Returns true when the component + // is ready and the caller should carry on with the operation. + bool EnsureComponentAvailable(QWidget* parent, Component component); + + // Downloads and installs the WinDbg/TTD package, or offers to update it if it is already installed + void RunInstaller(QWidget* parent); +} + +#endif diff --git a/ui/ttdrecord.cpp b/ui/ttdrecord.cpp index b35679d8..19394e0d 100644 --- a/ui/ttdrecord.cpp +++ b/ui/ttdrecord.cpp @@ -15,6 +15,7 @@ limitations under the License. */ #include "ttdrecord.h" +#include "ttdinstall.h" #include "uicontext.h" #include "qfiledialog.h" #include "fmt/format.h" @@ -273,63 +274,16 @@ void TTDRecordDialog::apply() } -static bool IsValidDbgEngTTDPaths(const std::string& path) -{ - if (path.empty()) - return false; - - auto enginePath = filesystem::path(path); - if (!filesystem::exists(enginePath)) - return false; - - if (!filesystem::exists(enginePath / "TTD.exe")) - return false; - - if (!filesystem::exists(enginePath / "TTDRecord.dll")) - return false; - - return true; -} - - -std::string TTDRecordDialog::GetTTDRecorderPath() -{ - std::string path = Settings::Instance()->Get("debugger.x64dbgEngPath"); - if (!path.empty()) - { - // If the user has specified the path in the setting, then check it for validity. If it is valid, then use it; - // if it is invalid, fail the operation -- do not fallback to the default one - auto userTTDPath = filesystem::path(path) / "TTD"; - if (IsValidDbgEngTTDPaths(userTTDPath.string())) - return userTTDPath.string(); - else - return ""; - } - - std::string pluginRoot; - if (getenv("BN_STANDALONE_DEBUGGER") != nullptr) - pluginRoot = GetUserPluginDirectory(); - else - pluginRoot = GetBundledPluginDirectory(); - - // If the user does not specify a path (the default case), find the one from the plugins/dbgeng/arch - auto TTDRecorderRoot = filesystem::path(pluginRoot) / "dbgeng" / "amd64" / "TTD"; - if (IsValidDbgEngTTDPaths(TTDRecorderRoot.string())) - return TTDRecorderRoot.string(); - - return ""; -} - - void TTDRecordDialog::DoTTDTrace() { - auto ttdPath = GetTTDRecorderPath(); - if (ttdPath.empty()) + auto problem = TTDInstall::DescribeUnavailableComponent(TTDInstall::Recorder); + if (!problem.isEmpty()) { - QMessageBox::critical(this, "Recording Failed", "The debugger cannot find the path for the TTD recorder. " - "If you have set debugger.x64dbgEngPath, check if it valid"); + QMessageBox::critical(this, "Recording Failed", problem); return; } + + auto ttdPath = TTDInstall::GetComponentPath(TTDInstall::Recorder); LogDebug("TTD Recorder in path %s", ttdPath.c_str()); auto ttdRecorder = fmt::format("\"{}\\TTD.exe\"", ttdPath); @@ -470,13 +424,14 @@ void TTDAttachDialog::apply() void TTDAttachDialog::DoTTDAttach(uint32_t pid) { - auto ttdPath = TTDRecordDialog::GetTTDRecorderPath(); - if (ttdPath.empty()) + auto problem = TTDInstall::DescribeUnavailableComponent(TTDInstall::Recorder); + if (!problem.isEmpty()) { - QMessageBox::critical(this, "Recording Failed", "The debugger cannot find the path for the TTD recorder. " - "If you have set debugger.x64dbgEngPath, check if it is valid"); + QMessageBox::critical(this, "Recording Failed", problem); return; } + + auto ttdPath = TTDInstall::GetComponentPath(TTDInstall::Recorder); LogDebug("TTD Recorder in path %s", ttdPath.c_str()); auto ttdRecorder = fmt::format("\"{}\\TTD.exe\"", ttdPath); diff --git a/ui/ttdrecord.h b/ui/ttdrecord.h index da98d8fc..e906527b 100644 --- a/ui/ttdrecord.h +++ b/ui/ttdrecord.h @@ -47,7 +47,6 @@ class TTDRecordDialog : public QDialog public: TTDRecordDialog(QWidget* parent, BinaryView* data); void DoTTDTrace(); - static std::string GetTTDRecorderPath(); private Q_SLOTS: void apply(); diff --git a/ui/ui.cpp b/ui/ui.cpp index f85cc332..d6a5157f 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -57,8 +57,8 @@ limitations under the License. #ifdef WIN32 #include "ttdrecord.h" + #include "ttdinstall.h" #include "scriptingconsole.h" - #include "windbgupdatedialog.h" #endif @@ -570,6 +570,14 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) return; } +#ifdef WIN32 + // Replaying a trace needs the DbgEng DLLs that come with the WinDbg/TTD package. Say so now, + // instead of failing with an opaque "Failed to initialize DbgEng" once the launch is under way. + if ((controller->GetAdapterType() == "DBGENG_TTD") + && !TTDInstall::EnsureComponentAvailable(context->mainWindow(), TTDInstall::ReplayEngine)) + return; +#endif + // TODO: we should have the adapter returns this property bool isLocalLaunch = true; auto adapter = controller->GetAdapterType(); @@ -1290,6 +1298,11 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) context->globalActions()->bindAction("Record TTD Trace", UIAction( [=](const UIActionContext& ctxt) { + // Tell the user up front if the TTD recorder was never downloaded, rather than after they have + // filled in the whole dialog + if (!TTDInstall::EnsureComponentAvailable(context->mainWindow(), TTDInstall::Recorder)) + return; + auto* dialog = new TTDRecordDialog(context->mainWindow(), ctxt.binaryView); dialog->show(); })); @@ -1299,6 +1312,9 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) context->globalActions()->bindAction("Attach and Record TTD Trace", UIAction( [=](const UIActionContext& ctxt) { + if (!TTDInstall::EnsureComponentAvailable(context->mainWindow(), TTDInstall::Recorder)) + return; + auto* dialog = new TTDAttachDialog(context->mainWindow(), ctxt.binaryView); dialog->show(); })); @@ -1307,7 +1323,7 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) UIAction::registerAction("Install WinDbg/TTD"); context->globalActions()->bindAction("Install WinDbg/TTD", UIAction( - [=](const UIActionContext& ctxt) { installTTD(ctxt); })); + [=](const UIActionContext& ctxt) { TTDInstall::RunInstaller(context->mainWindow()); })); debuggerMenu->addAction("Install WinDbg/TTD", "TTD"); #endif @@ -1600,115 +1616,6 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) } -#ifdef WIN32 -void GlobalDebuggerUI::installTTD(const UIActionContext& ctxt) -{ - // Determine install path - std::string userDir = BinaryNinja::GetUserDirectory(); - std::filesystem::path installTarget = std::filesystem::path(userDir) / "windbg"; - std::string installPath = installTarget.string(); - LogDebug("installTarget: %s", installPath.c_str()); - - // Check if WinDbg is already installed - if (std::filesystem::exists(installTarget) && IsWinDbgInstalled(installPath)) - { - // Get installed version - std::string installedVersion = GetWinDbgInstalledVersion(installPath); - if (installedVersion.empty()) { - installedVersion = "(unknown)"; - } - - // Show update dialog - WinDbgUpdateDialog dialog(ctxt.context->mainWindow(), installPath, installedVersion); - dialog.exec(); - return; - } - - // Not installed - proceed with fresh installation - QWidget* mainWindow = ctxt.context->mainWindow(); - - // Show confirmation dialog first - QMessageBox::StandardButton reply = QMessageBox::question( - mainWindow, - "Install WinDbg/TTD", - "The WinDbg/TTD installer will be launched in a separate window.\n\n" - "You can continue using Binary Ninja while the installation proceeds.\n" - "You will be notified when the installation completes.\n\n" - "Do you want to continue?", - QMessageBox::Yes | QMessageBox::No, - QMessageBox::Yes - ); - - if (reply != QMessageBox::Yes) { - return; - } - - // Create and start background installation task - class InstallWorker : public QThread { - public: - InstallWorker(const std::string& path, QObject* parent = nullptr) - : QThread(parent), m_installPath(path) {} - - void run() override { - m_result = InstallWinDbg(m_installPath); - } - - const InstallResult& result() const { return m_result; } - const std::string& installPath() const { return m_installPath; } - - private: - std::string m_installPath; - InstallResult m_result; - }; - - InstallWorker* worker = new InstallWorker(installPath, mainWindow); - - // When installation completes, show result dialog and configure settings - QObject::connect(worker, &QThread::finished, mainWindow, [worker, installPath, mainWindow]() { - const InstallResult& result = worker->result(); - worker->deleteLater(); - - if (result.success && IsWinDbgInstalled(installPath)) { - // Configure debugger settings - std::string dbgEngPath = installPath + "\\amd64"; - BinaryNinja::Settings::Instance()->Set("debugger.x64dbgEngPath", dbgEngPath); - LogInfo("Configured debugger.x64dbgEngPath: %s", dbgEngPath.c_str()); - - // Offer to restart Binary Ninja - QMessageBox msgBox(mainWindow); - msgBox.setWindowTitle("Installation Successful"); - msgBox.setText("WinDbg/TTD has been installed successfully!"); - msgBox.setInformativeText("The debugger settings have been configured automatically.\n\n" - "Would you like to restart Binary Ninja now?"); - msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::No); - msgBox.button(QMessageBox::Yes)->setText("Restart Now"); - msgBox.button(QMessageBox::No)->setText("Restart Later"); - - if (msgBox.exec() == QMessageBox::Yes) { - // Restart Binary Ninja by spawning a new instance before quitting - QStringList args = QCoreApplication::arguments(); - QString program = args.takeFirst(); - QProcess::startDetached(program, args); - QApplication::quit(); - } - } else { - // Show error message with specific failure reason - QString errorMsg = "WinDbg/TTD installation failed."; - if (!result.errorMessage.empty()) { - errorMsg += "\n\nError: " + QString::fromStdString(result.errorMessage); - } else { - errorMsg += "\n\nPlease check the installer console window for error details."; - } - QMessageBox::critical(mainWindow, "Installation Failed", errorMsg); - } - }); - - worker->start(); -} -#endif - - DebuggerUI::DebuggerUI(UIContext* context, DebuggerControllerRef controller) : m_context(context), m_controller(controller) { diff --git a/ui/ui.h b/ui/ui.h index c54dac6f..d5416414 100644 --- a/ui/ui.h +++ b/ui/ui.h @@ -42,8 +42,6 @@ class GlobalDebuggerUI : public QObject static void CreateGlobalAreaWidgets(UIContext* context); static void CloseGlobalAreaWidgets(UIContext* context); - void installTTD(const UIActionContext& ctxt); - public: GlobalDebuggerUI(UIContext* context); ~GlobalDebuggerUI();