Skip to content

Commit bca776f

Browse files
authored
react-native: android crash handler upgrade (#301)
* Upgrade crash handler to support the latest AGP versions * react-native: removed log * react-native: Fixed invalid reference to types not available in the project * react-native: use different method to generate native database path * Java Crash handler description
1 parent 10d5be3 commit bca776f

File tree

16 files changed

+238
-21
lines changed

16 files changed

+238
-21
lines changed

packages/react-native/android/src/main/java/backtrace/library/BacktraceReactNative.java

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
import java.io.File;
1717
import java.util.HashMap;
1818

19+
import backtraceio.library.nativeCalls.*;
20+
import backtraceio.library.models.nativeHandler.CrashHandlerConfiguration;
1921
import backtraceio.library.base.BacktraceBase;
20-
import backtraceio.library.BacktraceDatabase;
21-
import backtraceio.library.UnwindingMode;
2222

2323

2424
@ReactModule(name = BacktraceReactNative.NAME)
@@ -49,29 +49,48 @@ public String getName() {
4949
@ReactMethod(isBlockingSynchronousMethod = true)
5050
public Boolean initialize(String minidumpSubmissionUrl, String databasePath, ReadableMap readableAttributes, ReadableArray attachmentPaths) {
5151
Log.d(this.NAME, "Initializing native crash reporter");
52-
53-
String handlerPath = context.getApplicationInfo().nativeLibraryDir + _crashpadHandlerName;
54-
55-
if (!(new File(handlerPath).exists())) {
56-
Log.d(this.NAME, "Crashpad handler doesn't exist");
52+
CrashHandlerConfiguration crashHandlerConfiguration = new backtraceio.library.models.nativeHandler.CrashHandlerConfiguration();
53+
if (!crashHandlerConfiguration.isSupportedAbi()) {
54+
Log.d(this.NAME, "Unsupported ABI detected.");
5755
return false;
5856
}
57+
String handlerPath = context.getApplicationInfo().nativeLibraryDir + _crashpadHandlerName;
58+
5959
HashMap<String, Object> attributes = readableAttributes.toHashMap();
6060

6161
String[] keys = attributes.keySet().toArray(new String[0]);
6262
String[] values = attributes.values().toArray(new String[0]);
63-
64-
Boolean result = BacktraceDatabase.initialize(
65-
minidumpSubmissionUrl,
66-
databasePath,
67-
handlerPath,
68-
keys,
69-
values,
70-
attachmentPaths.toArrayList().toArray(new String[0]),
71-
false,
72-
null
73-
);
74-
63+
BacktraceCrashHandlerWrapper nativeCommunication = new BacktraceCrashHandlerWrapper();
64+
65+
// Depending on the AGP version, the crash handler executable might be extracted from APK or not.
66+
// Due to that, we need to have an option, to capture and send exceptions without the crash handler executable.
67+
// We can achieve the same via Java Crash Handler - the Java class that will be executed via app_process.
68+
69+
// The reason why we don't want to enable java crash handler by default is because of the proguard
70+
// support and testing potential limitations of the new java crash handler.
71+
Boolean result =
72+
new File(handlerPath).exists()
73+
? nativeCommunication.initializeCrashHandler(
74+
minidumpSubmissionUrl,
75+
databasePath,
76+
handlerPath,
77+
keys,
78+
values,
79+
attachmentPaths.toArrayList().toArray(new String[0]),
80+
false,
81+
null
82+
)
83+
: nativeCommunication.initializeJavaCrashHandler(
84+
minidumpSubmissionUrl,
85+
databasePath,
86+
crashHandlerConfiguration.getClassPath(),
87+
keys,
88+
values,
89+
attachmentPaths.toArrayList().toArray(new String[0]),
90+
crashHandlerConfiguration
91+
.getCrashHandlerEnvironmentVariables(this.context.getApplicationInfo())
92+
.toArray(new String[0])
93+
);
7594
return result;
7695
}
7796

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package backtraceio.library.common;
2+
3+
import android.os.Build;
4+
5+
public class AbiHelper {
6+
public static String getCurrentAbi() {
7+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
8+
// on newer Android versions, we'll return only the most important Abi version
9+
return Build.SUPPORTED_ABIS[0];
10+
}
11+
// on pre-Lollip versions, we got only one Abi
12+
return Build.CPU_ABI;
13+
}
14+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package backtraceio.library.models.nativeHandler;
2+
3+
import android.content.pm.ApplicationInfo;
4+
import android.text.TextUtils;
5+
6+
import java.io.File;
7+
import java.util.ArrayList;
8+
import java.util.Arrays;
9+
import java.util.HashSet;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Set;
13+
14+
import backtraceio.library.common.AbiHelper;
15+
import backtraceio.library.services.BacktraceCrashHandlerRunner;
16+
17+
public class CrashHandlerConfiguration {
18+
19+
public static final String BACKTRACE_CRASH_HANDLER = "BACKTRACE_CRASH_HANDLER";
20+
public static final Set<String> UNSUPPORTED_ABIS = new HashSet<String>(Arrays.asList(new String[]{"x86"}));
21+
private static final String CRASHPAD_DIRECTORY_PATH = "/crashpad";
22+
23+
private static final String BACKTRACE_NATIVE_LIBRARY_NAME = "libbacktrace-native.so";
24+
25+
26+
public Boolean isSupportedAbi() {
27+
return isSupportedAbi(AbiHelper.getCurrentAbi());
28+
}
29+
30+
public Boolean isSupportedAbi(String abi) {
31+
return !this.UNSUPPORTED_ABIS.contains(abi);
32+
}
33+
34+
public String getClassPath() {
35+
return BacktraceCrashHandlerRunner.class.getCanonicalName();
36+
}
37+
38+
public List<String> getCrashHandlerEnvironmentVariables(ApplicationInfo applicationInfo) {
39+
return getCrashHandlerEnvironmentVariables(applicationInfo.sourceDir, applicationInfo.nativeLibraryDir, AbiHelper.getCurrentAbi());
40+
}
41+
42+
public List<String> getCrashHandlerEnvironmentVariables(String apkPath, String nativeLibraryDirPath, String arch) {
43+
final List<String> environmentVariables = new ArrayList<>();
44+
45+
// convert available in the system environment variables
46+
for (Map.Entry<String, String> variable :
47+
System.getenv().entrySet()) {
48+
environmentVariables.add(String.format("%s=%s", variable.getKey(), variable.getValue()));
49+
}
50+
// extend system-specific environment variables, with variables needed to properly run app_process via crashpad
51+
File nativeLibraryDirectory = new File(nativeLibraryDirPath);
52+
53+
String backtraceNativeLibraryPath = getBacktraceNativeLibraryPath(nativeLibraryDirPath, apkPath, arch);
54+
File allNativeLibrariesDirectory = nativeLibraryDirectory.getParentFile();
55+
String allPossibleLibrarySearchPaths = TextUtils.join(File.pathSeparator, new String[]{
56+
nativeLibraryDirPath,
57+
allNativeLibrariesDirectory.getPath(),
58+
System.getProperty("java.library.path"),
59+
"/data/local"});
60+
61+
environmentVariables.add(String.format("CLASSPATH=%s", apkPath));
62+
environmentVariables.add(String.format("%s=%s", BACKTRACE_CRASH_HANDLER, backtraceNativeLibraryPath));
63+
environmentVariables.add(String.format("LD_LIBRARY_PATH=%s", allPossibleLibrarySearchPaths));
64+
environmentVariables.add("ANDROID_DATA=/data");
65+
66+
return environmentVariables;
67+
}
68+
69+
public String useCrashpadDirectory(String databaseDirectory) {
70+
String databasePath = databaseDirectory + CRASHPAD_DIRECTORY_PATH;
71+
File crashHandlerDir = new File(databasePath);
72+
// Create the crashpad directory if it doesn't exist
73+
if (!crashHandlerDir.exists()) {
74+
crashHandlerDir.mkdir();
75+
}
76+
return databasePath;
77+
}
78+
79+
private String getBacktraceNativeLibraryPath(String nativeLibraryDirPath, String apkPath, String arch) {
80+
String backtraceNativeLibraryPath = String.format("%s/%s", nativeLibraryDirPath, BACKTRACE_NATIVE_LIBRARY_NAME);
81+
File backtraceNativeLibrary = new File(backtraceNativeLibraryPath);
82+
83+
// If ndk libraries are already extracted, we shouldn't use libraries from the apk.
84+
// Otherwise. We need to find a path in the apk to use compressed libraries from there.
85+
return backtraceNativeLibrary.exists()
86+
? backtraceNativeLibraryPath
87+
: String.format("%s!/lib/%s/%s", apkPath, arch, BACKTRACE_NATIVE_LIBRARY_NAME);
88+
}
89+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package backtraceio.library.nativeCalls;
2+
3+
public class BacktraceCrashHandler {
4+
public static native boolean handleCrash(String[] args);
5+
6+
public static native boolean initializeJavaCrashHandler(String url, String databasePath, String classPath, String[] attributeKeys, String[] attributeValues,
7+
String[] attachmentPaths, String[] environmentVariables);
8+
9+
public static native boolean initializeCrashHandler(String url, String databasePath, String handlerPath,
10+
String[] attributeKeys, String[] attributeValues,
11+
String[] attachmentPaths, boolean enableClientSideUnwinding,
12+
Integer unwindingMode);
13+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package backtraceio.library.nativeCalls;
2+
3+
public class BacktraceCrashHandlerWrapper{
4+
public boolean handleCrash(String[] args) {
5+
return BacktraceCrashHandler.handleCrash(args);
6+
}
7+
8+
public boolean initializeJavaCrashHandler(String url, String databasePath, String classPath, String[] attributeKeys, String[] attributeValues,
9+
String[] attachmentPaths, String[] environmentVariables) {
10+
return BacktraceCrashHandler.initializeJavaCrashHandler(url, databasePath, classPath, attributeKeys, attributeValues, attachmentPaths, environmentVariables);
11+
}
12+
13+
public boolean initializeCrashHandler(String url, String databasePath, String handlerPath,
14+
String[] attributeKeys, String[] attributeValues,
15+
String[] attachmentPaths, boolean enableClientSideUnwinding,
16+
Integer unwindingMode) {
17+
return BacktraceCrashHandler.initializeCrashHandler(url, databasePath, handlerPath, attributeKeys, attributeValues, attachmentPaths, enableClientSideUnwinding, unwindingMode);
18+
}
19+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package backtraceio.library.nativeCalls;
2+
3+
public class SystemLoader {
4+
public void loadLibrary(String path) {
5+
System.load(path);
6+
}
7+
8+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package backtraceio.library.services;
2+
3+
import android.util.Log;
4+
5+
import java.util.Map;
6+
7+
import backtraceio.library.models.nativeHandler.CrashHandlerConfiguration;
8+
import backtraceio.library.nativeCalls.BacktraceCrashHandlerWrapper;
9+
import backtraceio.library.nativeCalls.SystemLoader;
10+
11+
public class BacktraceCrashHandlerRunner {
12+
private static final String LOG_TAG = BacktraceCrashHandlerRunner.class.getSimpleName();
13+
private final BacktraceCrashHandlerWrapper crashHandler;
14+
private final SystemLoader loader;
15+
16+
public static void main(String[] args) {
17+
BacktraceCrashHandlerRunner runner = new BacktraceCrashHandlerRunner();
18+
runner.run(args, System.getenv());
19+
}
20+
21+
public BacktraceCrashHandlerRunner() {
22+
this(new BacktraceCrashHandlerWrapper(), new SystemLoader());
23+
}
24+
25+
public BacktraceCrashHandlerRunner(BacktraceCrashHandlerWrapper crashHandler, SystemLoader loader) {
26+
this.crashHandler = crashHandler;
27+
this.loader = loader;
28+
}
29+
30+
public boolean run(String[] args, Map<String, String> environmentVariables) {
31+
if (environmentVariables == null) {
32+
Log.e(LOG_TAG, "Cannot capture crash dump. Environment variables are undefined");
33+
return false;
34+
}
35+
36+
String crashHandlerLibrary = environmentVariables.get(CrashHandlerConfiguration.BACKTRACE_CRASH_HANDLER);
37+
if (crashHandlerLibrary == null) {
38+
Log.e(LOG_TAG, String.format("Cannot capture crash dump. Cannot find %s environment variable", CrashHandlerConfiguration.BACKTRACE_CRASH_HANDLER));
39+
return false;
40+
}
41+
42+
43+
loader.loadLibrary(crashHandlerLibrary);
44+
45+
boolean result = crashHandler.handleCrash(args);
46+
if (!result) {
47+
Log.e(LOG_TAG, String.format("Cannot capture crash dump. Invocation parameters: %s", String.join(" ", args)));
48+
return false;
49+
}
50+
51+
Log.i(LOG_TAG, "Successfully ran crash handler code.");
52+
return true;
53+
}
54+
}
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)