Debugger: Enable attaching with -client option.

Make it possible to trigger a debug-attach in a running instance
of Qt Creator by means of -client, use that in qtcdebugger, thus
enabling it to attaching to crashing executables in run mode
(Windows/CDB).

Modify IPlugin and application so that the complete command line is
serialized and transmitted to a running instance via QtSingleApplication
if -client is specified. Introduce IPlugin::remoteArgument and use that
in core for opening files and in the debugger for attaching.

Use -client in qtcdebugger with some logic to keep it alive as long as
the debuggee, make CDB break in that case as the events are not replayed
correctly in case the debugger is not spawned by the registered handler.
Rubber-stamped-by: con <qtc-committer@nokia.com>
This commit is contained in:
Friedemann Kleint
2009-12-14 18:01:39 +01:00
parent d94b7b08c0
commit bd8d2b0b8a
11 changed files with 238 additions and 101 deletions

View File

@@ -134,40 +134,6 @@ static inline QString msgSendArgumentFailed()
return QCoreApplication::translate("Application", "Unable to send command line arguments to the already running instance. It appears to be not responding."); return QCoreApplication::translate("Application", "Unable to send command line arguments to the already running instance. It appears to be not responding.");
} }
// Prepare a remote argument: If it is a relative file, add the current directory
// since the the central instance might be running in a different directory.
static inline QString prepareRemoteArgument(const QString &a)
{
QFileInfo fi(a);
if (!fi.exists())
return a;
if (fi.isRelative())
return fi.absoluteFilePath();
return a;
}
// Send the arguments to an already running instance of Qt Creator
static bool sendArguments(SharedTools::QtSingleApplication &app, const QStringList &arguments)
{
if (!arguments.empty()) {
// Send off arguments
const QStringList::const_iterator acend = arguments.constEnd();
for (QStringList::const_iterator it = arguments.constBegin(); it != acend; ++it) {
if (!app.sendMessage(prepareRemoteArgument(*it))) {
displayError(msgSendArgumentFailed());
return false;
}
}
}
// Special empty argument means: Show and raise (the slot just needs to be triggered)
if (!app.sendMessage(QString())) {
displayError(msgSendArgumentFailed());
return false;
}
return true;
}
static inline QStringList getPluginPaths() static inline QStringList getPluginPaths()
{ {
QStringList rc; QStringList rc;
@@ -287,8 +253,13 @@ int main(int argc, char **argv)
} }
const bool isFirstInstance = !app.isRunning(); const bool isFirstInstance = !app.isRunning();
if (!isFirstInstance && foundAppOptions.contains(QLatin1String(CLIENT_OPTION))) if (!isFirstInstance && foundAppOptions.contains(QLatin1String(CLIENT_OPTION))) {
return sendArguments(app, pluginManager.arguments()) ? 0 : -1; if (!app.sendMessage(pluginManager.serializedArguments())) {
displayError(msgSendArgumentFailed());
return -1;
}
return 0;
}
pluginManager.loadPlugins(); pluginManager.loadPlugins();
if (coreplugin->hasError()) { if (coreplugin->hasError()) {
@@ -311,9 +282,10 @@ int main(int argc, char **argv)
// Silently fallback to unconnected instances for any subsequent // Silently fallback to unconnected instances for any subsequent
// instances. // instances.
app.initialize(); app.initialize();
QObject::connect(&app, SIGNAL(messageReceived(QString)), coreplugin->plugin(), SLOT(remoteArgument(QString))); QObject::connect(&app, SIGNAL(messageReceived(QString)),
&pluginManager, SLOT(remoteArguments(QString)));
} }
QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), coreplugin->plugin(), SLOT(remoteArgument(QString))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), coreplugin->plugin(), SLOT(fileOpenRequest(QString)));
// Do this after the event loop has started // Do this after the event loop has started
QTimer::singleShot(100, &pluginManager, SLOT(startTests())); QTimer::singleShot(100, &pluginManager, SLOT(startTests()));

View File

@@ -55,6 +55,7 @@ public:
virtual bool initialize(const QStringList &arguments, QString *errorString) = 0; virtual bool initialize(const QStringList &arguments, QString *errorString) = 0;
virtual void extensionsInitialized() = 0; virtual void extensionsInitialized() = 0;
virtual void shutdown() { } virtual void shutdown() { }
virtual void remoteCommand(const QStringList & /* options */, const QStringList & /* arguments */) { }
PluginSpec *pluginSpec() const; PluginSpec *pluginSpec() const;

