summaryrefslogtreecommitdiff
path: root/src/plugins/debugger/gdb/remotegdbprocess.cpp
diff options
context:
space:
mode:
authorck <qt-info@nokia.com>2010-05-05 12:49:08 +0200
committerck <qt-info@nokia.com>2010-05-05 12:51:21 +0200
commitef7c5fae607e0684d01213ab10d91ab8ae41d4ec (patch)
treef0fba43ddcaa1d6d867c43154bcd3dd00fd06d2c /src/plugins/debugger/gdb/remotegdbprocess.cpp
parenta945e96377cf0c8b72a96ce6de2bb5889abc62c2 (diff)
downloadqt-creator-ef7c5fae607e0684d01213ab10d91ab8ae41d4ec.tar.gz
Debugger: Support running GDB over SSH.
Reviewed-by: hjk
Diffstat (limited to 'src/plugins/debugger/gdb/remotegdbprocess.cpp')
-rw-r--r--src/plugins/debugger/gdb/remotegdbprocess.cpp333
1 files changed, 333 insertions, 0 deletions
diff --git a/src/plugins/debugger/gdb/remotegdbprocess.cpp b/src/plugins/debugger/gdb/remotegdbprocess.cpp
new file mode 100644
index 0000000000..580b1d1f7d
--- /dev/null
+++ b/src/plugins/debugger/gdb/remotegdbprocess.cpp
@@ -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