Files
archived-Reclass/plugins/WinDbgMemory/WinDbgMemoryPlugin.cpp
IChooseYou 0dc390ed86 fix: WinDbg plugin dynamic dbgeng loading, editor two-tone bg, UI polish
WinDbg plugin: load dbgeng.dll dynamically from Debugging Tools directory
instead of static linking (system dbgeng.dll lacks remote DebugConnect).
Copy tools dbghelp.dll next to exe so it loads before System32 version.
Add COM init on DbgEng thread, browse for tools dir, styled dialog.

Editor: derive darker background via theme.background.darker(115) for
visual depth between chrome and editor surfaces.

UI: global scrollbar styling, workspace accent bar 1px, pane tab font
from editor settings, workspace dock default width 128px.
2026-03-07 08:31:51 -07:00

778 lines
28 KiB
C++

#include "WinDbgMemoryPlugin.h"
#include <QStyle>
#include <QApplication>
#include <QMessageBox>
#include <QDialog>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QLabel>
#include <QDebug>
#include <QClipboard>
#include <QGuiApplication>
#include <QFileDialog>
#include <QFileInfo>
#include <QSettings>
#ifdef _WIN32
#include <windows.h>
#include <initguid.h>
#include <dbgeng.h>
// dbgeng.dll is loaded dynamically — see loadDbgEngTools()
// The system dbgeng.dll (C:\Windows\System32) does not support remote
// connections (DebugConnect returns 0x8007053d). The full version lives
// in the Debugging Tools for Windows directory. We load it dynamically
// so the plugin works without requiring the debugger tools on PATH.
static const char* const kDbgToolsDirs[] = {
"C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64",
"C:\\Program Files\\Windows Kits\\10\\Debuggers\\x64",
};
static const char* const kSettingsKey = "WinDbgPlugin/DbgToolsDir";
typedef HRESULT (STDAPICALLTYPE *PFN_DebugConnect)(PCSTR, REFIID, PVOID*);
typedef HRESULT (STDAPICALLTYPE *PFN_DebugCreate)(REFIID, PVOID*);
static QString s_loadedDir;
static HMODULE s_hDbgEng = nullptr;
static HMODULE tryLoadFrom(const char* dir) {
SetDllDirectoryA(dir);
// Pre-load dependencies so the tools versions are used instead of
// the older System32 copies (e.g. dbghelp.dll without StackWalk2).
char path[MAX_PATH];
for (auto dep : {"dbghelp.dll", "dbgcore.dll", "symsrv.dll"}) {
snprintf(path, sizeof(path), "%s\\%s", dir, dep);
LoadLibraryA(path); // OK if missing
}
snprintf(path, sizeof(path), "%s\\dbgeng.dll", dir);
HMODULE h = LoadLibraryA(path);
if (h) {
s_loadedDir = QString::fromLocal8Bit(dir);
qDebug() << "[WinDbg] Loaded dbgeng.dll from" << dir;
}
return h;
}
static HMODULE loadDbgEngTools() {
if (s_hDbgEng) return s_hDbgEng;
// 1. Try user-configured path from settings
QSettings settings;
QString userDir = settings.value(kSettingsKey).toString();
if (!userDir.isEmpty()) {
s_hDbgEng = tryLoadFrom(userDir.toLocal8Bit().constData());
if (s_hDbgEng) return s_hDbgEng;
}
// 2. Try well-known install paths
for (auto dir : kDbgToolsDirs) {
s_hDbgEng = tryLoadFrom(dir);
if (s_hDbgEng) return s_hDbgEng;
}
SetDllDirectoryA(nullptr);
return nullptr;
}
static bool dbgToolsFound() {
loadDbgEngTools();
return s_hDbgEng != nullptr;
}
static PFN_DebugConnect getDebugConnect() {
static PFN_DebugConnect pfn = nullptr;
static bool tried = false;
if (!tried) {
tried = true;
HMODULE h = loadDbgEngTools();
if (h) pfn = (PFN_DebugConnect)GetProcAddress(h, "DebugConnect");
if (!pfn) qWarning() << "[WinDbg] DebugConnect not available — Debugging Tools not found";
}
return pfn;
}
static PFN_DebugCreate getDebugCreate() {
static PFN_DebugCreate pfn = nullptr;
static bool tried = false;
if (!tried) {
tried = true;
HMODULE h = loadDbgEngTools();
if (h) pfn = (PFN_DebugCreate)GetProcAddress(h, "DebugCreate");
if (!pfn) qWarning() << "[WinDbg] DebugCreate not available — Debugging Tools not found";
}
return pfn;
}
#endif
// ──────────────────────────────────────────────────────────────────────────
// Thread dispatch helper
// ──────────────────────────────────────────────────────────────────────────
template<typename Fn>
void WinDbgMemoryProvider::dispatchToOwner(Fn&& fn) const
{
if (!m_dispatcher) { fn(); return; }
if (QThread::currentThread() == m_dispatcher->thread()) {
// Already on the owning thread — call directly
fn();
} else {
// Marshal to the owning thread and block until done
QMetaObject::invokeMethod(m_dispatcher, std::forward<Fn>(fn),
Qt::BlockingQueuedConnection);
}
}
// ──────────────────────────────────────────────────────────────────────────
// WinDbgMemoryProvider implementation
// ──────────────────────────────────────────────────────────────────────────
WinDbgMemoryProvider::WinDbgMemoryProvider(const QString& target)
{
// Create a dedicated thread for all DbgEng COM operations.
// DbgEng's remote transport (TCP/named-pipe) is thread-affine — all
// calls must happen on the thread that called DebugConnect/DebugCreate.
// A private thread with its own event loop guarantees:
// 1. dispatchToOwner() works from any calling thread (main, thread-pool, etc.)
// 2. No deadlock — the DbgEng thread is never blocked by the caller
m_dbgThread = new QThread();
m_dbgThread->setObjectName(QStringLiteral("DbgEngThread"));
m_dbgThread->start();
m_dispatcher = new DbgEngDispatcher();
m_dispatcher->moveToThread(m_dbgThread);
#ifdef _WIN32
// Run all DbgEng initialization on the dedicated thread.
// BlockingQueuedConnection blocks us until the lambda finishes,
// so member variables written inside are visible after the call.
dispatchToOwner([this, &target]() {
HRESULT hr;
// COM must be initialized on this thread for DbgEng
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
qDebug() << "[WinDbg] Opening target:" << target
<< "on DbgEng thread" << QThread::currentThread();
if (target.startsWith("tcp:", Qt::CaseInsensitive)
|| target.startsWith("npipe:", Qt::CaseInsensitive))
{
// ── Remote: connect to existing WinDbg debug server ──
auto pfnConnect = getDebugConnect();
if (!pfnConnect) { qWarning() << "[WinDbg] Debugging Tools required for remote connections"; return; }
QByteArray connUtf8 = target.toUtf8();
qDebug() << "[WinDbg] DebugConnect:" << target;
hr = pfnConnect(connUtf8.constData(), IID_IDebugClient, (void**)&m_client);
qDebug() << "[WinDbg] DebugConnect hr=" << Qt::hex << (unsigned long)hr
<< "client=" << (void*)m_client;
if (FAILED(hr) || !m_client) {
qWarning() << "[WinDbg] DebugConnect FAILED hr=0x" << Qt::hex << (unsigned long)hr;
return;
}
m_isRemote = true;
}
else
{
// ── Local: create debug client for pid/dump ──
auto pfnCreate = getDebugCreate();
if (!pfnCreate) { qWarning() << "[WinDbg] Debugging Tools required"; return; }
hr = pfnCreate(IID_IDebugClient, (void**)&m_client);
qDebug() << "[WinDbg] DebugCreate hr=" << Qt::hex << (unsigned long)hr
<< "client=" << (void*)m_client;
if (FAILED(hr) || !m_client) {
qWarning() << "[WinDbg] DebugCreate FAILED hr=0x" << Qt::hex << (unsigned long)hr;
return;
}
if (target.startsWith("pid:", Qt::CaseInsensitive))
{
bool ok = false;
ULONG pid = target.mid(4).trimmed().toULong(&ok);
if (!ok || pid == 0) {
qWarning() << "[WinDbg] Invalid PID in target:" << target;
cleanup();
return;
}
qDebug() << "[WinDbg] Attaching to PID" << pid << "(non-invasive)";
hr = m_client->AttachProcess(
0, pid,
DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND);
qDebug() << "[WinDbg] AttachProcess hr=" << Qt::hex << (unsigned long)hr;
if (FAILED(hr)) {
qWarning() << "[WinDbg] AttachProcess FAILED";
cleanup();
return;
}
}
else if (target.startsWith("dump:", Qt::CaseInsensitive))
{
QString path = target.mid(5).trimmed();
QByteArray pathUtf8 = path.toUtf8();
qDebug() << "[WinDbg] Opening dump file:" << path;
hr = m_client->OpenDumpFile(pathUtf8.constData());
qDebug() << "[WinDbg] OpenDumpFile hr=" << Qt::hex << (unsigned long)hr;
if (FAILED(hr)) {
qWarning() << "[WinDbg] OpenDumpFile FAILED";
cleanup();
return;
}
}
else
{
qWarning() << "[WinDbg] Unknown target format:" << target;
cleanup();
return;
}
}
initInterfaces();
// WaitForEvent to finalize the attach/dump load.
// For remote connections the server session is already active — skip.
if (m_control && !m_isRemote) {
qDebug() << "[WinDbg] WaitForEvent...";
hr = m_control->WaitForEvent(0, 10000);
qDebug() << "[WinDbg] WaitForEvent hr=" << Qt::hex << (unsigned long)hr;
}
querySessionInfo();
});
#else
Q_UNUSED(target);
#endif
}
void WinDbgMemoryProvider::initInterfaces()
{
#ifdef _WIN32
if (!m_client) return;
HRESULT hr;
hr = m_client->QueryInterface(IID_IDebugDataSpaces, (void**)&m_dataSpaces);
qDebug() << "[WinDbg] IDebugDataSpaces hr=" << Qt::hex << (unsigned long)hr
<< "ptr=" << (void*)m_dataSpaces;
hr = m_client->QueryInterface(IID_IDebugDataSpaces2, (void**)&m_dataSpaces2);
qDebug() << "[WinDbg] IDebugDataSpaces2 hr=" << Qt::hex << (unsigned long)hr
<< "ptr=" << (void*)m_dataSpaces2;
hr = m_client->QueryInterface(IID_IDebugControl, (void**)&m_control);
qDebug() << "[WinDbg] IDebugControl hr=" << Qt::hex << (unsigned long)hr
<< "ptr=" << (void*)m_control;
hr = m_client->QueryInterface(IID_IDebugSymbols, (void**)&m_symbols);
qDebug() << "[WinDbg] IDebugSymbols hr=" << Qt::hex << (unsigned long)hr
<< "ptr=" << (void*)m_symbols;
if (!m_dataSpaces) {
qWarning() << "[WinDbg] No IDebugDataSpaces — cleaning up";
cleanup();
}
#endif
}
void WinDbgMemoryProvider::querySessionInfo()
{
#ifdef _WIN32
if (!m_client) return;
HRESULT hr;
if (m_control) {
ULONG debugClass = 0, debugQualifier = 0;
hr = m_control->GetDebuggeeType(&debugClass, &debugQualifier);
qDebug() << "[WinDbg] GetDebuggeeType hr=" << Qt::hex << (unsigned long)hr
<< "class=" << debugClass << "qualifier=" << debugQualifier;
if (SUCCEEDED(hr)) {
m_isLive = (debugQualifier < DEBUG_DUMP_SMALL);
m_writable = m_isLive;
}
}
// Query effective processor type for pointer size detection
if (m_control) {
ULONG procType = 0;
hr = m_control->GetEffectiveProcessorType(&procType);
if (SUCCEEDED(hr)) {
// IMAGE_FILE_MACHINE_I386 = 0x014C
if (procType == 0x014C)
m_pointerSize = 4;
qDebug() << "[WinDbg] EffectiveProcessorType=" << Qt::hex << procType
<< "pointerSize=" << m_pointerSize;
}
}
// WinDbg provides access to the entire virtual address space.
// Do NOT auto-select a module as base — let the user set their
// own base address. m_base stays 0 so the controller won't
// override tree.baseAddress.
m_name = m_isLive ? QStringLiteral("WinDbg (Live)")
: QStringLiteral("WinDbg (Dump)");
qDebug() << "[WinDbg] Ready. name=" << m_name
<< "isLive=" << m_isLive;
#endif
}
WinDbgMemoryProvider::~WinDbgMemoryProvider()
{
#ifdef _WIN32
// Dispatch COM cleanup to the DbgEng thread (thread-affine release)
if (m_dbgThread && m_dbgThread->isRunning() && m_dispatcher) {
dispatchToOwner([this]() {
if (m_client) {
if (m_isRemote)
m_client->EndSession(DEBUG_END_DISCONNECT);
else
m_client->DetachProcesses();
}
cleanup();
CoUninitialize();
});
} else {
// Thread not running — clean up directly (best-effort)
if (m_client) {
if (m_isRemote)
m_client->EndSession(DEBUG_END_DISCONNECT);
else
m_client->DetachProcesses();
}
cleanup();
}
#else
cleanup();
#endif
// Stop the dedicated thread
if (m_dbgThread) {
m_dbgThread->quit();
m_dbgThread->wait(3000);
delete m_dbgThread;
m_dbgThread = nullptr;
}
delete m_dispatcher;
m_dispatcher = nullptr;
}
void WinDbgMemoryProvider::cleanup()
{
#ifdef _WIN32
if (m_symbols) { m_symbols->Release(); m_symbols = nullptr; }
if (m_control) { m_control->Release(); m_control = nullptr; }
if (m_dataSpaces2) { m_dataSpaces2->Release(); m_dataSpaces2 = nullptr; }
if (m_dataSpaces) { m_dataSpaces->Release(); m_dataSpaces = nullptr; }
if (m_client) { m_client->Release(); m_client = nullptr; }
#endif
}
bool WinDbgMemoryProvider::read(uint64_t addr, void* buf, int len) const
{
#ifdef _WIN32
if (!m_dataSpaces || len <= 0) return false;
bool result = false;
dispatchToOwner([&]() {
ULONG bytesRead = 0;
HRESULT hr = m_dataSpaces->ReadVirtual(addr, buf, (ULONG)len, &bytesRead);
if (SUCCEEDED(hr) && (int)bytesRead >= len) {
result = true;
return;
}
// Partial or failed read — zero-fill remainder and log
memset((char*)buf + bytesRead, 0, len - bytesRead);
++m_readFailCount;
if (m_readFailCount <= 5 || (m_readFailCount % 100) == 0)
qDebug() << "[WinDbg] ReadVirtual FAILED addr=0x" << Qt::hex << addr
<< "len=" << Qt::dec << len
<< "hr=0x" << Qt::hex << (unsigned long)hr
<< "got=" << Qt::dec << bytesRead;
result = bytesRead > 0;
});
return result;
#else
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
return false;
#endif
}
bool WinDbgMemoryProvider::write(uint64_t addr, const void* buf, int len)
{
#ifdef _WIN32
if (!m_dataSpaces || !m_writable || len <= 0) return false;
bool result = false;
dispatchToOwner([&]() {
ULONG bytesWritten = 0;
HRESULT hr = m_dataSpaces->WriteVirtual(addr, const_cast<void*>(buf),
(ULONG)len, &bytesWritten);
result = SUCCEEDED(hr) && bytesWritten == (ULONG)len;
});
return result;
#else
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
return false;
#endif
}
int WinDbgMemoryProvider::size() const
{
#ifdef _WIN32
return m_dataSpaces ? 0x10000 : 0;
#else
return 0;
#endif
}
bool WinDbgMemoryProvider::isReadable(uint64_t /*addr*/, int len) const
{
#ifdef _WIN32
// DbgEng's ReadVirtual can read any mapped virtual address.
return m_dataSpaces != nullptr && len >= 0;
#else
return false;
#endif
}
QString WinDbgMemoryProvider::getSymbol(uint64_t addr) const
{
#ifdef _WIN32
if (!m_symbols) return {};
QString result;
dispatchToOwner([&]() {
char nameBuf[512] = {};
ULONG nameSize = 0;
ULONG64 displacement = 0;
HRESULT hr = m_symbols->GetNameByOffset(addr, nameBuf, sizeof(nameBuf),
&nameSize, &displacement);
if (SUCCEEDED(hr) && nameSize > 0) {
result = QString::fromUtf8(nameBuf);
if (displacement > 0)
result += QStringLiteral("+0x%1").arg(displacement, 0, 16);
}
});
return result;
#else
Q_UNUSED(addr);
return {};
#endif
}
QVector<rcx::MemoryRegion> WinDbgMemoryProvider::enumerateRegions() const
{
QVector<rcx::MemoryRegion> regions;
#ifdef _WIN32
if (!m_dataSpaces) return regions;
// Enumerate modules — used for tagging (user-mode) or as the primary
// source of regions (kernel-mode, where QueryVirtual is unavailable).
struct ModInfo { uint64_t base; uint64_t size; QString name; };
QVector<ModInfo> modules;
if (m_symbols) {
dispatchToOwner([&]() {
ULONG loaded = 0, unloaded = 0;
if (FAILED(m_symbols->GetNumberModules(&loaded, &unloaded)))
return;
for (ULONG i = 0; i < loaded; i++) {
ULONG64 modBase = 0;
if (FAILED(m_symbols->GetModuleByIndex(i, &modBase)))
continue;
DEBUG_MODULE_PARAMETERS params = {};
if (FAILED(m_symbols->GetModuleParameters(1, &modBase, 0, &params)))
continue;
char nameBuf[256] = {};
ULONG nameSize = 0;
m_symbols->GetModuleNames(i, 0,
nullptr, 0, nullptr,
nameBuf, sizeof(nameBuf), &nameSize,
nullptr, 0, nullptr);
ModInfo mi;
mi.base = modBase;
mi.size = params.Size;
mi.name = QString::fromUtf8(nameBuf);
modules.append(mi);
}
});
}
// Try QueryVirtual first (user-mode debugging / user-mode dumps).
// MSDN: "This method is not available in kernel-mode debugging."
if (m_dataSpaces2) {
dispatchToOwner([&]() {
ULONG64 addr = 0;
int safety = 0;
constexpr int kMaxRegions = 500000;
while (safety++ < kMaxRegions) {
MEMORY_BASIC_INFORMATION64 mbi = {};
HRESULT hr = m_dataSpaces2->QueryVirtual(addr, &mbi);
if (FAILED(hr))
break;
if (mbi.State == MEM_COMMIT &&
!(mbi.Protect & PAGE_NOACCESS) &&
!(mbi.Protect & PAGE_GUARD))
{
rcx::MemoryRegion region;
region.base = mbi.BaseAddress;
region.size = mbi.RegionSize;
region.readable = true;
region.writable = (mbi.Protect & PAGE_READWRITE) ||
(mbi.Protect & PAGE_WRITECOPY) ||
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
region.executable = (mbi.Protect & PAGE_EXECUTE) ||
(mbi.Protect & PAGE_EXECUTE_READ) ||
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
for (const auto& mod : modules) {
if (region.base >= mod.base && region.base < mod.base + mod.size) {
region.moduleName = mod.name;
break;
}
}
regions.append(region);
}
ULONG64 next = mbi.BaseAddress + mbi.RegionSize;
if (next <= addr) break;
addr = next;
}
});
}
// Fallback for kernel-mode debugging: QueryVirtual is unavailable,
// so use loaded modules as scannable regions. Each module image
// becomes one region — the scanner reads through module code/data.
if (regions.isEmpty() && !modules.isEmpty()) {
for (const auto& mod : modules) {
if (mod.size == 0) continue;
rcx::MemoryRegion region;
region.base = mod.base;
region.size = mod.size;
region.readable = true;
region.writable = false;
region.executable = true;
region.moduleName = mod.name;
regions.append(region);
}
}
#endif
return regions;
}
// ──────────────────────────────────────────────────────────────────────────
// WinDbgMemoryPlugin implementation
// ──────────────────────────────────────────────────────────────────────────
QIcon WinDbgMemoryPlugin::Icon() const
{
return qApp->style()->standardIcon(QStyle::SP_DriveNetIcon);
}
bool WinDbgMemoryPlugin::canHandle(const QString& target) const
{
return target.startsWith("tcp:", Qt::CaseInsensitive)
|| target.startsWith("npipe:", Qt::CaseInsensitive)
|| target.startsWith("pid:", Qt::CaseInsensitive)
|| target.startsWith("dump:", Qt::CaseInsensitive);
}
std::unique_ptr<rcx::Provider> WinDbgMemoryPlugin::createProvider(const QString& target, QString* errorMsg)
{
auto provider = std::make_unique<WinDbgMemoryProvider>(target);
if (!provider->isValid())
{
if (errorMsg) {
if (target.startsWith("tcp:", Qt::CaseInsensitive)
|| target.startsWith("npipe:", Qt::CaseInsensitive))
*errorMsg = QString("Failed to connect to debug server.\n\n"
"Target: %1\n\n"
"Make sure WinDbg is running with a matching .server command\n"
"(e.g. .server tcp:port=5056) and the port/pipe is reachable.")
.arg(target);
else if (target.startsWith("pid:", Qt::CaseInsensitive))
*errorMsg = QString("Failed to attach to process.\n\n"
"Target: %1\n\n"
"Make sure the process is running and you have "
"sufficient privileges (try Run as Administrator).")
.arg(target);
else
*errorMsg = QString("Failed to open dump file.\n\n"
"Target: %1\n\n"
"Make sure the file exists and is a valid dump.")
.arg(target);
}
return nullptr;
}
return provider;
}
uint64_t WinDbgMemoryPlugin::getInitialBaseAddress(const QString& target) const
{
Q_UNUSED(target);
return 0;
}
bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
{
QDialog dlg(parent);
dlg.setWindowTitle("WinDbg Settings");
dlg.resize(480, 360);
QPalette dlgPal = qApp->palette();
dlg.setPalette(dlgPal);
dlg.setAutoFillBackground(true);
auto* layout = new QVBoxLayout(&dlg);
QColor editBg = dlgPal.window().color().darker(115);
QString editSS = QStringLiteral(
"QLineEdit { background: %1; color: %2; border: 1px solid %3;"
" border-radius: 3px; padding: 4px 6px; }")
.arg(editBg.name(),
dlgPal.color(QPalette::Text).name(),
dlgPal.color(QPalette::Mid).name());
layout->addWidget(new QLabel(
"Connect to a running WinDbg debug server.\n"
"In WinDbg, run: .server tcp:port=5056\n\n"
"Non-invasive debug and dump files only.\n"
"Execution control (bp, g, t, p) is not supported.\n"
"WinDbg Classic is recommended."));
layout->addSpacing(8);
layout->addWidget(new QLabel("Connection string:"));
auto* connEdit = new QLineEdit;
connEdit->setPlaceholderText("tcp:Port=5056,Server=127.0.0.1");
connEdit->setText("tcp:Port=5056,Server=127.0.0.1");
connEdit->setStyleSheet(editSS);
layout->addWidget(connEdit);
layout->addSpacing(4);
layout->addWidget(new QLabel("Run one of these in WinDbg first:"));
auto addExample = [&](const QString& text) {
auto* row = new QHBoxLayout;
auto* label = new QLabel(text);
QPalette lp = dlgPal;
lp.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
label->setPalette(lp);
label->setTextInteractionFlags(Qt::TextSelectableByMouse);
row->addWidget(label, 1);
auto* copyBtn = new QPushButton("Copy");
copyBtn->setFixedWidth(50);
copyBtn->setToolTip("Copy to clipboard");
QObject::connect(copyBtn, &QPushButton::clicked, [text]() {
QGuiApplication::clipboard()->setText(text);
});
row->addWidget(copyBtn);
layout->addLayout(row);
};
addExample(".server tcp:port=5056");
addExample(".server npipe:pipe=reclass");
// ── Debugger Tools status ──
layout->addSpacing(8);
#ifdef _WIN32
bool found = dbgToolsFound();
auto* toolsRow = new QHBoxLayout;
auto* toolsLabel = new QLabel;
if (found) {
toolsLabel->setText(QStringLiteral("Debugging Tools: %1").arg(s_loadedDir));
QPalette tp = dlgPal;
tp.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
toolsLabel->setPalette(tp);
} else {
toolsLabel->setText("Debugging Tools: not found");
QPalette tp = dlgPal;
tp.setColor(QPalette::WindowText, QColor(220, 120, 80));
toolsLabel->setPalette(tp);
}
toolsLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
toolsRow->addWidget(toolsLabel, 1);
auto* browseBtn = new QPushButton("Browse...");
browseBtn->setFixedWidth(70);
browseBtn->setToolTip("Locate Debugging Tools for Windows directory (contains dbgeng.dll)");
QObject::connect(browseBtn, &QPushButton::clicked, [&dlg, toolsLabel, &dlgPal]() {
QString dir = QFileDialog::getExistingDirectory(&dlg,
"Locate Debugging Tools for Windows",
"C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers");
if (dir.isEmpty()) return;
QString dllPath = dir + "/dbgeng.dll";
if (!QFileInfo::exists(dllPath)) {
QMessageBox::warning(&dlg, "Not Found",
"dbgeng.dll was not found in that directory.\n"
"Select the folder containing dbgeng.dll\n"
"(e.g. Debuggers\\x64).");
return;
}
QSettings settings;
settings.setValue(kSettingsKey, dir);
// Force reload on next use
s_hDbgEng = nullptr;
s_loadedDir.clear();
if (dbgToolsFound()) {
toolsLabel->setText(QStringLiteral("Debugging Tools: %1").arg(s_loadedDir));
QPalette tp = dlgPal;
tp.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
toolsLabel->setPalette(tp);
}
});
toolsRow->addWidget(browseBtn);
layout->addLayout(toolsRow);
if (!found) {
auto* note = new QLabel(
"The system dbgeng.dll does not support remote connections.\n"
"Install Debugging Tools for Windows or use Browse to locate them.");
QPalette np = dlgPal;
np.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
note->setPalette(np);
note->setWordWrap(true);
layout->addWidget(note);
}
#endif
layout->addStretch();
auto* btnLayout = new QHBoxLayout;
btnLayout->addStretch();
auto* okBtn = new QPushButton("OK");
auto* cancelBtn = new QPushButton("Cancel");
btnLayout->addWidget(okBtn);
btnLayout->addWidget(cancelBtn);
layout->addLayout(btnLayout);
QObject::connect(okBtn, &QPushButton::clicked, &dlg, &QDialog::accept);
QObject::connect(cancelBtn, &QPushButton::clicked, &dlg, &QDialog::reject);
if (dlg.exec() != QDialog::Accepted)
return false;
QString conn = connEdit->text().trimmed();
if (conn.isEmpty()) return false;
*target = conn;
return true;
}
// ──────────────────────────────────────────────────────────────────────────
// Plugin factory
// ──────────────────────────────────────────────────────────────────────────
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
{
return new WinDbgMemoryPlugin();
}