Debugger: Support running GDB over SSH.

Reviewed-by: hjk
This commit is contained in:
ck
2010-05-05 12:49:08 +02:00
parent a945e96377
commit ef7c5fae60
32 changed files with 1311 additions and 200 deletions

View File

@@ -0,0 +1,333 @@
/**************************************************************************
**
** 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 "remotegdbprocess.h"
#include "remoteplaingdbadapter.h"
namespace Debugger {
namespace Internal {
RemoteGdbProcess::RemoteGdbProcess(const Core::SshServerInfo &server,
RemotePlainGdbAdapter *adapter, QObject *parent)
: AbstractGdbProcess(parent), m_serverInfo(server), m_adapter(adapter)
{
}
QByteArray RemoteGdbProcess::readAllStandardOutput()
{
QByteArray output = m_gdbOutput;
m_gdbOutput.clear();
return output;
}
QByteArray RemoteGdbProcess::readAllStandardError()
{
QByteArray errorOutput = m_errorOutput;
m_errorOutput.clear();
return errorOutput;
}
void RemoteGdbProcess::start(const QString &cmd, const QStringList &args)
{
m_gdbState = CmdNotYetSent;
m_gdbConn = Core::InteractiveSshConnection::create(m_serverInfo);
if (m_gdbConn->hasError())
return;
m_appOutputReaderState = CmdNotYetSent;
m_appOutputConn = Core::InteractiveSshConnection::create(m_serverInfo);
if (m_appOutputConn->hasError())
return;
m_errOutputReaderState = CmdNotYetSent;
m_errOutputConn = Core::InteractiveSshConnection::create(m_serverInfo);
if (m_errOutputConn->hasError())
return;
m_command = cmd;
m_cmdArgs = args;
connect(m_gdbConn.data(), SIGNAL(remoteOutput(QByteArray)),
this, SLOT(handleGdbOutput(QByteArray)));
connect(m_appOutputConn.data(), SIGNAL(remoteOutput(QByteArray)),
this, SLOT(handleAppOutput(QByteArray)));
connect(m_errOutputConn.data(), SIGNAL(remoteOutput(QByteArray)),
this, SLOT(handleErrOutput(QByteArray)));
m_gdbConn->start();
m_errOutputConn->start();
m_appOutputConn->start();
}
bool RemoteGdbProcess::waitForStarted()
{
return true;
}
qint64 RemoteGdbProcess::write(const QByteArray &data)
{
if (m_gdbState != CmdReceived || !m_inputToSend.isEmpty()
|| !m_lastSeqNr.isEmpty()) {
m_inputToSend.enqueue(data);
return data.size();
} else {
return sendInput(data);
}
}
void RemoteGdbProcess::kill()
{
stopReaders();
Core::InteractiveSshConnection::Ptr controlConn
= Core::InteractiveSshConnection::create(m_serverInfo);
if (!controlConn->hasError()) {
if (controlConn->start())
controlConn->sendInput("pkill -x gdb\r\n");
}
m_gdbConn->quit();
emit finished(0, QProcess::CrashExit);
}
QProcess::ProcessState RemoteGdbProcess::state() const
{
switch (m_gdbState) {
case CmdNotYetSent:
return QProcess::NotRunning;
case CmdSent:
return QProcess::Starting;
case CmdReceived:
default:
return QProcess::Running;
}
}
QString RemoteGdbProcess::errorString() const
{
return m_gdbConn ? m_gdbConn->error() : QString();
}
void RemoteGdbProcess::handleGdbOutput(const QByteArray &output)
{
#if 0
qDebug("%s: output is '%s'", Q_FUNC_INFO, output.data());
#endif
if (m_gdbState == CmdNotYetSent)
return;
m_currentGdbOutput += removeCarriageReturn(output);
if (!m_currentGdbOutput.endsWith('\n'))
return;
if (m_gdbState == CmdSent) {
const int index = m_currentGdbOutput.indexOf(m_startCmdLine);
if (index != -1)
m_currentGdbOutput.remove(index, m_startCmdLine.size());
// Note: We can't guarantee that we will match the command line,
// because the remote terminal sometimes inserts control characters.
// Otherwise we could change the state to CmdReceived here.
}
m_gdbState = CmdReceived;
checkForGdbExit(m_currentGdbOutput);
if (m_currentGdbOutput.contains(m_lastSeqNr + '^'))
m_lastSeqNr.clear();
if (m_lastSeqNr.isEmpty() && !m_inputToSend.isEmpty()) {
#if 0
qDebug("Sending queued command: %s", m_inputToSend.head().data());
#endif
sendInput(m_inputToSend.dequeue());
}
if (!m_currentGdbOutput.isEmpty()) {
const int startPos
= m_gdbOutput.isEmpty() ? findAnchor(m_currentGdbOutput) : 0;
if (startPos != -1) {
m_gdbOutput += m_currentGdbOutput.mid(startPos);
m_currentGdbOutput.clear();
emit readyReadStandardOutput();
}
}
}
QProcessEnvironment RemoteGdbProcess::processEnvironment() const
{
return QProcessEnvironment(); // TODO: Provide actual environment.
}
void RemoteGdbProcess::setProcessEnvironment(const QProcessEnvironment &env)
{
// TODO: Do something.
}
void RemoteGdbProcess::setEnvironment(const QStringList &env)
{
// TODO: Do something.
}
void RemoteGdbProcess::setWorkingDirectory(const QString &dir)
{
m_wd = dir;
}
int RemoteGdbProcess::findAnchor(const QByteArray &data) const
{
for (int pos = 0; pos < data.count(); ++pos) {
const char c = data.at(pos);
if (isdigit(c) || c == '*' || c == '+' || c == '=' || c == '~'
|| c == '@' || c == '&' || c == '^')
return pos;
}
return -1;
}
qint64 RemoteGdbProcess::sendInput(const QByteArray &data)
{
int pos;
for (pos = 0; pos < data.size(); ++pos)
if (!isdigit(data.at(pos)))
break;
m_lastSeqNr = data.left(pos);
return m_gdbConn->sendInput(data) ? data.size() : 0;
}
void RemoteGdbProcess::handleAppOutput(const QByteArray &output)
{
if (!handleAppOrErrOutput(m_appOutputConn, m_appOutputReaderState,
m_initialAppOutput, AppOutputFile, output))
m_adapter->handleApplicationOutput(output);
}
void RemoteGdbProcess::handleErrOutput(const QByteArray &output)
{
if (!handleAppOrErrOutput(m_errOutputConn, m_errOutputReaderState,
m_initialErrOutput, ErrOutputFile, output)) {
m_errorOutput += output;
emit readyReadStandardError();
}
}
bool RemoteGdbProcess::handleAppOrErrOutput(Core::InteractiveSshConnection::Ptr &conn,
CmdState &cmdState, QByteArray &initialOutput, const QByteArray &file,
const QByteArray &output)
{
const QByteArray cmdLine1 = mkFifoCmdLine(file);
const QByteArray cmdLine2 = readerCmdLine(file);
if (cmdState == CmdNotYetSent) {
conn->sendInput(cmdLine1);
cmdState = CmdSent;
return true;
}
if (cmdState == CmdSent) {
initialOutput += output;
if (initialOutput.endsWith(cmdLine2)) {
cmdState = CmdReceived;
if (m_appOutputReaderState == m_errOutputReaderState
&& m_gdbState == CmdNotYetSent)
startGdb();
} else if (initialOutput.contains(cmdLine1)
&& !initialOutput.endsWith(cmdLine1)) {
initialOutput.clear();
conn->sendInput(cmdLine2);
}
return true;
}
return false;
}
void RemoteGdbProcess::startGdb()
{
m_startCmdLine = "stty -echo && " + m_command.toUtf8() + ' '
+ m_cmdArgs.join(QLatin1String(" ")).toUtf8()
+ " -tty=" + AppOutputFile + " 2>" + ErrOutputFile + '\n';
if (!m_wd.isEmpty())
m_startCmdLine.prepend("cd " + m_wd.toUtf8() + " && ");
sendInput(m_startCmdLine);
m_gdbState = CmdSent;
}
void RemoteGdbProcess::stopReaders()
{
if (m_appOutputConn) {
disconnect(m_appOutputConn.data(), SIGNAL(remoteOutput(QByteArray)),
this, SLOT(handleAppOutput(QByteArray)));
m_appOutputConn->sendInput(CtrlC);
m_appOutputConn->quit();
}
if (m_errOutputConn) {
disconnect(m_errOutputConn.data(), SIGNAL(remoteOutput(QByteArray)),
this, SLOT(handleErrOutput(QByteArray)));
m_errOutputConn->sendInput(CtrlC);
m_errOutputConn->quit();
}
}
QByteArray RemoteGdbProcess::mkFifoCmdLine(const QByteArray &file)
{
return "rm -f " + file + " && mkfifo " + file + "\r\n";
}
QByteArray RemoteGdbProcess::readerCmdLine(const QByteArray &file)
{
return "cat " + file + "\r\n";
}
QByteArray RemoteGdbProcess::removeCarriageReturn(const QByteArray &data)
{
QByteArray output;
for (int i = 0; i < data.size(); ++i) {
const char c = data.at(i);
if (c != '\r')
output += c;
}
return output;
}
void RemoteGdbProcess::checkForGdbExit(QByteArray &output)
{
const QByteArray exitString("^exit");
const int exitPos = output.indexOf(exitString);
if (exitPos != -1) {
disconnect(m_gdbConn.data(), SIGNAL(remoteOutput(QByteArray)),
this, SLOT(handleGdbOutput(QByteArray)));
output.remove(exitPos + exitString.size(), output.size());
stopReaders();
emit finished(0, QProcess::NormalExit);
}
}
const QByteArray RemoteGdbProcess::CtrlC = QByteArray(1, 0x3);
const QByteArray RemoteGdbProcess::AppOutputFile("app_output");
const QByteArray RemoteGdbProcess::ErrOutputFile("err_output");
} // namespace Internal
} // namespace Debugger