valgrindprocess.cpp 11.6 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
Mike McQuaid's avatar
Mike McQuaid committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
Mike McQuaid's avatar
Mike McQuaid committed
5
6
** Author: Milian Wolff, KDAB (milian.wolff@kdab.com)
**
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
13
** 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
Eike Ziller's avatar
Eike Ziller committed
14
15
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
Mike McQuaid's avatar
Mike McQuaid committed
16
17
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
18
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
19
20
21
22
23
24
** 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.
hjk's avatar
hjk committed
25
26
27
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
Mike McQuaid's avatar
Mike McQuaid committed
28
29
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
30
****************************************************************************/
Mike McQuaid's avatar
Mike McQuaid committed
31
32
33

#include "valgrindprocess.h"

34
35
36
#include <QDebug>
#include <QEventLoop>
#include <QFileInfo>
Mike McQuaid's avatar
Mike McQuaid committed
37

38
#include <utils/qtcassert.h>
39
#include <utils/qtcprocess.h>
40

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

hjk's avatar
hjk committed
43
44
ValgrindProcess::ValgrindProcess(bool isLocal, const QSsh::SshConnectionParameters &sshParams,
                                 QSsh::SshConnection *connection, QObject *parent)
45
46
47
    : QObject(parent),
      m_isLocal(isLocal),
      m_localRunMode(ProjectExplorer::ApplicationLauncher::Gui)
Mike McQuaid's avatar
Mike McQuaid committed
48
{
hjk's avatar
hjk committed
49
50
51
52
    m_remote.m_params = sshParams;
    m_remote.m_connection = connection;
    m_remote.m_error = QProcess::UnknownError;
    m_pid = 0;
Mike McQuaid's avatar
Mike McQuaid committed
53
54
}

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

hjk's avatar
hjk committed
62
void ValgrindProcess::setWorkingDirectory(const QString &path)
Mike McQuaid's avatar
Mike McQuaid committed
63
{
hjk's avatar
hjk committed
64
65
66
67
    if (isLocal())
        m_localProcess.setWorkingDirectory(path);
    else
        m_remote.m_workingDir = path;
Mike McQuaid's avatar
Mike McQuaid committed
68
69
}

hjk's avatar
hjk committed
70
QString ValgrindProcess::workingDirectory() const
Mike McQuaid's avatar
Mike McQuaid committed
71
{
hjk's avatar
hjk committed
72
73
74
75
    if (isLocal())
        return m_localProcess.workingDirectory();
    else
        return m_remote.m_workingDir;
Mike McQuaid's avatar
Mike McQuaid committed
76
77
}

hjk's avatar
hjk committed
78
bool ValgrindProcess::isRunning() const
79
{
hjk's avatar
hjk committed
80
    if (isLocal())
81
        return m_localProcess.isRunning();
hjk's avatar
hjk committed
82
83
    else
        return m_remote.m_process && m_remote.m_process->isRunning();
84
85
}

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
void ValgrindProcess::setValgrindExecutable(const QString &valgrindExecutable)
{
    m_valgrindExecutable = valgrindExecutable;
}

void ValgrindProcess::setValgrindArguments(const QStringList &valgrindArguments)
{
    m_valgrindArguments = valgrindArguments;
}

void ValgrindProcess::setDebuggeeExecutable(const QString &debuggeeExecutable)
{
    m_debuggeeExecutable = debuggeeExecutable;
}

void ValgrindProcess::setDebugeeArguments(const QString &debuggeeArguments)
{
    m_debuggeeArguments = debuggeeArguments;
}

hjk's avatar
hjk committed
106
void ValgrindProcess::setEnvironment(const Utils::Environment &environment)
Mike McQuaid's avatar
Mike McQuaid committed
107
{
hjk's avatar
hjk committed
108
109
110
    if (isLocal())
        m_localProcess.setEnvironment(environment);
    ///TODO: remote anything that should/could be done here?
Mike McQuaid's avatar
Mike McQuaid committed
111
112
}

113
114
115
116
117
void ValgrindProcess::setLocalRunMode(ProjectExplorer::ApplicationLauncher::Mode localRunMode)
{
    m_localRunMode = localRunMode;
}

hjk's avatar
hjk committed
118
void ValgrindProcess::close()
Mike McQuaid's avatar
Mike McQuaid committed
119
{
hjk's avatar
hjk committed
120
    if (isLocal()) {
121
        m_localProcess.stop();
hjk's avatar
hjk committed
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
    } 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
138
139
}

