forked from qt-creator/qt-creator
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:
424
share/qtcreator/qtcdebugger/main.cpp
Normal file
424
share/qtcreator/qtcdebugger/main.cpp
Normal 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] <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"
|
||||
<< " -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;
|
||||
}
|
||||
12
share/qtcreator/qtcdebugger/qtcdebugger.pro
Normal file
12
share/qtcreator/qtcdebugger/qtcdebugger.pro
Normal 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
|
||||
@@ -1,2 +1,6 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = qtcreator/static.pro qtcreator/translations
|
||||
|
||||
win32 {
|
||||
SUBDIRS += qtcreator/qtcdebugger
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user