diff --git a/packages/react-native/android/src/main/java/backtrace/library/BacktraceReactNative.java b/packages/react-native/android/src/main/java/backtrace/library/BacktraceReactNative.java index 63dcf470..2718bcf0 100644 --- a/packages/react-native/android/src/main/java/backtrace/library/BacktraceReactNative.java +++ b/packages/react-native/android/src/main/java/backtrace/library/BacktraceReactNative.java @@ -16,9 +16,9 @@ import java.io.File; import java.util.HashMap; +import backtraceio.library.nativeCalls.*; +import backtraceio.library.models.nativeHandler.CrashHandlerConfiguration; import backtraceio.library.base.BacktraceBase; -import backtraceio.library.BacktraceDatabase; -import backtraceio.library.UnwindingMode; @ReactModule(name = BacktraceReactNative.NAME) @@ -49,29 +49,48 @@ public String getName() { @ReactMethod(isBlockingSynchronousMethod = true) public Boolean initialize(String minidumpSubmissionUrl, String databasePath, ReadableMap readableAttributes, ReadableArray attachmentPaths) { Log.d(this.NAME, "Initializing native crash reporter"); - - String handlerPath = context.getApplicationInfo().nativeLibraryDir + _crashpadHandlerName; - - if (!(new File(handlerPath).exists())) { - Log.d(this.NAME, "Crashpad handler doesn't exist"); + CrashHandlerConfiguration crashHandlerConfiguration = new backtraceio.library.models.nativeHandler.CrashHandlerConfiguration(); + if (!crashHandlerConfiguration.isSupportedAbi()) { + Log.d(this.NAME, "Unsupported ABI detected."); return false; } + String handlerPath = context.getApplicationInfo().nativeLibraryDir + _crashpadHandlerName; + HashMap attributes = readableAttributes.toHashMap(); String[] keys = attributes.keySet().toArray(new String[0]); String[] values = attributes.values().toArray(new String[0]); - - Boolean result = BacktraceDatabase.initialize( - minidumpSubmissionUrl, - databasePath, - handlerPath, - keys, - values, - attachmentPaths.toArrayList().toArray(new String[0]), - false, - null - ); - + BacktraceCrashHandlerWrapper nativeCommunication = new BacktraceCrashHandlerWrapper(); + + // Depending on the AGP version, the crash handler executable might be extracted from APK or not. + // Due to that, we need to have an option, to capture and send exceptions without the crash handler executable. + // We can achieve the same via Java Crash Handler - the Java class that will be executed via app_process. + + // The reason why we don't want to enable java crash handler by default is because of the proguard + // support and testing potential limitations of the new java crash handler. + Boolean result = + new File(handlerPath).exists() + ? nativeCommunication.initializeCrashHandler( + minidumpSubmissionUrl, + databasePath, + handlerPath, + keys, + values, + attachmentPaths.toArrayList().toArray(new String[0]), + false, + null + ) + : nativeCommunication.initializeJavaCrashHandler( + minidumpSubmissionUrl, + databasePath, + crashHandlerConfiguration.getClassPath(), + keys, + values, + attachmentPaths.toArrayList().toArray(new String[0]), + crashHandlerConfiguration + .getCrashHandlerEnvironmentVariables(this.context.getApplicationInfo()) + .toArray(new String[0]) + ); return result; } diff --git a/packages/react-native/android/src/main/java/backtrace/library/common/AbiHelper.java b/packages/react-native/android/src/main/java/backtrace/library/common/AbiHelper.java new file mode 100644 index 00000000..3b452236 --- /dev/null +++ b/packages/react-native/android/src/main/java/backtrace/library/common/AbiHelper.java @@ -0,0 +1,14 @@ +package backtraceio.library.common; + +import android.os.Build; + +public class AbiHelper { + public static String getCurrentAbi() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + // on newer Android versions, we'll return only the most important Abi version + return Build.SUPPORTED_ABIS[0]; + } + // on pre-Lollip versions, we got only one Abi + return Build.CPU_ABI; + } +} diff --git a/packages/react-native/android/src/main/java/backtrace/library/models/nativeHandler/CrashHandlerConfiguration.java b/packages/react-native/android/src/main/java/backtrace/library/models/nativeHandler/CrashHandlerConfiguration.java new file mode 100644 index 00000000..ad9bcff6 --- /dev/null +++ b/packages/react-native/android/src/main/java/backtrace/library/models/nativeHandler/CrashHandlerConfiguration.java @@ -0,0 +1,89 @@ +package backtraceio.library.models.nativeHandler; + +import android.content.pm.ApplicationInfo; +import android.text.TextUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import backtraceio.library.common.AbiHelper; +import backtraceio.library.services.BacktraceCrashHandlerRunner; + +public class CrashHandlerConfiguration { + + public static final String BACKTRACE_CRASH_HANDLER = "BACKTRACE_CRASH_HANDLER"; + public static final Set UNSUPPORTED_ABIS = new HashSet(Arrays.asList(new String[]{"x86"})); + private static final String CRASHPAD_DIRECTORY_PATH = "/crashpad"; + + private static final String BACKTRACE_NATIVE_LIBRARY_NAME = "libbacktrace-native.so"; + + + public Boolean isSupportedAbi() { + return isSupportedAbi(AbiHelper.getCurrentAbi()); + } + + public Boolean isSupportedAbi(String abi) { + return !this.UNSUPPORTED_ABIS.contains(abi); + } + + public String getClassPath() { + return BacktraceCrashHandlerRunner.class.getCanonicalName(); + } + + public List getCrashHandlerEnvironmentVariables(ApplicationInfo applicationInfo) { + return getCrashHandlerEnvironmentVariables(applicationInfo.sourceDir, applicationInfo.nativeLibraryDir, AbiHelper.getCurrentAbi()); + } + + public List getCrashHandlerEnvironmentVariables(String apkPath, String nativeLibraryDirPath, String arch) { + final List environmentVariables = new ArrayList<>(); + + // convert available in the system environment variables + for (Map.Entry variable : + System.getenv().entrySet()) { + environmentVariables.add(String.format("%s=%s", variable.getKey(), variable.getValue())); + } + // extend system-specific environment variables, with variables needed to properly run app_process via crashpad + File nativeLibraryDirectory = new File(nativeLibraryDirPath); + + String backtraceNativeLibraryPath = getBacktraceNativeLibraryPath(nativeLibraryDirPath, apkPath, arch); + File allNativeLibrariesDirectory = nativeLibraryDirectory.getParentFile(); + String allPossibleLibrarySearchPaths = TextUtils.join(File.pathSeparator, new String[]{ + nativeLibraryDirPath, + allNativeLibrariesDirectory.getPath(), + System.getProperty("java.library.path"), + "/data/local"}); + + environmentVariables.add(String.format("CLASSPATH=%s", apkPath)); + environmentVariables.add(String.format("%s=%s", BACKTRACE_CRASH_HANDLER, backtraceNativeLibraryPath)); + environmentVariables.add(String.format("LD_LIBRARY_PATH=%s", allPossibleLibrarySearchPaths)); + environmentVariables.add("ANDROID_DATA=/data"); + + return environmentVariables; + } + + public String useCrashpadDirectory(String databaseDirectory) { + String databasePath = databaseDirectory + CRASHPAD_DIRECTORY_PATH; + File crashHandlerDir = new File(databasePath); + // Create the crashpad directory if it doesn't exist + if (!crashHandlerDir.exists()) { + crashHandlerDir.mkdir(); + } + return databasePath; + } + + private String getBacktraceNativeLibraryPath(String nativeLibraryDirPath, String apkPath, String arch) { + String backtraceNativeLibraryPath = String.format("%s/%s", nativeLibraryDirPath, BACKTRACE_NATIVE_LIBRARY_NAME); + File backtraceNativeLibrary = new File(backtraceNativeLibraryPath); + + // If ndk libraries are already extracted, we shouldn't use libraries from the apk. + // Otherwise. We need to find a path in the apk to use compressed libraries from there. + return backtraceNativeLibrary.exists() + ? backtraceNativeLibraryPath + : String.format("%s!/lib/%s/%s", apkPath, arch, BACKTRACE_NATIVE_LIBRARY_NAME); + } +} diff --git a/packages/react-native/android/src/main/java/backtrace/library/nativeCalls/BacktraceCrashHandler.java b/packages/react-native/android/src/main/java/backtrace/library/nativeCalls/BacktraceCrashHandler.java new file mode 100644 index 00000000..d0cfb994 --- /dev/null +++ b/packages/react-native/android/src/main/java/backtrace/library/nativeCalls/BacktraceCrashHandler.java @@ -0,0 +1,13 @@ +package backtraceio.library.nativeCalls; + +public class BacktraceCrashHandler { + public static native boolean handleCrash(String[] args); + + public static native boolean initializeJavaCrashHandler(String url, String databasePath, String classPath, String[] attributeKeys, String[] attributeValues, + String[] attachmentPaths, String[] environmentVariables); + + public static native boolean initializeCrashHandler(String url, String databasePath, String handlerPath, + String[] attributeKeys, String[] attributeValues, + String[] attachmentPaths, boolean enableClientSideUnwinding, + Integer unwindingMode); +} diff --git a/packages/react-native/android/src/main/java/backtrace/library/nativeCalls/BacktraceCrashHandlerWrapper.java b/packages/react-native/android/src/main/java/backtrace/library/nativeCalls/BacktraceCrashHandlerWrapper.java new file mode 100644 index 00000000..30942c56 --- /dev/null +++ b/packages/react-native/android/src/main/java/backtrace/library/nativeCalls/BacktraceCrashHandlerWrapper.java @@ -0,0 +1,19 @@ +package backtraceio.library.nativeCalls; + +public class BacktraceCrashHandlerWrapper{ + public boolean handleCrash(String[] args) { + return BacktraceCrashHandler.handleCrash(args); + } + + public boolean initializeJavaCrashHandler(String url, String databasePath, String classPath, String[] attributeKeys, String[] attributeValues, + String[] attachmentPaths, String[] environmentVariables) { + return BacktraceCrashHandler.initializeJavaCrashHandler(url, databasePath, classPath, attributeKeys, attributeValues, attachmentPaths, environmentVariables); + } + + public boolean initializeCrashHandler(String url, String databasePath, String handlerPath, + String[] attributeKeys, String[] attributeValues, + String[] attachmentPaths, boolean enableClientSideUnwinding, + Integer unwindingMode) { + return BacktraceCrashHandler.initializeCrashHandler(url, databasePath, handlerPath, attributeKeys, attributeValues, attachmentPaths, enableClientSideUnwinding, unwindingMode); + } +} diff --git a/packages/react-native/android/src/main/java/backtrace/library/nativeCalls/SystemLoader.java b/packages/react-native/android/src/main/java/backtrace/library/nativeCalls/SystemLoader.java new file mode 100644 index 00000000..c6104368 --- /dev/null +++ b/packages/react-native/android/src/main/java/backtrace/library/nativeCalls/SystemLoader.java @@ -0,0 +1,8 @@ +package backtraceio.library.nativeCalls; + +public class SystemLoader { + public void loadLibrary(String path) { + System.load(path); + } + +} diff --git a/packages/react-native/android/src/main/java/backtrace/library/services/BacktraceCrashHandlerRunner.java b/packages/react-native/android/src/main/java/backtrace/library/services/BacktraceCrashHandlerRunner.java new file mode 100644 index 00000000..fabd6b0c --- /dev/null +++ b/packages/react-native/android/src/main/java/backtrace/library/services/BacktraceCrashHandlerRunner.java @@ -0,0 +1,54 @@ +package backtraceio.library.services; + +import android.util.Log; + +import java.util.Map; + +import backtraceio.library.models.nativeHandler.CrashHandlerConfiguration; +import backtraceio.library.nativeCalls.BacktraceCrashHandlerWrapper; +import backtraceio.library.nativeCalls.SystemLoader; + +public class BacktraceCrashHandlerRunner { + private static final String LOG_TAG = BacktraceCrashHandlerRunner.class.getSimpleName(); + private final BacktraceCrashHandlerWrapper crashHandler; + private final SystemLoader loader; + + public static void main(String[] args) { + BacktraceCrashHandlerRunner runner = new BacktraceCrashHandlerRunner(); + runner.run(args, System.getenv()); + } + + public BacktraceCrashHandlerRunner() { + this(new BacktraceCrashHandlerWrapper(), new SystemLoader()); + } + + public BacktraceCrashHandlerRunner(BacktraceCrashHandlerWrapper crashHandler, SystemLoader loader) { + this.crashHandler = crashHandler; + this.loader = loader; + } + + public 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(CrashHandlerConfiguration.BACKTRACE_CRASH_HANDLER); + if (crashHandlerLibrary == null) { + Log.e(LOG_TAG, String.format("Cannot capture crash dump. Cannot find %s environment variable", CrashHandlerConfiguration.BACKTRACE_CRASH_HANDLER)); + return false; + } + + + loader.loadLibrary(crashHandlerLibrary); + + boolean result = crashHandler.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/packages/react-native/android/src/main/jniLibs/arm64-v8a/libbacktrace-native.so b/packages/react-native/android/src/main/jniLibs/arm64-v8a/libbacktrace-native.so index df12e449..e5042452 100644 Binary files a/packages/react-native/android/src/main/jniLibs/arm64-v8a/libbacktrace-native.so and b/packages/react-native/android/src/main/jniLibs/arm64-v8a/libbacktrace-native.so differ diff --git a/packages/react-native/android/src/main/jniLibs/arm64-v8a/libcrashpad_handler.so b/packages/react-native/android/src/main/jniLibs/arm64-v8a/libcrashpad_handler.so index ce674e06..e76c75ba 100644 Binary files a/packages/react-native/android/src/main/jniLibs/arm64-v8a/libcrashpad_handler.so and b/packages/react-native/android/src/main/jniLibs/arm64-v8a/libcrashpad_handler.so differ diff --git a/packages/react-native/android/src/main/jniLibs/arm64-v8a/libnative-lib.so b/packages/react-native/android/src/main/jniLibs/arm64-v8a/libnative-lib.so index 9f37473d..4113a011 100644 Binary files a/packages/react-native/android/src/main/jniLibs/arm64-v8a/libnative-lib.so and b/packages/react-native/android/src/main/jniLibs/arm64-v8a/libnative-lib.so differ diff --git a/packages/react-native/android/src/main/jniLibs/armeabi-v7a/libbacktrace-native.so b/packages/react-native/android/src/main/jniLibs/armeabi-v7a/libbacktrace-native.so index b2446411..ddd4b468 100644 Binary files a/packages/react-native/android/src/main/jniLibs/armeabi-v7a/libbacktrace-native.so and b/packages/react-native/android/src/main/jniLibs/armeabi-v7a/libbacktrace-native.so differ diff --git a/packages/react-native/android/src/main/jniLibs/armeabi-v7a/libcrashpad_handler.so b/packages/react-native/android/src/main/jniLibs/armeabi-v7a/libcrashpad_handler.so index de4e0eb1..2cd08aae 100644 Binary files a/packages/react-native/android/src/main/jniLibs/armeabi-v7a/libcrashpad_handler.so and b/packages/react-native/android/src/main/jniLibs/armeabi-v7a/libcrashpad_handler.so differ diff --git a/packages/react-native/android/src/main/jniLibs/armeabi-v7a/libnative-lib.so b/packages/react-native/android/src/main/jniLibs/armeabi-v7a/libnative-lib.so index 3e41caa7..905571ed 100644 Binary files a/packages/react-native/android/src/main/jniLibs/armeabi-v7a/libnative-lib.so and b/packages/react-native/android/src/main/jniLibs/armeabi-v7a/libnative-lib.so differ diff --git a/packages/react-native/android/src/main/jniLibs/x86/libbacktrace-native.so b/packages/react-native/android/src/main/jniLibs/x86/libbacktrace-native.so index 5dbde716..269a7c39 100644 Binary files a/packages/react-native/android/src/main/jniLibs/x86/libbacktrace-native.so and b/packages/react-native/android/src/main/jniLibs/x86/libbacktrace-native.so differ diff --git a/packages/react-native/android/src/main/jniLibs/x86/libnative-lib.so b/packages/react-native/android/src/main/jniLibs/x86/libnative-lib.so index d5da14b2..adc2df34 100644 Binary files a/packages/react-native/android/src/main/jniLibs/x86/libnative-lib.so and b/packages/react-native/android/src/main/jniLibs/x86/libnative-lib.so differ diff --git a/packages/react-native/src/crashReporter/CrashReporter.ts b/packages/react-native/src/crashReporter/CrashReporter.ts index 5e9e271f..5862120d 100644 --- a/packages/react-native/src/crashReporter/CrashReporter.ts +++ b/packages/react-native/src/crashReporter/CrashReporter.ts @@ -33,11 +33,12 @@ export class CrashReporter { return false; } - this._fileSystem.createDirSync(`${databasePath}/native`); + const nativeDatabasePath = `${databasePath}/native`; + this._fileSystem.createDirSync(nativeDatabasePath); CrashReporter.BacktraceReactNative.initialize( submissionUrl, - databasePath, + nativeDatabasePath, { ...this.convertAttributes(attributes), 'error.type': 'Crash',