From f53f346413ec93835b364254a01a8b02763b1d64 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Mon, 17 Jun 2024 10:53:37 +0200 Subject: [PATCH] Use Java crash handler on not extracted libraries (#218) * Update native android libraries to version 3.8.3 * Update NativeClient to use JavaCrashHandler on Android when NDK is not extracted * Warning comment change --- Android/BacktraceANRWatchdog.java | 4 +- ...idBackgroundUnhandledExceptionHandler.java | 2 +- Android/BacktraceCrashHandler.java | 41 +++++++ Android/BacktraceCrashHandler.java.meta | 32 +++++ Android/BacktraceCrashHelper.java | 2 +- Android/BacktraceThreadWatcher.java | 2 +- .../Attributes/MachineAttributeProvider.cs | 22 +++- Runtime/Native/Android/NativeClient.cs | 114 ++++++++++++++---- 8 files changed, 185 insertions(+), 34 deletions(-) create mode 100644 Android/BacktraceCrashHandler.java create mode 100644 Android/BacktraceCrashHandler.java.meta diff --git a/Android/BacktraceANRWatchdog.java b/Android/BacktraceANRWatchdog.java index 1fe43532..2ee92416 100644 --- a/Android/BacktraceANRWatchdog.java +++ b/Android/BacktraceANRWatchdog.java @@ -1,4 +1,4 @@ -package backtrace.io.backtrace_unity_android_plugin; +package backtraceio.unity; import android.os.Debug; import android.os.Handler; import android.os.Looper; @@ -72,7 +72,7 @@ public void run() { Log.d(LOG_TAG, "Starting ANR watchdog. Anr timeout: " + this.timeout); while (!shouldStop && !isInterrupted()) { - final backtrace.io.backtrace_unity_android_plugin.BacktraceThreadWatcher threadWatcher = new backtrace.io.backtrace_unity_android_plugin.BacktraceThreadWatcher(0, 0); + final BacktraceThreadWatcher threadWatcher = new BacktraceThreadWatcher(0, 0); mainThreadHandler.post(new Runnable() { @Override public void run() { diff --git a/Android/BacktraceAndroidBackgroundUnhandledExceptionHandler.java b/Android/BacktraceAndroidBackgroundUnhandledExceptionHandler.java index 6a4b8f4c..7fe7208d 100644 --- a/Android/BacktraceAndroidBackgroundUnhandledExceptionHandler.java +++ b/Android/BacktraceAndroidBackgroundUnhandledExceptionHandler.java @@ -1,4 +1,4 @@ -package backtrace.io.backtrace_unity_android_plugin; +package backtraceio.unity; import android.os.Build; import android.os.Looper; diff --git a/Android/BacktraceCrashHandler.java b/Android/BacktraceCrashHandler.java new file mode 100644 index 00000000..35a1fdab --- /dev/null +++ b/Android/BacktraceCrashHandler.java @@ -0,0 +1,41 @@ +package backtraceio.library.nativeCalls; + +import android.util.Log; + +import java.util.Map; + +public class BacktraceCrashHandler { + private static final String LOG_TAG = BacktraceCrashHandler.class.getSimpleName(); + + public static final String BACKTRACE_CRASH_HANDLER = "BACKTRACE_UNITY_CRASH_HANDLER"; + + + private static native boolean handleCrash(String[] args); + + public static void main(String[] args) { + run(args, System.getenv()); + } + + public static boolean run(String[] args, Map environmentVariables) { + if (environmentVariables == null) { + Log.e(LOG_TAG, "Cannot capture crash dump. Environment variables are undefined"); + return false; + } + + String crashHandlerLibrary = environmentVariables.get(BACKTRACE_CRASH_HANDLER); + if (crashHandlerLibrary == null) { + Log.e(LOG_TAG, String.format("Cannot capture crash dump. Cannot find %s environment variable", BACKTRACE_CRASH_HANDLER)); + return false; + } + System.load(crashHandlerLibrary); + + boolean result = handleCrash(args); + if (!result) { + Log.e(LOG_TAG, String.format("Cannot capture crash dump. Invocation parameters: %s", String.join(" ", args))); + return false; + } + + Log.i(LOG_TAG, "Successfully ran crash handler code."); + return true; + } +} diff --git a/Android/BacktraceCrashHandler.java.meta b/Android/BacktraceCrashHandler.java.meta new file mode 100644 index 00000000..77ef81af --- /dev/null +++ b/Android/BacktraceCrashHandler.java.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 5b5537a6606dd4c8d8a0f59f7a6e39e1 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Android/BacktraceCrashHelper.java b/Android/BacktraceCrashHelper.java index b48e42a8..e1aac325 100644 --- a/Android/BacktraceCrashHelper.java +++ b/Android/BacktraceCrashHelper.java @@ -1,4 +1,4 @@ -package backtrace.io.backtrace_unity_android_plugin; +package backtraceio.unity; import android.os.Handler; import android.os.Looper; diff --git a/Android/BacktraceThreadWatcher.java b/Android/BacktraceThreadWatcher.java index 6a5af2d1..aec14904 100644 --- a/Android/BacktraceThreadWatcher.java +++ b/Android/BacktraceThreadWatcher.java @@ -1,4 +1,4 @@ -package backtrace.io.backtrace_unity_android_plugin; +package backtraceio.unity; /** * This class is a representation of the state of the thread, diff --git a/Runtime/Model/Attributes/MachineAttributeProvider.cs b/Runtime/Model/Attributes/MachineAttributeProvider.cs index 34b51de4..89b13ddd 100644 --- a/Runtime/Model/Attributes/MachineAttributeProvider.cs +++ b/Runtime/Model/Attributes/MachineAttributeProvider.cs @@ -58,12 +58,23 @@ private void IncludeOsInformation(IDictionary attributes) attributes["device.manufacturer"] = build.GetStatic("MANUFACTURER").ToString(); attributes["device.brand"] = build.GetStatic("BRAND").ToString(); attributes["device.product"] = build.GetStatic("PRODUCT").ToString(); - } + using (var version = new AndroidJavaClass("android.os.Build$VERSION")) + { + attributes["uname.version"] = version.GetStatic("RELEASE").ToString(); - using (var version = new AndroidJavaClass("android.os.Build$VERSION")) - { - attributes["device.sdk"] = version.GetStatic("SDK_INT").ToString(); - attributes["uname.version"] = version.GetStatic("RELEASE").ToString(); + var deviceSdkVersion = version.GetStatic("SDK_INT"); + attributes["device.sdk"] = deviceSdkVersion.ToString(); + if(deviceSdkVersion >= 21) + { + string[] supportedAbis = build.GetStatic("SUPPORTED_ABIS"); + + if (supportedAbis != null && supportedAbis.Length > 0) + { + attributes["device.abi"] = supportedAbis[0]; + } + + } + } } attributes["uname.fullname"] = Environment.OSVersion.Version.ToString(); #else @@ -80,6 +91,7 @@ private void IncludeOsInformation(IDictionary attributes) attributes["uname.fullname"] = Environment.OSVersion.Version.ToString(); #endif } + private void IncludeGraphicCardInformation(IDictionary attributes) { // if a graphic card is not available diff --git a/Runtime/Native/Android/NativeClient.cs b/Runtime/Native/Android/NativeClient.cs index 284c9fd9..36adbce2 100644 --- a/Runtime/Native/Android/NativeClient.cs +++ b/Runtime/Native/Android/NativeClient.cs @@ -5,6 +5,7 @@ using Backtrace.Unity.Model.Breadcrumbs; using Backtrace.Unity.Runtime.Native.Base; using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -24,6 +25,9 @@ internal sealed class NativeClient : NativeClientBase, INativeClient [DllImport("backtrace-native")] private static extern bool Initialize(IntPtr submissionUrl, IntPtr databasePath, IntPtr handlerPath, IntPtr keys, IntPtr values, IntPtr attachments, bool enableClientSideUnwinding, int unwindingMode); + [DllImport("backtrace-native")] + private static extern bool InitializeJavaCrashHandler(IntPtr submissionUrl, IntPtr databasePath, IntPtr classPath, IntPtr keys, IntPtr values, IntPtr attachments, IntPtr environmentVariables); + [DllImport("backtrace-native")] private static extern bool AddAttribute(IntPtr key, IntPtr value); @@ -75,8 +79,12 @@ private void SetDefaultAttributeMaps() _attributeMapping.Add("VmallocUsed", "system.memory.vmalloc.used"); _attributeMapping.Add("VmallocChunk", "system.memory.vmalloc.chunk"); } - // Android native interface paths - private const string _namespace = "backtrace.io.backtrace_unity_android_plugin"; + + // Android base native interface path + private const string _baseNamespace = "backtraceio"; + + // Unity-Android native interface path + private const string _namespace = "backtraceio.unity"; /// /// unwinding mode @@ -93,6 +101,16 @@ private void SetDefaultAttributeMaps() /// private readonly string _unhandledExceptionPath = string.Format("{0}.{1}", _namespace, "BacktraceAndroidBackgroundUnhandledExceptionHandler"); + /// + /// Path to class responsible for generating and sending native dump on crash + /// + private readonly string _crashHandlerPath = string.Format("{0}.library.nativeCalls.BacktraceCrashHandler", _baseNamespace); + + /// + /// Backtrace-Android native library name + /// + private readonly string _nativeLibraryName = "libbacktrace-native.so"; + /// /// Determine if android integration should be enabled /// @@ -255,37 +273,20 @@ private void HandleNativeCrashes(IDictionary backtraceAttributes return; } + var minidumpUrl = new BacktraceCredentials(_configuration.GetValidServerUrl()).GetMinidumpSubmissionUrl().ToString(); + var libDirectory = GetNativeDirectoryPath(); if (string.IsNullOrEmpty(libDirectory) || !Directory.Exists(libDirectory)) { libDirectory = GuessNativeDirectoryPath(); } - if (!Directory.Exists(libDirectory)) - { - return; - } const string crashpadHandlerName = "libcrashpad_handler.so"; var crashpadHandlerPath = Path.Combine(libDirectory, crashpadHandlerName); - if (string.IsNullOrEmpty(crashpadHandlerPath) || !File.Exists(crashpadHandlerPath)) - { - Debug.LogWarning("Backtrace native integration status: Cannot find crashpad library"); - return; - } - - var minidumpUrl = new BacktraceCredentials(_configuration.GetValidServerUrl()).GetMinidumpSubmissionUrl().ToString(); + CaptureNativeCrashes = CanInitializeExecutableCrashHandler(libDirectory, crashpadHandlerPath) + ? InitializeExecutableCrashHandler(minidumpUrl, databasePath, crashpadHandlerPath, attachments) + : InitializeJavaCrashHandler(minidumpUrl, databasePath, backtraceAttributes["device.abi"], libDirectory, attachments); - // reassign to captureNativeCrashes - // to avoid doing anything on crashpad binary, when crashpad isn't available - CaptureNativeCrashes = Initialize( - AndroidJNI.NewStringUTF(minidumpUrl), - AndroidJNI.NewStringUTF(databasePath), - AndroidJNI.NewStringUTF(crashpadHandlerPath), - AndroidJNIHelper.ConvertToJNIArray(new string[0]), - AndroidJNIHelper.ConvertToJNIArray(new string[0]), - AndroidJNIHelper.ConvertToJNIArray(attachments.ToArray()), - _enableClientSideUnwinding, - (int)UnwindingMode); if (!CaptureNativeCrashes) { Debug.LogWarning("Backtrace native integration status: Cannot initialize Crashpad client"); @@ -309,6 +310,71 @@ private void HandleNativeCrashes(IDictionary backtraceAttributes AndroidJNI.NewStringUTF(CrashType)); } + private bool CanInitializeExecutableCrashHandler(String nativeLibraryDirectory, String handlerPath) { + return Directory.Exists(nativeLibraryDirectory) && File.Exists(handlerPath); + } + + private bool InitializeExecutableCrashHandler(String minidumpUrl, String databasePath, String crashpadHandlerPath, IEnumerable attachments) { + return Initialize( + AndroidJNI.NewStringUTF(minidumpUrl), + AndroidJNI.NewStringUTF(databasePath), + AndroidJNI.NewStringUTF(crashpadHandlerPath), + AndroidJNIHelper.ConvertToJNIArray(new string[0]), + AndroidJNIHelper.ConvertToJNIArray(new string[0]), + AndroidJNIHelper.ConvertToJNIArray(attachments.ToArray()), + _enableClientSideUnwinding, + (int)UnwindingMode); + } + + private bool InitializeJavaCrashHandler(String minidumpUrl, String databasePath, String deviceAbi, String nativeDirectory, IEnumerable attachments) { + if (String.IsNullOrEmpty(deviceAbi)) { + Debug.LogWarning("Cannot determine device ABI"); + return false; + } + + var envVariableDictionary = Environment.GetEnvironmentVariables(); + if (envVariableDictionary == null) { + Debug.LogWarning("Environment variables are not defined."); + return false; + } + + // verify if the library is already extracted + var backtraceNativeLibraryPath = Path.Combine(nativeDirectory, _nativeLibraryName); + if (!File.Exists(backtraceNativeLibraryPath)) { + backtraceNativeLibraryPath = string.Format("{0}!/lib/{1}/{2}", Application.dataPath, deviceAbi, _nativeLibraryName); + } + + // prepare native crash handler environment variables + List environmentVariables = new List () { + string.Format("CLASSPATH={0}", Application.dataPath), + string.Format("BACKTRACE_UNITY_CRASH_HANDLER={0}", backtraceNativeLibraryPath), + string.Format("LD_LIBRARY_PATH={0}", string.Join(":", nativeDirectory, Directory.GetParent(nativeDirectory), GetLibrarySystemPath(), "/data/local")), + "ANDROID_DATA=/data" + }; + + foreach (DictionaryEntry kvp in envVariableDictionary) { + environmentVariables.Add(string.Format("{0}={1}", kvp.Key, kvp.Value == null ? "NULL" : kvp.Value)); + } + + + return InitializeJavaCrashHandler( + AndroidJNI.NewStringUTF(minidumpUrl), + AndroidJNI.NewStringUTF(databasePath), + AndroidJNI.NewStringUTF(_crashHandlerPath), + AndroidJNIHelper.ConvertToJNIArray(new string[0]), + AndroidJNIHelper.ConvertToJNIArray(new string[0]), + AndroidJNIHelper.ConvertToJNIArray(attachments.ToArray()), + AndroidJNIHelper.ConvertToJNIArray(environmentVariables.ToArray()) + ); + } + + private string GetLibrarySystemPath() { + using (var systemClass = new AndroidJavaClass("java.lang.System")) + { + return systemClass.CallStatic("getProperty", "java.library.path"); + } + } + /// /// Retrieve Backtrace Attributes from the Android native code. ///