diff --git a/src/libs/libs.pro b/src/libs/libs.pro index 0bb942edada6771ec698ad21d787f10fa63953cb..f87e023c1281875070d6b98c5e751283bc132d84 100644 --- a/src/libs/libs.pro +++ b/src/libs/libs.pro @@ -6,4 +6,5 @@ SUBDIRS = \ aggregation \ extensionsystem \ utils \ + utils/process_stub.pro \ cplusplus diff --git a/src/libs/utils/consoleprocess.h b/src/libs/utils/consoleprocess.h index 28ae4956e240a864311f4e4fd55f506fefc75ef8..ac8d4b3eb9de2093fb835d401308d6b05505837f 100644 --- a/src/libs/utils/consoleprocess.h +++ b/src/libs/utils/consoleprocess.h @@ -37,9 +37,12 @@ #include <QtCore/QStringList> #include <QtCore/QProcess> +#include <QtNetwork/QLocalServer> + #ifdef Q_OS_WIN #include <windows.h> class QWinEventNotifier; +class QTemporaryFile; #endif namespace Core { @@ -50,41 +53,68 @@ class QWORKBENCH_UTILS_EXPORT ConsoleProcess : public QObject, public AbstractPr Q_OBJECT public: - ConsoleProcess(QObject *parent); + ConsoleProcess(QObject *parent = 0); ~ConsoleProcess(); bool start(const QString &program, const QStringList &args); void stop(); - bool isRunning() const; - qint64 applicationPID() const; - int exitCode() const; + void setDebug(bool on) { m_debug = on; } + bool isDebug() const { return m_debug; } + + bool isRunning() const; // This reflects the state of the console+stub + qint64 applicationPID() const { return m_appPid; } + int exitCode() const { return m_appCode; } // This will be the signal number if exitStatus == CrashExit + QProcess::ExitStatus exitStatus() const { return m_appStatus; } + +#ifdef Q_OS_WIN + // These are public for WinGuiProcess. Should be in AbstractProcess, but it has no .cpp so far. + static QString createCommandline(const QString &program, const QStringList &args); + static QStringList fixEnvironment(const QStringList &env); +#endif signals: void processError(const QString &error); + // These reflect the state of the actual client process void processStarted(); void processStopped(); -private: - bool m_isRunning; - -#ifdef Q_OS_WIN -public: - static QString createCommandline(const QString &program, - const QStringList &args); - static QByteArray createEnvironment(const QStringList &env); + // These reflect the state of the console+stub + void wrapperStarted(); + void wrapperStopped(); private slots: - void processDied(); + void stubConnectionAvailable(); + void readStubOutput(); + void stubExited(); +#ifdef Q_OS_WIN + void inferiorExited(); +#endif private: + QString stubServerListen(); + void stubServerShutdown(); +#ifdef Q_OS_WIN + void cleanupStub(); + void cleanupInferior(); +#endif + + bool m_debug; + qint64 m_appPid; + int m_appCode; + QString m_executable; + QProcess::ExitStatus m_appStatus; + QLocalServer m_stubServer; + QLocalSocket *m_stubSocket; +#ifdef Q_OS_WIN PROCESS_INFORMATION *m_pid; + HANDLE m_hInferior; + QWinEventNotifier *inferiorFinishedNotifier; QWinEventNotifier *processFinishedNotifier; + QTemporaryFile *m_tempFile; #else -private: QProcess m_process; -private slots: - void processFinished(int, QProcess::ExitStatus); + QByteArray m_stubServerDir; #endif }; diff --git a/src/libs/utils/consoleprocess_unix.cpp b/src/libs/utils/consoleprocess_unix.cpp index 34709253764668e4baf078a602042c0e8ecb36c0..610082377852150b6adad7a1bd7f3d6c3b103d07 100644 --- a/src/libs/utils/consoleprocess_unix.cpp +++ b/src/libs/utils/consoleprocess_unix.cpp @@ -29,57 +29,78 @@ #include "consoleprocess.h" +#include <QtCore/QCoreApplication> +#include <QtCore/QTemporaryFile> + +#include <QtNetwork/QLocalSocket> + +#include <sys/stat.h> +#include <sys/types.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + using namespace Core::Utils; ConsoleProcess::ConsoleProcess(QObject *parent) : QObject(parent) { - m_isRunning = false; -} + m_debug = false; + m_appPid = 0; + m_stubSocket = 0; -ConsoleProcess::~ConsoleProcess() -{ + connect(&m_stubServer, SIGNAL(newConnection()), SLOT(stubConnectionAvailable())); + + m_process.setProcessChannelMode(QProcess::ForwardedChannels); + connect(&m_process, SIGNAL(finished(int, QProcess::ExitStatus)), + SLOT(stubExited())); } -static QString shellEscape(const QString &in) +ConsoleProcess::~ConsoleProcess() { - QString out = in; - out.replace('\'', "'\''"); - out.prepend('\''); - out.append('\''); - return out; + stop(); } bool ConsoleProcess::start(const QString &program, const QStringList &args) { - if (m_process.state() != QProcess::NotRunning) + if (isRunning()) return false; - QString shellArgs; - shellArgs += QLatin1String("cd "); - shellArgs += shellEscape(workingDirectory()); - shellArgs += QLatin1Char(';'); - shellArgs += shellEscape(program); - foreach (const QString &arg, args) { - shellArgs += QLatin1Char(' '); - shellArgs += shellEscape(arg); - } - shellArgs += QLatin1String("; echo; echo \"Press enter to close this window\"; read DUMMY"); - m_process.setEnvironment(environment()); + QString err = stubServerListen(); + if (!err.isEmpty()) { + emit processError(tr("Cannot set up comm channel: %1").arg(err)); + return false; + } - connect(&m_process, SIGNAL(finished(int, QProcess::ExitStatus)), - this, SLOT(processFinished(int, QProcess::ExitStatus))); + QStringList xtermArgs; + xtermArgs << "-e" << (QCoreApplication::applicationDirPath() + "/qtcreator_process_stub") + << (m_debug ? "debug" : "exec") + << m_stubServer.fullServerName() + << tr("Press <RETURN> to close this window...") + << workingDirectory() << environment() << "" + << program << args; - m_process.start(QLatin1String("xterm"), QStringList() << QLatin1String("-e") << "/bin/sh" << "-c" << shellArgs); - if (!m_process.waitForStarted()) + m_process.start(QLatin1String("xterm"), xtermArgs); + if (!m_process.waitForStarted()) { + stubServerShutdown(); + emit processError(tr("Cannot start console emulator xterm.")); return false; - emit processStarted(); + } + m_executable = program; + emit wrapperStarted(); return true; } -void ConsoleProcess::processFinished(int, QProcess::ExitStatus) +void ConsoleProcess::stop() { - emit processStopped(); + if (!isRunning()) + return; + stubServerShutdown(); + m_appPid = 0; + m_process.terminate(); + if (!m_process.waitForFinished(1000)) + m_process.kill(); + m_process.waitForFinished(); } bool ConsoleProcess::isRunning() const @@ -87,19 +108,97 @@ bool ConsoleProcess::isRunning() const return m_process.state() != QProcess::NotRunning; } -void ConsoleProcess::stop() +QString ConsoleProcess::stubServerListen() { - m_process.terminate(); - m_process.waitForFinished(); + // We need to put the socket in a private directory, as some systems simply do not + // check the file permissions of sockets. + QString stubFifoDir; + forever { + { + QTemporaryFile tf; + if (!tf.open()) + return tr("Cannot create temporary file: %2").arg(tf.errorString()); + stubFifoDir = QFile::encodeName(tf.fileName()); + } + // By now the temp file was deleted again + m_stubServerDir = QFile::encodeName(stubFifoDir); + if (!::mkdir(m_stubServerDir.constData(), 0700)) + break; + if (errno != EEXIST) + return tr("Cannot create temporary directory %1: %2").arg(stubFifoDir, strerror(errno)); + } + QString stubServer = stubFifoDir + "/stub-socket"; + if (!m_stubServer.listen(stubServer)) { + ::rmdir(m_stubServerDir.constData()); + return tr("Cannot create socket %1: %2").arg(stubServer, m_stubServer.errorString()); + } + return QString(); +} + +void ConsoleProcess::stubServerShutdown() +{ + delete m_stubSocket; + m_stubSocket = 0; + if (m_stubServer.isListening()) { + m_stubServer.close(); + ::rmdir(m_stubServerDir.constData()); + } +} + +void ConsoleProcess::stubConnectionAvailable() +{ + m_stubSocket = m_stubServer.nextPendingConnection(); + connect(m_stubSocket, SIGNAL(readyRead()), SLOT(readStubOutput())); } -qint64 ConsoleProcess::applicationPID() const +static QString errorMsg(int code) { - return m_process.pid(); + return QString::fromLocal8Bit(strerror(code)); } -int ConsoleProcess::exitCode() const +void ConsoleProcess::readStubOutput() { - return m_process.exitCode(); + while (m_stubSocket->canReadLine()) { + QByteArray out = m_stubSocket->readLine(); + out.chop(1); // \n + if (out.startsWith("err:chdir ")) { + emit processError(tr("Cannot change to working directory %1: %2") + .arg(workingDirectory(), errorMsg(out.mid(10).toInt()))); + } else if (out.startsWith("err:exec ")) { + emit processError(tr("Cannot execute %1: %2") + .arg(m_executable, errorMsg(out.mid(9).toInt()))); + } else if (out.startsWith("pid ")) { + m_appPid = out.mid(4).toInt(); + emit processStarted(); + } else if (out.startsWith("exit ")) { + m_appStatus = QProcess::NormalExit; + m_appCode = out.mid(5).toInt(); + m_appPid = 0; + emit processStopped(); + } else if (out.startsWith("crash ")) { + m_appStatus = QProcess::CrashExit; + m_appCode = out.mid(6).toInt(); + m_appPid = 0; + emit processStopped(); + } else { + emit processError(tr("Unexpected output from helper program.")); + m_process.terminate(); + break; + } + } } +void ConsoleProcess::stubExited() +{ + // The stub exit might get noticed before we read the error status. + if (m_stubSocket && m_stubSocket->state() == QLocalSocket::ConnectedState) + m_stubSocket->waitForDisconnected(); + stubServerShutdown(); + if (m_appPid) { + m_appStatus = QProcess::CrashExit; + m_appCode = -1; + m_appPid = 0; + emit processStopped(); // Maybe it actually did not, but keep state consistent + } + emit wrapperStopped(); +} diff --git a/src/libs/utils/consoleprocess_win.cpp b/src/libs/utils/consoleprocess_win.cpp index 94409bee1be00047a55ceec0c7c5fe7e9e0fc2fe..969823c582b54f166e5e499677e84f1aeee6d16b 100644 --- a/src/libs/utils/consoleprocess_win.cpp +++ b/src/libs/utils/consoleprocess_win.cpp @@ -29,19 +29,31 @@ #include "consoleprocess.h" +#include <QtCore/QCoreApplication> #include <QtCore/QDir> -#include <QtCore/private/qwineventnotifier_p.h> +#include <QtCore/QTemporaryFile> #include <QtCore/QAbstractEventDispatcher> +#include <QtCore/private/qwineventnotifier_p.h> + +#include <QtNetwork/QLocalSocket> -#include <Tlhelp32.h> +#include <stdlib.h> using namespace Core::Utils; ConsoleProcess::ConsoleProcess(QObject *parent) : QObject(parent) { - m_isRunning = false; + m_debug = false; + m_appPid = 0; m_pid = 0; + m_hInferior = NULL; + m_tempFile = 0; + m_stubSocket = 0; + processFinishedNotifier = 0; + inferiorFinishedNotifier = 0; + + connect(&m_stubServer, SIGNAL(newConnection()), SLOT(stubConnectionAvailable())); } ConsoleProcess::~ConsoleProcess() @@ -49,156 +61,234 @@ ConsoleProcess::~ConsoleProcess() stop(); } -void ConsoleProcess::stop() -{ - if (m_pid) - TerminateProcess(m_pid->hProcess, -1); - m_isRunning = false; -} - bool ConsoleProcess::start(const QString &program, const QStringList &args) { - if (m_isRunning) + if (isRunning()) return false; + QString err = stubServerListen(); + if (!err.isEmpty()) { + emit processError(tr("Cannot set up comm channel: %1").arg(err)); + return false; + } + + if (!environment().isEmpty()) { + m_tempFile = new QTemporaryFile(); + if (!m_tempFile->open()) { + stubServerShutdown(); + emit processError(tr("Cannot create temp file: %1").arg(m_tempFile->errorString())); + delete m_tempFile; + m_tempFile = 0; + return false; + } + QTextStream out(m_tempFile); + out.setCodec("UTF-16LE"); + out.setGenerateByteOrderMark(false); + foreach (const QString &var, fixEnvironment(environment())) + out << var << QChar(0); + out << QChar(0); + } + STARTUPINFO si; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); - if (m_pid) { - CloseHandle(m_pid->hThread); - CloseHandle(m_pid->hProcess); - delete m_pid; - m_pid = 0; - } m_pid = new PROCESS_INFORMATION; ZeroMemory(m_pid, sizeof(PROCESS_INFORMATION)); - QString cmdLine = QLatin1String("cmd /k ") - + createCommandline(program, args) - + QLatin1String(" & pause & exit"); + QString workDir = QDir::toNativeSeparators(workingDirectory()); + if (!workDir.isEmpty() && !workDir.endsWith('\\')) + workDir.append('\\'); + + QStringList stubArgs; + stubArgs << (m_debug ? "debug" : "exec") + << m_stubServer.fullServerName() + << workDir + << (m_tempFile ? m_tempFile->fileName() : 0) + << createCommandline(program, args) + << tr("Press <RETURN> to close this window..."); + + QString cmdLine = createCommandline( + QCoreApplication::applicationDirPath() + "/qtcreator_process_stub.exe", stubArgs); bool success = CreateProcessW(0, (WCHAR*)cmdLine.utf16(), - 0, 0, TRUE, CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, - environment().isEmpty() ? 0 - : createEnvironment(environment()).data(), - workingDirectory().isEmpty() ? 0 - : (WCHAR*)QDir::convertSeparators(workingDirectory()).utf16(), + 0, 0, FALSE, CREATE_NEW_CONSOLE, + 0, 0, &si, m_pid); if (!success) { + delete m_pid; + m_pid = 0; + delete m_tempFile; + m_tempFile = 0; + stubServerShutdown(); emit processError(tr("The process could not be started!")); return false; } - if (QAbstractEventDispatcher::instance(thread())) { - processFinishedNotifier = new QWinEventNotifier(m_pid->hProcess, this); - QObject::connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), this, SLOT(processDied())); - processFinishedNotifier->setEnabled(true); + processFinishedNotifier = new QWinEventNotifier(m_pid->hProcess, this); + connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), SLOT(stubExited())); + emit wrapperStarted(); + return true; +} + +void ConsoleProcess::stop() +{ + if (m_hInferior != NULL) { + TerminateProcess(m_hInferior, (unsigned)-1); + cleanupInferior(); + } + if (m_pid) { + TerminateProcess(m_pid->hProcess, (unsigned)-1); + WaitForSingleObject(m_pid->hProcess, INFINITE); + cleanupStub(); } - m_isRunning = true; - emit processStarted(); - return success; } bool ConsoleProcess::isRunning() const { - return m_isRunning; + return m_pid != 0; } -void ConsoleProcess::processDied() +QString ConsoleProcess::stubServerListen() { - if (processFinishedNotifier) { - processFinishedNotifier->setEnabled(false); - delete processFinishedNotifier; - processFinishedNotifier = 0; - } - delete m_pid; - m_pid = 0; - m_isRunning = false; - emit processStopped(); + if (m_stubServer.listen(QString::fromLatin1("creator-%1-%2") + .arg(QCoreApplication::applicationPid()) + .arg(rand()))) + return QString(); + return m_stubServer.errorString(); } -qint64 ConsoleProcess::applicationPID() const +void ConsoleProcess::stubServerShutdown() { - if (m_pid) { - HANDLE hProcList = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - PROCESSENTRY32 procEntry; - procEntry.dwSize = sizeof(PROCESSENTRY32); - DWORD procId = 0; - BOOL moreProc = Process32First(hProcList, &procEntry); - while (moreProc) { - if (procEntry.th32ParentProcessID == m_pid->dwProcessId) { - procId = procEntry.th32ProcessID; - break; - } - moreProc = Process32Next(hProcList, &procEntry); - } + delete m_stubSocket; + m_stubSocket = 0; + if (m_stubServer.isListening()) + m_stubServer.close(); +} - CloseHandle(hProcList); - return procId; - } - return 0; +void ConsoleProcess::stubConnectionAvailable() +{ + m_stubSocket = m_stubServer.nextPendingConnection(); + connect(m_stubSocket, SIGNAL(readyRead()), SLOT(readStubOutput())); } -int ConsoleProcess::exitCode() const +static QString errorMsg(int code) { - DWORD exitCode; - if (GetExitCodeProcess(m_pid->hProcess, &exitCode)) - return exitCode; - return -1; + LPVOID lpMsgBuf; + + int len = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, (DWORD)code, 0, (LPTSTR)&lpMsgBuf, 0, NULL); + QString ret = QString::fromUtf16((ushort *)lpMsgBuf, len); + LocalFree(lpMsgBuf); + return ret; } -QByteArray ConsoleProcess::createEnvironment(const QStringList &env) +void ConsoleProcess::readStubOutput() { - QByteArray envlist; - if (!env.isEmpty()) { - QStringList envStrings = env; - int pos = 0; - // add PATH if necessary (for DLL loading) - if (envStrings.filter(QRegExp("^PATH=",Qt::CaseInsensitive)).isEmpty()) { - QByteArray path = qgetenv("PATH"); - if (!path.isEmpty()) - envStrings.prepend(QString(QLatin1String("PATH=%1")).arg(QString::fromLocal8Bit(path))); - } - // add systemroot if needed - if (envStrings.filter(QRegExp("^SystemRoot=",Qt::CaseInsensitive)).isEmpty()) { - QByteArray systemRoot = qgetenv("SystemRoot"); - if (!systemRoot.isEmpty()) - envStrings.prepend(QString(QLatin1String("SystemRoot=%1")).arg(QString::fromLocal8Bit(systemRoot))); - } -#ifdef UNICODE - if (!(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based)) { - for (QStringList::ConstIterator it = envStrings.constBegin(); it != envStrings.constEnd(); it++ ) { - QString tmp = *it; - uint tmpSize = sizeof(TCHAR) * (tmp.length()+1); - envlist.resize(envlist.size() + tmpSize); - memcpy(envlist.data()+pos, tmp.utf16(), tmpSize); - pos += tmpSize; - } - // add the 2 terminating 0 (actually 4, just to be on the safe side) - envlist.resize(envlist.size() + 4); - envlist[pos++] = 0; - envlist[pos++] = 0; - envlist[pos++] = 0; - envlist[pos++] = 0; - } else -#endif // UNICODE - { - for (QStringList::ConstIterator it = envStrings.constBegin(); it != envStrings.constEnd(); it++) { - QByteArray tmp = (*it).toLocal8Bit(); - uint tmpSize = tmp.length() + 1; - envlist.resize(envlist.size() + tmpSize); - memcpy(envlist.data()+pos, tmp.data(), tmpSize); - pos += tmpSize; + while (m_stubSocket->canReadLine()) { + QByteArray out = m_stubSocket->readLine(); + out.chop(2); // \r\n + if (out.startsWith("err:chdir ")) { + emit processError(tr("Cannot change to working directory %1: %2") + .arg(workingDirectory(), errorMsg(out.mid(10).toInt()))); + } else if (out.startsWith("err:exec ")) { + emit processError(tr("Cannot execute %1: %2") + .arg(m_executable, errorMsg(out.mid(9).toInt()))); + } else if (out.startsWith("pid ")) { + // Will not need it any more + delete m_tempFile; + m_tempFile = 0; + + m_appPid = out.mid(4).toInt(); + m_hInferior = OpenProcess( + SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, + FALSE, m_appPid); + if (m_hInferior == NULL) { + emit processError(tr("Cannot obtain a handle to the inferior: %1") + .arg(errorMsg(GetLastError()))); + // Uhm, and now what? + continue; } - // add the terminating 0 (actually 2, just to be on the safe side) - envlist.resize(envlist.size() + 2); - envlist[pos++] = 0; - envlist[pos++] = 0; + inferiorFinishedNotifier = new QWinEventNotifier(m_hInferior, this); + connect(inferiorFinishedNotifier, SIGNAL(activated(HANDLE)), SLOT(inferiorExited())); + emit processStarted(); + } else { + emit processError(tr("Unexpected output from helper program.")); + TerminateProcess(m_pid->hProcess, (unsigned)-1); + break; } } - return envlist; +} + +void ConsoleProcess::cleanupInferior() +{ + delete inferiorFinishedNotifier; + inferiorFinishedNotifier = 0; + CloseHandle(m_hInferior); + m_hInferior = NULL; + m_appPid = 0; +} + +void ConsoleProcess::inferiorExited() +{ + DWORD chldStatus; + + if (!GetExitCodeProcess(m_hInferior, &chldStatus)) + emit processError(tr("Cannot obtain exit status from inferior: %1") + .arg(errorMsg(GetLastError()))); + cleanupInferior(); + m_appStatus = QProcess::NormalExit; + m_appCode = chldStatus; + emit processStopped(); +} + +void ConsoleProcess::cleanupStub() +{ + stubServerShutdown(); + delete processFinishedNotifier; + processFinishedNotifier = 0; + CloseHandle(m_pid->hThread); + CloseHandle(m_pid->hProcess); + delete m_pid; + m_pid = 0; + delete m_tempFile; + m_tempFile = 0; +} + +void ConsoleProcess::stubExited() +{ + // The stub exit might get noticed before we read the pid for the kill. + if (m_stubSocket && m_stubSocket->state() == QLocalSocket::ConnectedState) + m_stubSocket->waitForDisconnected(); + cleanupStub(); + if (m_hInferior != NULL) { + TerminateProcess(m_hInferior, (unsigned)-1); + cleanupInferior(); + m_appStatus = QProcess::CrashExit; + m_appCode = -1; + emit processStopped(); + } + emit wrapperStopped(); +} + +QStringList ConsoleProcess::fixEnvironment(const QStringList &env) +{ + QStringList envStrings = env; + // add PATH if necessary (for DLL loading) + if (envStrings.filter(QRegExp("^PATH=",Qt::CaseInsensitive)).isEmpty()) { + QByteArray path = qgetenv("PATH"); + if (!path.isEmpty()) + envStrings.prepend(QString(QLatin1String("PATH=%1")).arg(QString::fromLocal8Bit(path))); + } + // add systemroot if needed + if (envStrings.filter(QRegExp("^SystemRoot=",Qt::CaseInsensitive)).isEmpty()) { + QByteArray systemRoot = qgetenv("SystemRoot"); + if (!systemRoot.isEmpty()) + envStrings.prepend(QString(QLatin1String("SystemRoot=%1")).arg(QString::fromLocal8Bit(systemRoot))); + } + return envStrings; } QString ConsoleProcess::createCommandline(const QString &program, const QStringList &args) diff --git a/src/libs/utils/process_stub.pro b/src/libs/utils/process_stub.pro new file mode 100644 index 0000000000000000000000000000000000000000..e0f0a473eebc5967f2a9d8f58265ef4d09aaf20d --- /dev/null +++ b/src/libs/utils/process_stub.pro @@ -0,0 +1,18 @@ +TEMPLATE = app +TARGET = qtcreator_process_stub +DESTDIR = ../../../bin + +CONFIG += warn_on console use_c_linker +CONFIG -= qt app_bundle + +build_all:!build_pass { + CONFIG -= build_all + CONFIG += release +} + +unix { + SOURCES += process_stub_unix.c +} else { + SOURCES += process_stub_win.c +} + diff --git a/src/libs/utils/process_stub_unix.c b/src/libs/utils/process_stub_unix.c new file mode 100644 index 0000000000000000000000000000000000000000..e49bea8d51de60cca07f38ed68a2527f6a85aaed --- /dev/null +++ b/src/libs/utils/process_stub_unix.c @@ -0,0 +1,216 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception +** version 1.3, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <sys/ptrace.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> + +extern char **environ; + +static int qtcFd; +static char *sleepMsg; + +static void __attribute__((noreturn)) doExit(int code) +{ + tcsetpgrp(0, getpid()); + puts(sleepMsg); + fgets(sleepMsg, 2, stdin); /* Minimal size to make it wait */ + exit(code); +} + +static void sendMsg(const char *msg, int num) +{ + int pidStrLen; + int ioRet; + char pidStr[64]; + + pidStrLen = sprintf(pidStr, msg, num); + if ((ioRet = write(qtcFd, pidStr, pidStrLen)) != pidStrLen) { + fprintf(stderr, "Cannot write to creator comm socket: %s\n", + (ioRet < 0) ? strerror(errno) : "short write"); + doExit(3); + } +} + +enum { + ArgCmd = 0, + ArgAction, + ArgSocket, + ArgMsg, + ArgDir, + ArgEnv +}; + +/* syntax: $0 {"run"|"debug"} <pid-socket> <continuation-msg> <workdir> <env...> "" <exe> <args...> */ +/* exit codes: 0 = ok, 1 = invocation error, 3 = internal error */ +int main(int argc, char *argv[]) +{ + int envIdx = ArgEnv; + int errNo; + int chldPid; + int chldStatus; + int chldPipe[2]; + struct sockaddr_un sau; + + if (argc < ArgEnv) { + fprintf(stderr, "This is an internal helper of Qt Creator. Do not run it manually.\n"); + return 1; + } + sleepMsg = argv[ArgMsg]; + + /* Connect to the master, i.e. Creator. */ + if ((qtcFd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { + perror("Cannot create creator comm socket"); + doExit(3); + } + sau.sun_family = AF_UNIX; + strcpy(sau.sun_path, argv[ArgSocket]); + if (connect(qtcFd, (struct sockaddr *)&sau, sizeof(sau))) { + fprintf(stderr, "Cannot connect creator comm socket %s: %s\n", sau.sun_path, strerror(errno)); + doExit(1); + } + + if (*argv[ArgDir] && chdir(argv[ArgDir])) { + /* Only expected error: no such file or direcotry */ + sendMsg("err:chdir %d\n", errno); + return 1; + } + + /* Create execution result notification pipe. */ + if (pipe(chldPipe)) { + perror("Cannot create status pipe"); + doExit(3); + } + /* The debugged program is not supposed to inherit these handles. But we cannot + * close the writing end before calling exec(). Just handle both ends the same way ... */ + fcntl(chldPipe[0], F_SETFD, FD_CLOEXEC); + fcntl(chldPipe[1], F_SETFD, FD_CLOEXEC); + switch ((chldPid = fork())) { + case -1: + perror("Cannot fork child process failed"); + doExit(3); + case 0: + close(qtcFd); + + /* 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. */ + /* If one of these calls fails, the world is about to end anyway, so + * don't bother checking the return values. */ + setpgid(0, 0); + tcsetpgrp(0, getpid()); + + /* Get a SIGTRAP after exec() has loaded the new program. */ +#ifdef __linux__ + ptrace(PTRACE_TRACEME); +#else + ptrace(PT_TRACE_ME, 0, 0, 0); +#endif + + for (envIdx = ArgEnv; *argv[envIdx]; ++envIdx) ; + if (envIdx != ArgEnv) { + argv[envIdx] = 0; + environ = argv + ArgEnv; + } + ++envIdx; + + execvp(argv[envIdx], argv + envIdx); + /* Only expected error: no such file or direcotry, i.e. executable not found */ + errNo = errno; + write(chldPipe[1], &errNo, sizeof(errNo)); /* Only realistic error case is SIGPIPE */ + _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); + } +#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; + } + } + sendMsg("exit %d\n", WEXITSTATUS(chldStatus)); + doExit(0); + } else { + sendMsg("crash %d\n", WTERMSIG(chldStatus)); + doExit(0); + } + } + break; + } +} diff --git a/src/libs/utils/process_stub_win.c b/src/libs/utils/process_stub_win.c new file mode 100644 index 0000000000000000000000000000000000000000..152c83c3b121811f6ef5dfae9c0feaff6c4fbb07 --- /dev/null +++ b/src/libs/utils/process_stub_win.c @@ -0,0 +1,202 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception +** version 1.3, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ + +#define _WIN32_WINNT 0x0501 /* WinXP, needed for DebugActiveProcessStop() */ + +#include <windows.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <direct.h> + +static FILE *qtcFd; +static wchar_t *sleepMsg; + +/* Print some "press enter" message, wait for that, exit. */ +static void doExit(int code) +{ + char buf[2]; + _putws(sleepMsg); + fgets(buf, 2, stdin); /* Minimal size to make it wait */ + exit(code); +} + +/* Print an error message for unexpected Windows system errors, wait, exit. */ +static void systemError(const char *str) +{ + fprintf(stderr, str, GetLastError()); + doExit(3); +} + +/* Send a message to the master. */ +static void sendMsg(const char *msg, int num) +{ + int pidStrLen; + char pidStr[64]; + + pidStrLen = sprintf(pidStr, msg, num); + if (fwrite(pidStr, pidStrLen, 1, qtcFd) != 1 || fflush(qtcFd)) { + fprintf(stderr, "Cannot write to creator comm socket: %s\n", + strerror(errno)); + doExit(3); + } +} + +/* Ignore the first ctrl-c/break within a second. */ +static BOOL WINAPI ctrlHandler(DWORD dwCtrlType) +{ + static ULARGE_INTEGER lastTime; + ULARGE_INTEGER thisTime; + SYSTEMTIME sysTime; + FILETIME fileTime; + + if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) { + GetSystemTime(&sysTime); + SystemTimeToFileTime(&sysTime, &fileTime); + thisTime.LowPart = fileTime.dwLowDateTime; + thisTime.HighPart = fileTime.dwHighDateTime; + if (lastTime.QuadPart + 10000000 < thisTime.QuadPart) { + lastTime.QuadPart = thisTime.QuadPart; + return TRUE; + } + } + return FALSE; +} + +enum { + ArgCmd = 0, + ArgAction, + ArgSocket, + ArgDir, + ArgEnv, + ArgCmdLine, + ArgMsg, + ArgCount +}; + +/* syntax: $0 {"run"|"debug"} <pid-socket> <workdir> <env-file> <cmdline> <continuation-msg> */ +/* exit codes: 0 = ok, 1 = invocation error, 3 = internal error */ +int main() +{ + int argc; + wchar_t **argv; + wchar_t *env = 0; + STARTUPINFOW si; + PROCESS_INFORMATION pi; + DEBUG_EVENT dbev; + + argv = CommandLineToArgvW(GetCommandLine(), &argc); + + if (argc != ArgCount) { + fprintf(stderr, "This is an internal helper of Qt Creator. Do not run it manually.\n"); + return 1; + } + sleepMsg = argv[ArgMsg]; + + /* Connect to the master, i.e. Creator. */ + if (!(qtcFd = _wfopen(argv[ArgSocket], L"w"))) { + fprintf(stderr, "Cannot connect creator comm pipe %S: %s\n", + argv[ArgSocket], strerror(errno)); + doExit(1); + } + + if (*argv[ArgDir] && !SetCurrentDirectoryW(argv[ArgDir])) { + /* Only expected error: no such file or direcotry */ + sendMsg("err:chdir %d\n", GetLastError()); + return 1; + } + + if (*argv[ArgEnv]) { + FILE *envFd; + long size; + if (!(envFd = _wfopen(argv[ArgEnv], L"r"))) { + fprintf(stderr, "Cannot read creator env file %S: %s\n", + argv[ArgEnv], strerror(errno)); + doExit(1); + } + fseek(envFd, 0, SEEK_END); + size = ftell(envFd); + rewind(envFd); + env = malloc(size); + if (fread(env, 1, size, envFd) != size) { + perror("Failed to read env file"); + doExit(1); + } + fclose(envFd); + } + + ZeroMemory(&pi, sizeof(pi)); + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + int creationFlags = CREATE_UNICODE_ENVIRONMENT; + if (!wcscmp(argv[ArgAction], L"debug")) + creationFlags |= DEBUG_ONLY_THIS_PROCESS; + if (!CreateProcessW(0, argv[ArgCmdLine], 0, 0, FALSE, creationFlags, env, 0, &si, &pi)) { + /* Only expected error: no such file or direcotry, i.e. executable not found */ + sendMsg("err:exec %d\n", GetLastError()); + doExit(1); + } + + /* This is somewhat convoluted. What we actually want is creating a + suspended process and letting gdb attach to it. Unfortunately, + the Windows kernel runs amok when we attempt this. + So instead we start a debugged process, eat all the initial + debug events, suspend the process and detach from it. If gdb + tries to attach *now*, everthing goes smoothly. Yay. */ + if (creationFlags & DEBUG_ONLY_THIS_PROCESS) { + do { + if (!WaitForDebugEvent (&dbev, INFINITE)) + systemError("Cannot fetch debug event, error %d\n"); + if (dbev.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { + /* The first exception to be delivered is a trap + which indicates completion of startup. */ + if (SuspendThread(pi.hThread) == (DWORD)-1) + systemError("Cannot suspend debugee, error %d\n"); + } + if (!ContinueDebugEvent(dbev.dwProcessId, dbev.dwThreadId, DBG_CONTINUE)) + systemError("Cannot continue debug event, error %d\n"); + } while (dbev.dwDebugEventCode != EXCEPTION_DEBUG_EVENT); + if (!DebugActiveProcessStop(dbev.dwProcessId)) + systemError("Cannot detach from debugee, error %d\n"); + } + + SetConsoleCtrlHandler(ctrlHandler, TRUE); + + sendMsg("pid %d\n", pi.dwProcessId); + + if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED) + systemError("Wait for debugee failed, error %d\n"); + doExit(0); + return 0; +} diff --git a/src/libs/utils/utils.pro b/src/libs/utils/utils.pro index 74f25b0488c4f3aa935c2a9d81c8df681019f5c1..e8a5451602faf968633c92b3f3ed4658c8d8ec13 100644 --- a/src/libs/utils/utils.pro +++ b/src/libs/utils/utils.pro @@ -1,5 +1,6 @@ TEMPLATE = lib TARGET = Utils +QT += network DEFINES += QWORKBENCH_UTILS_LIBRARY diff --git a/src/plugins/debugger/debugger.pro b/src/plugins/debugger/debugger.pro index f17533ee5d3de8736e1a496408ef4a38000bc54c..b3f98ec41b978280c0bdc54138f472dd6d96390a 100644 --- a/src/plugins/debugger/debugger.pro +++ b/src/plugins/debugger/debugger.pro @@ -9,6 +9,8 @@ include(../../plugins/coreplugin/coreplugin.pri) include(../../plugins/texteditor/texteditor.pri) include(../../plugins/cpptools/cpptools.pri) include(../../libs/cplusplus/cplusplus.pri) +include(../../libs/utils/utils.pri) +INCLUDEPATH += $$PWD/../../libs/utils QT += gui network script diff --git a/src/plugins/debugger/debuggermanager.h b/src/plugins/debugger/debuggermanager.h index 54a2f93f778c5cecfdf216684f2e101b9c554ef7..04f8c6c8e2360cb0a3a579db328a8ae8eb98f725 100644 --- a/src/plugins/debugger/debuggermanager.h +++ b/src/plugins/debugger/debuggermanager.h @@ -381,6 +381,7 @@ public: QString m_buildDir; QStringList m_processArgs; int m_attachedPID; + bool m_useTerminal; private: void init(); diff --git a/src/plugins/debugger/debuggerrunner.cpp b/src/plugins/debugger/debuggerrunner.cpp index 674ac47bb1f5191e8bbe1258b4303f19c8997c34..15116aaf38af11dde485dcd82cd612e8a944b91e 100644 --- a/src/plugins/debugger/debuggerrunner.cpp +++ b/src/plugins/debugger/debuggerrunner.cpp @@ -130,6 +130,8 @@ void DebuggerRunControl::start() m_manager->m_processArgs = rc->commandLineArguments(); m_manager->m_buildDir = project->buildDirectory(project->activeBuildConfiguration()); + m_manager->m_useTerminal = rc->runMode() == ApplicationRunConfiguration::Console; + //<daniel> andre: + "\qtc-gdbmacros\" //emit addToOutputWindow(this, tr("Debugging %1").arg(m_executable)); diff --git a/src/plugins/debugger/gdbengine.cpp b/src/plugins/debugger/gdbengine.cpp index dcc0382668bc373970ec86130c5a7179b57399fd..311608709bac99c4b56c8e7e7d26f02fdddc150a 100644 --- a/src/plugins/debugger/gdbengine.cpp +++ b/src/plugins/debugger/gdbengine.cpp @@ -98,6 +98,7 @@ enum GdbCommandType GdbAsyncOutput2, GdbStart, GdbAttached, + GdbStubAttached, GdbExecRun, GdbExecRunToFunction, GdbExecStep, @@ -241,6 +242,7 @@ GdbEngine::GdbEngine(DebuggerManager *parent) { q = parent; qq = parent->engineInterface(); + m_stubProc.setDebug(true); initializeVariables(); initializeConnections(); } @@ -263,6 +265,10 @@ void GdbEngine::initializeConnections() connect(&m_gdbProc, SIGNAL(finished(int, QProcess::ExitStatus)), q, SLOT(exitDebugger())); + connect(&m_stubProc, SIGNAL(processError(QString)), SLOT(stubError(QString))); + connect(&m_stubProc, SIGNAL(processStarted()), SLOT(stubStarted())); + connect(&m_stubProc, SIGNAL(wrapperStopped()), q, SLOT(exitDebugger())); + // Output connect(&m_outputCollector, SIGNAL(byteDelivery(QByteArray)), SLOT(readDebugeeOutput(QByteArray))); @@ -543,6 +549,25 @@ void GdbEngine::handleResponse(const QByteArray &buff) } } +void GdbEngine::handleStubAttached() +{ + qq->notifyInferiorStopped(); + m_waitingForBreakpointSynchronizationToContinue = true; + handleAqcuiredInferior(); +} + +void GdbEngine::stubStarted() +{ + q->m_attachedPID = m_stubProc.applicationPID(); + qq->notifyInferiorPidChanged(q->m_attachedPID); + sendCommand("attach " + QString::number(q->m_attachedPID), GdbStubAttached); +} + +void GdbEngine::stubError(const QString &msg) +{ + QMessageBox::critical(q->mainWindow(), tr("Debugger Error"), msg); +} + void GdbEngine::readGdbStandardError() { qWarning() << "Unexpected gdb stderr:" << m_gdbProc.readAllStandardError(); @@ -721,6 +746,9 @@ void GdbEngine::handleResult(const GdbResultRecord & record, int type, const QVariant & cookie) { switch (type) { + case GdbStubAttached: + handleStubAttached(); + break; case GdbExecNext: case GdbExecStep: case GdbExecNextI: @@ -1211,6 +1239,20 @@ void GdbEngine::handleAsyncOutput(const GdbMi &data) QVariant var = QVariant::fromValue<GdbMi>(data); sendCommand("p 0", GdbAsyncOutput2, var); // dummy } else { +#ifdef Q_OS_LINUX + // For some reason, attaching to a stopped process causes *two* stops + // when trying to continue (kernel 2.6.24-23-ubuntu). + // Interestingly enough, on MacOSX no signal is delivered at all. + if (reason == QLatin1String("signal-received") + && data.findChild("signal-name").data() == "SIGSTOP") { + GdbMi frameData = data.findChild("frame"); + if (frameData.findChild("func").data() == "_start" + && frameData.findChild("from").data() == "/lib/ld-linux.so.2") { + sendCommand("-exec-continue"); + return; + } + } +#endif q->showStatusMessage(tr("Stopped: \"%1\"").arg(reason)); handleAsyncOutput2(data); } @@ -1461,23 +1503,31 @@ bool GdbEngine::startDebugger() return false; } - if (!m_outputCollector.listen()) { - QMessageBox::critical(q->mainWindow(), tr("Debugger Startup Failure"), - tr("Cannot set up communication with child process: %1") - .arg(m_outputCollector.errorString())); - return false; - } - - gdbArgs.prepend(QLatin1String("--tty=") + m_outputCollector.serverName()); - //gdbArgs.prepend(QLatin1String("--quiet")); gdbArgs.prepend(QLatin1String("mi")); gdbArgs.prepend(QLatin1String("-i")); - if (!q->m_workingDir.isEmpty()) - m_gdbProc.setWorkingDirectory(q->m_workingDir); - if (!q->m_environment.isEmpty()) - m_gdbProc.setEnvironment(q->m_environment); + if (q->m_useTerminal) { + m_stubProc.stop(); // We leave the console open, so recycle it now. + + m_stubProc.setWorkingDirectory(q->m_workingDir); + m_stubProc.setEnvironment(q->m_environment); + if (!m_stubProc.start(q->m_executable, q->m_processArgs)) + return false; // Error message for user is delivered via a signal. + } else { + if (!m_outputCollector.listen()) { + QMessageBox::critical(q->mainWindow(), tr("Debugger Startup Failure"), + tr("Cannot set up communication with child process: %1") + .arg(m_outputCollector.errorString())); + return false; + } + gdbArgs.prepend(QLatin1String("--tty=") + m_outputCollector.serverName()); + + if (!q->m_workingDir.isEmpty()) + m_gdbProc.setWorkingDirectory(q->m_workingDir); + if (!q->m_environment.isEmpty()) + m_gdbProc.setEnvironment(q->m_environment); + } #if 0 qDebug() << "Command: " << q->settings()->m_gdbCmd; @@ -1495,6 +1545,9 @@ bool GdbEngine::startDebugger() QMessageBox::critical(q->mainWindow(), tr("Debugger Startup Failure"), tr("Cannot start debugger: %1").arg(m_gdbProc.errorString())); m_outputCollector.shutdown(); + m_stubProc.blockSignals(true); + m_stubProc.stop(); + m_stubProc.blockSignals(false); return false; } @@ -1575,7 +1628,7 @@ bool GdbEngine::startDebugger() if (q->startMode() == DebuggerManager::AttachExternal) { sendCommand("attach " + QString::number(q->m_attachedPID), GdbAttached); - } else { + } else if (!q->m_useTerminal) { // StartInternal or StartExternal sendCommand("-file-exec-and-symbols " + fileName, GdbFileExecAndSymbols); //sendCommand("file " + fileName, GdbFileExecAndSymbols); diff --git a/src/plugins/debugger/gdbengine.h b/src/plugins/debugger/gdbengine.h index b247202c534d3242902d4530714d435d9a9a8607..79def1362e5c36e0c68d318fa594695b77421b13 100644 --- a/src/plugins/debugger/gdbengine.h +++ b/src/plugins/debugger/gdbengine.h @@ -34,6 +34,8 @@ #include "gdbmi.h" #include "outputcollector.h" +#include <consoleprocess.h> + #include <QtCore/QByteArray> #include <QtCore/QHash> #include <QtCore/QMap> @@ -172,12 +174,15 @@ private slots: void readGdbStandardOutput(); void readGdbStandardError(); void readDebugeeOutput(const QByteArray &data); + void stubStarted(); + void stubError(const QString &msg); private: int terminationIndex(const QByteArray &buffer, int &length); void handleResponse(const QByteArray &buff); void handleStart(const GdbResultRecord &response); void handleAttach(); + void handleStubAttached(); void handleAqcuiredInferior(); void handleAsyncOutput2(const GdbMi &data); void handleAsyncOutput(const GdbMi &data); @@ -202,6 +207,8 @@ private: QProcess m_gdbProc; + Core::Utils::ConsoleProcess m_stubProc; + QHash<int, GdbCookie> m_cookieForToken; QHash<int, QByteArray> m_customOutputForToken; diff --git a/src/plugins/projectexplorer/winguiprocess.cpp b/src/plugins/projectexplorer/winguiprocess.cpp index 82ec4cf609c77b503a75111288f3a1cf958c4529..86b905091864a0862280ca656b8b087b37b5dce0 100644 --- a/src/plugins/projectexplorer/winguiprocess.cpp +++ b/src/plugins/projectexplorer/winguiprocess.cpp @@ -34,6 +34,22 @@ using namespace ProjectExplorer::Internal; +static QByteArray createEnvironment(const QStringList &env) +{ + QByteArray envlist; + int pos = 0; + foreach (const QString &tmp, env) { + uint tmpSize = sizeof(TCHAR) * (tmp.length() + 1); + envlist.resize(envlist.size() + tmpSize); + memcpy(envlist.data() + pos, tmp.utf16(), tmpSize); + pos += tmpSize; + } + envlist.resize(envlist.size() + 2); + envlist[pos++] = 0; + envlist[pos++] = 0; + return envlist; +} + WinGuiProcess::WinGuiProcess(QObject *parent) : QThread(parent) { @@ -113,7 +129,7 @@ void WinGuiProcess::run() bool success = CreateProcessW(0, (WCHAR*)cmdLine.utf16(), 0, 0, TRUE, CREATE_UNICODE_ENVIRONMENT, environment().isEmpty() ? 0 - : ConsoleProcess::createEnvironment(environment()).data(), + : createEnvironment(ConsoleProcess::fixEnvironment(environment())).data(), workingDirectory().isEmpty() ? 0 : (WCHAR*)QDir::convertSeparators(workingDirectory()).utf16(), &si, m_pid);