From 18fa486531a0cb274d67279e04f0b18ff6ee82e3 Mon Sep 17 00:00:00 2001 From: Fawzi Mohamed <fawzi.mohamed@digia.com> Date: Wed, 19 Dec 2012 19:04:36 +0100 Subject: [PATCH] consoleprocess: support Terminal.app on mac Distinguishes the process that starts the terminal from the stub process, as on mac to support Terminal.app they are different. Handle the stub not through the process that starts the terminal, but through the local socket (on *nix). Replace the blocking wait(...) in the main thread, with a nonblocking wait in the signal handler when receiving a SIGCHLD, to leave the main thread able to handle communication with creator. This change allows the use of terminal emulator commands that share a single instance or that fork. So this is also the real fix for QTCREATORBUG-1633 on linux. If creator crashes the stub and the debugged program live on. This was done on purpose, it could be changed if considered better. Task-number: QTCREATORBUG-6371 Task-number: QTCREATORBUG-1633 Change-Id: I4d4fb3a67b1987f4e46e2c603dcefe8c15152ad2 Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com> --- share/qtcreator/scripts/openTerminal.command | 21 +- src/libs/utils/consoleprocess.cpp | 2 + src/libs/utils/consoleprocess.h | 11 +- src/libs/utils/consoleprocess_p.h | 8 + src/libs/utils/consoleprocess_unix.cpp | 92 ++++++-- src/libs/utils/consoleprocess_win.cpp | 17 +- src/libs/utils/process_stub_unix.c | 210 ++++++++++++++----- src/plugins/debugger/cdb/cdbengine.cpp | 2 +- src/plugins/debugger/gdb/termgdbadapter.cpp | 2 +- 9 files changed, 272 insertions(+), 93 deletions(-) diff --git a/share/qtcreator/scripts/openTerminal.command b/share/qtcreator/scripts/openTerminal.command index 2d5791da8c2..1f81a59050b 100755 --- a/share/qtcreator/scripts/openTerminal.command +++ b/share/qtcreator/scripts/openTerminal.command @@ -1,11 +1,18 @@ #! /bin/bash -i=`pwd` -i=${i//\\/\\\\\\\\} -i=${i//\"/\\\\\\\"} -i=${i//\$/\\\\\\\$} -i=${i//\`/\\\\\\\`} -i=\\\"$i\\\" +# ugly escaping: for apple script \ and " need to be escaped, whereas %q takes care of all bash escaping +declare -a args +mydir=`pwd` +mydir=$(printf '%q' "$mydir") +mydir="${mydir//\\/\\\\}" +args[0]="cd ${mydir//\"/\\\"};" +for a in "$@" ; do + x=$(printf '%q ' "$a") + x="${x//\\/\\\\}" + args[${#args[@]}]="${x//\"/\\\"}" +done +mArgs=${args[@]:0} + osascript <<EOF --Terminal opens a window by default when it is not running, so check on applicationIsRunning(applicationName) @@ -14,7 +21,7 @@ osascript <<EOF end applicationIsRunning set terminalWasRunning to applicationIsRunning("Terminal") - set cdScript to "cd $i" + set cdScript to "$mArgs" tell application "Terminal" --do script will open a new window if none given, but terminal already opens one if not running if terminalWasRunning then diff --git a/src/libs/utils/consoleprocess.cpp b/src/libs/utils/consoleprocess.cpp index 964ae45eb2f..e6ee4c6f8a8 100644 --- a/src/libs/utils/consoleprocess.cpp +++ b/src/libs/utils/consoleprocess.cpp @@ -29,6 +29,8 @@ #include "consoleprocess_p.h" +#include <utils/hostosinfo.h> + #include <QSettings> namespace Utils { diff --git a/src/libs/utils/consoleprocess.h b/src/libs/utils/consoleprocess.h index 9a5403c4d7c..84dae362742 100644 --- a/src/libs/utils/consoleprocess.h +++ b/src/libs/utils/consoleprocess.h @@ -62,16 +62,23 @@ public: Environment environment() const; bool start(const QString &program, const QString &args); +public slots: void stop(); +public: void setMode(Mode m); Mode mode() const; bool isRunning() const; // This reflects the state of the console+stub qint64 applicationPID() const; + void killProcess(); + void killStub(); + #ifdef Q_OS_WIN qint64 applicationMainThreadID() const; +#else + void detachStub(); #endif int exitCode() const; @@ -99,8 +106,8 @@ signals: void processStopped(); // These reflect the state of the console+stub - void wrapperStarted(); - void wrapperStopped(); + void stubStarted(); + void stubStopped(); private slots: void stubConnectionAvailable(); diff --git a/src/libs/utils/consoleprocess_p.h b/src/libs/utils/consoleprocess_p.h index 3ad9132c70f..e031c7cb07a 100644 --- a/src/libs/utils/consoleprocess_p.h +++ b/src/libs/utils/consoleprocess_p.h @@ -37,6 +37,10 @@ #include <QLocalSocket> #include <QLocalServer> +QT_BEGIN_NAMESPACE +class QTimer; +QT_END_NAMESPACE + #ifdef Q_OS_WIN # if QT_VERSION >= 0x050000 # include <QWinEventNotifier> @@ -51,6 +55,7 @@ namespace Utils { struct ConsoleProcessPrivate { ConsoleProcessPrivate(); + static QString m_defaultConsoleProcess; ConsoleProcess::Mode m_mode; QString m_workingDir; Environment m_environment; @@ -66,6 +71,9 @@ struct ConsoleProcessPrivate { QProcess m_process; QByteArray m_stubServerDir; QSettings *m_settings; + bool m_stubConnected; + qint64 m_stubPid; + QTimer *m_stubConnectTimer; #else qint64 m_appMainThreadId; PROCESS_INFORMATION *m_pid; diff --git a/src/libs/utils/consoleprocess_unix.cpp b/src/libs/utils/consoleprocess_unix.cpp index a8ba73debc2..7190230fe7e 100644 --- a/src/libs/utils/consoleprocess_unix.cpp +++ b/src/libs/utils/consoleprocess_unix.cpp @@ -37,6 +37,7 @@ #include <QCoreApplication> #include <QDir> #include <QSettings> +#include <QTimer> #include <sys/stat.h> #include <sys/types.h> @@ -51,7 +52,10 @@ ConsoleProcessPrivate::ConsoleProcessPrivate() : m_appPid(0), m_stubSocket(0), m_tempFile(0), - m_settings(0) + m_settings(0), + m_stubConnected(false), + m_stubPid(0), + m_stubConnectTimer(0) { } @@ -61,8 +65,6 @@ ConsoleProcess::ConsoleProcess(QObject *parent) : connect(&d->m_stubServer, SIGNAL(newConnection()), SLOT(stubConnectionAvailable())); d->m_process.setProcessChannelMode(QProcess::ForwardedChannels); - connect(&d->m_process, SIGNAL(finished(int,QProcess::ExitStatus)), - SLOT(stubExited())); } void ConsoleProcess::setSettings(QSettings *settings) @@ -159,21 +161,54 @@ bool ConsoleProcess::start(const QString &program, const QString &args) d->m_tempFile = 0; return false; } + d->m_stubConnectTimer = new QTimer(this); + connect(d->m_stubConnectTimer, SIGNAL(timeout()), SLOT(stop())); + d->m_stubConnectTimer->setSingleShot(true); + d->m_stubConnectTimer->start(10000); d->m_executable = program; - emit wrapperStarted(); return true; } -void ConsoleProcess::stop() +void ConsoleProcess::killProcess() { - if (!isRunning()) - return; - stubServerShutdown(); + if (d->m_stubSocket && d->m_stubSocket->isWritable()) { + d->m_stubSocket->write("k", 1); + d->m_stubSocket->flush(); + } d->m_appPid = 0; - d->m_process.terminate(); - if (!d->m_process.waitForFinished(1000)) - d->m_process.kill(); - d->m_process.waitForFinished(); +} + +void ConsoleProcess::killStub() +{ + if (d->m_stubSocket && d->m_stubSocket->isWritable()) { + d->m_stubSocket->write("s", 1); + d->m_stubSocket->flush(); + } + stubServerShutdown(); + d->m_stubPid = 0; +} + +void ConsoleProcess::detachStub() +{ + if (d->m_stubSocket && d->m_stubSocket->isWritable()) { + d->m_stubSocket->write("d", 1); + d->m_stubSocket->flush(); + } + stubServerShutdown(); + d->m_stubPid = 0; +} + +void ConsoleProcess::stop() +{ + killProcess(); + killStub(); + if (isRunning()) { + d->m_process.terminate(); + if (!d->m_process.waitForFinished(1000)) { + d->m_process.kill(); + d->m_process.waitForFinished(); + } + } } bool ConsoleProcess::isRunning() const @@ -210,7 +245,8 @@ QString ConsoleProcess::stubServerListen() void ConsoleProcess::stubServerShutdown() { - delete d->m_stubSocket; + if (d->m_stubSocket) + d->m_stubSocket->deleteLater(); // we might be called from the disconnected signal of m_stubSocket d->m_stubSocket = 0; if (d->m_stubServer.isListening()) { d->m_stubServer.close(); @@ -220,8 +256,15 @@ void ConsoleProcess::stubServerShutdown() void ConsoleProcess::stubConnectionAvailable() { + if (d->m_stubConnectTimer) { + delete d->m_stubConnectTimer; + d->m_stubConnectTimer = 0; + } + d->m_stubConnected = true; + emit stubStarted(); d->m_stubSocket = d->m_stubServer.nextPendingConnection(); connect(d->m_stubSocket, SIGNAL(readyRead()), SLOT(readStubOutput())); + connect(d->m_stubSocket, SIGNAL(disconnected()), SLOT(stubExited())); } static QString errorMsg(int code) @@ -238,11 +281,12 @@ void ConsoleProcess::readStubOutput() emit processError(msgCannotChangeToWorkDir(workingDirectory(), errorMsg(out.mid(10).toInt()))); } else if (out.startsWith("err:exec ")) { emit processError(msgCannotExecute(d->m_executable, errorMsg(out.mid(9).toInt()))); - } else if (out.startsWith("pid ")) { - // Will not need it any more + } else if (out.startsWith("spid ")) { delete d->m_tempFile; d->m_tempFile = 0; + d->m_stubPid = out.mid(4).toInt(); + } else if (out.startsWith("pid ")) { d->m_appPid = out.mid(4).toInt(); emit processStarted(); } else if (out.startsWith("exit ")) { @@ -257,6 +301,7 @@ void ConsoleProcess::readStubOutput() emit processStopped(); } else { emit processError(msgUnexpectedOutput(out)); + d->m_stubPid = 0; d->m_process.terminate(); break; } @@ -269,6 +314,7 @@ void ConsoleProcess::stubExited() if (d->m_stubSocket && d->m_stubSocket->state() == QLocalSocket::ConnectedState) d->m_stubSocket->waitForDisconnected(); stubServerShutdown(); + d->m_stubPid = 0; delete d->m_tempFile; d->m_tempFile = 0; if (d->m_appPid) { @@ -277,7 +323,7 @@ void ConsoleProcess::stubExited() d->m_appPid = 0; emit processStopped(); // Maybe it actually did not, but keep state consistent } - emit wrapperStopped(); + emit stubStopped(); } struct Terminal { @@ -293,15 +339,18 @@ static const Terminal knownTerminals[] = {"rxvt", "-e"}, {"urxvt", "-e"}, {"xfce4-terminal", "-x"}, - {"konsole", "--nofork -e"}, + {"konsole", "-e"}, {"gnome-terminal", "-x"} }; QString ConsoleProcess::defaultTerminalEmulator() { - if (Utils::HostOsInfo::isMacHost()) + if (Utils::HostOsInfo::isMacHost()) { + QString termCmd = QCoreApplication::applicationDirPath() + QLatin1String("/../Resources/scripts/openTerminal.command"); + if (QFile(termCmd).exists()) + return termCmd.replace(QLatin1Char(' '), QLatin1String("\\ ")); return QLatin1String("/usr/X11/bin/xterm"); - + } const Environment env = Environment::systemEnvironment(); const int terminalCount = int(sizeof(knownTerminals) / sizeof(knownTerminals[0])); for (int i = 0; i < terminalCount; ++i) { @@ -317,9 +366,6 @@ QString ConsoleProcess::defaultTerminalEmulator() QStringList ConsoleProcess::availableTerminalEmulators() { - if (Utils::HostOsInfo::isMacHost()) - return QStringList(defaultTerminalEmulator()); - QStringList result; const Environment env = Environment::systemEnvironment(); const int terminalCount = int(sizeof(knownTerminals) / sizeof(knownTerminals[0])); @@ -331,6 +377,8 @@ QStringList ConsoleProcess::availableTerminalEmulators() result.push_back(terminal); } } + if (!result.contains(defaultTerminalEmulator())) + result.append(defaultTerminalEmulator()); result.sort(); return result; } diff --git a/src/libs/utils/consoleprocess_win.cpp b/src/libs/utils/consoleprocess_win.cpp index c038dd8d400..170ad4b8e4b 100644 --- a/src/libs/utils/consoleprocess_win.cpp +++ b/src/libs/utils/consoleprocess_win.cpp @@ -151,16 +151,20 @@ bool ConsoleProcess::start(const QString &program, const QString &args) d->processFinishedNotifier = new QWinEventNotifier(d->m_pid->hProcess, this); connect(d->processFinishedNotifier, SIGNAL(activated(HANDLE)), SLOT(stubExited())); - emit wrapperStarted(); return true; } -void ConsoleProcess::stop() + +void ConsoleProcess::killProcess() { if (d->m_hInferior != NULL) { TerminateProcess(d->m_hInferior, (unsigned)-1); cleanupInferior(); } +} + +void ConsoleProcess::killStub() +{ if (d->m_pid) { TerminateProcess(d->m_pid->hProcess, (unsigned)-1); WaitForSingleObject(d->m_pid->hProcess, INFINITE); @@ -168,6 +172,12 @@ void ConsoleProcess::stop() } } +void ConsoleProcess::stop() +{ + killProcess(); + killStub(); +} + bool ConsoleProcess::isRunning() const { return d->m_pid != 0; @@ -192,6 +202,7 @@ void ConsoleProcess::stubServerShutdown() void ConsoleProcess::stubConnectionAvailable() { + emit stubStarted(); d->m_stubSocket = d->m_stubServer.nextPendingConnection(); connect(d->m_stubSocket, SIGNAL(readyRead()), SLOT(readStubOutput())); } @@ -281,7 +292,7 @@ void ConsoleProcess::stubExited() d->m_appCode = -1; emit processStopped(); } - emit wrapperStopped(); + emit stubStopped(); } QStringList ConsoleProcess::fixWinEnvironment(const QStringList &env) diff --git a/src/libs/utils/process_stub_unix.c b/src/libs/utils/process_stub_unix.c index 323ba0f72db..738e2811f0e 100644 --- a/src/libs/utils/process_stub_unix.c +++ b/src/libs/utils/process_stub_unix.c @@ -45,6 +45,7 @@ #include <stdlib.h> #include <stdio.h> #include <errno.h> +#include <assert.h> /* For OpenBSD */ #ifndef EPROTO @@ -55,6 +56,10 @@ extern char **environ; static int qtcFd; static char *sleepMsg; +static int chldPipe[2]; +static int isDebug; +static volatile int isDetached; +static volatile int chldPid; static void __attribute__((noreturn)) doExit(int code) { @@ -71,10 +76,10 @@ static void sendMsg(const char *msg, int num) char pidStr[64]; pidStrLen = sprintf(pidStr, msg, num); - if ((ioRet = write(qtcFd, pidStr, pidStrLen)) != pidStrLen) { + if (!isDetached && (ioRet = write(qtcFd, pidStr, pidStrLen)) != pidStrLen) { fprintf(stderr, "Cannot write to creator comm socket: %s\n", (ioRet < 0) ? strerror(errno) : "short write"); - doExit(3); + isDetached = 2; } } @@ -88,16 +93,88 @@ enum { ArgExe }; +/* Handle sigchld */ +static void sigchldHandler(int sig) +{ + int chldStatus; + /* Currently we have only one child, so we exit in case of error. */ + int waitRes; + (void)sig; + for (;;) { + waitRes = waitpid(-1, &chldStatus, WNOHANG); + if (!waitRes) + break; + if (waitRes < 0) { + perror("Cannot obtain exit status of child process"); + doExit(3); + } + if (WIFSTOPPED(chldStatus)) { + /* The child stopped. This can be only the result of ptrace(TRACE_ME). */ + /* We won't need the notification pipe any more, as we know that + * the exec() succeeded. */ + close(chldPipe[0]); + close(chldPipe[1]); + chldPipe[0] = -1; + /* If we are not debugging, just skip the "handover enabler". + * This is suboptimal, as it makes us ignore setuid/-gid bits. */ + if (isDebug) { + /* Stop the child after we detach from it, so we can hand it over to gdb. + * If the signal delivery is not queued, things will go awry. It works on + * Linux and MacOSX ... */ + kill(chldPid, SIGSTOP); + } +#ifdef __linux__ + ptrace(PTRACE_DETACH, chldPid, 0, 0); +#else + ptrace(PT_DETACH, chldPid, 0, 0); +#endif + sendMsg("pid %d\n", chldPid); + if (isDetached == 2 && isDebug) { + /* qtcreator was not informed and died while debugging, killing the child */ + kill(chldPid, SIGKILL); + } + } else if (WIFEXITED(chldStatus)) { + int errNo; + + /* The child exited normally. */ + if (chldPipe[0] >= 0) { + /* The child exited before being stopped by ptrace(). That can only + * mean that the exec() failed. */ + switch (read(chldPipe[0], &errNo, sizeof(errNo))) { + default: + /* Read of unknown length. Should never happen ... */ + errno = EPROTO; + /* fallthrough */ + case -1: + /* Read failed. Should never happen, either ... */ + perror("Cannot read status from child process"); + doExit(3); + case sizeof(errNo): + /* Child telling us the errno from exec(). */ + sendMsg("err:exec %d\n", errNo); + doExit(3); + } + } + sendMsg("exit %d\n", WEXITSTATUS(chldStatus)); + doExit(0); + } else { + sendMsg("crash %d\n", WTERMSIG(chldStatus)); + doExit(0); + } + } +} + + /* syntax: $0 {"run"|"debug"} <pid-socket> <continuation-msg> <workdir> <env-file> <exe> <args...> */ /* exit codes: 0 = ok, 1 = invocation error, 3 = internal error */ int main(int argc, char *argv[]) { - int errNo; - int chldPid; - int chldStatus; - int chldPipe[2]; + int errNo, hadInvalidCommand = 0; char **env = 0; struct sockaddr_un sau; + struct sigaction act; + + memset(&act, 0, sizeof(act)); if (argc < ArgEnv) { fprintf(stderr, "This is an internal helper of Qt Creator. Do not run it manually.\n"); @@ -118,6 +195,11 @@ int main(int argc, char *argv[]) doExit(1); } + isDebug = !strcmp(argv[ArgAction], "debug"); + isDetached = 0; + + sendMsg("spid %ld\n", (long)getpid()); + if (*argv[ArgDir] && chdir(argv[ArgDir])) { /* Only expected error: no such file or direcotry */ sendMsg("err:chdir %d\n", errno); @@ -154,10 +236,26 @@ int main(int argc, char *argv[]) } - /* Ignore SIGTTOU. Without this, calling tcsetpgrp() from a background - * process group (in which we will be, once as child and once as parent) - * generates the mentioned signal and stops the concerned process. */ - signal(SIGTTOU, SIG_IGN); + /* + * set up the signal handlers + */ + { + /* Ignore SIGTTOU. Without this, calling tcsetpgrp() from a background + * process group (in which we will be, once as child and once as parent) + * generates the mentioned signal and stops the concerned process. */ + act.sa_handler = SIG_IGN; + if (sigaction(SIGTTOU, &act, 0)) { + perror("sigaction SIGTTOU"); + doExit(3); + } + + /* Handle SIGCHLD to keep track of what the child does without blocking */ + act.sa_handler = sigchldHandler; + if (sigaction(SIGCHLD, &act, 0)) { + perror("sigaction SIGCHLD"); + doExit(3); + } + } /* Create execution result notification pipe. */ if (pipe(chldPipe)) { @@ -175,6 +273,10 @@ int main(int argc, char *argv[]) case 0: close(qtcFd); + /* Remove the SIGCHLD handler from the child */ + act.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &act, 0); + /* Put the process into an own process group and make it the foregroud * group on this terminal, so it will receive ctrl-c events, etc. * This is the main reason for *all* this stub magic in the first place. */ @@ -200,57 +302,51 @@ int main(int argc, char *argv[]) _exit(0); default: for (;;) { - if (wait(&chldStatus) < 0) { - perror("Cannot obtain exit status of child process"); - doExit(3); - } - if (WIFSTOPPED(chldStatus)) { - /* The child stopped. This can be only the result of ptrace(TRACE_ME). */ - /* We won't need the notification pipe any more, as we know that - * the exec() succeeded. */ - close(chldPipe[0]); - close(chldPipe[1]); - chldPipe[0] = -1; - /* If we are not debugging, just skip the "handover enabler". - * This is suboptimal, as it makes us ignore setuid/-gid bits. */ - if (!strcmp(argv[ArgAction], "debug")) { - /* Stop the child after we detach from it, so we can hand it over to gdb. - * If the signal delivery is not queued, things will go awry. It works on - * Linux and MacOSX ... */ - kill(chldPid, SIGSTOP); + char buffer[100]; + int nbytes; + + nbytes = read(qtcFd, buffer, 100); + if (nbytes <= 0) { + if (nbytes < 0 && errno == EINTR) + continue; + if (!isDetached) { + isDetached = 2; + if (nbytes == 0) + fprintf(stderr, "Lost connection to QtCreator, detaching from it.\n"); + else + perror("Lost connection to QtCreator, detaching from it"); } -#ifdef __linux__ - ptrace(PTRACE_DETACH, chldPid, 0, 0); -#else - ptrace(PT_DETACH, chldPid, 0, 0); -#endif - sendMsg("pid %d\n", chldPid); - } else if (WIFEXITED(chldStatus)) { - /* The child exited normally. */ - if (chldPipe[0] >= 0) { - /* The child exited before being stopped by ptrace(). That can only - * mean that the exec() failed. */ - switch (read(chldPipe[0], &errNo, sizeof(errNo))) { - default: - /* Read of unknown length. Should never happen ... */ - errno = EPROTO; - case -1: - /* Read failed. Should never happen, either ... */ - perror("Cannot read status from child process"); - doExit(3); - case sizeof(errNo): - /* Child telling us the errno from exec(). */ - sendMsg("err:exec %d\n", errNo); - return 3; + break; + } else { + int i; + for (i = 0; i < nbytes; ++i) { + switch (buffer[i]) { + case 'k': + if (chldPid > 0) { + kill(chldPid, SIGTERM); + sleep(1); + kill(chldPid, SIGKILL); + } + break; + case 'd': + isDetached = 1; + break; + case 's': + exit(0); + default: + if (!hadInvalidCommand) { + fprintf(stderr, "Ignoring invalid commands from QtCreator.\n"); + hadInvalidCommand = 1; + } } } - sendMsg("exit %d\n", WEXITSTATUS(chldStatus)); - doExit(0); - } else { - sendMsg("crash %d\n", WTERMSIG(chldStatus)); - doExit(0); } } - break; + if (isDetached) { + for (;;) + pause(); /* will exit in the signal handler... */ + } } + assert(0); + return 0; } diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index 903a991caf3..c6f677c5edb 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -546,7 +546,7 @@ bool CdbEngine::startConsole(const DebuggerStartParameters &sp, QString *errorMe SLOT(consoleStubError(QString))); connect(m_consoleStub.data(), SIGNAL(processStarted()), SLOT(consoleStubProcessStarted())); - connect(m_consoleStub.data(), SIGNAL(wrapperStopped()), + connect(m_consoleStub.data(), SIGNAL(stubStopped()), SLOT(consoleStubExited())); m_consoleStub->setWorkingDirectory(sp.workingDirectory); if (sp.environment.size()) diff --git a/src/plugins/debugger/gdb/termgdbadapter.cpp b/src/plugins/debugger/gdb/termgdbadapter.cpp index 34256cf718e..88a4cff9679 100644 --- a/src/plugins/debugger/gdb/termgdbadapter.cpp +++ b/src/plugins/debugger/gdb/termgdbadapter.cpp @@ -103,7 +103,7 @@ void GdbTermEngine::setupEngine() connect(&m_stubProc, SIGNAL(processError(QString)), SLOT(stubError(QString))); connect(&m_stubProc, SIGNAL(processStarted()), SLOT(stubStarted())); - connect(&m_stubProc, SIGNAL(wrapperStopped()), SLOT(stubExited())); + connect(&m_stubProc, SIGNAL(stubStopped()), SLOT(stubExited())); // FIXME: Starting the stub implies starting the inferior. This is // fairly unclean as far as the state machine and error reporting go. -- GitLab