/**************************************************************************
**
** 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)
<< ":
"
<< "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;
}