diff options
Diffstat (limited to 'src/plugins/android/androidrunnerworker.cpp')
-rw-r--r-- | src/plugins/android/androidrunnerworker.cpp | 342 |
1 files changed, 228 insertions, 114 deletions
diff --git a/src/plugins/android/androidrunnerworker.cpp b/src/plugins/android/androidrunnerworker.cpp index 1fb878109f..9b1b067f76 100644 --- a/src/plugins/android/androidrunnerworker.cpp +++ b/src/plugins/android/androidrunnerworker.cpp @@ -30,6 +30,7 @@ #include "androidmanager.h" #include "androidrunconfiguration.h" +#include <debugger/debuggerkitinformation.h> #include <debugger/debuggerrunconfigurationaspect.h> #include <projectexplorer/environmentaspect.h> @@ -40,15 +41,21 @@ #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/synchronousprocess.h> #include <utils/temporaryfile.h> -#include <utils/qtcprocess.h> #include <utils/url.h> -#include <utils/fileutils.h> +#include <QDir> +#include <QDirIterator> +#include <QFileInfo> #include <QLoggingCategory> +#include <QScopeGuard> +#include <QRegularExpression> #include <QTcpServer> #include <QThread> @@ -72,20 +79,19 @@ 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 QString regExpLogcat = QStringLiteral("[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 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, @@ -118,7 +124,6 @@ static qint64 extractPID(const QByteArray &output, const QString &packageName) static void findProcessPID(QFutureInterface<qint64> &fi, QStringList selector, const QString &packageName, bool preNougat) { - qCDebug(androidRunWorkerLog) << "Finding PID. PreNougat:" << preNougat; if (packageName.isEmpty()) return; @@ -138,7 +143,7 @@ static void findProcessPID(QFutureInterface<qint64> &fi, QStringList selector, } } while (processPID == -1 && !isTimedOut(start) && !fi.isCanceled()); - qCDebug(androidRunWorkerLog) << "PID found:" << processPID; + qCDebug(androidRunWorkerLog) << "PID found:" << processPID << ", PreNougat:" << preNougat; if (!fi.isCanceled()) fi.reportResult(processPID); } @@ -151,23 +156,100 @@ static void deleter(QProcess *p) p->kill(); p->waitForFinished(); } - qCDebug(androidRunWorkerLog) << "Done killing process:" << p->objectName(); // Might get deleted from its own signal handler. p->deleteLater(); } +static QString gdbServerArch(const QString &androidAbi) +{ + if (androidAbi == "arm64-v8a") + return QString("arm64"); + if (androidAbi == "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 == "armeabi-v7a") + return QString("armeabi"); + // Correct for arm64-v8a "x86_64" and "x86", and best guess at anything that will evolve: + return androidAbi; // arm64-v8a, x86, x86_64 +} + +static QString lldbServerArch2(const QString &androidAbi) +{ + if (androidAbi == "armeabi-v7a") + return {"arm"}; + if (androidAbi == "x86") + return {"i386"}; + if (androidAbi == "arm64-v8a") + return {"aarch64"}; + // Correct for "x86_64" a and best guess at anything that will evolve: + return androidAbi; // arm64-v8a +} + +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_logCatRegExp(regExpLogcat) - , m_gdbServerProcess(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>(); - Core::Id runMode = runControl->runMode(); + Utils::Id runMode = runControl->runMode(); const bool debuggingMode = runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE; m_useCppDebugger = debuggingMode && aspect->useCppDebugger(); if (debuggingMode && aspect->useQmlDebugger()) @@ -178,8 +260,8 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa m_qmlDebugServices = QmlDebug::QmlPreviewServices; else m_qmlDebugServices = QmlDebug::NoQmlDebugServices; - m_localGdbServerPort = Utils::Port(5039); - QTC_CHECK(m_localGdbServerPort.isValid()); + m_localDebugServerPort = Utils::Port(5039); + QTC_CHECK(m_localDebugServerPort.isValid()); if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) { qCDebug(androidRunWorkerLog) << "QML debugging enabled"; QTcpServer server; @@ -203,8 +285,10 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa m_extraAppParams = runControl->runnable().commandLineArguments; - if (auto aspect = runControl->aspect(Constants::ANDROID_AMSTARTARGS)) - m_amStartExtraArgs = static_cast<BaseStringAspect *>(aspect)->value().split(' '); + if (auto aspect = runControl->aspect(Constants::ANDROID_AMSTARTARGS)) { + const QString startArgs = static_cast<BaseStringAspect *>(aspect)->value(); + m_amStartExtraArgs = QtcProcess::splitArgs(startArgs, OsTypeOtherUnix); + } if (auto aspect = runControl->aspect(Constants::ANDROID_PRESTARTSHELLCMDLIST)) { for (const QString &shellCmd : static_cast<BaseStringListAspect *>(aspect)->value()) @@ -220,16 +304,15 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa 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; + << ", 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()); - QString preferredAbi = AndroidManager::apkDevicePreferredAbi(target); - if (!preferredAbi.isEmpty()) - m_gdbserverPath = AndroidConfigurations::instance() - ->currentConfig().gdbServer(preferredAbi, version).toString(); m_useAppParamsForQmlDebugger = version->qtVersion() >= QtSupport::QtVersionNumber(5, 12); } @@ -254,45 +337,41 @@ bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *stdOut, return result.success(); } -bool AndroidRunnerWorker::uploadGdbServer() +bool AndroidRunnerWorker::uploadDebugServer(const QString &debugServerFileName) { - // Push the gdbserver to temp location and then to package dir. + // 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"; - bool foundUnique = true; - auto cleanUp = [this, &foundUnique] (QString *p) { - if (foundUnique && !runAdb({"shell", "rm", "-f", *p})) - qCDebug(androidRunWorkerLog) << "Gdbserver cleanup failed."; - delete p; - }; - std::unique_ptr<QString, decltype (cleanUp)> - tempGdbServerPath(new QString("/data/local/tmp/%1"), cleanUp); - - // Get a unique temp file name for gdbserver copy + // Get a unique temp file name for gdb/lldbserver copy + const QString tempDebugServerPathTemplate = "/data/local/tmp/%1"; int count = 0; - while (deviceFileExists(tempGdbServerPath->arg(++count))) { + while (deviceFileExists(tempDebugServerPathTemplate.arg(++count))) { if (count > GdbTempFileMaxCounter) { qCDebug(androidRunWorkerLog) << "Can not get temporary file name"; - foundUnique = false; return false; } } - *tempGdbServerPath = tempGdbServerPath->arg(count); + + 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_gdbserverPath , *tempGdbServerPath})) { - qCDebug(androidRunWorkerLog) << "Gdbserver upload to temp directory failed"; + 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" , *tempGdbServerPath, "./gdbserver"})) { - qCDebug(androidRunWorkerLog) << "Gdbserver copy from temp directory failed"; + 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", "./gdbserver"}), - qCDebug(androidRunWorkerLog) << "Gdbserver chmod 777 failed."); + QTC_ASSERT(runAdb({"shell", "run-as", m_packageName, "chmod", "777", debugServerFileName}), + qCDebug(androidRunWorkerLog) << "Debug server chmod 777 failed."); return true; } @@ -378,11 +457,12 @@ void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buff break; } } - if (m_logCatRegExp.exactMatch(line)) { + const QRegularExpressionMatch match = regExpLogcat.match(line); + if (match.hasMatch()) { // Android M - if (m_logCatRegExp.cap(1) == pidString) { - const QString &messagetype = m_logCatRegExp.cap(2); - QString output = line.mid(m_logCatRegExp.pos(2)); + if (match.captured(1) == pidString) { + const QString messagetype = match.captured(2); + const QString output = line.mid(match.capturedStart(2)); if (onlyError || messagetype == QLatin1String("F") @@ -428,7 +508,7 @@ void AndroidRunnerWorker::asyncStartHelper() } for (const QString &entry : m_beforeStartAdbCommands) - runAdb(entry.split(' ', QString::SkipEmptyParts)); + runAdb(entry.split(' ', Utils::SkipEmptyParts)); QStringList args({"shell", "am", "start"}); args << m_amStartExtraArgs; @@ -447,32 +527,47 @@ void AndroidRunnerWorker::asyncStartHelper() // e.g. on Android 8 with NDK 10e runAdb({"shell", "run-as", m_packageName, "chmod", "a+x", packageDir.trimmed()}); - QString gdbServerExecutable = "gdbserver"; - QString gdbServerPrefix = "./lib/"; - auto findGdbServer = [this, &gdbServerExecutable, gdbServerPrefix](const QString& gdbEx) { - if (!packageFileExists(gdbServerPrefix + gdbEx)) - return false; - gdbServerExecutable = gdbEx; - return true; - }; - - if (!findGdbServer("gdbserver") && !findGdbServer("libgdbserver.so")) { - // Armv8. symlink lib is not available. - // Kill the previous instances of gdbserver. Do this before copying the gdbserver. - runAdb({"shell", "run-as", m_packageName, "killall", gdbServerExecutable}); - if (!m_gdbserverPath.isEmpty() && uploadGdbServer()) { - gdbServerPrefix = "./"; - } else { - emit remoteProcessFinished(tr("Cannot find or copy C++ debug server.")); + 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. Maybe " + "sdk_definitions.json does not contain 'lldb;x.y' as " + "sdk_essential_package or LLDB was not installed."); + } + 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 { - qCDebug(androidRunWorkerLog) << "Found GDB server under ./lib"; - runAdb({"shell", "run-as", m_packageName, "killall", gdbServerExecutable}); + 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, gdbServerPrefix, gdbServerExecutable, &debuggerServerErr)) { + if (!startDebuggerServer(packageDir, debugServerFile, &debuggerServerErr)) { emit remoteProcessFinished(debuggerServerErr); return; } @@ -525,37 +620,56 @@ void AndroidRunnerWorker::asyncStartHelper() } bool AndroidRunnerWorker::startDebuggerServer(const QString &packageDir, - const QString &gdbServerPrefix, - const QString &gdbServerExecutable, + const QString &debugServerFile, QString *errorStr) { - QString gdbServerSocket = packageDir + "/debug-socket"; - runAdb({"shell", "run-as", m_packageName, "rm", gdbServerSocket}); - - QString gdbProcessErr; - QStringList gdbServerArgs = selector(); - gdbServerArgs << "shell" << "run-as" << m_packageName << gdbServerPrefix + gdbServerExecutable - << "--multi" << "+" + gdbServerSocket; - m_gdbServerProcess.reset(AndroidManager::runAdbCommandDetached(gdbServerArgs, &gdbProcessErr)); - - if (!m_gdbServerProcess) { - qCDebug(androidRunWorkerLog) << "Debugger process failed to start" << gdbProcessErr; - if (errorStr) - *errorStr = tr("Failed to start debugger server."); - return false; - } - qCDebug(androidRunWorkerLog) << "Debugger process started"; - m_gdbServerProcess->setObjectName("AndroidDebugServerProcess"); + 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"); - QStringList removeForward{"forward", "--remove", "tcp:" + m_localGdbServerPort.toString()}; - runAdb(removeForward); - if (!runAdb({"forward", "tcp:" + m_localGdbServerPort.toString(), - "localfilesystem:" + gdbServerSocket})) { - if (errorStr) - *errorStr = tr("Failed to forward C++ debugging ports."); - return false; + } 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"); + + QStringList removeForward{"forward", "--remove", "tcp:" + m_localDebugServerPort.toString()}; + runAdb(removeForward); + if (!runAdb({"forward", "tcp:" + m_localDebugServerPort.toString(), + "localfilesystem:" + gdbServerSocket})) { + if (errorStr) + *errorStr = tr("Failed to forward C++ debugging ports."); + return false; + } + m_afterFinishAdbCommands.push_back(removeForward.join(' ')); } - m_afterFinishAdbCommands.push_back(removeForward.join(' ')); return true; } @@ -577,7 +691,7 @@ void AndroidRunnerWorker::asyncStop() forceStop(); m_jdbProcess.reset(); - m_gdbServerProcess.reset(); + m_debugServerProcess.reset(); } void AndroidRunnerWorker::handleJdbWaiting() @@ -612,7 +726,7 @@ void AndroidRunnerWorker::handleJdbWaiting() void AndroidRunnerWorker::handleJdbSettled() { qCDebug(androidRunWorkerLog) << "Handle JDB settled"; - auto waitForCommand = [&]() { + auto waitForCommand = [this]() { for (int i= 0; i < 5 && m_jdbProcess->state() == QProcess::Running; ++i) { m_jdbProcess->waitForReadyRead(500); QByteArray lines = m_jdbProcess->readAll(); @@ -655,19 +769,19 @@ void AndroidRunnerWorker::onProcessIdChanged(qint64 pid) if (pid == -1) { emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.") .arg(m_packageName)); - // App died/killed. Reset log, monitor, jdb & gdb processes. + // App died/killed. Reset log, monitor, jdb & gdbserver/lldb-server processes. m_adbLogcatProcess.reset(); m_psIsAlive.reset(); m_jdbProcess.reset(); - m_gdbServerProcess.reset(); + m_debugServerProcess.reset(); // Run adb commands after application quit. for (const QString &entry: m_afterFinishAdbCommands) - runAdb(entry.split(' ', QString::SkipEmptyParts)); + runAdb(entry.split(' ', Utils::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_localGdbServerPort, m_qmlServer, m_processPID); + emit remoteProcessStarted(m_localDebugServerPort, m_qmlServer, m_processPID); logcatReadStandardOutput(); QTC_ASSERT(!m_psIsAlive, /**/); QStringList isAliveArgs = selector() << "shell" << pidPollingScript.arg(m_processPID); |