valgrindprocess.cpp 10.9 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
Mike McQuaid's avatar
Mike McQuaid committed
2
**
3
** Copyright (C) 2016 The Qt Company Ltd.
Mike McQuaid's avatar
Mike McQuaid committed
4
** Author: Milian Wolff, KDAB (milian.wolff@kdab.com)
5
** Contact: https://www.qt.io/licensing/
Mike McQuaid's avatar
Mike McQuaid committed
6
**
hjk's avatar
hjk committed
7
** This file is part of Qt Creator.
Mike McQuaid's avatar
Mike McQuaid committed
8
**
hjk's avatar
hjk committed
9 10 11 12
** 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
13 14 15
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
Mike McQuaid's avatar
Mike McQuaid committed
16
**
17 18 19 20 21 22 23
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
Mike McQuaid's avatar
Mike McQuaid committed
24
**
hjk's avatar
hjk committed
25
****************************************************************************/
Mike McQuaid's avatar
Mike McQuaid committed
26 27 28

#include "valgrindprocess.h"

29 30
#include <ssh/sshconnectionmanager.h>

31
#include <utils/fileutils.h>
32
#include <utils/qtcassert.h>
33
#include <utils/qtcprocess.h>
34

35 36 37
#include <QDebug>
#include <QEventLoop>

38 39
using namespace ProjectExplorer;