140
void ValgrindProcess::run()
Mike McQuaid's avatar
Mike McQuaid committed
141
{
hjk's avatar
hjk committed
142
    if (isLocal()) {
143
        connect(&m_localProcess, SIGNAL(processExited(int,QProcess::ExitStatus)),
hjk's avatar
hjk committed
144
                this, SIGNAL(finished(int,QProcess::ExitStatus)));
145
146
        connect(&m_localProcess, SIGNAL(processStarted()),
                this, SLOT(localProcessStarted()));
hjk's avatar
hjk committed
147
148
        connect(&m_localProcess, SIGNAL(error(QProcess::ProcessError)),
                this, SIGNAL(error(QProcess::ProcessError)));
149
150
151
152
153
154
        connect(&m_localProcess, SIGNAL(appendMessage(QString,Utils::OutputFormat)),
                this, SIGNAL(processOutput(QString,Utils::OutputFormat)));

        m_localProcess.start(m_localRunMode, m_valgrindExecutable,
                             argumentString(Utils::HostOsInfo::hostOs()));

hjk's avatar
hjk committed
155
    } else {
156
157
        m_remote.m_valgrindExe = m_valgrindExecutable;
        m_remote.m_debuggee = m_debuggeeExecutable;
hjk's avatar
hjk committed
158
159
160
161
162
163
164
165

        // connect to host and wait for connection
        if (!m_remote.m_connection)
            m_remote.m_connection = new QSsh::SshConnection(m_remote.m_params, this);

        if (m_remote.m_connection->state() != QSsh::SshConnection::Connected) {
            connect(m_remote.m_connection, SIGNAL(connected()), this, SLOT(connected()));
            connect(m_remote.m_connection, SIGNAL(error(QSsh::SshError)),
166
                    this, SLOT(handleError(QSsh::SshError)));
hjk's avatar
hjk committed
167
168
169
170
171
172
            if (m_remote.m_connection->state() == QSsh::SshConnection::Unconnected)
                m_remote.m_connection->connectToHost();
        } else {
            connected();
        }
    }
Mike McQuaid's avatar
Mike McQuaid committed
173
174
}

hjk's avatar
hjk committed
175
QString ValgrindProcess::errorString() const
Mike McQuaid's avatar
Mike McQuaid committed
176
{
hjk's avatar
hjk committed
177
178
179
180
    if (isLocal())
        return m_localProcess.errorString();
    else
        return m_remote.m_errorString;
Mike McQuaid's avatar
Mike McQuaid committed
181
182
}

hjk's avatar
hjk committed
183
QProcess::ProcessError ValgrindProcess::error() const
Mike McQuaid's avatar
Mike McQuaid committed
184
{
hjk's avatar
hjk committed
185
186
187
188
    if (isLocal())
        return m_localProcess.error();
    else
        return m_remote.m_error;
Mike McQuaid's avatar
Mike McQuaid committed
189
190
}

hjk's avatar
hjk committed
191
void ValgrindProcess::handleError(QSsh::SshError error)
Mike McQuaid's avatar
Mike McQuaid committed
192
{
Orgad Shaneh's avatar
Orgad Shaneh committed
193
    if (!isLocal()) {
hjk's avatar
hjk committed
194
195
196
197
198
199
200
201
202
203
204
        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
205
206
}

hjk's avatar
hjk committed
207
qint64 ValgrindProcess::pid() const
Mike McQuaid's avatar
Mike McQuaid committed
208
{
209
    return m_pid;
Mike McQuaid's avatar
Mike McQuaid committed
210
211
}

212
void ValgrindProcess::handleRemoteStderr()
Mike McQuaid's avatar
Mike McQuaid committed
213
{
214
    const QString b = QString::fromUtf8(m_remote.m_process->readAllStandardError());
Mike McQuaid's avatar
Mike McQuaid committed
215
    if (!b.isEmpty())
216
        emit processOutput(b, Utils::StdErrFormat);
Mike McQuaid's avatar
Mike McQuaid committed
217
218
}

219
void ValgrindProcess::handleRemoteStdout()
Mike McQuaid's avatar
Mike McQuaid committed
220
{
221
    const QString b = QString::fromUtf8(m_remote.m_process->readAllStandardOutput());
Mike McQuaid's avatar
Mike McQuaid committed
222
    if (!b.isEmpty())
223
        emit processOutput(b, Utils::StdOutFormat);
Mike McQuaid's avatar
Mike McQuaid committed
224
225
}

hjk's avatar
hjk committed
226
227
/// Remote
void ValgrindProcess::connected()
228
{
hjk's avatar
hjk committed
229
    QTC_ASSERT(m_remote.m_connection->state() == QSsh::SshConnection::Connected, return);
230

231
232
    emit localHostAddressRetrieved(m_remote.m_connection->connectionInfo().localAddress);

233
234
235
    // connected, run command
    QString cmd;

hjk's avatar
hjk committed
236
237
    if (!m_remote.m_workingDir.isEmpty())
        cmd += QString::fromLatin1("cd '%1' && ").arg(m_remote.m_workingDir);
238

239
    cmd += m_remote.m_valgrindExe + QLatin1Char(' ') + argumentString(Utils::OsTypeLinux);
240

hjk's avatar
hjk committed
241
242
    m_remote.m_process = m_remote.m_connection->createRemoteProcess(cmd.toUtf8());
    connect(m_remote.m_process.data(), SIGNAL(readyReadStandardError()),
243
            this, SLOT(handleRemoteStderr()));
hjk's avatar
hjk committed
244
    connect(m_remote.m_process.data(), SIGNAL(readyReadStandardOutput()),
245
            this, SLOT(handleRemoteStdout()));
hjk's avatar
hjk committed
246
    connect(m_remote.m_process.data(), SIGNAL(closed(int)),
247
            this, SLOT(closed(int)));
hjk's avatar
hjk committed
248
    connect(m_remote.m_process.data(), SIGNAL(started()),
249
            this, SLOT(remoteProcessStarted()));
hjk's avatar
hjk committed
250
    m_remote.m_process->start();
251
252
}

hjk's avatar
hjk committed
253
QSsh::SshConnection *ValgrindProcess::connection() const
254
{
hjk's avatar
hjk committed
255
    return m_remote.m_connection;
Mike McQuaid's avatar
Mike McQuaid committed
256
}
257

258
259
260
261
262
263
264
void ValgrindProcess::localProcessStarted()
{
    m_pid = m_localProcess.applicationPID();
    emit started();
}

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

268
269
270
271
272
273
274
275
276
    // 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.
hjk's avatar
hjk committed
277
    const QString proc = m_remote.m_valgrindExe.split(QLatin1Char(' ')).last();
278
279
    // sleep required since otherwise we might only match "bash -c..."
    //  and not the actual valgrind run
280
281
282
283
284
    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
hjk's avatar
hjk committed
285
                                            ).arg(proc, QFileInfo(m_remote.m_debuggee).fileName());
