Android: Automotive debugging capability

Android Automotive images use special system users to run Android apps
and activities. To maintain compatibility with all Android flavors not
only the process ID but also the user ID of the process should be
detected in order to be able to start the debugger with the correct
user credentials. Failing to do so caused the debugging server to
terminate upon not being able to connect to the app to be debugged.
This also disabled the native debugging in Qt Creator.

Task-number: QTCREATORBUG-28851
Change-Id: Ib4cd0ba7f252096cb7b8b14f959c8f0c743d8bf2
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Zoltan Gera
2023-04-12 18:11:34 +03:00
parent 9beec99452
commit 2d01003c4e
2 changed files with 124 additions and 73 deletions

View File

@@ -51,6 +51,7 @@ namespace Android {
namespace Internal { namespace Internal {
static const QString pidPollingScript = QStringLiteral("while [ -d /proc/%1 ]; do sleep 1; done"); static const QString pidPollingScript = QStringLiteral("while [ -d /proc/%1 ]; do sleep 1; done");
static const QRegularExpression userIdPattern("u(\\d+)_a");
static int APP_START_TIMEOUT = 45000; static int APP_START_TIMEOUT = 45000;
static bool isTimedOut(const chrono::high_resolution_clock::time_point &start, static bool isTimedOut(const chrono::high_resolution_clock::time_point &start,
@@ -77,8 +78,8 @@ static qint64 extractPID(const QString &output, const QString &packageName)
return pid; return pid;
} }
static void findProcessPID(QFutureInterface<qint64> &fi, QStringList selector, static void findProcessPIDAndUser(QFutureInterface<PidUserPair> &fi, QStringList selector,
const QString &packageName, bool preNougat) const QString &packageName, bool preNougat)
{ {
if (packageName.isEmpty()) if (packageName.isEmpty())
return; return;
@@ -108,8 +109,32 @@ static void findProcessPID(QFutureInterface<qint64> &fi, QStringList selector,
} while ((processPID == -1 || processPID == 0) && !isTimedOut(start) && !fi.isCanceled()); } while ((processPID == -1 || processPID == 0) && !isTimedOut(start) && !fi.isCanceled());
qCDebug(androidRunWorkerLog) << "PID found:" << processPID << ", PreNougat:" << preNougat; qCDebug(androidRunWorkerLog) << "PID found:" << processPID << ", PreNougat:" << preNougat;
qint64 processUser = 0;
if (processPID > 0 && !fi.isCanceled()) {
args = {selector};
args.append({"shell", "ps", "-o", "user", "-p"});
args.append(QString::number(processPID));
QtcProcess proc;
proc.setCommand({adbPath, args});
proc.runBlocking();
const QString out = proc.allOutput();
if (!out.isEmpty()) {
QRegularExpressionMatch match;
qsizetype matchPos = out.indexOf(userIdPattern, 0, &match);
if (matchPos >= 0 && match.hasCaptured(1) && match.capturedLength(1) > 0) {
bool ok = false;
processUser = match.captured(1).toInt(&ok);
if (!ok)
processUser = 0;
}
}
}
qCDebug(androidRunWorkerLog) << "USER found:" << processUser;
if (!fi.isCanceled()) if (!fi.isCanceled())
fi.reportResult(processPID); fi.reportResult(PidUserPair(processPID, processUser));
} }
static void deleter(QProcess *p) static void deleter(QProcess *p)
@@ -325,13 +350,16 @@ bool AndroidRunnerWorker::uploadDebugServer(const QString &debugServerFileName)
return false; return false;
} }
QStringList adbArgs = {"shell", "run-as", m_packageName};
if (m_processUser > 0)
adbArgs << "--user" << QString::number(m_processUser);
// Copy gdbserver from temp location to app directory // Copy gdbserver from temp location to app directory
if (!runAdb({"shell", "run-as", m_packageName, "cp" , tempDebugServerPath, debugServerFileName})) { if (!runAdb(adbArgs + QStringList({"cp" , tempDebugServerPath, debugServerFileName}))) {
qCDebug(androidRunWorkerLog) << "Debug server copy from temp directory failed"; qCDebug(androidRunWorkerLog) << "Debug server copy from temp directory failed";
return false; return false;
} }
const bool ok = runAdb({"shell", "run-as", m_packageName, "chmod", "777", debugServerFileName}); const bool ok = runAdb(adbArgs + QStringList({"chmod", "777", debugServerFileName}));
QTC_ASSERT(ok, qCDebug(androidRunWorkerLog) << "Debug server chmod 777 failed."); QTC_ASSERT(ok, qCDebug(androidRunWorkerLog) << "Debug server chmod 777 failed.");
return true; return true;
} }
@@ -346,7 +374,11 @@ bool AndroidRunnerWorker::deviceFileExists(const QString &filePath)
bool AndroidRunnerWorker::packageFileExists(const QString &filePath) bool AndroidRunnerWorker::packageFileExists(const QString &filePath)
{ {
QString output; QString output;
const bool success = runAdb({"shell", "run-as", m_packageName, "ls", filePath, "2>/dev/null"}, &output); QStringList adbArgs = {"shell", "run-as", m_packageName};
if (m_processUser > 0)
adbArgs << "--user" << QString::number(m_processUser);
const bool success = runAdb(adbArgs + QStringList({"ls", filePath, "2>/dev/null"}),
&output);
return success && !output.trimmed().isEmpty(); return success && !output.trimmed().isEmpty();
} }
@@ -513,62 +545,8 @@ void AndroidRunnerWorker::asyncStartHelper()
QStringList args({"shell", "am", "start"}); QStringList args({"shell", "am", "start"});
args << "-n" << m_intentName; args << "-n" << m_intentName;
if (m_useCppDebugger) { if (m_useCppDebugger)
args << "-D"; 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::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 (!m_debugServerPath.exists()) {
QString msg = Tr::tr("Cannot find C++ debug server in NDK installation.");
if (m_useLldb)
msg += "\n" + Tr::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::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::tr("Cannot copy C++ debug server."));
return;
}
}
}
QString debuggerServerErr;
if (!startDebuggerServer(packageDir, debugServerFile, &debuggerServerErr)) {
emit remoteProcessFinished(debuggerServerErr);
return;
}
}
if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) { if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
// currently forward to same port on device and host // currently forward to same port on device and host
@@ -624,17 +602,79 @@ void AndroidRunnerWorker::asyncStartHelper()
} }
} }
void AndroidRunnerWorker::startNativeDebugging()
{
// run-as <package-name> pwd fails on API 22 so route the pwd through shell.
QString packageDir;
QStringList adbArgs = {"shell", "run-as", m_packageName};
if (m_processUser > 0)
adbArgs << "--user" << QString::number(m_processUser);
if (!runAdb(adbArgs + QStringList({"/system/bin/sh", "-c", "pwd"}),
&packageDir)) {
emit remoteProcessFinished(Tr::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(adbArgs + QStringList({"chmod", "a+x", packageDir.trimmed()}));
if (!m_debugServerPath.exists()) {
QString msg = Tr::tr("Cannot find C++ debug server in NDK installation.");
if (m_useLldb)
msg += "\n" + Tr::tr("The lldb-server binary has not been found.");
emit remoteProcessFinished(msg);
return;
}
QString debugServerFile;
if (m_useLldb) {
debugServerFile = "./lldb-server";
runAdb(adbArgs + QStringList({"killall", "lldb-server"}));
if (!uploadDebugServer(debugServerFile)) {
emit remoteProcessFinished(Tr::tr("Cannot copy C++ debug server."));
return;
}
} else {
if (packageFileExists("./lib/gdbserver")) {
debugServerFile = "./lib/gdbserver";
qCDebug(androidRunWorkerLog) << "Found GDB server " + debugServerFile;
runAdb(adbArgs + QStringList({"killall", "gdbserver"}));
} else if (packageFileExists("./lib/libgdbserver.so")) {
debugServerFile = "./lib/libgdbserver.so";
qCDebug(androidRunWorkerLog) << "Found GDB server " + debugServerFile;
runAdb(adbArgs + QStringList({"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(adbArgs + QStringList({"killall", "gdbserver"}));
if (!uploadDebugServer("./gdbserver")) {
emit remoteProcessFinished(Tr::tr("Cannot copy C++ debug server."));
return;
}
}
}
QString debuggerServerErr;
if (!startDebuggerServer(packageDir, debugServerFile, &debuggerServerErr)) {
emit remoteProcessFinished(debuggerServerErr);
return;
}
}
bool AndroidRunnerWorker::startDebuggerServer(const QString &packageDir, bool AndroidRunnerWorker::startDebuggerServer(const QString &packageDir,
const QString &debugServerFile, const QString &debugServerFile,
QString *errorStr) QString *errorStr)
{ {
QStringList adbArgs = {"shell", "run-as", m_packageName};
if (m_processUser > 0)
adbArgs << "--user" << QString::number(m_processUser);
if (m_useLldb) { if (m_useLldb) {
QString lldbServerErr; QString lldbServerErr;
QStringList lldbServerArgs = selector(); QStringList lldbServerArgs = selector();
lldbServerArgs << "shell" << "run-as" << m_packageName << debugServerFile lldbServerArgs += adbArgs;
<< "platform" lldbServerArgs << debugServerFile
// << "--server" // Can lead to zombie servers << "platform"
<< "--listen" << QString("*:%1").arg(m_localDebugServerPort.toString()); // << "--server" // Can lead to zombie servers
<< "--listen" << QString("*:%1").arg(m_localDebugServerPort.toString());
m_debugServerProcess.reset(AndroidManager::runAdbCommandDetached(lldbServerArgs, &lldbServerErr)); m_debugServerProcess.reset(AndroidManager::runAdbCommandDetached(lldbServerArgs, &lldbServerErr));
if (!m_debugServerProcess) { if (!m_debugServerProcess) {
@@ -648,12 +688,13 @@ bool AndroidRunnerWorker::startDebuggerServer(const QString &packageDir,
} else { } else {
QString gdbServerSocket = packageDir + "/debug-socket"; QString gdbServerSocket = packageDir + "/debug-socket";
runAdb({"shell", "run-as", m_packageName, "rm", gdbServerSocket}); runAdb(adbArgs + QStringList({"rm", gdbServerSocket}));
QString gdbProcessErr; QString gdbProcessErr;
QStringList gdbServerErr = selector(); QStringList gdbServerErr = selector();
gdbServerErr << "shell" << "run-as" << m_packageName << debugServerFile gdbServerErr += adbArgs;
<< "--multi" << "+" + gdbServerSocket; gdbServerErr << debugServerFile
<< "--multi" << "+" + gdbServerSocket;
m_debugServerProcess.reset(AndroidManager::runAdbCommandDetached(gdbServerErr, &gdbProcessErr)); m_debugServerProcess.reset(AndroidManager::runAdbCommandDetached(gdbServerErr, &gdbProcessErr));
if (!m_debugServerProcess) { if (!m_debugServerProcess) {
@@ -683,7 +724,7 @@ void AndroidRunnerWorker::asyncStart()
{ {
asyncStartHelper(); asyncStartHelper();
m_pidFinder = Utils::onResultReady(Utils::runAsync(findProcessPID, selector(), m_pidFinder = Utils::onResultReady(Utils::runAsync(findProcessPIDAndUser, selector(),
m_packageName, m_isPreNougat), m_packageName, m_isPreNougat),
bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1)); bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
} }
@@ -793,13 +834,16 @@ void AndroidRunnerWorker::removeForwardPort(const QString &port)
} }
} }
void AndroidRunnerWorker::onProcessIdChanged(qint64 pid) void AndroidRunnerWorker::onProcessIdChanged(PidUserPair pidUser)
{ {
qint64 pid = pidUser.first;
qint64 user = pidUser.second;
// Don't write to m_psProc from a different thread // Don't write to m_psProc from a different thread
QTC_ASSERT(QThread::currentThread() == thread(), return); QTC_ASSERT(QThread::currentThread() == thread(), return);
qCDebug(androidRunWorkerLog) << "Process ID changed from:" << m_processPID qCDebug(androidRunWorkerLog) << "Process ID changed from:" << m_processPID
<< "to:" << pid; << "to:" << pid;
m_processPID = pid; m_processPID = pid;
m_processUser = user;
if (pid == -1) { if (pid == -1) {
emit remoteProcessFinished(QLatin1String("\n\n") + Tr::tr("\"%1\" died.") emit remoteProcessFinished(QLatin1String("\n\n") + Tr::tr("\"%1\" died.")
.arg(m_packageName)); .arg(m_packageName));
@@ -813,6 +857,8 @@ void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
for (const QString &entry: std::as_const(m_afterFinishAdbCommands)) for (const QString &entry: std::as_const(m_afterFinishAdbCommands))
runAdb(entry.split(' ', Qt::SkipEmptyParts)); runAdb(entry.split(' ', Qt::SkipEmptyParts));
} else { } else {
if (m_useCppDebugger)
startNativeDebugging();
// In debugging cases this will be funneled to the engine to actually start // In debugging cases this will be funneled to the engine to actually start
// and attach gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below. // and attach gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
emit remoteProcessStarted(m_localDebugServerPort, m_qmlServer, m_processPID); emit remoteProcessStarted(m_localDebugServerPort, m_qmlServer, m_processPID);
@@ -824,7 +870,7 @@ void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
m_psIsAlive->setObjectName("IsAliveProcess"); m_psIsAlive->setObjectName("IsAliveProcess");
m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels); m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels);
connect(m_psIsAlive.get(), &QProcess::finished, connect(m_psIsAlive.get(), &QProcess::finished,
this, bind(&AndroidRunnerWorker::onProcessIdChanged, this, -1)); this, bind(&AndroidRunnerWorker::onProcessIdChanged, this, PidUserPair(-1, -1)));
} }
} }

View File

@@ -10,6 +10,7 @@
#include <utils/port.h> #include <utils/port.h>
#include <QFuture> #include <QFuture>
#include <utility>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QProcess; class QProcess;
@@ -26,6 +27,8 @@ namespace Internal {
const int MIN_SOCKET_HANDSHAKE_PORT = 20001; const int MIN_SOCKET_HANDSHAKE_PORT = 20001;
using PidUserPair = std::pair<qint64, qint64>;
class AndroidRunnerWorker : public QObject class AndroidRunnerWorker : public QObject
{ {
Q_OBJECT Q_OBJECT
@@ -61,6 +64,7 @@ signals:
private: private:
void asyncStartHelper(); void asyncStartHelper();
void startNativeDebugging();
bool startDebuggerServer(const QString &packageDir, const QString &debugServerFile, QString *errorStr = nullptr); bool startDebuggerServer(const QString &packageDir, const QString &debugServerFile, QString *errorStr = nullptr);
bool deviceFileExists(const QString &filePath); bool deviceFileExists(const QString &filePath);
bool packageFileExists(const QString& filePath); bool packageFileExists(const QString& filePath);
@@ -72,7 +76,7 @@ private:
Waiting, Waiting,
Settled Settled
}; };
void onProcessIdChanged(qint64 pid); void onProcessIdChanged(PidUserPair pidUser);
using Deleter = void (*)(QProcess *); using Deleter = void (*)(QProcess *);
// Create the processes and timer in the worker thread, for correct thread affinity // Create the processes and timer in the worker thread, for correct thread affinity
@@ -83,11 +87,12 @@ private:
QStringList m_afterFinishAdbCommands; QStringList m_afterFinishAdbCommands;
QStringList m_amStartExtraArgs; QStringList m_amStartExtraArgs;
qint64 m_processPID = -1; qint64 m_processPID = -1;
qint64 m_processUser = -1;
std::unique_ptr<QProcess, Deleter> m_adbLogcatProcess; std::unique_ptr<QProcess, Deleter> m_adbLogcatProcess;
std::unique_ptr<QProcess, Deleter> m_psIsAlive; std::unique_ptr<QProcess, Deleter> m_psIsAlive;
QByteArray m_stdoutBuffer; QByteArray m_stdoutBuffer;
QByteArray m_stderrBuffer; QByteArray m_stderrBuffer;
QFuture<qint64> m_pidFinder; QFuture<PidUserPair> m_pidFinder;
bool m_useCppDebugger = false; bool m_useCppDebugger = false;
bool m_useLldb = false; // FIXME: Un-implemented currently. bool m_useLldb = false; // FIXME: Un-implemented currently.
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices; QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;