AndroidTestRunner: use ndk-stack to pretty print crash reports

Use ndk-stack [*] tool provided by the Android NDK to pretty
print crash stacktraces to contain <source-file>:<line-number>
lines for calls, making debugging crashes much easier.

The ndk-stack path can be provided with the new param --ndk-stack,
otherwise, it would be deduced using ANDROID_NDK_ROOT env var.

If the tool is not found or the unstripped libs path cannot be
obtained, the default crash report is printed instead.

[*] https://developer.android.com/ndk/guides/ndk-stack

Task-number: QTQAINFRA-5928
Pick-to: 6.5
Change-Id: I22c3ba131a44050c8fcbfd176d5ced096761d229
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
(cherry picked from commit 58fc33239e54ca429e41d0b5be8a1c6f917671e2)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Assam Boudjelthia 2023-11-24 13:51:47 +02:00 committed by Qt Cherry-pick Bot
parent 9c84620d4b
commit 06e46b77ec

View File

@ -125,6 +125,7 @@ struct Options
QHash<QString, QString> outFiles;
QString testArgs;
QString apkPath;
QString ndkStackPath;
int sdkVersion = -1;
int pid = -1;
bool showLogcatOutput = false;
@ -201,6 +202,11 @@ static bool parseOptions()
g_options.skipAddInstallRoot = true;
} else if (argument.compare(QStringLiteral("--show-logcat"), Qt::CaseInsensitive) == 0) {
g_options.showLogcatOutput = true;
} else if (argument.compare("--ndk-stack"_L1, Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
g_options.helpRequested = true;
else
g_options.ndkStackPath = arguments.at(++i);
} else if (argument.compare(QStringLiteral("--timeout"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
g_options.helpRequested = true;
@ -226,6 +232,14 @@ static bool parseOptions()
QString serial = qEnvironmentVariable("ANDROID_DEVICE_SERIAL");
if (!serial.isEmpty())
g_options.adbCommand += QStringLiteral(" -s %1").arg(serial);
if (g_options.ndkStackPath.isEmpty()) {
const QString ndkPath = qEnvironmentVariable("ANDROID_NDK_ROOT");
const QString ndkStackPath = ndkPath + QDir::separator() + "ndk-stack"_L1;
if (QFile::exists(ndkStackPath))
g_options.ndkStackPath = ndkStackPath;
}
return true;
}
@ -259,6 +273,9 @@ static void printHelp()
"\n"
" --show-logcat: Print Logcat output to stdout.\n"
"\n"
" --ndk-stack: Path to ndk-stack tool that symbolizes crash stacktraces.\n"
" By default, ANDROID_NDK_ROOT env var is used to deduce the tool path.\n"
"\n"
" -- Arguments that will be passed to the test application.\n"
"\n"
" --verbose: Prints out information during processing.\n"
@ -517,10 +534,42 @@ void printLogcat(const QString &formattedTime)
qDebug() << "****** End logcat output ******";
}
static QString getDeviceABI()
{
const QString abiCmd = "%1 shell getprop ro.product.cpu.abi"_L1.arg(g_options.adbCommand);
QByteArray abi;
if (!execCommand(abiCmd, &abi)) {
qWarning() << "Warning: failed to get the device abi, fallback to first libs dir";
return {};
}
return QString::fromUtf8(abi.simplified());
}
void printLogcatCrashBuffer(const QString &formattedTime)
{
QString crashCmd = "%1 logcat -b crash -t '%2'"_L1.arg(g_options.adbCommand, formattedTime);
if (!g_options.ndkStackPath.isEmpty()) {
auto libsPath = "%1/libs/"_L1.arg(g_options.buildPath);
QString abi = getDeviceABI();
if (abi.isEmpty()) {
QStringList subDirs = QDir(libsPath).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
if (!subDirs.isEmpty())
abi = subDirs.first();
}
if (!abi.isEmpty()) {
libsPath += abi;
crashCmd += " | %1 -sym %2"_L1.arg(g_options.ndkStackPath, libsPath);
} else {
qWarning() << "Warning: failed to get the libs abi, ndk-stack cannot be used.";
}
} else {
qWarning() << "Warning: ndk-stack path not provided and couldn't be deduced "
"using the ANDROID_NDK_ROOT environment variable.";
}
QByteArray crashLogcat;
if (!execCommand(crashCmd, &crashLogcat)) {
qCritical() << "Error: failed to fetch logcat crash buffer";