Commit ef7c5fae authored by ck's avatar ck

Debugger: Support running GDB over SSH.

Reviewed-by: hjk
parent a945e963
......@@ -362,7 +362,7 @@ void CdbDebugEngine::startDebugger(const QSharedPointer<DebuggerStartParameters>
}
switch (sp->startMode) {
case AttachCore:
case StartRemote:
case AttachToRemote:
warning(QLatin1String("Internal error: Mode not supported."));
setState(AdapterStartFailed, Q_FUNC_INFO, __LINE__);
emit startFailed();
......
......@@ -114,7 +114,8 @@ enum DebuggerStartMode
AttachCrashedExternal, // Attach to crashed process by process id
AttachTcf, // Attach to a running Target Communication Framework agent
AttachCore, // Attach to a core file
StartRemote // Start and attach to a remote process
AttachToRemote, // Start and attach to a remote process
StartRemoteGdb // Start gdb itself remotely
};
enum DebuggerCapabilities
......
......@@ -1097,7 +1097,7 @@ void DebuggerManager::startNewDebugger(const DebuggerStartParametersPtr &sp)
d->m_engine = debuggerEngineForToolChain(sp->toolChainType);
if (d->m_engine == 0
&& startMode != StartRemote
&& startMode != AttachToRemote
&& !sp->executable.isEmpty())
d->m_engine = debuggerEngineForExecutable(
sp->executable, &errorMessage, &settingsIdHint);
......
......@@ -33,6 +33,8 @@
#include "debugger_global.h"
#include "debuggerconstants.h"
#include <coreplugin/ssh/sshconnection.h>
#include <QtCore/QObject>
#include <QtCore/QSharedPointer>
#include <QtCore/QStringList>
......@@ -122,11 +124,12 @@ public:
QString sysRoot;
QString debuggerCommand;
int toolChainType;
QString remoteDumperLib;
QByteArray remoteDumperLib;
QString qtInstallPath;
QString dumperLibrary;
QStringList dumperLibraryLocations;
Core::SshServerInfo sshserver;
DebuggerStartMode startMode;
};
......
......@@ -1509,7 +1509,7 @@ void DebuggerPlugin::startRemoteApplication()
sp->debuggerCommand = dlg.debugger(); // Override toolchain-detection.
if (!sp->debuggerCommand.isEmpty())
sp->toolChainType = ProjectExplorer::ToolChain::INVALID;
sp->startMode = StartRemote;
sp->startMode = AttachToRemote;
if (dlg.useServerStartScript())
sp->serverStartScript = dlg.serverStartScript();
sp->sysRoot = dlg.sysRoot();
......
......@@ -29,6 +29,8 @@
#include "abstractgdbadapter.h"
#include "abstractgdbprocess.h"
#include <utils/qtcassert.h>
#include <QtCore/QProcess>
......@@ -61,7 +63,7 @@ const char *AbstractGdbAdapter::inferiorShutdownCommand() const
void AbstractGdbAdapter::write(const QByteArray &data)
{
m_engine->m_gdbProc.write(data);
gdbProc()->write(data);
}
bool AbstractGdbAdapter::isTrkAdapter() const
......
......@@ -38,6 +38,8 @@
namespace Debugger {
namespace Internal {
class AbstractGdbProcess;
// AbstractGdbAdapter is inherited by PlainGdbAdapter used for local
// debugging and TrkGdbAdapter used for on-device debugging.
// In the PlainGdbAdapter case it's just a wrapper around a QProcess running
......@@ -64,6 +66,7 @@ public:
virtual void interruptInferior() = 0;
virtual void shutdown();
virtual const char *inferiorShutdownCommand() const;
virtual AbstractGdbProcess *gdbProc() = 0;
virtual DumperHandling dumperHandling() const = 0;
......
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (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 http://qt.nokia.com/contact.
**
**************************************************************************/
#include "abstractgdbprocess.h"
namespace Debugger {
namespace Internal {
} // namespace Internal
} // namespace Debugger
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (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 http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef GDBPROCESSWRAPPER_H
#define GDBPROCESSWRAPPER_H
#include <QtCore/QObject>
#include <QtCore/QProcess>
namespace Debugger {
namespace Internal {
class AbstractGdbProcess : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(AbstractGdbProcess)
public:
virtual QByteArray readAllStandardOutput() = 0;
virtual QByteArray readAllStandardError() = 0;
virtual void start(const QString &cmd, const QStringList &args) = 0;
virtual bool waitForStarted() = 0;
virtual qint64 write(const QByteArray &data) = 0;
virtual void kill() = 0;
virtual QProcess::ProcessState state() const = 0;
virtual QString errorString() const = 0;
virtual QProcessEnvironment processEnvironment() const = 0;
virtual void setProcessEnvironment(const QProcessEnvironment &env) = 0;
virtual void setEnvironment(const QStringList &env) = 0;
virtual void setWorkingDirectory(const QString &dir) = 0;
virtual ~AbstractGdbProcess() {}
signals:
void error(QProcess::ProcessError);
void finished(int exitCode, QProcess::ExitStatus exitStatus);
void readyReadStandardError();
void readyReadStandardOutput();
protected:
explicit AbstractGdbProcess(QObject *parent = 0) : QObject(parent) {}
};
} // namespace Internal
} // namespace Debugger
#endif // GDBPROCESSWRAPPER_H
......@@ -27,104 +27,53 @@
**
**************************************************************************/
#include "plaingdbadapter.h"
#include "abstractplaingdbadapter.h"
#include "gdbengine.h"
#include "procinterrupt.h"
#include "debuggerstringutils.h"
#include "debuggeractions.h"
#include "debuggerstringutils.h"
#include <utils/qtcassert.h>
#include <QtCore/QFileInfo>
namespace Debugger {
namespace Internal {
#define CB(callback) \
static_cast<GdbEngine::AdapterCallback>(&PlainGdbAdapter::callback), \
static_cast<GdbEngine::AdapterCallback>(&AbstractPlainGdbAdapter::callback), \
STRINGIFY(callback)
///////////////////////////////////////////////////////////////////////
//
// PlainGdbAdapter
//
///////////////////////////////////////////////////////////////////////
PlainGdbAdapter::PlainGdbAdapter(GdbEngine *engine, QObject *parent)
AbstractPlainGdbAdapter::AbstractPlainGdbAdapter(GdbEngine *engine,
QObject *parent)
: AbstractGdbAdapter(engine, parent)
{
// Output
connect(&m_outputCollector, SIGNAL(byteDelivery(QByteArray)),
engine, SLOT(readDebugeeOutput(QByteArray)));
}
AbstractGdbAdapter::DumperHandling PlainGdbAdapter::dumperHandling() const
{
// LD_PRELOAD fails for System-Qt on Mac.
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
return DumperLoadedByGdb;
#else
return DumperLoadedByGdbPreload;
#endif
}
void PlainGdbAdapter::startAdapter()
{
QTC_ASSERT(state() == EngineStarting, qDebug() << state());
setState(AdapterStarting);
debugMessage(_("TRYING TO START ADAPTER"));
QStringList gdbArgs;
if (!m_outputCollector.listen()) {
emit adapterStartFailed(tr("Cannot set up communication with child process: %1")
.arg(m_outputCollector.errorString()), QString());
return;
}
gdbArgs.append(_("--tty=") + m_outputCollector.serverName());
if (!startParameters().workingDir.isEmpty())
m_engine->m_gdbProc.setWorkingDirectory(startParameters().workingDir);
if (!startParameters().environment.isEmpty())
m_engine->m_gdbProc.setEnvironment(startParameters().environment);
if (!m_engine->startGdb(gdbArgs)) {
m_outputCollector.shutdown();
return;
}
emit adapterStarted();
}
void PlainGdbAdapter::startInferior()
void AbstractPlainGdbAdapter::startInferior()
{
QTC_ASSERT(state() == InferiorStarting, qDebug() << state());
if (!startParameters().processArgs.isEmpty()) {
QString args = startParameters().processArgs.join(_(" "));
m_engine->postCommand("-exec-arguments " + args.toLocal8Bit());
m_engine->postCommand("-exec-arguments " + toLocalEncoding(args));
}
QFileInfo fi(startParameters().executable);
QByteArray path = fi.absoluteFilePath().toLocal8Bit();
m_engine->postCommand("-file-exec-and-symbols \"" + path + '"',
m_engine->postCommand("-file-exec-and-symbols \"" + execFilePath() + '"',
CB(handleFileExecAndSymbols));
}
void PlainGdbAdapter::handleFileExecAndSymbols(const GdbResponse &response)
void AbstractPlainGdbAdapter::handleFileExecAndSymbols(const GdbResponse &response)
{
QTC_ASSERT(state() == InferiorStarting, qDebug() << state());
if (response.resultClass == GdbResultDone) {
#ifdef Q_OS_LINUX
// Old gdbs do not announce the PID for programs without pthreads.
// Note that successfully preloading the debugging helpers will
// automatically load pthreads, so this will be unnecessary.
if (m_engine->m_gdbVersion < 70000)
m_engine->postCommand("info target", CB(handleInfoTarget));
#endif
if (infoTargetNecessary()) {
// Old gdbs do not announce the PID for programs without pthreads.
// Note that successfully preloading the debugging helpers will
// automatically load pthreads, so this will be unnecessary.
if (m_engine->m_gdbVersion < 70000)
m_engine->postCommand("info target", CB(handleInfoTarget));
}
emit inferiorPrepared();
} else {
QByteArray ba = response.data.findChild("msg").data();
QString msg = QString::fromLocal8Bit(ba);
QString msg = fromLocalEncoding(ba);
// Extend the message a bit in unknown cases.
if (!ba.endsWith("File format not recognized"))
msg = tr("Starting executable failed:\n") + msg;
......@@ -132,36 +81,13 @@ void PlainGdbAdapter::handleFileExecAndSymbols(const GdbResponse &response)
}
}
#ifdef Q_OS_LINUX
void PlainGdbAdapter::handleInfoTarget(const GdbResponse &response)
{
if (response.resultClass == GdbResultDone) {
// [some leading stdout here]
// >&" Entry point: 0x80831f0 0x08048134 - 0x08048147 is .interp\n"
// [some trailing stdout here]
QString msg = _(response.data.findChild("consolestreamoutput").data());
QRegExp needle(_("\\bEntry point: 0x([0-9a-f]+)\\b"));
if (needle.indexIn(msg) != -1) {
m_engine->m_entryPoint =
"0x" + needle.cap(1).toLatin1().rightJustified(sizeof(void *) * 2, '0');
m_engine->postCommand("tbreak *0x" + needle.cap(1).toAscii());
// Do nothing here - inferiorPrepared handles the sequencing.
} else {
emit inferiorStartFailed(_("Parsing start address failed"));
}
} else if (response.resultClass == GdbResultError) {
emit inferiorStartFailed(_("Fetching start address failed"));
}
}
#endif
void PlainGdbAdapter::startInferiorPhase2()
void AbstractPlainGdbAdapter::startInferiorPhase2()
{
setState(InferiorRunningRequested);
m_engine->postCommand("-exec-run", GdbEngine::RunRequest, CB(handleExecRun));
}
void PlainGdbAdapter::handleExecRun(const GdbResponse &response)
void AbstractPlainGdbAdapter::handleExecRun(const GdbResponse &response)
{
if (response.resultClass == GdbResultRunning) {
QTC_ASSERT(state() == InferiorRunning, qDebug() << state());
......@@ -172,30 +98,33 @@ void PlainGdbAdapter::handleExecRun(const GdbResponse &response)
m_engine->postCommand("target record");
} else {
QTC_ASSERT(state() == InferiorRunningRequested, qDebug() << state());
QString msg = QString::fromLocal8Bit(response.data.findChild("msg").data());
QString msg = fromLocalEncoding(response.data.findChild("msg").data());
//QTC_ASSERT(status() == InferiorRunning, /**/);
//interruptInferior();
emit inferiorStartFailed(msg);
}
}
void PlainGdbAdapter::interruptInferior()
void AbstractPlainGdbAdapter::handleInfoTarget(const GdbResponse &response)
{
const qint64 attachedPID = m_engine->inferiorPid();
if (attachedPID <= 0) {
debugMessage(_("TRYING TO INTERRUPT INFERIOR BEFORE PID WAS OBTAINED"));
return;
if (response.resultClass == GdbResultDone) {
// [some leading stdout here]
// >&" Entry point: 0x80831f0 0x08048134 - 0x08048147 is .interp\n"
// [some trailing stdout here]
QString msg = _(response.data.findChild("consolestreamoutput").data());
QRegExp needle(_("\\bEntry point: 0x([0-9a-f]+)\\b"));
if (needle.indexIn(msg) != -1) {
m_engine->m_entryPoint =
"0x" + needle.cap(1).toLatin1().rightJustified(sizeof(void *) * 2, '0');
m_engine->postCommand("tbreak *0x" + needle.cap(1).toAscii());
// Do nothing here - inferiorPrepared handles the sequencing.
} else {
emit inferiorStartFailed(_("Parsing start address failed"));
}
} else if (response.resultClass == GdbResultError) {
emit inferiorStartFailed(_("Fetching start address failed"));
}
if (!interruptProcess(attachedPID))
debugMessage(_("CANNOT INTERRUPT %1").arg(attachedPID));
}
void PlainGdbAdapter::shutdown()
{
debugMessage(_("PLAIN ADAPTER SHUTDOWN %1").arg(state()));
m_outputCollector.shutdown();
}
} // namespace Internal
} // namespace Debugger
} // namespace Internal
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (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 http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef ABSTRACTPLAINGDBADAPTER_H
#define ABSTRACTPLAINGDBADAPTER_H
#include "abstractgdbadapter.h"
namespace Debugger {
namespace Internal {
class AbstractPlainGdbAdapter : public AbstractGdbAdapter
{
public:
AbstractPlainGdbAdapter(GdbEngine *engine, QObject *parent = 0);
virtual void startInferior();
virtual void startInferiorPhase2();
protected:
void handleInfoTarget(const GdbResponse &response);
private:
virtual QByteArray execFilePath() const = 0;
virtual bool infoTargetNecessary() const = 0;
virtual QByteArray toLocalEncoding(const QString &s) const = 0;
virtual QString fromLocalEncoding(const QByteArray &b) const = 0;
void handleExecRun(const GdbResponse &response);
void handleFileExecAndSymbols(const GdbResponse &response);
};
} // namespace Debugger
} // namespace Internal
#endif // ABSTRACTPLAINGDBADAPTER_H
......@@ -32,6 +32,8 @@
#include "abstractgdbadapter.h"
#include "abstractgdbprocess.h"
namespace Debugger {
namespace Internal {
......@@ -54,9 +56,12 @@ public:
void startInferior();
void interruptInferior();
const char *inferiorShutdownCommand() const { return "detach"; }
AbstractGdbProcess *gdbProc() { return &m_gdbProc; }
private:
void handleAttach(const GdbResponse &response);
LocalGdbProcess m_gdbProc;
};
} // namespace Internal
......
......@@ -513,8 +513,9 @@ void GdbEngine::tryLoadDebuggingHelpersClassic()
m_debuggingHelperState = DebuggingHelperLoadTried;
QByteArray dlopenLib;
if (startParameters().startMode == StartRemote)
dlopenLib = startParameters().remoteDumperLib.toLocal8Bit();
if (startParameters().startMode == AttachToRemote
|| startParameters().startMode == StartRemoteGdb)
dlopenLib = startParameters().remoteDumperLib;
else
dlopenLib = manager()->qtDumperLibraryName().toLocal8Bit();
......
......@@ -32,6 +32,8 @@
#include "abstractgdbadapter.h"
#include "abstractgdbprocess.h"
#ifdef Q_OS_LINUX
# define EXE_FROM_CORE
#endif
......@@ -57,6 +59,7 @@ public:
void startAdapter();
void startInferior();
void interruptInferior();
AbstractGdbProcess *gdbProc() { return &m_gdbProc; }
private:
void loadExeAndSyms();
......@@ -68,6 +71,7 @@ private:
int m_round;
#endif
QString m_executable;
LocalGdbProcess m_gdbProc;
};
} // namespace Internal
......
......@@ -6,11 +6,16 @@ HEADERS += \
$$PWD/abstractgdbadapter.h \
$$PWD/attachgdbadapter.h \
$$PWD/coregdbadapter.h \
$$PWD/plaingdbadapter.h \
$$PWD/localplaingdbadapter.h \
$$PWD/termgdbadapter.h \
$$PWD/remotegdbadapter.h \
$$PWD/remotegdbserveradapter.h \
$$PWD/trkgdbadapter.h \
$$PWD/s60debuggerbluetoothstarter.h
$$PWD/s60debuggerbluetoothstarter.h \
$$PWD/abstractgdbprocess.h \
$$PWD/localgdbprocess.h \
$$PWD/remotegdbprocess.h \
$$PWD/remoteplaingdbadapter.h \
$$PWD/abstractplaingdbadapter.h
SOURCES += \
$$PWD/gdbmi.cpp \
......@@ -22,11 +27,16 @@ SOURCES += \
$$PWD/abstractgdbadapter.cpp \
$$PWD/attachgdbadapter.cpp \
$$PWD/coregdbadapter.cpp \
$$PWD/plaingdbadapter.cpp \
$$PWD/localplaingdbadapter.cpp \
$$PWD/termgdbadapter.cpp \
$$PWD/remotegdbadapter.cpp \
$$PWD/remotegdbserveradapter.cpp \
$$PWD/trkgdbadapter.cpp \
$$PWD/s60debuggerbluetoothstarter.cpp
$$PWD/s60debuggerbluetoothstarter.cpp \
$$PWD/abstractgdbprocess.cpp \
$$PWD/localgdbprocess.cpp \
$$PWD/remotegdbprocess.cpp \
$$PWD/remoteplaingdbadapter.cpp \
$$PWD/abstractplaingdbadapter.cpp
FORMS += $$PWD/gdboptionspage.ui
......
......@@ -36,9 +36,10 @@
#include "attachgdbadapter.h"
#include "coregdbadapter.h"
#include "plaingdbadapter.h"
#include "localplaingdbadapter.h"
#include "termgdbadapter.h"
#include "remotegdbadapter.h"
#include "remotegdbserveradapter.h"
#include "remoteplaingdbadapter.h"
#include "trkgdbadapter.h"
#include "watchutils.h"
......@@ -218,10 +219,16 @@ QMainWindow *GdbEngine::mainWindow() const
return DebuggerUISwitcher::instance()->mainWindow();
}
AbstractGdbProcess *GdbEngine::gdbProc() const
{
return m_gdbAdapter->gdbProc();
}
GdbEngine::~GdbEngine()
{
// Prevent sending error messages afterwards.
disconnect(&m_gdbProc, 0, this, 0);
if (m_gdbAdapter)
disconnect(gdbProc(), 0, this, 0);
delete m_gdbAdapter;
m_gdbAdapter = 0;
}
......@@ -614,7 +621,7 @@ void GdbEngine::handleResponse(const QByteArray &buff)
void GdbEngine::readGdbStandardError()
{
QByteArray err = m_gdbProc.readAllStandardError();
QByteArray err = gdbProc()->readAllStandardError();
debugMessage(_("UNEXPECTED GDB STDERR: " + err));
if (err == "Undefined command: \"bb\". Try \"help\".\n")
return;
......@@ -631,7 +638,7 @@ void GdbEngine::readGdbStandardOutput()
int newstart = 0;
int scan = m_inbuffer.size();