Added QtcDebugger (Dispatcher for crashed Windows processes).

Called with crashed process and prompts the user to
choose between Qt Creator and Default Debugger.
Acked-by: Daniel Molkentin <daniel.molkentin@nokia.com>
This commit is contained in:
Friedemann Kleint
2009-05-29 11:11:53 +02:00
parent 957a8f3479
commit 27f9d9727d
3 changed files with 440 additions and 0 deletions

View File

@@ -0,0 +1,424 @@
/**************************************************************************
**
** 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 <QtGui/QApplication>
#include <QtGui/QMessageBox>
#include <QtCore/QDebug>
#include <QtCore/QTextStream>
#include <QtCore/QFileInfo>
#include <QtCore/QByteArray>
#include <QtCore/QString>
#include <QtCore/QDir>
#include <QtCore/QProcess>
#include <QtGui/QPushButton>
#include <windows.h>
#include <psapi.h>
enum { debug = 0 };
static const char *titleC = "Qt Creator Debugger";
static const char *organizationC = "Nokia";
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";
#ifdef __GNUC__
static inline QString wCharToQString(const WCHAR *w) { return QString::fromUtf16(reinterpret_cast<const ushort*>(w)); }
#define RRF_RT_ANY 0x0000ffff // no type restriction
#else
static inline QString wCharToQString(const WCHAR *w) { return QString::fromUtf16(w); }
#endif
enum Mode { HelpMode, 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("<unknown error>");
}
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("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;
}
}
}
if (optMode != HelpMode && 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 << "<html><body>";
if (message.isEmpty()) {
str << "<h1>" << titleC << "</h1>"
<< "<p>Dispatcher that launches the desired debugger for a crashed process"
" according to <a href=\"" << linkC << "\">Enabling Postmortem Debugging</a>.</p>";
} else {
str << "<b>" << message << "</b>";
}
str << "<pre>"
<< "Usage: " << QFileInfo(binary).baseName() << "[-wow] [-help|-?|qtcreator|default] &lt;process-id> &lt;event-id>\n"
<< "Options: -help, -? Display this help\n"
<< " -qtcreator Launch Qt Creator without prompting\n"
<< " -default Launch Default handler without prompting\n"
<< " -wow Indicates Wow32 call\n"
<< "</pre>"
<< "<p>To install, modify the registry key <i>HKEY_LOCAL_MACHINE\\" << wCharToQString(debuggerRegistryKeyC)
<< "</i>:</p><ul>"
<< "<li>Create a copy of the string value <i>" << wCharToQString(debuggerRegistryValueNameC)
<< "</i> as <i>" << wCharToQString(debuggerRegistryDefaultValueNameC) << "</i>"
<< "<li>Change the value of <i>" << wCharToQString(debuggerRegistryValueNameC) << "</i> to "
<< "<pre>\"" << QDir::toNativeSeparators(binary) << "\" %ld %ld</pre>"
<< "</ul>"
<< "<p>On 64-bit systems, do the same for the key <i>HKEY_LOCAL_MACHINE\\" << wCharToQString(debuggerWow32RegistryKeyC) << "</i>, "
<< "setting the new value to <pre>\"" << QDir::toNativeSeparators(binary) << "\" -wow %ld %ld</pre></p>"
<< "</body></html>";
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<const char*>(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<const unsigned short*>(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<const BYTE *>(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 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;
}
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;
}
if (ex && !errorMessage.isEmpty()) {
qWarning("%s\n", qPrintable(errorMessage));
QMessageBox::warning(0, QLatin1String(titleC), errorMessage, QMessageBox::Ok);
}
return ex;
}

View File

@@ -0,0 +1,12 @@
TARGET = qtcdebugger
TEMPLATE = app
CONFIG += console
SOURCES += main.cpp
LIBS *= -lpsapi
# PS API and registry functions
contains(QMAKE_CXX, cl) {
LIBS *= -ladvapi32
}
DESTDIR=..\..\..\bin

View File

@@ -1,2 +1,6 @@
TEMPLATE = subdirs
SUBDIRS = qtcreator/static.pro qtcreator/translations
win32 {
SUBDIRS += qtcreator/qtcdebugger
}