From bd8d2b0b8a01c08f18cf402de260cc823da832f1 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint <Friedemann.Kleint@nokia.com> Date: Mon, 14 Dec 2009 18:01:39 +0100 Subject: [PATCH] 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> --- src/app/main.cpp | 48 +++-------- src/libs/extensionsystem/iplugin.h | 1 + src/libs/extensionsystem/pluginmanager.cpp | 85 +++++++++++++++++++ src/libs/extensionsystem/pluginmanager.h | 6 ++ src/plugins/coreplugin/coreplugin.cpp | 17 ++-- src/plugins/coreplugin/coreplugin.h | 3 +- src/plugins/debugger/cdb/cdbdebugengine.cpp | 19 ++++- src/plugins/debugger/cdb/cdbdebugengine.h | 1 + src/plugins/debugger/debuggerplugin.cpp | 90 +++++++++++++-------- src/plugins/debugger/debuggerplugin.h | 24 +++--- src/tools/qtcdebugger/main.cpp | 45 +++++++++-- 11 files changed, 238 insertions(+), 101 deletions(-) diff --git a/src/app/main.cpp b/src/app/main.cpp index 9ed1cf10dda..f4d17c4bf43 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -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."); } -// 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() { QStringList rc; @@ -287,8 +253,13 @@ int main(int argc, char **argv) } const bool isFirstInstance = !app.isRunning(); - if (!isFirstInstance && foundAppOptions.contains(QLatin1String(CLIENT_OPTION))) - return sendArguments(app, pluginManager.arguments()) ? 0 : -1; + if (!isFirstInstance && foundAppOptions.contains(QLatin1String(CLIENT_OPTION))) { + if (!app.sendMessage(pluginManager.serializedArguments())) { + displayError(msgSendArgumentFailed()); + return -1; + } + return 0; + } pluginManager.loadPlugins(); if (coreplugin->hasError()) { @@ -311,9 +282,10 @@ int main(int argc, char **argv) // Silently fallback to unconnected instances for any subsequent // instances. 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 QTimer::singleShot(100, &pluginManager, SLOT(startTests())); diff --git a/src/libs/extensionsystem/iplugin.h b/src/libs/extensionsystem/iplugin.h index e546b72db8b..39bd3671eee 100644 --- a/src/libs/extensionsystem/iplugin.h +++ b/src/libs/extensionsystem/iplugin.h @@ -55,6 +55,7 @@ public: virtual bool initialize(const QStringList &arguments, QString *errorString) = 0; virtual void extensionsInitialized() = 0; virtual void shutdown() { } + virtual void remoteCommand(const QStringList & /* options */, const QStringList & /* arguments */) { } PluginSpec *pluginSpec() const; diff --git a/src/libs/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp index 8cf365b6704..6d2be40413b 100644 --- a/src/libs/extensionsystem/pluginmanager.cpp +++ b/src/libs/extensionsystem/pluginmanager.cpp @@ -320,6 +320,91 @@ QList<PluginSpec *> PluginManager::plugins() const 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) Takes the list of command line options in \a args and parses them. diff --git a/src/libs/extensionsystem/pluginmanager.h b/src/libs/extensionsystem/pluginmanager.h index 79c0b683564..f4140da9d14 100644 --- a/src/libs/extensionsystem/pluginmanager.h +++ b/src/libs/extensionsystem/pluginmanager.h @@ -108,6 +108,8 @@ public: void formatPluginOptions(QTextStream &str, int optionIndentation, int descriptionIndentation) const; void formatPluginVersions(QTextStream &str) const; + QString serializedArguments() const; + bool runningTests() const; QString testDataDirectory() const; @@ -116,6 +118,10 @@ signals: void aboutToRemoveObject(QObject *obj); void pluginsChanged(); + +public slots: + void remoteArguments(const QString &serializedArguments); + private slots: void startTests(); diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp index da2825ecf84..8b9317fb651 100644 --- a/src/plugins/coreplugin/coreplugin.cpp +++ b/src/plugins/coreplugin/coreplugin.cpp @@ -87,16 +87,15 @@ void CorePlugin::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 - // of the window via QtSingleApplication. It should be - // the last of a sequence. - if (arg.isEmpty()) { - m_mainWindow->activateWindow(); - } else { - m_mainWindow->openFiles(QStringList(arg)); - } + m_mainWindow->openFiles(args); + m_mainWindow->activateWindow(); +} + +void CorePlugin::fileOpenRequest(const QString &f) +{ + remoteCommand(QStringList(), QStringList(f)); } void CorePlugin::shutdown() diff --git a/src/plugins/coreplugin/coreplugin.h b/src/plugins/coreplugin/coreplugin.h index 58e5958cc85..3c03c7b836c 100644 --- a/src/plugins/coreplugin/coreplugin.h +++ b/src/plugins/coreplugin/coreplugin.h @@ -49,9 +49,10 @@ public: virtual bool initialize(const QStringList &arguments, QString *errorMessage = 0); virtual void extensionsInitialized(); virtual void shutdown(); + virtual void remoteCommand(const QStringList & /* options */, const QStringList &args); public slots: - void remoteArgument(const QString&); + void fileOpenRequest(const QString&); private: void parseArguments(const QStringList & arguments); diff --git a/src/plugins/debugger/cdb/cdbdebugengine.cpp b/src/plugins/debugger/cdb/cdbdebugengine.cpp index b538b7fe675..333a84dfc94 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine.cpp +++ b/src/plugins/debugger/cdb/cdbdebugengine.cpp @@ -812,8 +812,14 @@ void CdbDebugEnginePrivate::processCreatedAttached(ULONG64 processHandle, ULONG6 if (!crashParameter.isEmpty()) { ULONG64 evtNr = crashParameter.toULongLong(); 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->setState(InferiorRunning, Q_FUNC_INFO, __LINE__); @@ -1234,6 +1240,17 @@ bool CdbDebugEnginePrivate::interruptInterferiorProcess(QString *errorMessage) 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() { if (!m_d->m_hDebuggeeProcess || !m_d->isDebuggeeRunning()) diff --git a/src/plugins/debugger/cdb/cdbdebugengine.h b/src/plugins/debugger/cdb/cdbdebugengine.h index 077faf4e14f..59d7658a925 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine.h +++ b/src/plugins/debugger/cdb/cdbdebugengine.h @@ -111,6 +111,7 @@ private slots: void slotConsoleStubStarted(); void slotConsoleStubError(const QString &msg); void slotConsoleStubTerminated(); + void slotBreakAttachToCrashed(); void warning(const QString &w); private: diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index 1e91222a06b..e1b938815c1 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -505,14 +505,19 @@ bool DebuggingHelperOptionPage::matches(const QString &s) const // /////////////////////////////////////////////////////////////////////// + +DebuggerPlugin::AttachRemoteParameters::AttachRemoteParameters() : + attachPid(0), + winCrashEvent(0) +{ +} + DebuggerPlugin::DebuggerPlugin() : m_manager(0), m_debugMode(0), m_locationMark(0), m_gdbRunningContext(0), m_cmdLineEnabledEngines(AllEngineTypes), - m_cmdLineAttachPid(0), - m_cmdLineWinCrashEvent(0), m_toggleLockedAction(0) {} @@ -555,9 +560,10 @@ static QString msgInvalidNumericParameter(const QString &a, const QString &numbe } // Parse arguments -bool DebuggerPlugin::parseArgument(QStringList::const_iterator &it, - const QStringList::const_iterator &cend, - QString *errorMessage) +static bool parseArgument(QStringList::const_iterator &it, + const QStringList::const_iterator &cend, + DebuggerPlugin::AttachRemoteParameters *attachRemoteParameters, + unsigned *enabledEngines, QString *errorMessage) { const QString &option = *it; // '-debug <pid>' @@ -568,10 +574,10 @@ bool DebuggerPlugin::parseArgument(QStringList::const_iterator &it, return false; } bool ok; - m_cmdLineAttachPid = it->toULongLong(&ok); + attachRemoteParameters->attachPid = it->toULongLong(&ok); if (!ok) { - m_cmdLineAttachPid = 0; - m_cmdLineAttachCore = *it; + attachRemoteParameters->attachPid = 0; + attachRemoteParameters->attachCore = *it; } return true; } @@ -584,7 +590,7 @@ bool DebuggerPlugin::parseArgument(QStringList::const_iterator &it, return false; } bool ok; - m_cmdLineWinCrashEvent = it->toULongLong(&ok); + attachRemoteParameters->winCrashEvent = it->toULongLong(&ok); if (!ok) { *errorMessage = msgInvalidNumericParameter(option, *it); return false; @@ -593,40 +599,55 @@ bool DebuggerPlugin::parseArgument(QStringList::const_iterator &it, } // engine disabling if (option == QLatin1String("-disable-cdb")) { - m_cmdLineEnabledEngines &= ~CdbEngineType; + *enabledEngines &= ~Debugger::CdbEngineType; return true; } if (option == QLatin1String("-disable-gdb")) { - m_cmdLineEnabledEngines &= ~GdbEngineType; + *enabledEngines &= ~Debugger::GdbEngineType; return true; } if (option == QLatin1String("-disable-sdb")) { - m_cmdLineEnabledEngines &= ~ScriptEngineType; + *enabledEngines &= ~Debugger::ScriptEngineType; return true; } - *errorMessage = tr("Invalid debugger option: %1").arg(option); + *errorMessage = DebuggerPlugin::tr("Invalid debugger option: %1").arg(option); 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(); for (QStringList::const_iterator it = args.constBegin(); it != cend; ++it) - if (!parseArgument(it, cend, errorMessage)) + if (!parseArgument(it, cend, attachRemoteParameters, enabledEngines, errorMessage)) return false; if (Debugger::Constants::Internal::debug) qDebug().nospace() << args << "engines=0x" - << QString::number(m_cmdLineEnabledEngines, 16) - << " pid" << m_cmdLineAttachPid - << " core" << m_cmdLineAttachCore << '\n'; + << QString::number(*enabledEngines, 16) + << " pid" << attachRemoteParameters->attachPid + << " core" << attachRemoteParameters->attachCore << '\n'; 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, ¶meters, &dummy, &errorMessage) + && parameters.attachPid != quint64(QCoreApplication::applicationPid())) { + m_attachRemoteParameters = parameters; + attachCmdLine(); + } +} + bool DebuggerPlugin::initialize(const QStringList &arguments, QString *errorMessage) { // 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") .arg(*errorMessage); qWarning("%s\n", qPrintable(*errorMessage)); @@ -1000,18 +1021,25 @@ void DebuggerPlugin::extensionsInitialized() //qDebug() << "EXTENSIONS INITIALIZED:" << env; if (!env.isEmpty()) m_manager->runTest(QString::fromLocal8Bit(env)); - if (m_cmdLineAttachPid) - QTimer::singleShot(0, this, SLOT(attachCmdLinePid())); - if (!m_cmdLineAttachCore.isEmpty()) - QTimer::singleShot(0, this, SLOT(attachCmdLineCore())); + if (m_attachRemoteParameters.attachPid || !m_attachRemoteParameters.attachCore.isEmpty()) + QTimer::singleShot(0, this, SLOT(attachCmdLine())); } -void DebuggerPlugin::attachCmdLinePid() +void DebuggerPlugin::attachCmdLine() { - m_manager->showStatusMessage(tr("Attaching to PID %1.").arg(m_cmdLineAttachPid)); - const QString crashParameter = - m_cmdLineWinCrashEvent ? QString::number(m_cmdLineWinCrashEvent) : QString(); - attachExternalApplication(m_cmdLineAttachPid, crashParameter); + 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 = + m_attachRemoteParameters.winCrashEvent ? QString::number(m_attachRemoteParameters.winCrashEvent) : QString(); + 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. */ @@ -1326,12 +1354,6 @@ void DebuggerPlugin::attachExternalApplication(qint64 pid, const QString &crashP 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() { AttachCoreDialog dlg(m_manager->mainWindow()); diff --git a/src/plugins/debugger/debuggerplugin.h b/src/plugins/debugger/debuggerplugin.h index 1e1af9cb8f7..db064b20b2a 100644 --- a/src/plugins/debugger/debuggerplugin.h +++ b/src/plugins/debugger/debuggerplugin.h @@ -36,7 +36,6 @@ #include <QtCore/QStringList> QT_BEGIN_NAMESPACE -class QAbstractItemView; class QAction; class QCursor; class QMenu; @@ -68,6 +67,15 @@ class DebuggerPlugin : public ExtensionSystem::IPlugin Q_OBJECT public: + struct AttachRemoteParameters { + AttachRemoteParameters(); + + quint64 attachPid; + QString attachCore; + // Event handle for attaching to crashed Windows processes. + quint64 winCrashEvent; + }; + DebuggerPlugin(); ~DebuggerPlugin(); @@ -75,6 +83,7 @@ private: virtual bool initialize(const QStringList &arguments, QString *error_message); virtual void shutdown(); virtual void extensionsInitialized(); + virtual void remoteCommand(const QStringList &options, const QStringList &arguments); QVariant configValue(const QString &name) const; @@ -106,16 +115,11 @@ private slots: void startRemoteApplication(); void attachExternalApplication(); void attachCore(); - void attachCmdLinePid(); - void attachCmdLineCore(); + void attachCmdLine(); private: void readSettings(); 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 attachCore(const QString &core, const QString &exeFileName); @@ -131,11 +135,9 @@ private: QString m_previousMode; TextEditor::BaseTextMark *m_locationMark; int m_gdbRunningContext; + AttachRemoteParameters m_attachRemoteParameters; 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_startExternalAction; diff --git a/src/tools/qtcdebugger/main.cpp b/src/tools/qtcdebugger/main.cpp index 8be595a0ce1..616fc82727d 100644 --- a/src/tools/qtcdebugger/main.cpp +++ b/src/tools/qtcdebugger/main.cpp @@ -41,6 +41,7 @@ #include <QtCore/QByteArray> #include <QtCore/QString> #include <QtCore/QDir> +#include <QtCore/QTime> #include <QtCore/QProcess> #include <QtGui/QPushButton> @@ -61,6 +62,7 @@ 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 const char *creatorBinaryC = "qtcreator.exe"; static inline QString wCharToQString(const WCHAR *w) { return QString::fromUtf16(reinterpret_cast<const ushort *>(w)); } #ifdef __GNUC__ @@ -343,23 +345,49 @@ static QString getProcessBaseName(DWORD pid) // ------- 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 binary = dir + QLatin1String("/qtcreator.exe"); + const QString binary = dir + QLatin1Char('/') + QLatin1String(creatorBinaryC); QStringList args; + if (asClient) + args << QLatin1String("-client"); args << QLatin1String("-debug") << QString::number(argProcessId) << QLatin1String("-wincrashevent") << QString::number(argWinCrashEvent); if (debug) qDebug() << binary << args; QProcess p; p.setWorkingDirectory(dir); + QTime executionTime; + executionTime.start(); p.start(binary, args, QIODevice::NotOpen); if (!p.waitForStarted()) { *errorMessage = QString::fromLatin1("Unable to start %1!").arg(binary); 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; } @@ -408,7 +436,8 @@ bool startDefaultDebugger(QString *errorMessage) bool chooseDebugger(QString *errorMessage) { 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); QPushButton *creatorButton = msgBox.addButton(QLatin1String("Debug with Qt Creator"), QMessageBox::AcceptRole); QPushButton *defaultButton = msgBox.addButton(QLatin1String("Debug with default debugger"), QMessageBox::AcceptRole); @@ -416,8 +445,10 @@ bool chooseDebugger(QString *errorMessage) && !defaultDebugger.isEmpty()); msgBox.exec(); if (msgBox.clickedButton() == creatorButton) { - // Just in case, default to standard - if (startCreatorAsDebugger(errorMessage)) + // Just in case, default to standard. Do not run as client in the unlikely case + // Creator crashed + const bool canRunAsClient = !processName.contains(QLatin1String(creatorBinaryC), Qt::CaseInsensitive); + if (startCreatorAsDebugger(canRunAsClient, errorMessage)) return true; return startDefaultDebugger(errorMessage); } @@ -552,7 +583,7 @@ int main(int argc, char *argv[]) usage(QCoreApplication::applicationFilePath(), errorMessage); break; case ForceCreatorMode: - ex = startCreatorAsDebugger(&errorMessage) ? 0 : -1; + ex = startCreatorAsDebugger(true, &errorMessage) ? 0 : -1; break; case ForceDefaultMode: ex = startDefaultDebugger(&errorMessage) ? 0 : -1; -- GitLab