// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
/* A debug dispatcher for Windows that can be registered for calls with crashed
* processes. It offers debugging using either Qt Creator or
* the previously registered default debugger.
* See usage() on how to install/use.
* Installs itself in the bin directory of Qt Creator. */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef QTCREATOR_PCH_H
#define CALLBACK WINAPI
#endif
#include
#include
#include
using namespace RegistryAccess;
enum { debug = 0 };
static const char titleC[] = "Qt Creator Debugger";
// Optional
static const WCHAR debuggerWow32RegistryKeyC[] = L"Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug";
static const WCHAR debuggerRegistryDefaultValueNameC[] = L"Debugger.Default";
static const char linkC[] = "http://msdn.microsoft.com/en-us/library/cc266343.aspx";
static const char creatorBinaryC[] = "qtcreator.exe";
#ifdef __GNUC__
#define RRF_RT_ANY 0x0000ffff // no type restriction
#endif
enum Mode { HelpMode, RegisterMode, UnregisterMode, PromptMode, ForceCreatorMode, ForceDefaultMode };
Mode optMode = PromptMode;
// WOW: Indicates registry key access mode:
// - Accessing 32bit using a 64bit built Qt Creator or,
// - Accessing 64bit using a 32bit built Qt Creator on 64bit Windows
bool optIsWow = false;
bool noguiMode = false;
unsigned long argProcessId = 0;
quint64 argWinCrashEvent = 0;
static bool parseArguments(const QStringList &args, QString *errorMessage)
{
int argNumber = 0;
const int count = args.size();
const QChar dash = QLatin1Char('-');
const QChar slash = QLatin1Char('/');
for (int i = 1; i < count; i++) {
QString arg = args.at(i);
if (arg.startsWith(dash) || arg.startsWith(slash)) { // option
arg.remove(0, 1);
if (arg == QLatin1String("help") || arg == QLatin1String("?")) {
optMode = HelpMode;
} else if (arg == QLatin1String("qtcreator")) {
optMode = ForceCreatorMode;
} else if (arg == QLatin1String("default")) {
optMode = ForceDefaultMode;
} else if (arg == QLatin1String("register")) {
optMode = RegisterMode;
} else if (arg == QLatin1String("unregister")) {
optMode = UnregisterMode;
} else if (arg == QLatin1String("wow")) {
optIsWow = true;
} else if (arg == QLatin1String("nogui")) {
noguiMode = true;
} else if (arg == QLatin1String("p")) {
// Ignore, see QTCREATORBUG-18194.
} else {
*errorMessage = QString::fromLatin1("Unexpected option: %1").arg(arg);
return false;
}
} else { // argument
bool ok = false;
switch (argNumber++) {
case 0:
argProcessId = arg.toULong(&ok);
break;
case 1:
argWinCrashEvent = arg.toULongLong(&ok);
break;
}
if (!ok) {
*errorMessage = QString::fromLatin1("Invalid argument: %1").arg(arg);
return false;
}
}
}
switch (optMode) {
case HelpMode:
case RegisterMode:
case UnregisterMode:
break;
default:
if (argProcessId == 0) {
*errorMessage = QString::fromLatin1("Please specify the process-id.");
return false;
}
}
return true;
}
static bool readDebugger(const wchar_t *key, QString *debugger,
QString *errorMessage)
{
bool success = false;
HKEY handle;
const RegistryAccess::AccessMode accessMode = optIsWow
#ifdef Q_OS_WIN64
? RegistryAccess::Registry32Mode
#else
? RegistryAccess::Registry64Mode
#endif
: RegistryAccess::DefaultAccessMode;
if (openRegistryKey(HKEY_LOCAL_MACHINE, debuggerRegistryKeyC, false, &handle, accessMode, errorMessage)) {
success = registryReadStringKey(handle, key, debugger, errorMessage);
RegCloseKey(handle);
}
return success;
}
static void usage(const QString &binary, const QString &message = QString())
{
QString msg;
QTextStream str(&msg);
str << "";
if (message.isEmpty()) {
str << "" << titleC << "
"
<< "Dispatcher that launches the desired debugger for a crashed process"
" according to Enabling Postmortem Debugging.
";
} else {
str << "" << message << "";
}
str << ""
<< "Usage: " << QFileInfo(binary).baseName() << "[-wow] [-help|-?|qtcreator|default|register|unregister] <process-id> <event-id>\n"
<< "Options: -help, -? Display this help\n"
<< " -qtcreator Launch Qt Creator without prompting\n"
<< " -default Launch Default handler without prompting\n"
<< " -register Register as post mortem debugger (requires administrative privileges)\n"
<< " -unregister Unregister as post mortem debugger (requires administrative privileges)\n"
<< " -wow Indicates Wow32 call\n"
<< " -nogui Do not show error messages in popup windows\n"
<< "
"
<< "To install, modify the registry key HKEY_LOCAL_MACHINE\\" << wCharToQString(debuggerRegistryKeyC)
<< ":
"
<< "On 64-bit systems, do the same for the key HKEY_LOCAL_MACHINE\\" << wCharToQString(debuggerWow32RegistryKeyC) << ", "
<< "setting the new value to
\"" << QDir::toNativeSeparators(binary) << "\" -wow %ld %ld
"
<< "How to run a command with administrative privileges:
"
<< "runas /env /noprofile /user:Administrator \"command arguments\"
";
QString currentDebugger;
QString errorMessage;
if (readDebugger(debuggerRegistryValueNameC, ¤tDebugger, &errorMessage))
str << "Currently registered debugger:
" << currentDebugger << "
";
str << "Qt " << QT_VERSION_STR << ", " << QSysInfo::WordSize
<< "bit
";
QMessageBox msgBox(QMessageBox::Information, QLatin1String(titleC), msg, QMessageBox::Ok);
msgBox.setTextInteractionFlags(Qt::TextBrowserInteraction);
msgBox.exec();
}
#ifndef Q_OS_WIN64
static bool is64BitWindowsSystem() // Courtesy utils library
{
SYSTEM_INFO systemInfo;
GetNativeSystemInfo(&systemInfo);
return systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64
|| systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64;
}
#endif
// ------- Registry helpers
static inline bool registryWriteBinaryKey(HKEY handle,
const WCHAR *valueName,
DWORD type,
const BYTE *data,
DWORD size,
QString *errorMessage)
{
const LONG rc = RegSetValueEx(handle, valueName, 0, type, data, size);
if (rc != ERROR_SUCCESS) {
*errorMessage = msgRegistryOperationFailed("write", valueName, msgFunctionFailed("RegSetValueEx", rc));
return false;
}
return true;
}
static inline bool registryWriteStringKey(HKEY handle, // HKEY_LOCAL_MACHINE, etc.
const WCHAR *key,
const QString &s,
QString *errorMessage)
{
const BYTE *data = reinterpret_cast(s.utf16());
const DWORD size = 2 * s.size(); // excluding 0
return registryWriteBinaryKey(handle, key, REG_SZ, data, size, errorMessage);
}
static inline bool registryReplaceStringKey(HKEY rootHandle, // HKEY_LOCAL_MACHINE, etc.
const WCHAR *key,
const WCHAR *valueName,
const QString &newValue,
QString *oldValue,
QString *errorMessage)
{
HKEY handle = 0;
bool rc = false;
do {
if (!openRegistryKey(rootHandle, key, true, &handle, errorMessage))
break;
if (!registryReadStringKey(handle, valueName, oldValue, errorMessage))
break;
if (*oldValue != newValue) {
if (!registryWriteStringKey(handle, valueName, newValue, errorMessage))
break;
}
rc = true;
} while (false);
if (handle)
RegCloseKey(handle);
return rc;
}
static inline bool registryDeleteValue(HKEY handle,
const WCHAR *valueName,
QString *errorMessage)
{
const LONG rc = RegDeleteValue(handle, valueName);
if (rc != ERROR_SUCCESS) {
*errorMessage = msgFunctionFailed("RegDeleteValue", rc);
return false;
}
return true;
}
static QString getProcessBaseName(DWORD pid)
{
QString rc;
const HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (handle != NULL) {
WCHAR buffer[MAX_PATH];
if (GetModuleBaseName(handle, 0, buffer, MAX_PATH))
rc = wCharToQString(buffer);
CloseHandle(handle);
}
return rc;
}
// ------- main modes
static bool waitForProcess(DWORD pid)
{
HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION|READ_CONTROL|SYNCHRONIZE, false, pid);
if (handle == NULL)
return false;
const DWORD waitResult = WaitForSingleObject(handle, INFINITE);
CloseHandle(handle);
return waitResult == WAIT_OBJECT_0;
}
bool startCreatorAsDebugger(bool asClient, QString *errorMessage)
{
const QString dir = QApplication::applicationDirPath();
const QString binary = dir + QLatin1Char('/') + QLatin1String(creatorBinaryC);
QStringList args;
// Send to running Creator: Unstable with directly linked CDB engine.
if (asClient)
args << QLatin1String("-client");
if (argWinCrashEvent != 0) {
args << QLatin1String("-wincrashevent")
<< QString::fromLatin1("%1:%2").arg(argWinCrashEvent).arg(argProcessId);
} else {
args << QLatin1String("-debug")
<< QString::fromLatin1("%1").arg(argProcessId);
}
if (debug)
qDebug() << binary << args;
QProcess p;
p.setWorkingDirectory(dir);
QElapsedTimer executionTime;
executionTime.start();
p.start(binary, args, QIODevice::NotOpen);
if (!p.waitForStarted()) {
*errorMessage = QString::fromLatin1("Unable to start %1!").arg(binary);
return false;
}
// Short execution time: indicates that -client was passed on attach to
// another running instance of Qt Creator. Keep alive as long as user
// does not close the process. If that fails, try to launch 2nd instance.
const bool waitResult = p.waitForFinished(-1);
const bool ranAsClient = asClient && (executionTime.elapsed() < 10000);
if (waitResult && p.exitStatus() == QProcess::NormalExit && ranAsClient) {
if (p.exitCode() == 0) {
waitForProcess(argProcessId);
} else {
errorMessage->clear();
return startCreatorAsDebugger(false, errorMessage);
}
}
return true;
}
bool readDefaultDebugger(QString *defaultDebugger,
QString *errorMessage)
{
return readDebugger(debuggerRegistryDefaultValueNameC, defaultDebugger, errorMessage);
}
bool startDefaultDebugger(QString *errorMessage)
{
QString defaultDebugger;
if (!readDefaultDebugger(&defaultDebugger, errorMessage))
return false;
// binary, replace placeholders by pid/event id
if (debug)
qDebug() << "Default" << defaultDebugger;
const QString placeHolder = QLatin1String("%ld");
const int pidPlaceHolderPos = defaultDebugger.indexOf(placeHolder);
if (pidPlaceHolderPos == -1)
return true; // was empty or sth
defaultDebugger.replace(pidPlaceHolderPos, placeHolder.size(), QString::number(argProcessId));
const int evtPlaceHolderPos = defaultDebugger.indexOf(placeHolder);
if (evtPlaceHolderPos != -1)
defaultDebugger.replace(evtPlaceHolderPos, placeHolder.size(), QString::number(argWinCrashEvent));
if (debug)
qDebug() << "Default" << defaultDebugger;
QProcess p;
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
QStringList arguments = QProcess::splitCommand(defaultDebugger);
const QString executable = arguments.takeFirst();
p.start(executable, arguments, QIODevice::NotOpen);
#else
p.start(defaultDebugger, QIODevice::NotOpen);
#endif
if (!p.waitForStarted()) {
*errorMessage = QString::fromLatin1("Unable to start %1!").arg(defaultDebugger);
return false;
}
p.waitForFinished(-1);
return true;
}
bool chooseDebugger(QString *errorMessage)
{
QString defaultDebugger;
const QString processName = getProcessBaseName(argProcessId);
const QString msg = QString::fromLatin1("The application \"%1\" (process id %2) crashed. Would you like to debug it?").arg(processName).arg(argProcessId);
QMessageBox msgBox(QMessageBox::Information, QLatin1String(titleC), msg, QMessageBox::Cancel);
QPushButton *creatorButton = msgBox.addButton(QLatin1String("Debug with Qt Creator"), QMessageBox::AcceptRole);
QPushButton *defaultButton = msgBox.addButton(QLatin1String("Debug with default debugger"), QMessageBox::AcceptRole);
defaultButton->setEnabled(readDefaultDebugger(&defaultDebugger, errorMessage)
&& !defaultDebugger.isEmpty());
msgBox.exec();
if (msgBox.clickedButton() == creatorButton) {
// Just in case, default to standard. Do not run as client in the unlikely case
// Creator crashed
// TODO: pass asClient=true for new CDB engine.
const bool canRunAsClient = !processName.contains(QLatin1String(creatorBinaryC), Qt::CaseInsensitive);
Q_UNUSED(canRunAsClient)
if (startCreatorAsDebugger(false, errorMessage))
return true;
return startDefaultDebugger(errorMessage);
}
if (msgBox.clickedButton() == defaultButton)
return startDefaultDebugger(errorMessage);
return true;
}
// registration helper: Register ourselves in a debugger registry key.
// Make a copy of the old value as "Debugger.Default" and have the
// "Debug" key point to us.
static bool registerDebuggerKey(const WCHAR *key,
const QString &call,
RegistryAccess::AccessMode access,
QString *errorMessage)
{
HKEY handle = 0;
bool success = false;
do {
if (!openRegistryKey(HKEY_LOCAL_MACHINE, key, true, &handle, access, errorMessage))
break;
// Make sure to automatically open the qtcdebugger dialog on a crash
QString autoVal;
registryReadStringKey(handle, autoRegistryValueNameC, &autoVal, errorMessage);
if (autoVal != "1") {
if (!registryWriteStringKey(handle, autoRegistryValueNameC, "1", errorMessage))
break;
}
// Save old key, which might be missing
QString oldDebugger;
if (isRegistered(handle, call, errorMessage, &oldDebugger)) {
*errorMessage = QLatin1String("The program is already registered as post mortem debugger.");
break;
}
if (!(oldDebugger.contains(QLatin1String(debuggerApplicationFileC), Qt::CaseInsensitive)
|| registryWriteStringKey(handle, debuggerRegistryDefaultValueNameC, oldDebugger, errorMessage)))
break;
if (debug)
qDebug() << "registering self as " << call;
if (!registryWriteStringKey(handle, debuggerRegistryValueNameC, call, errorMessage))
break;
success = true;
} while (false);
if (handle)
RegCloseKey(handle);
return success;
}
bool install(QString *errorMessage)
{
if (!registerDebuggerKey(debuggerRegistryKeyC, debuggerCall(), RegistryAccess::DefaultAccessMode, errorMessage))
return false;
#ifdef Q_OS_WIN64
if (!registerDebuggerKey(debuggerRegistryKeyC, debuggerCall(QLatin1String("-wow")), RegistryAccess::Registry32Mode, errorMessage))
return false;
#else
if (is64BitWindowsSystem()) {
if (!registerDebuggerKey(debuggerRegistryKeyC, debuggerCall(QLatin1String("-wow")), RegistryAccess::Registry64Mode, errorMessage))
return false;
}
#endif
return true;
}
// Unregister helper: Restore the original debugger key
static bool unregisterDebuggerKey(const WCHAR *key,
const QString &call,
RegistryAccess::AccessMode access,
QString *errorMessage)
{
HKEY handle = 0;
bool success = false;
do {
if (!openRegistryKey(HKEY_LOCAL_MACHINE, key, true, &handle, access, errorMessage))
break;
QString debugger;
if (!isRegistered(handle, call, errorMessage, &debugger) && !debugger.isEmpty()) {
*errorMessage = QLatin1String("The program is not registered as post mortem debugger.");
break;
}
QString oldDebugger;
registryReadStringKey(handle, debuggerRegistryDefaultValueNameC, &oldDebugger, errorMessage);
// Re-register old debugger or delete key if it was empty.
if (oldDebugger.isEmpty()) {
if (!registryDeleteValue(handle, debuggerRegistryValueNameC, errorMessage))
break;
} else {
if (!registryWriteStringKey(handle, debuggerRegistryValueNameC, oldDebugger, errorMessage))
break;
}
if (!registryDeleteValue(handle, debuggerRegistryDefaultValueNameC, errorMessage))
break;
success = true;
} while (false);
if (handle)
RegCloseKey(handle);
return success;
}
bool uninstall(QString *errorMessage)
{
if (!unregisterDebuggerKey(debuggerRegistryKeyC, debuggerCall(), RegistryAccess::DefaultAccessMode, errorMessage))
return false;
#ifdef Q_OS_WIN64
if (!unregisterDebuggerKey(debuggerRegistryKeyC, debuggerCall(QLatin1String("-wow")), RegistryAccess::Registry32Mode, errorMessage))
return false;
#else
if (is64BitWindowsSystem()) {
if (!unregisterDebuggerKey(debuggerRegistryKeyC, debuggerCall(QLatin1String("-wow")), RegistryAccess::Registry64Mode, errorMessage))
return false;
}
#endif
return true;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QApplication::setApplicationName(QLatin1String(titleC));
QApplication::setOrganizationName(QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR));
QString errorMessage;
if (!parseArguments(QApplication::arguments(), &errorMessage)) {
qWarning("%s\n", qPrintable(errorMessage));
usage(QCoreApplication::applicationFilePath(), errorMessage);
return -1;
}
if (debug)
qDebug() << "Mode=" << optMode << " PID=" << argProcessId << " Evt=" << argWinCrashEvent;
int ex = 0;
switch (optMode) {
case HelpMode:
usage(QCoreApplication::applicationFilePath(), errorMessage);
break;
case ForceCreatorMode:
// TODO: pass asClient=true for new CDB engine.
ex = startCreatorAsDebugger(false, &errorMessage) ? 0 : -1;
break;
case ForceDefaultMode:
ex = startDefaultDebugger(&errorMessage) ? 0 : -1;
break;
case PromptMode:
ex = chooseDebugger(&errorMessage) ? 0 : -1;
break;
case RegisterMode:
ex = install(&errorMessage) ? 0 : -1;
break;
case UnregisterMode:
ex = uninstall(&errorMessage) ? 0 : -1;
break;
}
if (ex && !errorMessage.isEmpty()) {
if (noguiMode)
qWarning("%s\n", qPrintable(errorMessage));
else
QMessageBox::warning(0, QLatin1String(titleC), errorMessage, QMessageBox::Ok);
}
return ex;
}