From 03883cc976ee27113126d75d7d130c6271df931b Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Thu, 18 Jan 2024 09:59:18 -0500 Subject: [PATCH] Bug 1875294 - Record breadbcrumbs before crashing with UnsatsisfiedLinkError Report breadcrumbs to Sentry before crashing with `UnsatsisfiedLinkError`: - The files inside the app directory, to detect if the library is present or not. - The files inside the APK, to detect if a file inside the APK didn't get installed correctly. - The name of the installer package, so that we can know if the APK was installed from a 3rd-party source. - The path to the APK. I don't really see this being useful, but it's needed to determine all the other info, so we might as well report it. The idea here is that hopefully one of these will detect that something was off with their install. For example, maybe they installed an APK for the wrong arch or maybe `libjnidispatch` was not extracted correctly. If not, at least we can rule these possibilities out as the root cause. --- .../org/mozilla/fenix/FenixApplication.kt | 76 +++++++++++++++++-- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 264b9dc71..43df89613 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -112,8 +112,11 @@ import org.mozilla.fenix.session.VisibilityLifecycleCallback import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD import org.mozilla.fenix.wallpapers.Wallpaper +import java.io.File +import java.io.FileInputStream import java.util.UUID import java.util.concurrent.TimeUnit +import java.util.zip.ZipInputStream import kotlin.math.roundToLong private const val RAM_THRESHOLD_MEGABYTES = 1024 @@ -521,13 +524,76 @@ open class FenixApplication : LocaleAwareApplication(), Provider { * thread, early in the app startup sequence. */ private fun beginSetupMegazord() { - // Note: Megazord.init() must be called as soon as possible ... - Megazord.init() + try { + // Note: Megazord.init() must be called as soon as possible ... + Megazord.init() + + initializeRustErrors(components.analytics.crashReporter) + // ... but RustHttpConfig.setClient() and RustLog.enable() can be called later. - initializeRustErrors(components.analytics.crashReporter) - // ... but RustHttpConfig.setClient() and RustLog.enable() can be called later. + RustLog.enable() + } catch (e: UnsatisfiedLinkError) { + @Suppress("TooGenericExceptionCaught") + try { + reportUnsatisfiedLinkErrorBreadcrumbs() + } catch (e: Throwable) { + // This shouldn't happen, but if it does it's better to ignore the exception from + // the breadcrumb code and rethrow the initial exception. + } + throw e + } + } - RustLog.enable() + private fun reportUnsatisfiedLinkErrorBreadcrumbs() { + val breadcrumbStrings = mutableListOf() + val apkPath = applicationContext.getApplicationInfo().sourceDir + breadcrumbStrings.add("APK: $apkPath") + val apkDir = File(apkPath).getParentFile() + val installSourcePackage = if (SDK_INT >= Build.VERSION_CODES.R) { + packageManager.getInstallSourceInfo(packageName).installingPackageName + } else { + @Suppress("DEPRECATION") + packageManager.getInstallerPackageName(packageName) + } + breadcrumbStrings.add("Installer package name: $installSourcePackage") + + val installDirFileSet = if (apkDir != null) { + apkDir.walk() + .filter { it != apkDir && !it.isDirectory() } + .map { it.relativeTo(apkDir).toString() } + .filter { it.startsWith("lib/") } + .toHashSet() + } else { + HashSet() + } + val apkFileSet = ZipInputStream(FileInputStream(apkPath)).use { + generateSequence { it.nextEntry } + .map { it.name } + .filter { it.startsWith("lib/") } + .toHashSet() + } + fun formatFileSet(filenames: Set) = if (filenames.size > 0) { + filenames.joinToString(", ") + } else { + "" + } + val installDirOnly = formatFileSet(installDirFileSet - apkFileSet) + val apkFileOnly = formatFileSet(apkFileSet - installDirFileSet) + val both = formatFileSet(installDirFileSet union apkFileSet) + + breadcrumbStrings.add("Files only inside lib/ dir: $installDirOnly") + breadcrumbStrings.add("Files only inside APK lib/ dir: $apkFileOnly") + breadcrumbStrings.add("Files inside both lib/ dirs: $both") + + for (breadcrumbString in breadcrumbStrings) { + components.analytics.crashReporter.recordCrashBreadcrumb( + Breadcrumb( + category = "Startup", + message = breadcrumbString, + level = Breadcrumb.Level.INFO, + ), + ) + } } @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage