Commit bd8d2b0b authored by Friedemann Kleint's avatar Friedemann Kleint

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: default avatarcon <qtc-committer@nokia.com>
parent d94b7b08
......@@ -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()));
......
......@@ -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;
......
......@@ -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.
......
......@@ -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();
......
......@@ -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()
......
......@@ -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);
......
......@@ -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())
......
......@@ -111,6 +111,7 @@ private slots:
void slotConsoleStubStarted();
void slotConsoleStubError(const QString &msg);
void slotConsoleStubTerminated();
void slotBreakAttachToCrashed();
void warning(const QString &w);
private:
......
......@@ -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, &parameters, &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());
......
......@@ -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;
......
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment