From 3c8a8eb8e882c201a857f5477c4b303c8b01f280 Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 14 Jan 2015 16:58:10 +0100 Subject: [PATCH] Debugger: Introduce a pty based channel to inferiors This is experimental for now and requires QTC_USE_PTY=1 in the environment to be used. Change-Id: I460ce7b9283467d481e903f731f4243b7e5ec094 Reviewed-by: Fawzi Mohamed Reviewed-by: hjk --- src/plugins/debugger/debugger.pro | 2 + src/plugins/debugger/debugger.qbs | 1 + src/plugins/debugger/debuggerengine.cpp | 21 +++ src/plugins/debugger/debuggerengine.h | 2 + src/plugins/debugger/gdb/gdbengine.cpp | 7 + src/plugins/debugger/terminal.cpp | 169 ++++++++++++++++++++++++ src/plugins/debugger/terminal.h | 72 ++++++++++ 7 files changed, 274 insertions(+) create mode 100644 src/plugins/debugger/terminal.cpp create mode 100644 src/plugins/debugger/terminal.h diff --git a/src/plugins/debugger/debugger.pro b/src/plugins/debugger/debugger.pro index a9c89ef76ad..83e790c0427 100644 --- a/src/plugins/debugger/debugger.pro +++ b/src/plugins/debugger/debugger.pro @@ -56,6 +56,7 @@ HEADERS += \ stackframe.h \ stackhandler.h \ stackwindow.h \ + terminal.h \ threadswindow.h \ watchhandler.h \ watchutils.h \ @@ -110,6 +111,7 @@ SOURCES += \ stackwindow.cpp \ threadshandler.cpp \ threadswindow.cpp \ + terminal.cpp \ watchdata.cpp \ watchhandler.cpp \ watchutils.cpp \ diff --git a/src/plugins/debugger/debugger.qbs b/src/plugins/debugger/debugger.qbs index f915f65d841..7001f560b3f 100644 --- a/src/plugins/debugger/debugger.qbs +++ b/src/plugins/debugger/debugger.qbs @@ -75,6 +75,7 @@ QtcPlugin { "stackframe.cpp", "stackframe.h", "stackhandler.cpp", "stackhandler.h", "stackwindow.cpp", "stackwindow.h", + "terminal.cpp", "terminal.h", "threaddata.h", "threadshandler.cpp", "threadshandler.h", "threadswindow.cpp", "threadswindow.h", diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp index 801abef5a7f..2ac1e090273 100644 --- a/src/plugins/debugger/debuggerengine.cpp +++ b/src/plugins/debugger/debuggerengine.cpp @@ -45,6 +45,7 @@ #include "registerhandler.h" #include "sourcefileshandler.h" #include "stackhandler.h" +#include "terminal.h" #include "threadshandler.h" #include "watchhandler.h" #include @@ -299,6 +300,7 @@ public: // State of RemoteSetup signal/slots. RemoteSetupState m_remoteSetupState; + Terminal m_terminal; qint64 m_inferiorPid; ModulesHandler m_modulesHandler; @@ -542,6 +544,20 @@ void DebuggerEngine::startDebugger(DebuggerRunControl *runControl) d->m_lastGoodState = DebuggerNotReady; d->m_targetState = DebuggerNotReady; d->m_progress.setProgressValue(200); + + d->m_terminal.setup(); + if (d->m_terminal.isUsable()) { + connect(&d->m_terminal, &Terminal::stdOutReady, [this, runControl](const QString &msg) { + runControl->appendMessage(msg, Utils::StdOutFormatSameLine); + }); + connect(&d->m_terminal, &Terminal::stdErrReady, [this, runControl](const QString &msg) { + runControl->appendMessage(msg, Utils::StdErrFormatSameLine); + }); + connect(&d->m_terminal, &Terminal::error, [this, runControl](const QString &msg) { + runControl->appendMessage(msg, Utils::ErrorMessageFormat); + }); + } + d->queueSetupEngine(); } @@ -1383,6 +1399,11 @@ DebuggerRunControl *DebuggerEngine::runControl() const return d->runControl(); } +Terminal *DebuggerEngine::terminal() const +{ + return &d->m_terminal; +} + bool DebuggerEngine::setToolTipExpression(TextEditor::TextEditorWidget *, const DebuggerToolTipContext &) { diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h index f5d06c7d4ec..5009601806e 100644 --- a/src/plugins/debugger/debuggerengine.h +++ b/src/plugins/debugger/debuggerengine.h @@ -75,6 +75,7 @@ class QmlAdapter; class QmlCppEngine; class DebuggerToolTipContext; class MemoryViewSetupData; +class Terminal; struct WatchUpdateFlags { @@ -352,6 +353,7 @@ protected: void setMasterEngine(DebuggerEngine *masterEngine); DebuggerRunControl *runControl() const; + Terminal *terminal() const; static QString msgStopped(const QString &reason = QString()); static QString msgStoppedBySignal(const QString &meaning, const QString &name); diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index ed9926cd6ad..a480c5239c1 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -34,6 +34,7 @@ #include "coregdbadapter.h" #include "gdbplainengine.h" #include "termgdbadapter.h" +#include "terminal.h" #include "remotegdbserveradapter.h" #include "gdboptionspage.h" @@ -790,6 +791,9 @@ void GdbEngine::interruptInferior() QTC_ASSERT(state() == InferiorStopRequested, qDebug() << "INTERRUPT INFERIOR: " << state(); return); + if (terminal()->sendInterrupt()) + return; + if (usesExecInterrupt()) { postCommand("-exec-interrupt", Immediate); } else { @@ -4354,6 +4358,9 @@ void GdbEngine::startGdb(const QStringList &args) const QByteArray dumperSourcePath = ICore::resourcePath().toLocal8Bit() + "/debugger/"; + if (terminal()->isUsable()) + postCommand("set inferior-tty " + terminal()->slaveDevice()); + const QFileInfo gdbBinaryFile(m_gdb); const QByteArray uninstalledData = gdbBinaryFile.absolutePath().toLocal8Bit() + "/data-directory/python"; diff --git a/src/plugins/debugger/terminal.cpp b/src/plugins/debugger/terminal.cpp new file mode 100644 index 00000000000..e133f365724 --- /dev/null +++ b/src/plugins/debugger/terminal.cpp @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://www.qt.io/licensing. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** 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 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "terminal.h" + +#include +#include +#include + +#include + +#ifdef Q_OS_UNIX +# define DEBUGGER_USE_TERMINAL +#endif + +#ifdef DEBUGGER_USE_TERMINAL +# include +# include +# include +# include +# include +# include +# include +#endif + +namespace Debugger { +namespace Internal { + +static QString currentError() +{ + int err = errno; + return QString::fromLatin1(strerror(err)); +} + +Terminal::Terminal(QObject *parent) + : QObject(parent), m_isUsable(false), m_masterFd(-1), m_masterReader(0) +{ +} + +void Terminal::setup() +{ +#ifdef DEBUGGER_USE_TERMINAL + if (!qEnvironmentVariableIsSet("QTC_USE_PTY")) + return; + + m_masterFd = ::open("/dev/ptmx", O_RDWR); + if (m_masterFd < 0) { + error(tr("Terminal: Cannot open /dev/ptmx: %1").arg(currentError())); + return; + } + + const char *sName = ptsname(m_masterFd); + if (!sName) { + error(tr("Terminal: ptsname failed: %1").arg(currentError())); + return; + } + m_slaveName = sName; + + struct stat s; + int r = ::stat(m_slaveName.constData(), &s); + if (r != 0) { + error(tr("Terminal: Error: %1").arg(currentError())); + return; + } + if (!S_ISCHR(s.st_mode)) { + error(tr("Terminal: Slave is no character device")); + return; + } + + m_masterReader = new QSocketNotifier(m_masterFd, QSocketNotifier::Read, this); + connect(m_masterReader, &QSocketNotifier::activated, + this, &Terminal::onSlaveReaderActivated); + + r = grantpt(m_masterFd); + if (r != 0) { + error(tr("Terminal: grantpt failed: %1").arg(currentError())); + return; + } + + r = unlockpt(m_masterFd); + if (r != 0) { + error(tr("Terminal: unlock failed: %1").arg(currentError())); + return; + } + + m_isUsable = true; +#endif +} + +bool Terminal::isUsable() const +{ + return m_isUsable; +} + +int Terminal::write(const QByteArray &msg) +{ +#ifdef DEBUGGER_USE_TERMINAL + return ::write(m_masterFd, msg.constData(), msg.size()); +#else + Q_UNUSED(msg); + return -1; +#endif +} + +bool Terminal::sendInterrupt() +{ +#ifdef DEBUGGER_USE_TERMINAL + if (!m_isUsable) + return false; + ssize_t written = ::write(m_masterFd, "\003", 1); + return written == 1; +#else + return false; +#endif +} + +void Terminal::onSlaveReaderActivated(int fd) +{ +#ifdef DEBUGGER_USE_TERMINAL + ssize_t available = 0; + int ret = ::ioctl(fd, FIONREAD, (char *) &available); + if (ret != 0) + return; + + QByteArray buffer(available, Qt::Uninitialized); + ssize_t got = ::read(fd, buffer.data(), available); + int err = errno; + if (got < 0) { + error(tr("Terminal: Read failed: %1").arg(QString::fromLatin1(strerror(err)))); + return; + } + buffer.resize(got); + if (got >= 0) + stdOutReady(QString::fromUtf8(buffer)); +#else + Q_UNUSED(fd); +#endif +} + +} // namespace Internal +} // namespace Debugger + diff --git a/src/plugins/debugger/terminal.h b/src/plugins/debugger/terminal.h new file mode 100644 index 00000000000..bbac6877f39 --- /dev/null +++ b/src/plugins/debugger/terminal.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://www.qt.io/licensing. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** 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 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef DEBUGGER_TERMINAL_H +#define DEBUGGER_TERMINAL_H + +#include +#include + +namespace Debugger { +namespace Internal { + +class Terminal : public QObject +{ + Q_OBJECT + +public: + Terminal(QObject *parent = 0); + + void setup(); + bool isUsable() const; + + QByteArray slaveDevice() const { return m_slaveName; } + + int write(const QByteArray &msg); + bool sendInterrupt(); + +signals: + void stdOutReady(const QString &); + void stdErrReady(const QString &); + void error(const QString &); + +private: + void onSlaveReaderActivated(int fd); + + bool m_isUsable; + int m_masterFd; + QSocketNotifier *m_masterReader; + QByteArray m_slaveName; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_TERMINAL_H