View File

@@ -320,6 +320,91 @@ QList<PluginSpec *> PluginManager::plugins() const
return d->pluginSpecs; return d->pluginSpecs;
} }
/*!
\fn QString PluginManager::serializedArguments() const
Serialize plugin options and arguments for sending in a single string
via QtSingleApplication:
":myplugin|-option1|-option2|:arguments|argument1|argument2",
as a list of lists started by a keyword with a colon. Arguments are last.
\sa setPluginPaths()
*/
static const char argumentKeywordC[] = ":arguments";
QString PluginManager::serializedArguments() const
{
const QChar separator = QLatin1Char('|');
QString rc;
foreach (const PluginSpec *ps, plugins()) {
if (!ps->arguments().isEmpty()) {
if (!rc.isEmpty())
rc += separator;
rc += QLatin1Char(':');
rc += ps->name();
rc += separator;
rc += ps->arguments().join(QString(separator));
}
}
if (!d->arguments.isEmpty()) {
if (!rc.isEmpty())
rc += separator;
rc += QLatin1String(argumentKeywordC);
// If the argument appears to be a file, make it absolute
// when sending to another instance.
foreach(const QString &argument, d->arguments) {
rc += separator;
const QFileInfo fi(argument);
if (fi.exists() && fi.isRelative()) {
rc += fi.absoluteFilePath();
} else {
rc += argument;
}
}
}
return rc;
}
/* Extract a sublist from the serialized arguments
* indicated by a keyword starting with a colon indicator:
* ":a,i1,i2,:b:i3,i4" with ":a" -> "i1,i2"
*/
static QStringList subList(const QStringList &in, const QString &key)
{
QStringList rc;
// Find keyword and copy arguments until end or next keyword
const QStringList::const_iterator inEnd = in.constEnd();
QStringList::const_iterator it = qFind(in.constBegin(), inEnd, key);
if (it != inEnd) {
const QChar nextIndicator = QLatin1Char(':');
for (++it; it != inEnd && !it->startsWith(nextIndicator); ++it)
rc.append(*it);
}
return rc;
}
/*!
\fn PluginManager::remoteArguments(const QString &argument)
Parses the options encoded by serializedArguments() const
and passes them on to the respective plugins along with the arguments.
*/
void PluginManager::remoteArguments(const QString &serializedArgument)
{
if (serializedArgument.isEmpty())
return;
QStringList serializedArguments = serializedArgument.split(QLatin1Char('|'));
const QStringList arguments = subList(serializedArguments, QLatin1String(argumentKeywordC));
foreach (const PluginSpec *ps, plugins()) {
if (ps->state() == PluginSpec::Running) {
const QStringList pluginOptions = subList(serializedArguments, QLatin1Char(':') + ps->name());
ps->plugin()->remoteCommand(pluginOptions, arguments);
}
}
}
/*! /*!
\fn bool PluginManager::parseOptions(const QStringList &args, const QMap<QString, bool> &appOptions, QMap<QString, QString> *foundAppOptions, QString *errorString) \fn bool PluginManager::parseOptions(const QStringList &args, const QMap<QString, bool> &appOptions, QMap<QString, QString> *foundAppOptions, QString *errorString)
Takes the list of command line options in \a args and parses them. Takes the list of command line options in \a args and parses them.

View File

@@ -108,6 +108,8 @@ public:
void formatPluginOptions(QTextStream &str, int optionIndentation, int descriptionIndentation) const; void formatPluginOptions(QTextStream &str, int optionIndentation, int descriptionIndentation) const;
void formatPluginVersions(QTextStream &str) const; void formatPluginVersions(QTextStream &str) const;
QString serializedArguments() const;
bool runningTests() const; bool runningTests() const;
QString testDataDirectory() const; QString testDataDirectory() const;
@@ -116,6 +118,10 @@ signals:
void aboutToRemoveObject(QObject *obj); void aboutToRemoveObject(QObject *obj);
void pluginsChanged(); void pluginsChanged();
public slots:
void remoteArguments(const QString &serializedArguments);
private slots: private slots:
void startTests(); void startTests();

View File

@@ -87,16 +87,15 @@ void CorePlugin::extensionsInitialized()
m_mainWindow->extensionsInitialized(); m_mainWindow->extensionsInitialized();
} }
void CorePlugin::remoteArgument(const QString& arg) void CorePlugin::remoteCommand(const QStringList & /* options */, const QStringList &args)
{ {
// An empty argument is sent to trigger activation m_mainWindow->openFiles(args);
// of the window via QtSingleApplication. It should be
// the last of a sequence.
if (arg.isEmpty()) {
m_mainWindow->activateWindow(); m_mainWindow->activateWindow();
} else { }
m_mainWindow->openFiles(QStringList(arg));
} void CorePlugin::fileOpenRequest(const QString &f)
{
remoteCommand(QStringList(), QStringList(f));
} }
void CorePlugin::shutdown() void CorePlugin::shutdown()