286

hjk's avatar
hjk committed
287
288
    m_remote.m_findPID = m_remote.m_connection->createRemoteProcess(cmd.toUtf8());
    connect(m_remote.m_findPID.data(), SIGNAL(readyReadStandardError()),
289
            this, SLOT(handleRemoteStderr()));
hjk's avatar
hjk committed
290
291
292
    connect(m_remote.m_findPID.data(), SIGNAL(readyReadStandardOutput()),
            this, SLOT(findPIDOutputReceived()));
    m_remote.m_findPID->start();
293
294
}

hjk's avatar
hjk committed
295
void ValgrindProcess::findPIDOutputReceived()
296
297
{
    bool ok;
hjk's avatar
hjk committed
298
    m_pid = m_remote.m_findPID->readAllStandardOutput().trimmed().toLongLong(&ok);
299
300
    if (!ok) {
        m_pid = 0;
hjk's avatar
hjk committed
301
302
        m_remote.m_errorString = tr("Could not determine remote PID.");
        m_remote.m_error = QProcess::FailedToStart;
303
304
305
306
307
308
309
        emit ValgrindProcess::error(QProcess::FailedToStart);
        close();
    } else {
        emit started();
    }
}

310
311
312
QString ValgrindProcess::argumentString(Utils::OsType osType) const
{
    QString arguments = Utils::QtcProcess::joinArgs(m_valgrindArguments, osType);
313
314
    if (!m_debuggeeExecutable.isEmpty())
        Utils::QtcProcess::addArg(&arguments, m_debuggeeExecutable, osType);
315
    Utils::QtcProcess::addArgs(&arguments, m_debuggeeArguments);
316
317
318
    return arguments;
}

319

hjk's avatar
hjk committed
320
///////////
321

hjk's avatar
hjk committed
322
void ValgrindProcess::closed(int status)
323
{
hjk's avatar
hjk committed
324
    QTC_ASSERT(m_remote.m_process, return);
325

hjk's avatar
hjk committed
326
    m_remote.m_errorString = m_remote.m_process->errorString();
327
    if (status == QSsh::SshRemoteProcess::FailedToStart) {
hjk's avatar
hjk committed
328
        m_remote.m_error = QProcess::FailedToStart;
329
        emit ValgrindProcess::error(QProcess::FailedToStart);
330
    } else if (status == QSsh::SshRemoteProcess::NormalExit) {
hjk's avatar
hjk committed
331
        emit finished(m_remote.m_process->exitCode(), QProcess::NormalExit);
332
    } else if (status == QSsh::SshRemoteProcess::CrashExit) {
hjk's avatar
hjk committed
333
334
        m_remote.m_error = QProcess::Crashed;
        emit finished(m_remote.m_process->exitCode(), QProcess::CrashExit);
335
336
337
    }
}

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