Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react-native: android crash handler upgrade #301

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<String, Object> 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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<String> UNSUPPORTED_ABIS = new HashSet<String>(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<String> getCrashHandlerEnvironmentVariables(ApplicationInfo applicationInfo) {
return getCrashHandlerEnvironmentVariables(applicationInfo.sourceDir, applicationInfo.nativeLibraryDir, AbiHelper.getCurrentAbi());
}

public List<String> getCrashHandlerEnvironmentVariables(String apkPath, String nativeLibraryDirPath, String arch) {
final List<String> environmentVariables = new ArrayList<>();

// convert available in the system environment variables
for (Map.Entry<String, String> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package backtraceio.library.nativeCalls;

public class SystemLoader {
public void loadLibrary(String path) {
System.load(path);
}

}
Original file line number Diff line number Diff line change
@@ -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<String, String> 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;
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
5 changes: 3 additions & 2 deletions packages/react-native/src/crashReporter/CrashReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down