/************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Qt Software Information (qt-info@nokia.com) ** ** Commercial Usage ** ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** **************************************************************************/ /* 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 enum { debug = 0 }; static const char *titleC = "Qt Creator Debugger"; static const char *organizationC = "Nokia"; static const char *applicationFileC = "qtcdebugger"; static const WCHAR *debuggerRegistryKeyC = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug"; // Optional static const WCHAR *debuggerWow32RegistryKeyC = L"Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug"; static const WCHAR *debuggerRegistryValueNameC = L"Debugger"; static const WCHAR *debuggerRegistryDefaultValueNameC = L"Debugger.Default"; static const char *linkC = "http://msdn.microsoft.com/en-us/library/cc266343.aspx"; static inline QString wCharToQString(const WCHAR *w) { return QString::fromUtf16(reinterpret_cast(w)); } #ifdef __GNUC__ #define RRF_RT_ANY 0x0000ffff // no type restriction #endif enum Mode { HelpMode, InstallMode, UninstallMode, PromptMode, ForceCreatorMode, ForceDefaultMode }; Mode optMode = PromptMode; bool optIsWow = false; unsigned long argProcessId = 0; quint64 argWinCrashEvent = 0; static QString winErrorMessage(unsigned long error) { QString rc = QString::fromLatin1("#%1: ").arg(error); ushort *lpMsgBuf; const int len = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error, 0, (LPTSTR)&lpMsgBuf, 0, NULL); if (len) { rc = QString::fromUtf16(lpMsgBuf, len); LocalFree(lpMsgBuf); } else { rc += QString::fromLatin1(""); } return rc; } static inline QString msgFunctionFailed(const char *f, unsigned long error) { return QString::fromLatin1("'%1' failed: %2").arg(QLatin1String(f), winErrorMessage(error)); } 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("install")) { optMode = InstallMode; } else if (arg == QLatin1String("uninstall")) { optMode = UninstallMode; } else if (arg == QLatin1String("wow")) { optIsWow = true; } 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 InstallMode: case UninstallMode: break; default: if (argProcessId == 0) { *errorMessage = QString::fromLatin1("Please specify the process-id."); return false; } } return true; } 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|install|uninstall] <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"
        << "         -install    Install itself (requires administrative privileges)\n"
        << "         -uninstall  Uninstall itself (requires administrative privileges)\n"
        << "         -wow        Indicates Wow32 call\n"
        << "
" << "

To install, modify the registry key HKEY_LOCAL_MACHINE\\" << wCharToQString(debuggerRegistryKeyC) << ":

    " << "
  • Create a copy of the string value " << wCharToQString(debuggerRegistryValueNameC) << " as " << wCharToQString(debuggerRegistryDefaultValueNameC) << "" << "
  • Change the value of " << wCharToQString(debuggerRegistryValueNameC) << " to " << "
    \"" << QDir::toNativeSeparators(binary) << "\" %ld %ld
    " << "
" << "

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\"
" << ""; QMessageBox msgBox(QMessageBox::Information, QLatin1String(titleC), msg, QMessageBox::Ok); msgBox.exec(); } // ------- Registry helpers static bool openRegistryKey(HKEY category, // HKEY_LOCAL_MACHINE, etc. const WCHAR *key, bool readWrite, HKEY *keyHandle, QString *errorMessage) { REGSAM accessRights = KEY_READ; if (readWrite) accessRights |= KEY_SET_VALUE; const LONG rc = RegOpenKeyEx(category, key, 0, accessRights, keyHandle); if (rc != ERROR_SUCCESS) { *errorMessage = msgFunctionFailed("RegOpenKeyEx", rc); return false; } return true; } static inline QString msgRegistryOperationFailed(const char *op, const WCHAR *valueName, const QString &why) { QString rc = QLatin1String("Registry "); rc += QLatin1String(op); rc += QLatin1String(" of "); rc += wCharToQString(valueName); rc += QLatin1String(" failed: "); rc += why; return rc; } static bool registryReadBinaryKey(HKEY handle, // HKEY_LOCAL_MACHINE, etc. const WCHAR *valueName, QByteArray *data, QString *errorMessage) { data->clear(); DWORD type; DWORD size; // get size and retrieve LONG rc = RegQueryValueEx(handle, valueName, 0, &type, 0, &size); if (rc != ERROR_SUCCESS) { *errorMessage = msgRegistryOperationFailed("read", valueName, msgFunctionFailed("RegQueryValueEx1", rc)); return false; } BYTE *dataC = new BYTE[size + 1]; // Will be Utf16 in case of a string rc = RegQueryValueEx(handle, valueName, 0, &type, dataC, &size); if (rc != ERROR_SUCCESS) { *errorMessage = msgRegistryOperationFailed("read", valueName, msgFunctionFailed("RegQueryValueEx2", rc)); return false; } *data = QByteArray(reinterpret_cast(dataC), size); delete [] dataC; return true; } static bool registryReadStringKey(HKEY handle, // HKEY_LOCAL_MACHINE, etc. const WCHAR *valueName, QString *s, QString *errorMessage) { QByteArray data; if (!registryReadBinaryKey(handle, valueName, &data, errorMessage)) return false; data += '\0'; data += '\0'; *s = QString::fromUtf16(reinterpret_cast(data.data())); return true; } 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 bool startCreatorAsDebugger(const QApplication &a, QString *errorMessage) { const QString dir = a.applicationDirPath(); const QString binary = dir + QLatin1String("/qtcreator.exe"); QStringList args; args << QLatin1String("-debug") << QString::number(argProcessId) << QLatin1String("-wincrashevent") << QString::number(argWinCrashEvent); if (debug) qDebug() << binary << args; QProcess p; p.setWorkingDirectory(dir); p.start(binary, args, QIODevice::NotOpen); if (!p.waitForStarted()) { *errorMessage = QString::fromLatin1("Unable to start %1!").arg(binary); return false; } p.waitForFinished(-1); return true; } bool startDefaultDebugger(const QApplication & /*a*/, QString *errorMessage) { // Read out default value HKEY handle; if (!openRegistryKey(HKEY_LOCAL_MACHINE, optIsWow ? debuggerWow32RegistryKeyC : debuggerRegistryKeyC, false, &handle, errorMessage)) return false; QString defaultDebugger; if (!registryReadStringKey(handle, debuggerRegistryDefaultValueNameC, &defaultDebugger, errorMessage)) { RegCloseKey(handle); return false; } RegCloseKey(handle); // 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; p.start(defaultDebugger, QIODevice::NotOpen); if (!p.waitForStarted()) { *errorMessage = QString::fromLatin1("Unable to start %1!").arg(defaultDebugger); return false; } p.waitForFinished(-1); return true; } bool chooseDebugger(const QApplication &a, QString *errorMessage) { const QString msg = QString::fromLatin1("The application \"%1\" (process id %2) crashed. Would you like to debug it?").arg(getProcessBaseName(argProcessId)).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); msgBox.exec(); if (msgBox.clickedButton() == creatorButton) { // Just in case, default to standard if (startCreatorAsDebugger(a, errorMessage)) return true; return startDefaultDebugger(a, errorMessage); } if (msgBox.clickedButton() == defaultButton) return startDefaultDebugger(a, errorMessage); return true; } // Installation helpers: Format the debugger call with placeholders for PID and event // '"[path]\qtcdebugger" [-wow] %ld %ld'. static QString debuggerCall(const QApplication &a, const QString &additionalOption = QString()) { QString rc; QTextStream str(&rc); str << '"' << QDir::toNativeSeparators(a.applicationFilePath()) << '"'; if (!additionalOption.isEmpty()) str << ' ' << additionalOption; str << " %ld %ld"; return rc; } // Installation 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, QString *errorMessage) { HKEY handle = 0; bool success = false; do { if (!openRegistryKey(HKEY_LOCAL_MACHINE, key, true, &handle, errorMessage)) break; QString oldDebugger; if (!registryReadStringKey(handle, debuggerRegistryValueNameC, &oldDebugger, errorMessage)) break; if (oldDebugger.contains(QLatin1String(applicationFileC), Qt::CaseInsensitive)) { *errorMessage = QLatin1String("The program is already installed."); return false; } if (!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(const QApplication &a, QString *errorMessage) { if (!registerDebuggerKey(debuggerRegistryKeyC, debuggerCall(a), errorMessage)) return false; #ifdef Q_OS_WIN64 if (!registerDebuggerKey(debuggerWow32RegistryKeyC, debuggerCall(a, QLatin1String("-wow")), errorMessage)) return false; #endif return true; } // Uninstall helper: Restore the original debugger key static bool unregisterDebuggerKey(const WCHAR *key, QString *errorMessage) { HKEY handle = 0; bool success = false; do { if (!openRegistryKey(HKEY_LOCAL_MACHINE, key, true, &handle, errorMessage)) break; QString oldDebugger; if (!registryReadStringKey(handle, debuggerRegistryDefaultValueNameC, &oldDebugger, errorMessage)) break; 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(const QApplication & /*a*/, QString *errorMessage) { if (!unregisterDebuggerKey(debuggerRegistryKeyC, errorMessage)) return false; #ifdef Q_OS_WIN64 if (!unregisterDebuggerKey(debuggerWow32RegistryKeyC, errorMessage)) return false; #endif return true; } int main(int argc, char *argv[]) { QApplication a(argc, argv); a.setApplicationName(QLatin1String(titleC)); a.setOrganizationName(QLatin1String(organizationC)); QString errorMessage; if (!parseArguments(a.arguments(), &errorMessage)) { qWarning("%s\n", qPrintable(errorMessage)); usage(QCoreApplication::applicationFilePath(), errorMessage); return -1; } if (debug) qDebug() << "Mode=" << optMode << " PID=" << argProcessId << " Evt=" << argWinCrashEvent; bool ex = 0; switch (optMode) { case HelpMode: usage(QCoreApplication::applicationFilePath(), errorMessage); break; case ForceCreatorMode: ex = startCreatorAsDebugger(a, &errorMessage) ? 0 : -1; break; case ForceDefaultMode: ex = startDefaultDebugger(a, &errorMessage) ? 0 : -1; break; case PromptMode: ex = chooseDebugger(a, &errorMessage) ? 0 : -1; break; case InstallMode: ex = install(a, &errorMessage) ? 0 : -1; break; case UninstallMode: ex = uninstall(a, &errorMessage) ? 0 : -1; break; } if (ex && !errorMessage.isEmpty()) { qWarning("%s\n", qPrintable(errorMessage)); QMessageBox::warning(0, QLatin1String(titleC), errorMessage, QMessageBox::Ok); } return ex; }