2023-03-07 17:55:38 +01:00
|
|
|
// Copyright (C) 2022 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
|
|
|
|
|
|
|
|
|
#include <QCommandLineParser>
|
|
|
|
|
#include <QCoreApplication>
|
|
|
|
|
#include <QDir>
|
|
|
|
|
#include <QLocalSocket>
|
|
|
|
|
#include <QLoggingCategory>
|
|
|
|
|
#include <QMutex>
|
|
|
|
|
#include <QProcess>
|
|
|
|
|
#include <QSocketNotifier>
|
|
|
|
|
#include <QThread>
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
#include <QWinEventNotifier>
|
|
|
|
|
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
#else
|
|
|
|
|
#include <optional>
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <sys/mman.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef Q_OS_LINUX
|
|
|
|
|
#include <sys/ptrace.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
|
|
Q_LOGGING_CATEGORY(log, "qtc.process_stub", QtWarningMsg);
|
|
|
|
|
|
|
|
|
|
// Global variables
|
|
|
|
|
|
|
|
|
|
QCommandLineParser commandLineParser;
|
|
|
|
|
|
|
|
|
|
// The inferior command and arguments
|
|
|
|
|
QStringList inferiorCmdAndArguments;
|
|
|
|
|
// Whether to Suspend the inferior process on startup (to allow a debugger to attach)
|
|
|
|
|
bool debugMode = false;
|
|
|
|
|
// Whether to run in test mode (i.e. to start manually from the command line)
|
|
|
|
|
bool testMode = false;
|
|
|
|
|
// The control socket used to communicate with Qt Creator
|
|
|
|
|
QLocalSocket controlSocket;
|
|
|
|
|
// Environment variables to set for the inferior process
|
|
|
|
|
std::optional<QStringList> environmentVariables;
|
|
|
|
|
|
|
|
|
|
QProcess inferiorProcess;
|
|
|
|
|
int inferiorId{0};
|
|
|
|
|
|
|
|
|
|
#ifndef Q_OS_WIN
|
|
|
|
|
|
|
|
|
|
#ifdef Q_OS_DARWIN
|
|
|
|
|
// A memory mapped helper to retrieve the pid of the inferior process in debugMode
|
|
|
|
|
static int *shared_child_pid = nullptr;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
using OSSocketNotifier = QSocketNotifier;
|
|
|
|
|
#else
|
|
|
|
|
Q_PROCESS_INFORMATION *win_process_information = nullptr;
|
|
|
|
|
using OSSocketNotifier = QWinEventNotifier;
|
|
|
|
|
#endif
|
|
|
|
|
// Helper to read a single character from stdin in testMode
|
|
|
|
|
OSSocketNotifier *stdInNotifier;
|
|
|
|
|
|
|
|
|
|
QThread processThread;
|
|
|
|
|
|
|
|
|
|
// Helper to create the shared memory mapped segment
|
|
|
|
|
void setupSharedPid();
|
|
|
|
|
// Parses the command line, returns a status code in case of error
|
|
|
|
|
std::optional<int> tryParseCommandLine(QCoreApplication &app);
|
|
|
|
|
// Sets the working directory, returns a status code in case of error
|
|
|
|
|
std::optional<int> trySetWorkingDir();
|
|
|
|
|
// Reads the environment variables from the env file, returns a status code in case of error
|
|
|
|
|
std::optional<int> readEnvFile();
|
|
|
|
|
|
|
|
|
|
void setupControlSocket();
|
|
|
|
|
void setupSignalHandlers();
|
|
|
|
|
void startProcess(const QString &program, const QStringList &arguments, const QString &workingDir);
|
|
|
|
|
void readKey();
|
|
|
|
|
void sendSelfPid();
|
|
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
QCoreApplication a(argc, argv);
|
|
|
|
|
|
|
|
|
|
setupSharedPid();
|
|
|
|
|
|
|
|
|
|
auto error = tryParseCommandLine(a);
|
|
|
|
|
if (error)
|
|
|
|
|
return error.value();
|
|
|
|
|
|
|
|
|
|
qCInfo(log) << "Debug helper started: ";
|
|
|
|
|
qCInfo(log) << "Socket:" << commandLineParser.value("socket");
|
|
|
|
|
qCInfo(log) << "Inferior:" << inferiorCmdAndArguments.join(QChar::Space);
|
|
|
|
|
qCInfo(log) << "Working Directory" << commandLineParser.value("workingDir");
|
|
|
|
|
qCInfo(log) << "Env file:" << commandLineParser.value("envfile");
|
|
|
|
|
qCInfo(log) << "Mode:"
|
|
|
|
|
<< QLatin1String(testMode ? "test | " : "")
|
|
|
|
|
+ QLatin1String(debugMode ? "debug" : "run");
|
|
|
|
|
|
|
|
|
|
error = trySetWorkingDir();
|
|
|
|
|
if (error)
|
|
|
|
|
return error.value();
|
|
|
|
|
|
|
|
|
|
error = readEnvFile();
|
|
|
|
|
if (error)
|
|
|
|
|
return error.value();
|
|
|
|
|
|
|
|
|
|
if (testMode) {
|
|
|
|
|
sendSelfPid();
|
|
|
|
|
setupSignalHandlers();
|
|
|
|
|
|
|
|
|
|
startProcess(inferiorCmdAndArguments[0],
|
|
|
|
|
inferiorCmdAndArguments.mid(1),
|
|
|
|
|
commandLineParser.value("workingDir"));
|
|
|
|
|
|
|
|
|
|
if (debugMode) {
|
|
|
|
|
qDebug() << "Press 'c' to continue or 'k' to kill, followed by 'enter'";
|
|
|
|
|
readKey();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return a.exec();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupControlSocket();
|
|
|
|
|
|
|
|
|
|
return a.exec();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendMsg(const QByteArray &msg)
|
|
|
|
|
{
|
|
|
|
|
if (controlSocket.state() == QLocalSocket::ConnectedState) {
|
|
|
|
|
controlSocket.write(msg);
|
|
|
|
|
} else {
|
|
|
|
|
qDebug() << "MSG:" << msg;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-24 09:26:42 +01:00
|
|
|
void sendQtcMarker(const QByteArray &marker)
|
|
|
|
|
{
|
|
|
|
|
sendMsg(QByteArray("qtc: ") + marker + "\n");
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 17:55:38 +01:00
|
|
|
void sendPid(int inferiorPid)
|
|
|
|
|
{
|
|
|
|
|
sendMsg(QString("pid %1\n").arg(inferiorPid).toUtf8());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendThreadId(int inferiorThreadPid)
|
|
|
|
|
{
|
|
|
|
|
sendMsg(QString("thread %1\n").arg(inferiorThreadPid).toUtf8());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendSelfPid()
|
|
|
|
|
{
|
|
|
|
|
sendMsg(QString("spid %1\n").arg(QCoreApplication::applicationPid()).toUtf8());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendExit(int exitCode)
|
|
|
|
|
{
|
|
|
|
|
sendMsg(QString("exit %1\n").arg(exitCode).toUtf8());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendCrash(int exitCode)
|
|
|
|
|
{
|
|
|
|
|
sendMsg(QString("crash %1\n").arg(exitCode).toUtf8());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendErrChDir()
|
|
|
|
|
{
|
|
|
|
|
sendMsg(QString("err:chdir %1\n").arg(errno).toUtf8());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void doExit(int exitCode)
|
|
|
|
|
{
|
|
|
|
|
if (controlSocket.state() == QLocalSocket::ConnectedState && controlSocket.bytesToWrite())
|
|
|
|
|
controlSocket.waitForBytesWritten(1000);
|
|
|
|
|
|
2023-03-21 10:15:54 +01:00
|
|
|
if (!commandLineParser.value("wait").isEmpty()) {
|
|
|
|
|
std::cout << commandLineParser.value("wait").toStdString();
|
|
|
|
|
std::cin.get();
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 17:55:38 +01:00
|
|
|
exit(exitCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onInferiorFinished(int exitCode, QProcess::ExitStatus status)
|
|
|
|
|
{
|
|
|
|
|
qCInfo(log) << "Inferior finished";
|
|
|
|
|
|
|
|
|
|
if (status == QProcess::CrashExit) {
|
|
|
|
|
sendCrash(exitCode);
|
|
|
|
|
doExit(exitCode);
|
|
|
|
|
} else {
|
|
|
|
|
sendExit(exitCode);
|
|
|
|
|
doExit(exitCode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onInferiorErrorOccurered(QProcess::ProcessError error)
|
|
|
|
|
{
|
|
|
|
|
qCInfo(log) << "Inferior error: " << error << inferiorProcess.errorString();
|
|
|
|
|
sendCrash(inferiorProcess.exitCode());
|
|
|
|
|
doExit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onInferiorStarted()
|
|
|
|
|
{
|
|
|
|
|
inferiorId = inferiorProcess.processId();
|
|
|
|
|
qCInfo(log) << "Inferior started ( pid:" << inferiorId << ")";
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
sendThreadId(win_process_information->dwThreadId);
|
|
|
|
|
sendPid(inferiorId);
|
|
|
|
|
#elif defined(Q_OS_DARWIN)
|
|
|
|
|
// In debug mode we use the poll timer to send the pid.
|
|
|
|
|
if (!debugMode)
|
|
|
|
|
sendPid(inferiorId);
|
|
|
|
|
#else
|
|
|
|
|
ptrace(PTRACE_DETACH, inferiorId, 0, SIGSTOP);
|
|
|
|
|
sendPid(inferiorId);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setupUnixInferior()
|
|
|
|
|
{
|
|
|
|
|
#ifndef Q_OS_WIN
|
|
|
|
|
if (debugMode) {
|
|
|
|
|
qCInfo(log) << "Debug mode enabled";
|
|
|
|
|
#ifdef Q_OS_DARWIN
|
|
|
|
|
// We are using raise(SIGSTOP) to stop the child process, macOS does not support ptrace(...)
|
|
|
|
|
inferiorProcess.setChildProcessModifier([] {
|
|
|
|
|
// Let the parent know our pid ...
|
|
|
|
|
*shared_child_pid = getpid();
|
|
|
|
|
// Suspend ourselves ...
|
|
|
|
|
raise(SIGSTOP);
|
|
|
|
|
});
|
|
|
|
|
#else
|
|
|
|
|
// PTRACE_TRACEME will stop execution of the child process as soon as execve is called.
|
|
|
|
|
inferiorProcess.setChildProcessModifier([] { ptrace(PTRACE_TRACEME, 0, 0, 0); });
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setupWindowsInferior()
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
inferiorProcess.setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments *args) {
|
|
|
|
|
if (debugMode)
|
|
|
|
|
args->flags |= CREATE_SUSPENDED;
|
|
|
|
|
win_process_information = args->processInformation;
|
|
|
|
|
});
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setupPidPollTimer()
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_DARWIN
|
|
|
|
|
if (!debugMode)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
static QTimer pollPidTimer;
|
|
|
|
|
|
|
|
|
|
pollPidTimer.setInterval(1);
|
|
|
|
|
pollPidTimer.setSingleShot(false);
|
|
|
|
|
QObject::connect(&pollPidTimer, &QTimer::timeout, &pollPidTimer, [&] {
|
|
|
|
|
if (*shared_child_pid) {
|
|
|
|
|
qCInfo(log) << "Received pid during polling:" << *shared_child_pid;
|
|
|
|
|
inferiorId = *shared_child_pid;
|
|
|
|
|
sendPid(inferiorId);
|
|
|
|
|
pollPidTimer.stop();
|
|
|
|
|
munmap(shared_child_pid, sizeof(int));
|
|
|
|
|
} else {
|
|
|
|
|
qCDebug(log) << "Waiting for inferior to start...";
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
pollPidTimer.start();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2023-03-24 09:26:42 +01:00
|
|
|
|
|
|
|
|
enum class Out { StdOut, StdErr };
|
|
|
|
|
|
|
|
|
|
void writeToOut(const QByteArray &data, Out out)
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
static const HANDLE outHandle = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
|
static const HANDLE errHandle = GetStdHandle(STD_ERROR_HANDLE);
|
|
|
|
|
WriteFile(out == Out::StdOut ? outHandle : errHandle,
|
|
|
|
|
data.constData(),
|
|
|
|
|
data.size(),
|
|
|
|
|
nullptr,
|
|
|
|
|
nullptr);
|
|
|
|
|
#else
|
|
|
|
|
auto fp = out == Out::StdOut ? stdout : stderr;
|
|
|
|
|
::fwrite(data.constData(), 1, data.size(), fp);
|
|
|
|
|
::fflush(fp);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 17:55:38 +01:00
|
|
|
void startProcess(const QString &executable, const QStringList &arguments, const QString &workingDir)
|
|
|
|
|
{
|
|
|
|
|
setupPidPollTimer();
|
|
|
|
|
|
|
|
|
|
qCInfo(log) << "Starting Inferior";
|
|
|
|
|
|
|
|
|
|
QObject::connect(&inferiorProcess,
|
|
|
|
|
&QProcess::finished,
|
|
|
|
|
QCoreApplication::instance(),
|
|
|
|
|
&onInferiorFinished);
|
|
|
|
|
QObject::connect(&inferiorProcess,
|
|
|
|
|
&QProcess::errorOccurred,
|
|
|
|
|
QCoreApplication::instance(),
|
|
|
|
|
&onInferiorErrorOccurered);
|
|
|
|
|
QObject::connect(&inferiorProcess,
|
|
|
|
|
&QProcess::started,
|
|
|
|
|
QCoreApplication::instance(),
|
|
|
|
|
&onInferiorStarted);
|
|
|
|
|
|
2023-03-24 09:26:42 +01:00
|
|
|
inferiorProcess.setProcessChannelMode(QProcess::SeparateChannels);
|
|
|
|
|
|
|
|
|
|
QObject::connect(&inferiorProcess,
|
|
|
|
|
&QProcess::readyReadStandardOutput,
|
|
|
|
|
QCoreApplication::instance(),
|
|
|
|
|
[] {
|
|
|
|
|
const QByteArray data = inferiorProcess.readAllStandardOutput();
|
|
|
|
|
static bool isFirst = true;
|
|
|
|
|
if (isFirst) {
|
|
|
|
|
isFirst = false;
|
|
|
|
|
if (data.startsWith("__qtc")) {
|
|
|
|
|
int lineBreak = data.indexOf("\r\n");
|
|
|
|
|
sendQtcMarker(data.mid(0, lineBreak));
|
|
|
|
|
if (lineBreak != -1)
|
|
|
|
|
writeToOut(data.mid(lineBreak + 2), Out::StdOut);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
writeToOut(data, Out::StdOut);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
QObject::connect(&inferiorProcess,
|
|
|
|
|
&QProcess::readyReadStandardError,
|
|
|
|
|
QCoreApplication::instance(),
|
2023-04-04 11:15:57 +02:00
|
|
|
[] { writeToOut(inferiorProcess.readAllStandardError(), Out::StdErr); });
|
2023-03-24 09:26:42 +01:00
|
|
|
|
2023-03-07 17:55:38 +01:00
|
|
|
if (!(testMode && debugMode))
|
|
|
|
|
inferiorProcess.setInputChannelMode(QProcess::ForwardedInputChannel);
|
|
|
|
|
inferiorProcess.setWorkingDirectory(workingDir);
|
|
|
|
|
inferiorProcess.setProgram(executable);
|
|
|
|
|
inferiorProcess.setArguments(arguments);
|
|
|
|
|
|
|
|
|
|
if (environmentVariables)
|
|
|
|
|
inferiorProcess.setEnvironment(*environmentVariables);
|
|
|
|
|
|
|
|
|
|
setupWindowsInferior();
|
|
|
|
|
setupUnixInferior();
|
|
|
|
|
|
|
|
|
|
inferiorProcess.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<int> readEnvFile()
|
|
|
|
|
{
|
|
|
|
|
if (!commandLineParser.isSet("envfile"))
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
|
|
const QString path = commandLineParser.value("envfile");
|
|
|
|
|
qCInfo(log) << "Reading env file: " << path << "...";
|
|
|
|
|
QFile file(path);
|
|
|
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
|
|
|
qCWarning(log) << "Failed to open env file: " << path;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
environmentVariables = QStringList{};
|
|
|
|
|
|
|
|
|
|
while (!file.atEnd()) {
|
|
|
|
|
QByteArray data = file.readAll();
|
|
|
|
|
if (!data.isEmpty()) {
|
|
|
|
|
for (const auto &line : data.split('\0')) {
|
|
|
|
|
if (!line.isEmpty())
|
|
|
|
|
*environmentVariables << QString::fromUtf8(line);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qCDebug(log) << "Env: ";
|
|
|
|
|
for (const auto &env : *environmentVariables)
|
|
|
|
|
qCDebug(log) << env;
|
|
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifndef Q_OS_WIN
|
|
|
|
|
void forwardSignal(int signum)
|
|
|
|
|
{
|
|
|
|
|
qCDebug(log) << "SIGTERM received, terminating inferior...";
|
|
|
|
|
kill(inferiorId, signum);
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
static BOOL WINAPI ctrlHandler(DWORD dwCtrlType)
|
|
|
|
|
{
|
|
|
|
|
if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) {
|
|
|
|
|
qCDebug(log) << "Terminate inferior...";
|
|
|
|
|
inferiorProcess.terminate();
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
void setupSignalHandlers()
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
SetConsoleCtrlHandler(ctrlHandler, TRUE);
|
|
|
|
|
#else
|
|
|
|
|
struct sigaction act;
|
|
|
|
|
memset(&act, 0, sizeof(act));
|
|
|
|
|
|
|
|
|
|
act.sa_handler = SIG_IGN;
|
|
|
|
|
if (sigaction(SIGTTOU, &act, NULL)) {
|
|
|
|
|
qCWarning(log) << "sigaction SIGTTOU: " << strerror(errno);
|
|
|
|
|
doExit(3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
act.sa_handler = forwardSignal;
|
|
|
|
|
if (sigaction(SIGTERM, &act, NULL)) {
|
|
|
|
|
qCWarning(log) << "sigaction SIGTERM: " << strerror(errno);
|
|
|
|
|
doExit(3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sigaction(SIGINT, &act, NULL)) {
|
|
|
|
|
qCWarning(log) << "sigaction SIGINT: " << strerror(errno);
|
|
|
|
|
doExit(3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qCDebug(log) << "Signals set up";
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<int> tryParseCommandLine(QCoreApplication &app)
|
|
|
|
|
{
|
|
|
|
|
commandLineParser.setApplicationDescription("Debug helper for QtCreator");
|
|
|
|
|
commandLineParser.addHelpOption();
|
|
|
|
|
commandLineParser.addOption(QCommandLineOption({"d", "debug"}, "Start inferior in debug mode"));
|
|
|
|
|
commandLineParser.addOption(QCommandLineOption({"t", "test"}, "Don't start the control socket"));
|
|
|
|
|
commandLineParser.addOption(
|
|
|
|
|
QCommandLineOption({"s", "socket"}, "Path to the unix socket", "socket"));
|
|
|
|
|
commandLineParser.addOption(
|
|
|
|
|
QCommandLineOption({"w", "workingDir"}, "Working directory for inferior", "workingDir"));
|
|
|
|
|
commandLineParser.addOption(QCommandLineOption({"v", "verbose"}, "Print debug messages"));
|
|
|
|
|
commandLineParser.addOption(QCommandLineOption({"e", "envfile"}, "Path to env file", "envfile"));
|
2023-03-21 10:15:54 +01:00
|
|
|
commandLineParser.addOption(
|
|
|
|
|
QCommandLineOption("wait",
|
|
|
|
|
"Message to display to the user while waiting for key press",
|
|
|
|
|
"waitmessage",
|
|
|
|
|
"Press enter to continue ..."));
|
2023-03-07 17:55:38 +01:00
|
|
|
|
|
|
|
|
commandLineParser.process(app);
|
|
|
|
|
|
|
|
|
|
inferiorCmdAndArguments = commandLineParser.positionalArguments();
|
|
|
|
|
debugMode = commandLineParser.isSet("debug");
|
|
|
|
|
testMode = commandLineParser.isSet("test");
|
|
|
|
|
|
|
|
|
|
if (!(commandLineParser.isSet("socket") || testMode) || inferiorCmdAndArguments.isEmpty()) {
|
|
|
|
|
commandLineParser.showHelp(1);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (commandLineParser.isSet("verbose"))
|
|
|
|
|
QLoggingCategory::setFilterRules("qtc.process_stub=true");
|
|
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<int> trySetWorkingDir()
|
|
|
|
|
{
|
|
|
|
|
if (commandLineParser.isSet("workingDir")) {
|
|
|
|
|
if (!QDir::setCurrent(commandLineParser.value("workingDir"))) {
|
|
|
|
|
qCWarning(log) << "Failed to change working directory to: "
|
|
|
|
|
<< commandLineParser.value("workingDir");
|
|
|
|
|
sendErrChDir();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setupSharedPid()
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_DARWIN
|
|
|
|
|
shared_child_pid = (int *) mmap(NULL,
|
|
|
|
|
sizeof *shared_child_pid,
|
|
|
|
|
PROT_READ | PROT_WRITE,
|
|
|
|
|
MAP_SHARED | MAP_ANONYMOUS,
|
|
|
|
|
-1,
|
|
|
|
|
0);
|
|
|
|
|
*shared_child_pid = 0;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onControlSocketConnected()
|
|
|
|
|
{
|
|
|
|
|
qCInfo(log) << "Connected to control socket";
|
|
|
|
|
|
|
|
|
|
sendSelfPid();
|
|
|
|
|
setupSignalHandlers();
|
|
|
|
|
|
|
|
|
|
startProcess(inferiorCmdAndArguments[0],
|
|
|
|
|
inferiorCmdAndArguments.mid(1),
|
|
|
|
|
commandLineParser.value("workingDir"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void resumeInferior()
|
|
|
|
|
{
|
|
|
|
|
qCDebug(log) << "Continuing inferior... (" << inferiorId << ")";
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
ResumeThread(win_process_information->hThread);
|
|
|
|
|
#else
|
|
|
|
|
kill(inferiorId, SIGCONT);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void killInferior()
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
inferiorProcess.kill();
|
|
|
|
|
#else
|
|
|
|
|
kill(inferiorId, SIGKILL);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onControlSocketReadyRead()
|
|
|
|
|
{
|
|
|
|
|
//k = kill, i = interrupt, c = continue, s = shutdown
|
|
|
|
|
QByteArray data = controlSocket.readAll();
|
|
|
|
|
for (auto ch : data) {
|
|
|
|
|
qCDebug(log) << "Received:" << ch;
|
|
|
|
|
|
|
|
|
|
switch (ch) {
|
|
|
|
|
case 'k': {
|
|
|
|
|
qCDebug(log) << "Killing inferior...";
|
|
|
|
|
killInferior();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
#ifndef Q_OS_WIN
|
|
|
|
|
case 'i': {
|
|
|
|
|
qCDebug(log) << "Interrupting inferior...";
|
|
|
|
|
kill(inferiorId, SIGINT);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
case 'c': {
|
|
|
|
|
resumeInferior();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 's': {
|
|
|
|
|
qCDebug(log) << "Shutting down...";
|
|
|
|
|
doExit(0);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onControlSocketErrorOccurred(QLocalSocket::LocalSocketError socketError)
|
|
|
|
|
{
|
|
|
|
|
qCWarning(log) << "Control socket error:" << socketError;
|
|
|
|
|
doExit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setupControlSocket()
|
|
|
|
|
{
|
|
|
|
|
QObject::connect(&controlSocket, &QLocalSocket::connected, &onControlSocketConnected);
|
|
|
|
|
QObject::connect(&controlSocket, &QLocalSocket::readyRead, &onControlSocketReadyRead);
|
|
|
|
|
QObject::connect(&controlSocket, &QLocalSocket::errorOccurred, &onControlSocketErrorOccurred);
|
|
|
|
|
|
|
|
|
|
qCInfo(log) << "Waiting for connection...";
|
|
|
|
|
controlSocket.connectToServer(commandLineParser.value("socket"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onStdInReadyRead()
|
|
|
|
|
{
|
|
|
|
|
char ch;
|
|
|
|
|
std::cin >> ch;
|
|
|
|
|
if (ch == 'k') {
|
|
|
|
|
killInferior();
|
|
|
|
|
} else {
|
|
|
|
|
resumeInferior();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void readKey()
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
stdInNotifier = new QWinEventNotifier(GetStdHandle(STD_INPUT_HANDLE));
|
|
|
|
|
#else
|
|
|
|
|
stdInNotifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read);
|
|
|
|
|
#endif
|
|
|
|
|
QObject::connect(stdInNotifier, &OSSocketNotifier::activated, &onStdInReadyRead);
|
|
|
|
|
}
|