View File

@@ -49,9 +49,10 @@ public:
virtual bool initialize(const QStringList &arguments, QString *errorMessage = 0); virtual bool initialize(const QStringList &arguments, QString *errorMessage = 0);
virtual void extensionsInitialized(); virtual void extensionsInitialized();
virtual void shutdown(); virtual void shutdown();
virtual void remoteCommand(const QStringList & /* options */, const QStringList &args);
public slots: public slots:
void remoteArgument(const QString&); void fileOpenRequest(const QString&);
private: private:
void parseArguments(const QStringList & arguments); void parseArguments(const QStringList & arguments);

View File

@@ -812,10 +812,16 @@ void CdbDebugEnginePrivate::processCreatedAttached(ULONG64 processHandle, ULONG6
if (!crashParameter.isEmpty()) { if (!crashParameter.isEmpty()) {
ULONG64 evtNr = crashParameter.toULongLong(); ULONG64 evtNr = crashParameter.toULongLong();
const HRESULT hr = m_cif.debugControl->SetNotifyEventHandle(evtNr); const HRESULT hr = m_cif.debugControl->SetNotifyEventHandle(evtNr);
if (FAILED(hr)) // Unless QtCreator is spawned by the debugger and inherits the handles,
// the event handling does not work reliably
// (that is, the crash event is not delivered).
if (SUCCEEDED(hr)) {
QTimer::singleShot(0, m_engine, SLOT(slotBreakAttachToCrashed()));
} else {
m_engine->warning(QString::fromLatin1("Handshake failed on event #%1: %2").arg(evtNr).arg(msgComFailed("SetNotifyEventHandle", hr))); m_engine->warning(QString::fromLatin1("Handshake failed on event #%1: %2").arg(evtNr).arg(msgComFailed("SetNotifyEventHandle", hr)));
} }
} }
}
m_engine->setState(InferiorRunning, Q_FUNC_INFO, __LINE__); m_engine->setState(InferiorRunning, Q_FUNC_INFO, __LINE__);
if (debugCDBExecution) if (debugCDBExecution)
qDebug() << "<processCreatedAttached" << executionStatusString(m_cif.debugControl); qDebug() << "<processCreatedAttached" << executionStatusString(m_cif.debugControl);
@@ -1234,6 +1240,17 @@ bool CdbDebugEnginePrivate::interruptInterferiorProcess(QString *errorMessage)
return true; return true;
} }
void CdbDebugEngine::slotBreakAttachToCrashed()
{
// Force a break when attaching to crashed process (if Creator was not spawned
// from handler).
if (state() != InferiorStopped) {
manager()->showDebuggerOutput(LogMisc, QLatin1String("Forcing break..."));
m_d->m_dumper->disable();
interruptInferior();
}
}
void CdbDebugEngine::interruptInferior() void CdbDebugEngine::interruptInferior()
{ {
if (!m_d->m_hDebuggeeProcess || !m_d->isDebuggeeRunning()) if (!m_d->m_hDebuggeeProcess || !m_d->isDebuggeeRunning())

View File

@@ -111,6 +111,7 @@ private slots:
void slotConsoleStubStarted(); void slotConsoleStubStarted();
void slotConsoleStubError(const QString &msg); void slotConsoleStubError(const QString &msg);
void slotConsoleStubTerminated(); void slotConsoleStubTerminated();
void slotBreakAttachToCrashed();
void warning(const QString &w); void warning(const QString &w);
private: private:

View File

@@ -505,14 +505,19 @@ bool DebuggingHelperOptionPage::matches(const QString &s) const
// //
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
DebuggerPlugin::AttachRemoteParameters::AttachRemoteParameters() :
attachPid(0),
winCrashEvent(0)
{
}
DebuggerPlugin::DebuggerPlugin() DebuggerPlugin::DebuggerPlugin()
: m_manager(0), : m_manager(0),
m_debugMode(0), m_debugMode(0),
m_locationMark(0), m_locationMark(0),
m_gdbRunningContext(0), m_gdbRunningContext(0),
m_cmdLineEnabledEngines(AllEngineTypes), m_cmdLineEnabledEngines(AllEngineTypes),
m_cmdLineAttachPid(0),
m_cmdLineWinCrashEvent(0),
m_toggleLockedAction(0) m_toggleLockedAction(0)
{} {}
@@ -555,9 +560,10 @@ static QString msgInvalidNumericParameter(const QString &a, const QString &numbe
} }
// Parse arguments // Parse arguments
bool DebuggerPlugin::parseArgument(QStringList::const_iterator &it, static bool parseArgument(QStringList::const_iterator &it,
const QStringList::const_iterator &cend, const QStringList::const_iterator &cend,
QString *errorMessage) DebuggerPlugin::AttachRemoteParameters *attachRemoteParameters,
unsigned *enabledEngines, QString *errorMessage)
{ {
const QString &option = *it; const QString &option = *it;
// '-debug <pid>' // '-debug <pid>'
@@ -568,10 +574,10 @@ bool DebuggerPlugin::parseArgument(QStringList::const_iterator &it,
return false; return false;
} }
bool ok; bool ok;
m_cmdLineAttachPid = it->toULongLong(&ok); attachRemoteParameters->attachPid = it->toULongLong(&ok);
if (!ok) { if (!ok) {
m_cmdLineAttachPid = 0; attachRemoteParameters->attachPid = 0;
m_cmdLineAttachCore = *it; attachRemoteParameters->attachCore = *it;
} }
return true; return true;
} }
@@ -584,7 +590,7 @@ bool DebuggerPlugin::parseArgument(QStringList::const_iterator &it,
return false; return false;
} }
bool ok; bool ok;
m_cmdLineWinCrashEvent = it->toULongLong(&ok); attachRemoteParameters->winCrashEvent = it->toULongLong(&ok);
if (!ok) { if (!ok) {
*errorMessage = msgInvalidNumericParameter(option, *it); *errorMessage = msgInvalidNumericParameter(option, *it);
return false; return false;
@@ -593,40 +599,55 @@ bool DebuggerPlugin::parseArgument(QStringList::const_iterator &it,
} }
// engine disabling // engine disabling
if (option == QLatin1String("-disable-cdb")) { if (option == QLatin1String("-disable-cdb")) {
m_cmdLineEnabledEngines &= ~CdbEngineType; *enabledEngines &= ~Debugger::CdbEngineType;
return true; return true;
} }
if (option == QLatin1String("-disable-gdb")) { if (option == QLatin1String("-disable-gdb")) {
m_cmdLineEnabledEngines &= ~GdbEngineType; *enabledEngines &= ~Debugger::GdbEngineType;
return true; return true;
} }
if (option == QLatin1String("-disable-sdb")) { if (option == QLatin1String("-disable-sdb")) {
m_cmdLineEnabledEngines &= ~ScriptEngineType; *enabledEngines &= ~Debugger::ScriptEngineType;
return true; return true;
} }
*errorMessage = tr("Invalid debugger option: %1").arg(option); *errorMessage = DebuggerPlugin::tr("Invalid debugger option: %1").arg(option);
return false; return false;
} }
bool DebuggerPlugin::parseArguments(const QStringList &args, QString *errorMessage) static bool parseArguments(const QStringList &args,
DebuggerPlugin::AttachRemoteParameters *attachRemoteParameters,
unsigned *enabledEngines, QString *errorMessage)
{ {
const QStringList::const_iterator cend = args.constEnd(); const QStringList::const_iterator cend = args.constEnd();
for (QStringList::const_iterator it = args.constBegin(); it != cend; ++it) for (QStringList::const_iterator it = args.constBegin(); it != cend; ++it)
if (!parseArgument(it, cend, errorMessage)) if (!parseArgument(it, cend, attachRemoteParameters, enabledEngines, errorMessage))
return false; return false;
if (Debugger::Constants::Internal::debug) if (Debugger::Constants::Internal::debug)
qDebug().nospace() << args << "engines=0x" qDebug().nospace() << args << "engines=0x"
<< QString::number(m_cmdLineEnabledEngines, 16) << QString::number(*enabledEngines, 16)
<< " pid" << m_cmdLineAttachPid << " pid" << attachRemoteParameters->attachPid
<< " core" << m_cmdLineAttachCore << '\n'; << " core" << attachRemoteParameters->attachCore << '\n';
return true; return true;
} }
void DebuggerPlugin::remoteCommand(const QStringList &options, const QStringList &)
{
QString errorMessage;
AttachRemoteParameters parameters;
unsigned dummy = 0;
// Did we receive a request for debugging (unless it is ourselves)?
if (parseArguments(options, &parameters, &dummy, &errorMessage)
&& parameters.attachPid != quint64(QCoreApplication::applicationPid())) {
m_attachRemoteParameters = parameters;
attachCmdLine();
}
}
bool DebuggerPlugin::initialize(const QStringList &arguments, QString *errorMessage) bool DebuggerPlugin::initialize(const QStringList &arguments, QString *errorMessage)
{ {
// Do not fail the whole plugin if something goes wrong here // Do not fail the whole plugin if something goes wrong here
if (!parseArguments(arguments, errorMessage)) { if (!parseArguments(arguments, &m_attachRemoteParameters, &m_cmdLineEnabledEngines, errorMessage)) {
*errorMessage = tr("Error evaluating command line arguments: %1") *errorMessage = tr("Error evaluating command line arguments: %1")
.arg(*errorMessage); .arg(*errorMessage);
qWarning("%s\n", qPrintable(*errorMessage)); qWarning("%s\n", qPrintable(*errorMessage));
@@ -1000,18 +1021,25 @@ void DebuggerPlugin::extensionsInitialized()
//qDebug() << "EXTENSIONS INITIALIZED:" << env; //qDebug() << "EXTENSIONS INITIALIZED:" << env;
if (!env.isEmpty()) if (!env.isEmpty())
m_manager->runTest(QString::fromLocal8Bit(env)); m_manager->runTest(QString::fromLocal8Bit(env));
if (m_cmdLineAttachPid) if (m_attachRemoteParameters.attachPid || !m_attachRemoteParameters.attachCore.isEmpty())
QTimer::singleShot(0, this, SLOT(attachCmdLinePid())); QTimer::singleShot(0, this, SLOT(attachCmdLine()));
if (!m_cmdLineAttachCore.isEmpty())
QTimer::singleShot(0, this, SLOT(attachCmdLineCore()));
} }
void DebuggerPlugin::attachCmdLinePid() void DebuggerPlugin::attachCmdLine()
{ {
m_manager->showStatusMessage(tr("Attaching to PID %1.").arg(m_cmdLineAttachPid)); if (m_manager->state() != DebuggerNotReady)
return;
if (m_attachRemoteParameters.attachPid) {
m_manager->showStatusMessage(tr("Attaching to PID %1.").arg(m_attachRemoteParameters.attachPid));
const QString crashParameter = const QString crashParameter =
m_cmdLineWinCrashEvent ? QString::number(m_cmdLineWinCrashEvent) : QString(); m_attachRemoteParameters.winCrashEvent ? QString::number(m_attachRemoteParameters.winCrashEvent) : QString();
attachExternalApplication(m_cmdLineAttachPid, crashParameter); attachExternalApplication(m_attachRemoteParameters.attachPid, crashParameter);
return;
}
if (!m_attachRemoteParameters.attachCore.isEmpty()) {
m_manager->showStatusMessage(tr("Attaching to core %1.").arg(m_attachRemoteParameters.attachCore));
attachCore(m_attachRemoteParameters.attachCore, QString());
}
} }
/*! Activates the previous mode when the current mode is the debug mode. */ /*! Activates the previous mode when the current mode is the debug mode. */
@@ -1326,12 +1354,6 @@ void DebuggerPlugin::attachExternalApplication(qint64 pid, const QString &crashP
ProjectExplorerPlugin::instance()->startRunControl(runControl, ProjectExplorer::Constants::DEBUGMODE); ProjectExplorerPlugin::instance()->startRunControl(runControl, ProjectExplorer::Constants::DEBUGMODE);
} }
void DebuggerPlugin::attachCmdLineCore()
{
m_manager->showStatusMessage(tr("Attaching to core %1.").arg(m_cmdLineAttachCore));
attachCore(m_cmdLineAttachCore, QString());
}
void DebuggerPlugin::attachCore() void DebuggerPlugin::attachCore()
{ {
AttachCoreDialog dlg(m_manager->mainWindow()); AttachCoreDialog dlg(m_manager->mainWindow());

View File

@@ -36,7 +36,6 @@
#include <QtCore/QStringList> #include <QtCore/QStringList>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QAbstractItemView;
class QAction; class QAction;
class QCursor; class QCursor;
class QMenu; class QMenu;
@@ -68,6 +67,15 @@ class DebuggerPlugin : public ExtensionSystem::IPlugin
Q_OBJECT Q_OBJECT
public: public:
struct AttachRemoteParameters {
AttachRemoteParameters();
quint64 attachPid;
QString attachCore;
// Event handle for attaching to crashed Windows processes.
quint64 winCrashEvent;
};
DebuggerPlugin(); DebuggerPlugin();
~DebuggerPlugin(); ~DebuggerPlugin();
@@ -75,6 +83,7 @@ private:
virtual bool initialize(const QStringList &arguments, QString *error_message); virtual bool initialize(const QStringList &arguments, QString *error_message);
virtual void shutdown(); virtual void shutdown();
virtual void extensionsInitialized(); virtual void extensionsInitialized();
virtual void remoteCommand(const QStringList &options, const QStringList &arguments);
QVariant configValue(const QString &name) const; QVariant configValue(const QString &name) const;
@@ -106,16 +115,11 @@ private slots:
void startRemoteApplication(); void startRemoteApplication();
void attachExternalApplication(); void attachExternalApplication();
void attachCore(); void attachCore();
void attachCmdLinePid(); void attachCmdLine();
void attachCmdLineCore();
private: private:
void readSettings(); void readSettings();
void writeSettings() const; void writeSettings() const;
bool parseArguments(const QStringList &args, QString *errorMessage);
inline bool parseArgument(QStringList::const_iterator &it,
const QStringList::const_iterator& end,
QString *errorMessage);
void attachExternalApplication(qint64 pid, const QString &crashParameter = QString()); void attachExternalApplication(qint64 pid, const QString &crashParameter = QString());
void attachCore(const QString &core, const QString &exeFileName); void attachCore(const QString &core, const QString &exeFileName);
@@ -131,11 +135,9 @@ private:
QString m_previousMode; QString m_previousMode;
TextEditor::BaseTextMark *m_locationMark; TextEditor::BaseTextMark *m_locationMark;
int m_gdbRunningContext; int m_gdbRunningContext;
AttachRemoteParameters m_attachRemoteParameters;
unsigned m_cmdLineEnabledEngines; unsigned m_cmdLineEnabledEngines;
quint64 m_cmdLineAttachPid;
QString m_cmdLineAttachCore;
// Event handle for attaching to crashed Windows processes.
quint64 m_cmdLineWinCrashEvent;
QAction *m_toggleLockedAction; QAction *m_toggleLockedAction;
QAction *m_startExternalAction; QAction *m_startExternalAction;

View File

@@ -41,6 +41,7 @@
#include <QtCore/QByteArray> #include <QtCore/QByteArray>
#include <QtCore/QString> #include <QtCore/QString>
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QtCore/QTime>
#include <QtCore/QProcess> #include <QtCore/QProcess>
#include <QtGui/QPushButton> #include <QtGui/QPushButton>
@@ -61,6 +62,7 @@ static const WCHAR *debuggerRegistryValueNameC = L"Debugger";
static const WCHAR *debuggerRegistryDefaultValueNameC = L"Debugger.Default"; static const WCHAR *debuggerRegistryDefaultValueNameC = L"Debugger.Default";
static const char *linkC = "http://msdn.microsoft.com/en-us/library/cc266343.aspx"; static const char *linkC = "http://msdn.microsoft.com/en-us/library/cc266343.aspx";
static const char *creatorBinaryC = "qtcreator.exe";
static inline QString wCharToQString(const WCHAR *w) { return QString::fromUtf16(reinterpret_cast<const ushort *>(w)); } static inline QString wCharToQString(const WCHAR *w) { return QString::fromUtf16(reinterpret_cast<const ushort *>(w)); }
#ifdef __GNUC__ #ifdef __GNUC__
@@ -343,23 +345,49 @@ static QString getProcessBaseName(DWORD pid)
// ------- main modes // ------- main modes
bool startCreatorAsDebugger(QString *errorMessage) 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 dir = QApplication::applicationDirPath();
const QString binary = dir + QLatin1String("/qtcreator.exe"); const QString binary = dir + QLatin1Char('/') + QLatin1String(creatorBinaryC);
QStringList args; QStringList args;
if (asClient)
args << QLatin1String("-client");
args << QLatin1String("-debug") << QString::number(argProcessId) args << QLatin1String("-debug") << QString::number(argProcessId)
<< QLatin1String("-wincrashevent") << QString::number(argWinCrashEvent); << QLatin1String("-wincrashevent") << QString::number(argWinCrashEvent);
if (debug) if (debug)
qDebug() << binary << args; qDebug() << binary << args;
QProcess p; QProcess p;
p.setWorkingDirectory(dir); p.setWorkingDirectory(dir);
QTime executionTime;
executionTime.start();
p.start(binary, args, QIODevice::NotOpen); p.start(binary, args, QIODevice::NotOpen);
if (!p.waitForStarted()) { if (!p.waitForStarted()) {
*errorMessage = QString::fromLatin1("Unable to start %1!").arg(binary); *errorMessage = QString::fromLatin1("Unable to start %1!").arg(binary);
return false; return false;
} }
p.waitForFinished(-1); // 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; return true;
} }
@@ -408,7 +436,8 @@ bool startDefaultDebugger(QString *errorMessage)
bool chooseDebugger(QString *errorMessage) bool chooseDebugger(QString *errorMessage)
{ {
QString defaultDebugger; QString defaultDebugger;
const QString msg = QString::fromLatin1("The application \"%1\" (process id %2) crashed. Would you like to debug it?").arg(getProcessBaseName(argProcessId)).arg(argProcessId); 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); QMessageBox msgBox(QMessageBox::Information, QLatin1String(titleC), msg, QMessageBox::Cancel);
QPushButton *creatorButton = msgBox.addButton(QLatin1String("Debug with Qt Creator"), QMessageBox::AcceptRole); QPushButton *creatorButton = msgBox.addButton(QLatin1String("Debug with Qt Creator"), QMessageBox::AcceptRole);
QPushButton *defaultButton = msgBox.addButton(QLatin1String("Debug with default debugger"), QMessageBox::AcceptRole); QPushButton *defaultButton = msgBox.addButton(QLatin1String("Debug with default debugger"), QMessageBox::AcceptRole);
@@ -416,8 +445,10 @@ bool chooseDebugger(QString *errorMessage)
&& !defaultDebugger.isEmpty()); && !defaultDebugger.isEmpty());
msgBox.exec(); msgBox.exec();
if (msgBox.clickedButton() == creatorButton) { if (msgBox.clickedButton() == creatorButton) {
// Just in case, default to standard // Just in case, default to standard. Do not run as client in the unlikely case
if (startCreatorAsDebugger(errorMessage)) // Creator crashed
const bool canRunAsClient = !processName.contains(QLatin1String(creatorBinaryC), Qt::CaseInsensitive);
if (startCreatorAsDebugger(canRunAsClient, errorMessage))
return true; return true;
return startDefaultDebugger(errorMessage); return startDefaultDebugger(errorMessage);
} }
@@ -552,7 +583,7 @@ int main(int argc, char *argv[])
usage(QCoreApplication::applicationFilePath(), errorMessage); usage(QCoreApplication::applicationFilePath(), errorMessage);
break; break;
case ForceCreatorMode: case ForceCreatorMode:
ex = startCreatorAsDebugger(&errorMessage) ? 0 : -1; ex = startCreatorAsDebugger(true, &errorMessage) ? 0 : -1;
break; break;
case ForceDefaultMode: case ForceDefaultMode:
ex = startDefaultDebugger(&errorMessage) ? 0 : -1; ex = startDefaultDebugger(&errorMessage) ? 0 : -1;