Files
qt-creator/src/plugins/android/androidrunnerworker.cpp
hjk c23cdd9262 Utils: Merge {synchronous,qtc}process.{h,cpp} file pairs
Mechanical to prepare merging the actual classes.
Adapting #includes.

Change-Id: I77a2c28129287778bc870c30cb890cd26bc2e62b
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
2021-05-10 09:47:51 +00:00

852 lines
34 KiB
C++

/****************************************************************************
**
** Copyright (C) 2018 BogDan Vatra <bog_dan_ro@yahoo.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "androidrunnerworker.h"
#include "androidconfigurations.h"
#include "androidconstants.h"
#include "androidmanager.h"
#include "androidrunconfiguration.h"
#include <debugger/debuggerkitinformation.h>
#include <debugger/debuggerrunconfigurationaspect.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/environmentaspect.h>
#include <projectexplorer/runconfigurationaspects.h>
#include <projectexplorer/runcontrol.h>
#include <projectexplorer/target.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
#include <utils/qtcprocess.h>
#include <utils/runextensions.h>
#include <utils/stringutils.h>
#include <utils/temporaryfile.h>
#include <utils/url.h>
#include <QDate>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QLoggingCategory>
#include <QScopeGuard>
#include <QRegularExpression>
#include <QTcpServer>
#include <QThread>
#include <chrono>
namespace {
static Q_LOGGING_CATEGORY(androidRunWorkerLog, "qtc.android.run.androidrunnerworker", QtWarningMsg)
static const int GdbTempFileMaxCounter = 20;
}
using namespace std;
using namespace std::placeholders;
using namespace ProjectExplorer;
using namespace Utils;
namespace Android {
namespace Internal {
static const QString pidScript = "pidof -s '%1'";
static const QString pidScriptPreNougat = QStringLiteral("for p in /proc/[0-9]*; "
"do cat <$p/cmdline && echo :${p##*/}; done");
static const QString pidPollingScript = QStringLiteral("while [ -d /proc/%1 ]; do sleep 1; done");
static const QRegularExpression regExpLogcat{"^[0-9\\-]*" // date
"\\s+"
"[0-9\\-:.]*"// time
"\\s*"
"(\\d*)" // pid 1. capture
"\\s+"
"\\d*" // unknown
"\\s+"
"(\\w)" // message type 2. capture
"\\s+"
"(.*): " // source 3. capture
"(.*)" // message 4. capture
"[\\n\\r]*$"};
static int APP_START_TIMEOUT = 45000;
static bool isTimedOut(const chrono::high_resolution_clock::time_point &start,
int msecs = APP_START_TIMEOUT)
{
bool timedOut = false;
auto end = chrono::high_resolution_clock::now();
if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs)
timedOut = true;
return timedOut;
}
static qint64 extractPID(const QByteArray &output, const QString &packageName)
{
qint64 pid = -1;
foreach (auto tuple, output.split('\n')) {
tuple = tuple.simplified();
if (!tuple.isEmpty()) {
auto parts = tuple.split(':');
QString commandName = QString::fromLocal8Bit(parts.first());
if (parts.length() == 2 && commandName == packageName) {
pid = parts.last().toLongLong();
break;
}
}
}
return pid;
}
static void findProcessPID(QFutureInterface<qint64> &fi, QStringList selector,
const QString &packageName, bool preNougat)
{
if (packageName.isEmpty())
return;
QStringList args = {selector};
FilePath adbPath = AndroidConfigurations::currentConfig().adbToolPath();
args.append("shell");
args.append(preNougat ? pidScriptPreNougat : pidScript.arg(packageName));
qint64 processPID = -1;
chrono::high_resolution_clock::time_point start = chrono::high_resolution_clock::now();
do {
QThread::msleep(200);
const auto out = SynchronousProcess().runBlocking({adbPath, args}).allRawOutput();
if (preNougat) {
processPID = extractPID(out, packageName);
} else {
if (!out.isEmpty())
processPID = out.trimmed().toLongLong();
}
} while ((processPID == -1 || processPID == 0) && !isTimedOut(start) && !fi.isCanceled());
qCDebug(androidRunWorkerLog) << "PID found:" << processPID << ", PreNougat:" << preNougat;
if (!fi.isCanceled())
fi.reportResult(processPID);
}
static void deleter(QProcess *p)
{
qCDebug(androidRunWorkerLog) << "Killing process:" << p->objectName();
p->terminate();
if (!p->waitForFinished(1000)) {
p->kill();
p->waitForFinished();
}
// Might get deleted from its own signal handler.
p->deleteLater();
}
static QString gdbServerArch(const QString &androidAbi)
{
if (androidAbi == ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A)
return QString("arm64");
if (androidAbi == ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A)
return QString("arm");
// That's correct for x86_64 and x86, and best guess at anything that will evolve:
return androidAbi;
}
static QString lldbServerArch(const QString &androidAbi)
{
if (androidAbi == ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A)
return {ProjectExplorer::Constants::ANDROID_ABI_ARMEABI};
// Correct for arm64-v8a, x86 and x86_64, and best guess at anything that will evolve:
return androidAbi; // arm64-v8a, x86, x86_64
}
static QString lldbServerArch2(const QString &androidAbi)
{
if (androidAbi == ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A)
return {"arm"};
if (androidAbi == ProjectExplorer::Constants::ANDROID_ABI_X86)
return {"i386"};
if (androidAbi == ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A)
return {"aarch64"};
// Correct for x86_64 and best guess at anything that will evolve:
return androidAbi; // x86_64
}
static FilePath debugServer(bool useLldb, const Target *target)
{
QtSupport::BaseQtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(target->kit());
QString preferredAbi = AndroidManager::apkDevicePreferredAbi(target);
const AndroidConfig &config = AndroidConfigurations::currentConfig();
if (useLldb) {
// Search suitable lldb-server binary.
const FilePath prebuilt = config.ndkLocation(qtVersion) / "toolchains/llvm/prebuilt";
const QString abiNeedle = lldbServerArch2(preferredAbi);
// The new, built-in LLDB.
QDirIterator it(prebuilt.toString(), QDir::Files|QDir::Executable, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
const QString filePath = it.filePath();
if (filePath.endsWith(abiNeedle + "/lldb-server")) {
return FilePath::fromString(filePath);
}
}
// Older: Find LLDB version. sdk_definitions.json contains something like "lldb;3.1". Use that.
const QStringList packages = config.defaultEssentials();
for (const QString &package : packages) {
if (package.startsWith("lldb;")) {
const QString lldbVersion = package.mid(5);
const FilePath path = config.sdkLocation()
/ QString("lldb/%1/android/%2/lldb-server")
.arg(lldbVersion, lldbServerArch(preferredAbi));
if (path.exists())
return path;
}
}
} else {
// Search suitable gdbserver binary.
const FilePath path = config.ndkLocation(qtVersion)
.pathAppended(QString("prebuilt/android-%1/gdbserver/gdbserver")
.arg(gdbServerArch(preferredAbi)));
if (path.exists())
return path;
}
return {};
}
AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packageName)
: m_packageName(packageName)
, m_adbLogcatProcess(nullptr, deleter)
, m_psIsAlive(nullptr, deleter)
, m_debugServerProcess(nullptr, deleter)
, m_jdbProcess(nullptr, deleter)
{
auto runControl = runner->runControl();
m_useLldb = Debugger::DebuggerKitAspect::engineType(runControl->kit())
== Debugger::LldbEngineType;
auto aspect = runControl->aspect<Debugger::DebuggerRunConfigurationAspect>();
Utils::Id runMode = runControl->runMode();
const bool debuggingMode = runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE;
m_useCppDebugger = debuggingMode && aspect->useCppDebugger();
if (debuggingMode && aspect->useQmlDebugger())
m_qmlDebugServices = QmlDebug::QmlDebuggerServices;
else if (runMode == ProjectExplorer::Constants::QML_PROFILER_RUN_MODE)
m_qmlDebugServices = QmlDebug::QmlProfilerServices;
else if (runMode == ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE)
m_qmlDebugServices = QmlDebug::QmlPreviewServices;
else
m_qmlDebugServices = QmlDebug::NoQmlDebugServices;
m_localDebugServerPort = Utils::Port(5039);
QTC_CHECK(m_localDebugServerPort.isValid());
if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
qCDebug(androidRunWorkerLog) << "QML debugging enabled";
QTcpServer server;
QTC_ASSERT(server.listen(QHostAddress::LocalHost),
qDebug() << tr("No free ports available on host for QML debugging."));
m_qmlServer.setScheme(Utils::urlTcpScheme());
m_qmlServer.setHost(server.serverAddress().toString());
m_qmlServer.setPort(server.serverPort());
qCDebug(androidRunWorkerLog) << "QML server:" << m_qmlServer.toDisplayString();
}
m_localJdbServerPort = Utils::Port(5038);
QTC_CHECK(m_localJdbServerPort.isValid());
auto target = runControl->target();
m_deviceSerialNumber = AndroidManager::deviceSerialNumber(target);
m_apiLevel = AndroidManager::deviceApiLevel(target);
m_extraEnvVars = runControl->aspect<EnvironmentAspect>()->environment();
qCDebug(androidRunWorkerLog) << "Environment variables for the app"
<< m_extraEnvVars.toStringList();
if (target->buildConfigurations().first()->buildType() != BuildConfiguration::BuildType::Release) {
m_extraAppParams = runControl->runnable().commandLineArguments;
}
if (auto aspect = runControl->aspect(Constants::ANDROID_AMSTARTARGS)) {
QTC_CHECK(aspect->value().type() == QVariant::String);
const QString startArgs = aspect->value().toString();
m_amStartExtraArgs = QtcProcess::splitArgs(startArgs, OsTypeOtherUnix);
}
if (auto aspect = runControl->aspect(Constants::ANDROID_PRESTARTSHELLCMDLIST)) {
QTC_CHECK(aspect->value().type() == QVariant::String);
const QStringList commands = aspect->value().toString().split('\n', Qt::SkipEmptyParts);
for (const QString &shellCmd : commands)
m_beforeStartAdbCommands.append(QString("shell %1").arg(shellCmd));
}
for (const QString &shellCmd : runner->recordedData(Constants::ANDROID_PRESTARTSHELLCMDLIST).toStringList())
m_beforeStartAdbCommands.append(QString("shell %1").arg(shellCmd));
if (auto aspect = runControl->aspect(Constants::ANDROID_POSTFINISHSHELLCMDLIST)) {
QTC_CHECK(aspect->value().type() == QVariant::String);
const QStringList commands = aspect->value().toString().split('\n', Qt::SkipEmptyParts);
for (const QString &shellCmd : commands)
m_afterFinishAdbCommands.append(QString("shell %1").arg(shellCmd));
}
for (const QString &shellCmd : runner->recordedData(Constants::ANDROID_POSTFINISHSHELLCMDLIST).toStringList())
m_afterFinishAdbCommands.append(QString("shell %1").arg(shellCmd));
m_debugServerPath = debugServer(m_useLldb, target).toString();
qCDebug(androidRunWorkerLog) << "Device Serial:" << m_deviceSerialNumber
<< ", API level:" << m_apiLevel
<< ", Extra Start Args:" << m_amStartExtraArgs
<< ", Before Start ADB cmds:" << m_beforeStartAdbCommands
<< ", After finish ADB cmds:" << m_afterFinishAdbCommands
<< ", Debug server path:" << m_debugServerPath;
QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(target->kit());
m_useAppParamsForQmlDebugger = version->qtVersion() >= QtSupport::QtVersionNumber(5, 12);
}
AndroidRunnerWorker::~AndroidRunnerWorker()
{
if (m_processPID != -1)
forceStop();
if (!m_pidFinder.isFinished())
m_pidFinder.cancel();
}
bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *stdOut,
const QByteArray &writeData)
{
QStringList adbArgs = selector() + args;
SdkToolResult result = AndroidManager::runAdbCommand(adbArgs, writeData);
if (!result.success())
emit remoteErrorOutput(result.stdErr());
if (stdOut)
*stdOut = result.stdOut();
return result.success();
}
bool AndroidRunnerWorker::uploadDebugServer(const QString &debugServerFileName)
{
// Push the gdbserver or lldb-server to temp location and then to package dir.
// the files can't be pushed directly to package because of permissions.
qCDebug(androidRunWorkerLog) << "Uploading GdbServer";
// Get a unique temp file name for gdb/lldbserver copy
const QString tempDebugServerPathTemplate = "/data/local/tmp/%1";
int count = 0;
while (deviceFileExists(tempDebugServerPathTemplate.arg(++count))) {
if (count > GdbTempFileMaxCounter) {
qCDebug(androidRunWorkerLog) << "Can not get temporary file name";
return false;
}
}
const QString tempDebugServerPath = tempDebugServerPathTemplate.arg(count);
auto cleanUp = qScopeGuard([this, tempDebugServerPath] {
if (!runAdb({"shell", "rm", "-f", tempDebugServerPath}))
qCDebug(androidRunWorkerLog) << "Debug server cleanup failed.";
});
// Copy gdbserver to temp location
if (!runAdb({"push", m_debugServerPath , tempDebugServerPath})) {
qCDebug(androidRunWorkerLog) << "Debug server upload to temp directory failed";
return false;
}
// Copy gdbserver from temp location to app directory
if (!runAdb({"shell", "run-as", m_packageName, "cp" , tempDebugServerPath, debugServerFileName})) {
qCDebug(androidRunWorkerLog) << "Debug server copy from temp directory failed";
return false;
}
QTC_ASSERT(runAdb({"shell", "run-as", m_packageName, "chmod", "777", debugServerFileName}),
qCDebug(androidRunWorkerLog) << "Debug server chmod 777 failed.");
return true;
}
bool AndroidRunnerWorker::deviceFileExists(const QString &filePath)
{
QString output;
const bool success = runAdb({"shell", "ls", filePath, "2>/dev/null"}, &output);
return success && !output.trimmed().isEmpty();
}
bool AndroidRunnerWorker::packageFileExists(const QString &filePath)
{
QString output;
const bool success = runAdb({"shell", "run-as", m_packageName, "ls", filePath, "2>/dev/null"}, &output);
return success && !output.trimmed().isEmpty();
}
void AndroidRunnerWorker::adbKill(qint64 pid)
{
if (!runAdb({"shell", "run-as", m_packageName, "kill", "-9", QString::number(pid)}))
runAdb({"shell", "kill", "-9", QString::number(pid)});
}
QStringList AndroidRunnerWorker::selector() const
{
return AndroidDeviceInfo::adbSelector(m_deviceSerialNumber);
}
void AndroidRunnerWorker::forceStop()
{
runAdb({"shell", "am", "force-stop", m_packageName});
// try killing it via kill -9
QString output;
runAdb({"shell", "pidof", m_packageName}, &output);
if (m_processPID != -1 && output == QString::number(m_processPID))
adbKill(m_processPID);
}
void AndroidRunnerWorker::logcatReadStandardError()
{
if (m_processPID != -1)
logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
}
void AndroidRunnerWorker::logcatReadStandardOutput()
{
if (m_processPID != -1)
logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
}
void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError)
{
QList<QByteArray> lines = text.split('\n');
// lines always contains at least one item
lines[0].prepend(buffer);
if (!lines.last().endsWith('\n')) {
// incomplete line
buffer = lines.last();
lines.removeLast();
} else {
buffer.clear();
}
QString pidString = QString::number(m_processPID);
foreach (const QByteArray &msg, lines) {
const QString line = QString::fromUtf8(msg).trimmed() + QLatin1Char('\n');
if (!line.contains(pidString))
continue;
if (m_useCppDebugger) {
switch (m_jdbState) {
case JDBState::Idle:
if (msg.trimmed().endsWith("Sending WAIT chunk")) {
m_jdbState = JDBState::Waiting;
handleJdbWaiting();
}
break;
case JDBState::Waiting:
if (msg.indexOf("debugger has settled") > 0) {
m_jdbState = JDBState::Settled;
handleJdbSettled();
}
break;
default:
break;
}
}
const QRegularExpressionMatch match = regExpLogcat.match(line);
if (match.hasMatch()) {
// Android M
if (match.captured(1) == pidString) {
const QString messagetype = match.captured(2);
const QString output = line.mid(match.capturedStart(2));
if (onlyError
|| messagetype == QLatin1String("F")
|| messagetype == QLatin1String("E")
|| messagetype == QLatin1String("W"))
emit remoteErrorOutput(output);
else
emit remoteOutput(output);
}
} else {
if (onlyError || line.startsWith("F/")
|| line.startsWith("E/")
|| line.startsWith("W/"))
emit remoteErrorOutput(line);
else
emit remoteOutput(line);
}
}
}
void AndroidRunnerWorker::setAndroidDeviceInfo(const AndroidDeviceInfo &info)
{
m_deviceSerialNumber = info.serialNumber;
m_apiLevel = info.sdk;
qCDebug(androidRunWorkerLog) << "Android Device Info changed"
<< m_deviceSerialNumber << m_apiLevel;
}
void Android::Internal::AndroidRunnerWorker::asyncStartLogcat()
{
// Its assumed that the device or avd returned by selector() is online.
// Start the logcat process before app starts.
QTC_ASSERT(!m_adbLogcatProcess, /**/);
// Ideally AndroidManager::runAdbCommandDetached() should be used, but here
// we need to connect the readyRead signals from logcat otherwise we might
// lost some output between the process start and connecting those signals.
m_adbLogcatProcess.reset(new QProcess());
connect(m_adbLogcatProcess.get(), &QProcess::readyReadStandardOutput,
this, &AndroidRunnerWorker::logcatReadStandardOutput);
connect(m_adbLogcatProcess.get(), &QProcess::readyReadStandardError,
this, &AndroidRunnerWorker::logcatReadStandardError);
// Get target current time to fetch only recent logs
QString dateInSeconds;
QStringList timeArg;
if (runAdb({"shell", "date", "+%s"}, &dateInSeconds)) {
timeArg << "-T";
timeArg << QDateTime::fromSecsSinceEpoch(dateInSeconds.toInt())
.toString("MM-dd hh:mm:ss.mmm");
}
const QStringList logcatArgs = selector() << "logcat" << timeArg;
const QString adb = AndroidConfigurations::currentConfig().adbToolPath().toString();
qCDebug(androidRunWorkerLog) << "Running logcat command (async):"
<< CommandLine(adb, logcatArgs).toUserOutput();
m_adbLogcatProcess->start(adb, logcatArgs);
if (m_adbLogcatProcess->waitForStarted(500) && m_adbLogcatProcess->state() == QProcess::Running)
m_adbLogcatProcess->setObjectName("AdbLogcatProcess");
}
void AndroidRunnerWorker::asyncStartHelper()
{
forceStop();
asyncStartLogcat();
for (const QString &entry : qAsConst(m_beforeStartAdbCommands))
runAdb(entry.split(' ', Qt::SkipEmptyParts));
QStringList args({"shell", "am", "start"});
args << m_amStartExtraArgs;
args << "-n" << m_intentName;
if (m_useCppDebugger) {
args << "-D";
// run-as <package-name> pwd fails on API 22 so route the pwd through shell.
QString packageDir;
if (!runAdb({"shell", "run-as", m_packageName, "/system/bin/sh", "-c", "pwd"},
&packageDir)) {
emit remoteProcessFinished(tr("Failed to find application directory."));
return;
}
// Add executable flag to package dir. Gdb can't connect to running server on device on
// e.g. on Android 8 with NDK 10e
runAdb({"shell", "run-as", m_packageName, "chmod", "a+x", packageDir.trimmed()});
if (!QFileInfo::exists(m_debugServerPath)) {
QString msg = tr("Cannot find C++ debug server in NDK installation.");
if (m_useLldb)
msg += "\n" + tr("The lldb-server binary has not been found.");
emit remoteProcessFinished(msg);
return;
}
QString debugServerFile;
if (m_useLldb) {
debugServerFile = "./lldb-server";
runAdb({"shell", "run-as", m_packageName, "killall", "lldb-server"});
if (!uploadDebugServer(debugServerFile)) {
emit remoteProcessFinished(tr("Cannot copy C++ debug server."));
return;
}
} else {
if (packageFileExists("./lib/gdbserver")) {
debugServerFile = "./lib/gdbserver";
qCDebug(androidRunWorkerLog) << "Found GDB server " + debugServerFile;
runAdb({"shell", "run-as", m_packageName, "killall", "gdbserver"});
} else if (packageFileExists("./lib/libgdbserver.so")) {
debugServerFile = "./lib/libgdbserver.so";
qCDebug(androidRunWorkerLog) << "Found GDB server " + debugServerFile;
runAdb({"shell", "run-as", m_packageName, "killall", "libgdbserver.so"});
} else {
// Armv8. symlink lib is not available.
debugServerFile = "./gdbserver";
// Kill the previous instances of gdbserver. Do this before copying the gdbserver.
runAdb({"shell", "run-as", m_packageName, "killall", "gdbserver"});
if (!uploadDebugServer("./gdbserver")) {
emit remoteProcessFinished(tr("Cannot copy C++ debug server."));
return;
}
}
}
QString debuggerServerErr;
if (!startDebuggerServer(packageDir, debugServerFile, &debuggerServerErr)) {
emit remoteProcessFinished(debuggerServerErr);
return;
}
}
if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
// currently forward to same port on device and host
const QString port = QString("tcp:%1").arg(m_qmlServer.port());
QStringList removeForward{{"forward", "--remove", port}};
removeForwardPort(port);
if (!runAdb({"forward", port, port})) {
emit remoteProcessFinished(tr("Failed to forward QML debugging ports."));
return;
}
m_afterFinishAdbCommands.push_back(removeForward.join(' '));
const QString qmljsdebugger = QString("port:%1,block,services:%2")
.arg(m_qmlServer.port()).arg(QmlDebug::qmlDebugServices(m_qmlDebugServices));
if (m_useAppParamsForQmlDebugger) {
if (!m_extraAppParams.isEmpty())
m_extraAppParams.prepend(' ');
m_extraAppParams.prepend("-qmljsdebugger=" + qmljsdebugger);
} else {
args << "-e" << "qml_debug" << "true"
<< "-e" << "qmljsdebugger"
<< qmljsdebugger;
}
}
if (!m_extraAppParams.isEmpty()) {
QStringList appArgs =
Utils::QtcProcess::splitArgs(m_extraAppParams, Utils::OsType::OsTypeLinux);
qCDebug(androidRunWorkerLog) << "Using application arguments: " << appArgs;
args << "-e" << "extraappparams"
<< QString::fromLatin1(appArgs.join(' ').toUtf8().toBase64());
}
if (m_extraEnvVars.size() > 0) {
args << "-e" << "extraenvvars"
<< QString::fromLatin1(m_extraEnvVars.toStringList().join('\t')
.toUtf8().toBase64());
}
if (!runAdb(args)) {
emit remoteProcessFinished(tr("Failed to start the activity."));
return;
}
}
bool AndroidRunnerWorker::startDebuggerServer(const QString &packageDir,
const QString &debugServerFile,
QString *errorStr)
{
if (m_useLldb) {
QString lldbServerErr;
QStringList lldbServerArgs = selector();
lldbServerArgs << "shell" << "run-as" << m_packageName << debugServerFile
<< "platform"
// << "--server" // Can lead to zombie servers
<< "--listen" << QString("*:%1").arg(m_localDebugServerPort.toString());
m_debugServerProcess.reset(AndroidManager::runAdbCommandDetached(lldbServerArgs, &lldbServerErr));
if (!m_debugServerProcess) {
qCDebug(androidRunWorkerLog) << "Debugger process failed to start" << lldbServerErr;
if (errorStr)
*errorStr = tr("Failed to start debugger server.");
return false;
}
qCDebug(androidRunWorkerLog) << "Debugger process started";
m_debugServerProcess->setObjectName("AndroidDebugServerProcess");
} else {
QString gdbServerSocket = packageDir + "/debug-socket";
runAdb({"shell", "run-as", m_packageName, "rm", gdbServerSocket});
QString gdbProcessErr;
QStringList gdbServerErr = selector();
gdbServerErr << "shell" << "run-as" << m_packageName << debugServerFile
<< "--multi" << "+" + gdbServerSocket;
m_debugServerProcess.reset(AndroidManager::runAdbCommandDetached(gdbServerErr, &gdbProcessErr));
if (!m_debugServerProcess) {
qCDebug(androidRunWorkerLog) << "Debugger process failed to start" << gdbServerErr;
if (errorStr)
*errorStr = tr("Failed to start debugger server.");
return false;
}
qCDebug(androidRunWorkerLog) << "Debugger process started";
m_debugServerProcess->setObjectName("AndroidDebugServerProcess");
const QString port = "tcp:" + m_localDebugServerPort.toString();
const QStringList removeForward{"forward", "--remove", port};
removeForwardPort(port);
if (!runAdb({"forward", port,
"localfilesystem:" + gdbServerSocket})) {
if (errorStr)
*errorStr = tr("Failed to forward C++ debugging ports.");
return false;
}
m_afterFinishAdbCommands.push_back(removeForward.join(' '));
}
return true;
}
void AndroidRunnerWorker::asyncStart()
{
asyncStartHelper();
m_pidFinder = Utils::onResultReady(Utils::runAsync(findProcessPID, selector(),
m_packageName, m_isPreNougat),
bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
}
void AndroidRunnerWorker::asyncStop()
{
if (!m_pidFinder.isFinished())
m_pidFinder.cancel();
if (m_processPID != -1)
forceStop();
m_jdbProcess.reset();
m_debugServerProcess.reset();
}
void AndroidRunnerWorker::handleJdbWaiting()
{
const QString port = "tcp:" + m_localJdbServerPort.toString();
const QStringList removeForward{"forward", "--remove", port};
removeForwardPort(port);
if (!runAdb({"forward", port,
"jdwp:" + QString::number(m_processPID)})) {
emit remoteProcessFinished(tr("Failed to forward JDB debugging ports."));
return;
}
m_afterFinishAdbCommands.push_back(removeForward.join(' '));
auto jdbPath = AndroidConfigurations::currentConfig().openJDKLocation().pathAppended("bin");
jdbPath = jdbPath.pathAppended(Utils::HostOsInfo::withExecutableSuffix("jdb"));
QStringList jdbArgs("-connect");
jdbArgs << QString("com.sun.jdi.SocketAttach:hostname=localhost,port=%1")
.arg(m_localJdbServerPort.toString());
qCDebug(androidRunWorkerLog) << "Starting JDB:" << CommandLine(jdbPath, jdbArgs).toUserOutput();
std::unique_ptr<QProcess, Deleter> jdbProcess(new QProcess, &deleter);
jdbProcess->setProcessChannelMode(QProcess::MergedChannels);
jdbProcess->start(jdbPath.toString(), jdbArgs);
if (!jdbProcess->waitForStarted()) {
emit remoteProcessFinished(tr("Failed to start JDB."));
return;
}
m_jdbProcess = std::move(jdbProcess);
m_jdbProcess->setObjectName("JdbProcess");
}
void AndroidRunnerWorker::handleJdbSettled()
{
qCDebug(androidRunWorkerLog) << "Handle JDB settled";
auto waitForCommand = [this]() {
for (int i= 0; i < 5 && m_jdbProcess->state() == QProcess::Running; ++i) {
m_jdbProcess->waitForReadyRead(500);
QByteArray lines = m_jdbProcess->readAll();
for (const auto &line: lines.split('\n')) {
auto msg = line.trimmed();
if (msg.startsWith(">"))
return true;
}
}
return false;
};
if (waitForCommand()) {
m_jdbProcess->write("cont\n");
if (m_jdbProcess->waitForBytesWritten(5000) && waitForCommand()) {
m_jdbProcess->write("exit\n");
m_jdbProcess->waitForBytesWritten(5000);
if (!m_jdbProcess->waitForFinished(5000)) {
m_jdbProcess->terminate();
if (!m_jdbProcess->waitForFinished(5000)) {
qCDebug(androidRunWorkerLog) << "Killing JDB process";
m_jdbProcess->kill();
m_jdbProcess->waitForFinished();
}
} else if (m_jdbProcess->exitStatus() == QProcess::NormalExit && m_jdbProcess->exitCode() == 0) {
qCDebug(androidRunWorkerLog) << "JDB settled";
return;
}
}
}
emit remoteProcessFinished(tr("Cannot attach JDB to the running application."));
}
void AndroidRunnerWorker::removeForwardPort(const QString &port)
{
bool found = false;
SdkToolResult result = AndroidManager::runAdbCommand({"forward", "--list"});
QString string = result.stdOut();
for (const QString &line : string.split('\n')) {
if (line.contains(port)) {
found = true;
break;
}
}
if (found) {
QStringList removeForward{"forward", "--remove", port};
runAdb(removeForward);
}
}
void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
{
// Don't write to m_psProc from a different thread
QTC_ASSERT(QThread::currentThread() == thread(), return);
qCDebug(androidRunWorkerLog) << "Process ID changed from:" << m_processPID
<< "to:" << pid;
m_processPID = pid;
if (pid == -1) {
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
.arg(m_packageName));
// App died/killed. Reset log, monitor, jdb & gdbserver/lldb-server processes.
m_adbLogcatProcess.reset();
m_psIsAlive.reset();
m_jdbProcess.reset();
m_debugServerProcess.reset();
// Run adb commands after application quit.
for (const QString &entry: qAsConst(m_afterFinishAdbCommands))
runAdb(entry.split(' ', Qt::SkipEmptyParts));
} else {
// In debugging cases this will be funneled to the engine to actually start
// and attach gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
emit remoteProcessStarted(m_localDebugServerPort, m_qmlServer, m_processPID);
logcatReadStandardOutput();
QTC_ASSERT(!m_psIsAlive, /**/);
QStringList isAliveArgs = selector() << "shell" << pidPollingScript.arg(m_processPID);
m_psIsAlive.reset(AndroidManager::runAdbCommandDetached(isAliveArgs));
QTC_ASSERT(m_psIsAlive, return);
m_psIsAlive->setObjectName("IsAliveProcess");
m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels);
connect(m_psIsAlive.get(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, bind(&AndroidRunnerWorker::onProcessIdChanged, this, -1));
}
}
} // namespace Internal
} // namespace Android