Mike McQuaid's avatar
Mike McQuaid committed
40 41
namespace Valgrind {

42 43 44
ValgrindProcess::ValgrindProcess(const IDevice::ConstPtr &device,
                                 QObject *parent)
    : QObject(parent), m_device(device)
Mike McQuaid's avatar
Mike McQuaid committed
45
{
46
    m_remote.m_connection = 0;
hjk's avatar
hjk committed
47 48
    m_remote.m_error = QProcess::UnknownError;
    m_pid = 0;
Mike McQuaid's avatar
Mike McQuaid committed
49 50
}

51 52 53 54 55 56
ValgrindProcess::~ValgrindProcess()
{
    if (m_remote.m_connection)
        QSsh::releaseConnection(m_remote.m_connection);
}

hjk's avatar
hjk committed
57
void ValgrindProcess::setProcessChannelMode(QProcess::ProcessChannelMode mode)
Mike McQuaid's avatar
Mike McQuaid committed
58
{
hjk's avatar
hjk committed
59 60 61
    if (isLocal())
        m_localProcess.setProcessChannelMode(mode);
    ///TODO: remote support this by handling the mode internally
Mike McQuaid's avatar
Mike McQuaid committed
62 63
}

hjk's avatar
hjk committed
64
QString ValgrindProcess::workingDirectory() const
Mike McQuaid's avatar
Mike McQuaid committed
65
{
66
    return m_debuggee.workingDirectory;
Mike McQuaid's avatar
Mike McQuaid committed
67 68
}

hjk's avatar
hjk committed
69
bool ValgrindProcess::isRunning() const
70
{
hjk's avatar
hjk committed
71
    if (isLocal())
72
        return m_localProcess.isRunning();
hjk's avatar
hjk committed
73 74
    else
        return m_remote.m_process && m_remote.m_process->isRunning();
75 76
}

77 78 79 80 81
void ValgrindProcess::setValgrindExecutable(const QString &valgrindExecutable)
{
    m_valgrindExecutable = valgrindExecutable;
}

82
void ValgrindProcess::setDebuggee(const StandardRunnable &debuggee)
83
{
84
    m_debuggee = debuggee;
Mike McQuaid's avatar
Mike McQuaid committed
85 86
}

87
void ValgrindProcess::setValgrindArguments(const QStringList &valgrindArguments)
88
{
89
    m_valgrindArguments = valgrindArguments;
90 91
}

hjk's avatar
hjk committed
92
void ValgrindProcess::close()
Mike McQuaid's avatar
Mike McQuaid committed
93
{
hjk's avatar
hjk committed
94
    if (isLocal()) {
95
        m_localProcess.stop();
hjk's avatar
hjk committed
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
    } else {
        QTC_ASSERT(m_remote.m_connection->state() == QSsh::SshConnection::Connected, return);
        if (m_remote.m_process) {
            if (m_pid) {
                const QString killTemplate = QString::fromLatin1("kill -%2 %1" // kill
                                                    ).arg(m_pid);

                const QString niceKill = killTemplate.arg(QLatin1String("SIGTERM"));
                const QString brutalKill = killTemplate.arg(QLatin1String("SIGKILL"));
                const QString remoteCall = niceKill + QLatin1String("; sleep 1; ") + brutalKill;

                QSsh::SshRemoteProcess::Ptr cleanup = m_remote.m_connection->createRemoteProcess(remoteCall.toUtf8());
                cleanup->start();
            }
        }
    }
Mike McQuaid's avatar
Mike McQuaid committed
112 113
}

114
void ValgrindProcess::run()
Mike McQuaid's avatar
Mike McQuaid committed
115
{
hjk's avatar
hjk committed
116
    if (isLocal()) {
117 118 119 120 121 122 123 124
        connect(&m_localProcess, &ApplicationLauncher::processExited,
                this, &ValgrindProcess::finished);
        connect(&m_localProcess, &ApplicationLauncher::processStarted,
                this, &ValgrindProcess::localProcessStarted);
        connect(&m_localProcess, &ApplicationLauncher::error,
                this, &ValgrindProcess::error);
        connect(&m_localProcess, &ApplicationLauncher::appendMessage,
                this, &ValgrindProcess::processOutput);
125

126 127 128 129 130 131 132
        StandardRunnable valgrind;
        valgrind.executable = m_valgrindExecutable;
        valgrind.runMode = m_debuggee.runMode;
        valgrind.commandLineArguments = argumentString(Utils::HostOsInfo::hostOs());
        valgrind.workingDirectory = m_debuggee.workingDirectory;
        valgrind.environment = m_debuggee.environment;
        m_localProcess.start(valgrind);
133

hjk's avatar
hjk committed
134 135 136
    } else {
        // connect to host and wait for connection
        if (!m_remote.m_connection)
137
            m_remote.m_connection = QSsh::acquireConnection(m_device->sshParameters());
hjk's avatar
hjk committed
138 139

        if (m_remote.m_connection->state() != QSsh::SshConnection::Connected) {
140 141 142 143
            connect(m_remote.m_connection, &QSsh::SshConnection::connected,
                    this, &ValgrindProcess::connected);
            connect(m_remote.m_connection, &QSsh::SshConnection::error,
                    this, &ValgrindProcess::handleError);
hjk's avatar
hjk committed
144 145 146 147 148 149
            if (m_remote.m_connection->state() == QSsh::SshConnection::Unconnected)
                m_remote.m_connection->connectToHost();
        } else {
            connected();
        }
    }
Mike McQuaid's avatar
Mike McQuaid committed
150 151
}

hjk's avatar
hjk committed
152
QString ValgrindProcess::errorString() const
Mike McQuaid's avatar
Mike McQuaid committed
153
{
hjk's avatar
hjk committed
154 155 156 157
    if (isLocal())
        return m_localProcess.errorString();
    else
        return m_remote.m_errorString;
Mike McQuaid's avatar
Mike McQuaid committed
158 159
}

160
QProcess::ProcessError ValgrindProcess::processError() const
Mike McQuaid's avatar
Mike McQuaid committed
161
{
hjk's avatar
hjk committed
162
    if (isLocal())
163
        return m_localProcess.processError();
hjk's avatar
hjk committed
164 165
    else
        return m_remote.m_error;
Mike McQuaid's avatar
Mike McQuaid committed
166 167
}

hjk's avatar
hjk committed
168
void ValgrindProcess::handleError(QSsh::SshError error)
Mike McQuaid's avatar
Mike McQuaid committed
169
{
Orgad Shaneh's avatar
Orgad Shaneh committed
170
    if (!isLocal()) {
hjk's avatar
hjk committed
171 172 173 174 175 176 177 178 179 180 181
        switch (error) {
            case QSsh::SshTimeoutError:
                m_remote.m_error = QProcess::Timedout;
                break;
            default:
                m_remote.m_error = QProcess::FailedToStart;
                break;
        }
    }
    m_remote.m_errorString = m_remote.m_connection->errorString();
    emit this->error(m_remote.m_error);
Mike McQuaid's avatar
Mike McQuaid committed
182 183
}

hjk's avatar
hjk committed
184
qint64 ValgrindProcess::pid() const
Mike McQuaid's avatar
Mike McQuaid committed
185
{
186
    return m_pid;
Mike McQuaid's avatar
Mike McQuaid committed
187 188
}

189
void ValgrindProcess::handleRemoteStderr()
Mike McQuaid's avatar
Mike McQuaid committed
190
{
191
    const QString b = QString::fromUtf8(m_remote.m_process->readAllStandardError());
Mike McQuaid's avatar
Mike McQuaid committed
192
    if (!b.isEmpty())
193
        emit processOutput(b, Utils::StdErrFormat);
Mike McQuaid's avatar
Mike McQuaid committed
194 195
}

196
void ValgrindProcess::handleRemoteStdout()
Mike McQuaid's avatar
Mike McQuaid committed
197
{
198
    const QString b = QString::fromUtf8(m_remote.m_process->readAllStandardOutput());
Mike McQuaid's avatar
Mike McQuaid committed
199
    if (!b.isEmpty())
200
        emit processOutput(b, Utils::StdOutFormat);
Mike McQuaid's avatar
Mike McQuaid committed
201 202
}

hjk's avatar
hjk committed
203 204
/// Remote
void ValgrindProcess::connected()
205
{
hjk's avatar
hjk committed
206
    QTC_ASSERT(m_remote.m_connection->state() == QSsh::SshConnection::Connected, return);
207

208 209
    emit localHostAddressRetrieved(m_remote.m_connection->connectionInfo().localAddress);

210 211 212
    // connected, run command
    QString cmd;

213 214
    if (!m_debuggee.workingDirectory.isEmpty())
        cmd += QString::fromLatin1("cd '%1' && ").arg(m_debuggee.workingDirectory);
215

216
    cmd += m_valgrindExecutable + QLatin1Char(' ') + argumentString(Utils::OsTypeLinux);
217

hjk's avatar
hjk committed
218
    m_remote.m_process = m_remote.m_connection->createRemoteProcess(cmd.toUtf8());
219 220 221 222 223 224 225 226
    connect(m_remote.m_process.data(), &QSsh::SshRemoteProcess::readyReadStandardError,
            this, &ValgrindProcess::handleRemoteStderr);
    connect(m_remote.m_process.data(), &QSsh::SshRemoteProcess::readyReadStandardOutput,
            this, &ValgrindProcess::handleRemoteStdout);
    connect(m_remote.m_process.data(), &QSsh::SshRemoteProcess::closed,
            this, &ValgrindProcess::closed);
    connect(m_remote.m_process.data(), &QSsh::SshRemoteProcess::started,
            this, &ValgrindProcess::remoteProcessStarted);
hjk's avatar
hjk committed
227
    m_remote.m_process->start();
228 229
}

hjk's avatar
hjk committed
230
QSsh::SshConnection *ValgrindProcess::connection() const
231
{
hjk's avatar
hjk committed
232
    return m_remote.m_connection;
Mike McQuaid's avatar
Mike McQuaid committed
233
}
234

235 236
bool ValgrindProcess::isLocal() const
{
237
    return m_device->type() == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE;
238 239
}

240 241 242 243 244 245 246
void ValgrindProcess::localProcessStarted()
{
    m_pid = m_localProcess.applicationPID();
    emit started();
}

void ValgrindProcess::remoteProcessStarted()
247
{
hjk's avatar
hjk committed
248
    QTC_ASSERT(m_remote.m_connection->state() == QSsh::SshConnection::Connected, return);
249

250 251 252 253 254 255 256 257 258
    // find out what PID our process has

    // NOTE: valgrind cloaks its name,
    // e.g.: valgrind --tool=memcheck foobar
    // => ps aux, pidof will see valgrind.bin
    // => pkill/killall/top... will see memcheck-amd64-linux or similar
    // hence we need to do something more complex...

    // plain path to exe, m_valgrindExe contains e.g. env vars etc. pp.
259
    const QString proc = m_valgrindExecutable.split(QLatin1Char(' ')).last();
260 261
    // sleep required since otherwise we might only match "bash -c..."
    //  and not the actual valgrind run
262 263 264 265 266
    const QString cmd = QString::fromLatin1("sleep 1; ps ax" // list all processes with aliased name
                                            " | grep '\\b%1.*%2'" // find valgrind process
                                            " | tail -n 1" // limit to single process
                                            // we pick the last one, first would be "bash -c ..."
                                            " | awk '{print $1;}'" // get pid
267
                                            ).arg(proc, Utils::FileName::fromString(m_debuggee.executable).fileName());
268

hjk's avatar
hjk committed
269
    m_remote.m_findPID = m_remote.m_connection->createRemoteProcess(cmd.toUtf8());
270 271 272 273
    connect(m_remote.m_findPID.data(), &QSsh::SshRemoteProcess::readyReadStandardError,
            this, &ValgrindProcess::handleRemoteStderr);
    connect(m_remote.m_findPID.data(), &QSsh::SshRemoteProcess::readyReadStandardOutput,
            this, &ValgrindProcess::findPIDOutputReceived);
hjk's avatar
hjk committed
274
    m_remote.m_findPID->start();
275 276
}

hjk's avatar
hjk committed
277
void ValgrindProcess::findPIDOutputReceived()
278 279
{
    bool ok;
hjk's avatar
hjk committed
280
    m_pid = m_remote.m_findPID->readAllStandardOutput().trimmed().toLongLong(&ok);
281 282
    if (!ok) {
        m_pid = 0;
hjk's avatar
hjk committed
283 284
        m_remote.m_errorString = tr("Could not determine remote PID.");
        m_remote.m_error = QProcess::FailedToStart;
285 286 287 288 289 290 291
        emit ValgrindProcess::error(QProcess::FailedToStart);
        close();
    } else {
        emit started();
    }
}

292 293 294
QString ValgrindProcess::argumentString(Utils::OsType osType) const
{
    QString arguments = Utils::QtcProcess::joinArgs(m_valgrindArguments, osType);
295 296 297
    if (!m_debuggee.executable.isEmpty())
        Utils::QtcProcess::addArg(&arguments, m_debuggee.executable, osType);
    Utils::QtcProcess::addArgs(&arguments, m_debuggee.commandLineArguments);
298 299 300
    return arguments;
}

hjk's avatar
hjk committed
301
void ValgrindProcess::closed(int status)
302
{
hjk's avatar
hjk committed
303
    QTC_ASSERT(m_remote.m_process, return);
304

hjk's avatar
hjk committed
305
    m_remote.m_errorString = m_remote.m_process->errorString();
306
    if (status == QSsh::SshRemoteProcess::FailedToStart) {
hjk's avatar
hjk committed
307
        m_remote.m_error = QProcess::FailedToStart;
308
        emit ValgrindProcess::error(QProcess::FailedToStart);
309
    } else if (status == QSsh::SshRemoteProcess::NormalExit) {
hjk's avatar
hjk committed
310
        emit finished(m_remote.m_process->exitCode(), QProcess::NormalExit);
311
    } else if (status == QSsh::SshRemoteProcess::CrashExit) {
hjk's avatar
hjk committed
312 313
        m_remote.m_error = QProcess::Crashed;
        emit finished(m_remote.m_process->exitCode(), QProcess::CrashExit);
314 315 316
    }
}

hjk's avatar
hjk committed
317
} // namespace Valgrind