Bug 1483329 - Add crash handling API to GeckoRuntime r=jchen,esawin

This commit is contained in:
James Willcox 2018-08-22 12:45:56 -05:00
Родитель 6d9557476c
Коммит 91381ffcaa
7 изменённых файлов: 501 добавлений и 44 удалений

Просмотреть файл

@ -145,10 +145,6 @@ android {
exclude 'org/mozilla/gecko/media/Utils.java'
}
if (!mozconfig.substs.MOZ_CRASHREPORTER) {
exclude 'org/mozilla/gecko/CrashReporterService.java'
}
if (mozconfig.substs.MOZ_WEBRTC) {
srcDir "${topsrcdir}/media/webrtc/trunk/webrtc/base/java/src"
srcDir "${topsrcdir}/media/webrtc/trunk/webrtc/modules/audio_device/android/java/src"

Просмотреть файл

@ -40,6 +40,7 @@ import org.mozilla.geckoview.BuildConfig;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@ -164,6 +165,15 @@ public class GeckoAppShell
return sCrashHandler;
}
private static Class<? extends Service> sCrashHandlerService;
public static synchronized void setCrashHandlerService(final Class<? extends Service> handlerService) {
sCrashHandlerService = handlerService;
}
public static synchronized Class<? extends Service> getCrashHandlerService() {
return sCrashHandlerService;
}
public static synchronized boolean isCrashHandlingEnabled() {
return sCrashHandler != null;
}

Просмотреть файл

@ -5,6 +5,16 @@
package org.mozilla.gecko.mozglue;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.annotation.JNITarget;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.geckoview.BuildConfig;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
@ -13,19 +23,6 @@ import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import java.util.ArrayList;
import android.util.Log;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.annotation.JNITarget;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.geckoview.BuildConfig;
public final class GeckoLoader {
private static final String LOGTAG = "GeckoLoader";
@ -139,6 +136,12 @@ public final class GeckoLoader {
putenv("LANG=" + Locale.getDefault().toString());
final Class<?> crashHandler = GeckoAppShell.getCrashHandlerService();
if (crashHandler != null) {
putenv("MOZ_ANDROID_CRASH_HANDLER=" +
context.getPackageName() + "/" + crashHandler.getName());
}
putenv("MOZ_ANDROID_DEVICE_SDK_VERSION=" + Build.VERSION.SDK_INT);
// env from extras could have reset out linker flags; set them again.

Просмотреть файл

@ -0,0 +1,311 @@
package org.mozilla.geckoview;
import org.mozilla.gecko.util.ProxySelector;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPOutputStream;
/**
* Sends a crash report to the Mozilla <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
* crash report server.
*/
public class CrashReporter {
private static final String LOGTAG = "GeckoCrashReporter";
private static final String MINI_DUMP_PATH_KEY = "upload_file_minidump";
private static final String MINI_DUMP_SUCCESS_KEY = "minidumpSuccess";
private static final String PAGE_URL_KEY = "URL";
private static final String NOTES_KEY = "Notes";
private static final String SERVER_URL_KEY = "ServerURL";
/**
* Sends a crash report to the Mozilla <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
* crash report server.
*
* @param context The current Context
* @param intent The Intent sent to the {@link GeckoRuntime} crash handler
* @throws IOException This can be thrown if there was a networking error while sending the report.
* @throws URISyntaxException This can be thrown if the crash server URI from the extra data was invalid.
* @see GeckoRuntimeSettings.Builder#crashHandler(Class)
* @see GeckoRuntime#ACTION_CRASHED
*/
public static void sendCrashReport(Context context, Intent intent)
throws IOException, URISyntaxException {
sendCrashReport(context, intent.getExtras());
}
/**
* Sends a crash report to the Mozilla <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
* crash report server.
*
* @param context The current Context
* @param intentExtras The Bundle of extras attached to the Intent received by a crash handler.
* @throws IOException This can be thrown if there was a networking error while sending the report.
* @throws URISyntaxException This can be thrown if the crash server URI from the extra data was invalid.
* @see GeckoRuntimeSettings.Builder#crashHandler(Class)
* @see GeckoRuntime#ACTION_CRASHED
*/
public static void sendCrashReport(Context context, Bundle intentExtras)
throws IOException, URISyntaxException {
final File dumpFile = new File(intentExtras.getString(GeckoRuntime.EXTRA_MINIDUMP_PATH));
final File extrasFile = new File(intentExtras.getString(GeckoRuntime.EXTRA_EXTRAS_PATH));
final boolean success = intentExtras.getBoolean(GeckoRuntime.EXTRA_MINIDUMP_SUCCESS, false);
sendCrashReport(context, dumpFile, extrasFile, success);
}
/**
* Sends a crash report to the Mozilla <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
* crash report server.
*
* @param context The current {@link Context}
* @param minidumpFile A {@link File} referring to the minidump.
* @param extrasFile A {@link File} referring to the extras file.
* @param success A boolean indicating whether the dump was successfully generated.
* @throws IOException This can be thrown if there was a networking error while sending the report.
* @throws URISyntaxException This can be thrown if the crash server URI from the extra data was invalid.
* @see GeckoRuntimeSettings.Builder#crashHandler(Class)
* @see GeckoRuntime#ACTION_CRASHED
*/
public static void sendCrashReport(Context context, File minidumpFile, File extrasFile, boolean success) throws IOException, URISyntaxException {
// Compute the minidump hash and generate the stack traces
computeMinidumpHash(extrasFile, minidumpFile);
// Extract the annotations from the .extra file
HashMap<String, String> extrasMap = readStringsFromFile(extrasFile.getPath());
sendCrashReport(context, minidumpFile, extrasMap, success);
}
/**
* Sends a crash report to the Mozilla <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
* crash report server.
*
* @param context The current {@link Context}
* @param minidumpFile A {@link File} referring to the minidump.
* @param extras A {@link HashMap} with the parsed key-value pairs from the extras file.
* @param success A boolean indicating whether the dump was successfully generated.
* @throws IOException This can be thrown if there was a networking error while sending the report.
* @throws URISyntaxException This can be thrown if the crash server URI from the extra data was invalid.
* @see GeckoRuntimeSettings.Builder#crashHandler(Class)
* @see GeckoRuntime#ACTION_CRASHED
*/
public static void sendCrashReport(Context context, File minidumpFile, Map<String, String> extras, boolean success) throws IOException, URISyntaxException {
Log.i(LOGTAG, "sendCrashReport: " + minidumpFile.getPath());
String spec = extras.get(SERVER_URL_KEY);
if (spec == null) {
return;
}
HttpURLConnection conn = null;
try {
final URL url = new URL(URLDecoder.decode(spec, "UTF-8"));
final URI uri = new URI(url.getProtocol(), url.getUserInfo(),
url.getHost(), url.getPort(),
url.getPath(), url.getQuery(), url.getRef());
conn = (HttpURLConnection) ProxySelector.openConnectionWithProxy(uri);
conn.setRequestMethod("POST");
String boundary = generateBoundary();
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
conn.setRequestProperty("Content-Encoding", "gzip");
OutputStream os = new GZIPOutputStream(conn.getOutputStream());
for (String key : extras.keySet()) {
if (key.equals(PAGE_URL_KEY)) {
continue;
}
if (!key.equals(SERVER_URL_KEY) && !key.equals(NOTES_KEY)) {
sendPart(os, boundary, key, extras.get(key));
}
}
StringBuilder sb = new StringBuilder();
sb.append(extras.containsKey(NOTES_KEY) ? extras.get(NOTES_KEY) + "\n" : "");
sb.append(Build.MANUFACTURER).append(' ')
.append(Build.MODEL).append('\n')
.append(Build.FINGERPRINT);
sendPart(os, boundary, NOTES_KEY, sb.toString());
sendPart(os, boundary, "Android_Manufacturer", Build.MANUFACTURER);
sendPart(os, boundary, "Android_Model", Build.MODEL);
sendPart(os, boundary, "Android_Board", Build.BOARD);
sendPart(os, boundary, "Android_Brand", Build.BRAND);
sendPart(os, boundary, "Android_Device", Build.DEVICE);
sendPart(os, boundary, "Android_Display", Build.DISPLAY);
sendPart(os, boundary, "Android_Fingerprint", Build.FINGERPRINT);
sendPart(os, boundary, "Android_CPU_ABI", Build.CPU_ABI);
sendPart(os, boundary, "Android_PackageName", context.getPackageName());
try {
sendPart(os, boundary, "Android_CPU_ABI2", Build.CPU_ABI2);
sendPart(os, boundary, "Android_Hardware", Build.HARDWARE);
} catch (Exception ex) {
Log.e(LOGTAG, "Exception while sending SDK version 8 keys", ex);
}
sendPart(os, boundary, "Android_Version", Build.VERSION.SDK_INT + " (" + Build.VERSION.CODENAME + ")");
sendPart(os, boundary, MINI_DUMP_SUCCESS_KEY, success ? "True" : "False");
sendFile(os, boundary, MINI_DUMP_PATH_KEY, minidumpFile);
os.write(("\r\n--" + boundary + "--\r\n").getBytes());
os.flush();
os.close();
BufferedReader br = null;
try {
br = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
HashMap<String, String> responseMap = readStringsFromReader(br);
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
String crashid = responseMap.get("CrashID");
Log.i(LOGTAG, "Successfully sent crash report: " + crashid);
} else {
Log.w(LOGTAG, "Received failure HTTP response code from server: " + conn.getResponseCode());
}
} finally {
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
}
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
private static void computeMinidumpHash(File extraFile, File minidump) {
try {
FileInputStream stream = new FileInputStream(minidump);
MessageDigest md = MessageDigest.getInstance("SHA-256");
try {
byte[] buffer = new byte[4096];
int readBytes;
while ((readBytes = stream.read(buffer)) != -1) {
md.update(buffer, 0, readBytes);
}
} finally {
stream.close();
}
byte[] digest = md.digest();
StringBuilder hash = new StringBuilder(84);
hash.append("MinidumpSha256Hash=");
for (int i = 0; i < digest.length; i++) {
hash.append(Integer.toHexString((digest[i] & 0xf0) >> 4));
hash.append(Integer.toHexString(digest[i] & 0x0f));
}
hash.append('\n');
FileWriter writer = new FileWriter(extraFile, /* append */ true);
try {
writer.write(hash.toString());
} finally {
writer.close();
}
} catch (Exception e) {
Log.e(LOGTAG, "exception while computing the minidump hash: ", e);
}
}
private static HashMap<String, String> readStringsFromFile(String filePath) throws IOException {
FileReader fileReader = null;
BufferedReader bufReader = null;
try {
fileReader = new FileReader(filePath);
bufReader = new BufferedReader(fileReader);
return readStringsFromReader(bufReader);
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
if (bufReader != null) {
bufReader.close();
}
} catch (IOException e) {
}
}
}
private static HashMap<String, String> readStringsFromReader(BufferedReader reader) throws IOException {
String line;
HashMap<String, String> map = new HashMap<>();
while ((line = reader.readLine()) != null) {
int equalsPos = -1;
if ((equalsPos = line.indexOf('=')) != -1) {
String key = line.substring(0, equalsPos);
String val = unescape(line.substring(equalsPos + 1));
map.put(key, val);
}
}
return map;
}
private static String generateBoundary() {
// Generate some random numbers to fill out the boundary
int r0 = (int)(Integer.MAX_VALUE * Math.random());
int r1 = (int)(Integer.MAX_VALUE * Math.random());
return String.format("---------------------------%08X%08X", r0, r1);
}
private static void sendPart(OutputStream os, String boundary, String name, String data) {
try {
os.write(("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"" + name + "\"\r\n" +
"\r\n" +
data + "\r\n"
).getBytes());
} catch (Exception ex) {
Log.e(LOGTAG, "Exception when sending \"" + name + "\"", ex);
}
}
private static void sendFile(OutputStream os, String boundary, String name, File file) throws IOException {
os.write(("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"" + name + "\"; " +
"filename=\"" + file.getName() + "\"\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n"
).getBytes());
FileChannel fc = new FileInputStream(file).getChannel();
fc.transferTo(0, fc.size(), Channels.newChannel(os));
fc.close();
}
private static String unescape(String string) {
return string.replaceAll("\\\\\\\\", "\\").replaceAll("\\\\n", "\n").replaceAll("\\\\t", "\t");
}
}

Просмотреть файл

@ -6,11 +6,17 @@
package org.mozilla.geckoview;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.content.Context;
import android.os.Process;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import org.mozilla.gecko.EventDispatcher;
@ -21,6 +27,7 @@ import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.StringUtils;
import org.mozilla.gecko.util.ThreadUtils;
import java.io.File;
@ -29,6 +36,53 @@ public final class GeckoRuntime implements Parcelable {
private static final String LOGTAG = "GeckoRuntime";
private static final boolean DEBUG = false;
/**
* Intent action sent to the crash handler when a crash is encountered.
* @see GeckoRuntimeSettings.Builder#crashHandler(Class)
*/
public static final String ACTION_CRASHED = "org.mozilla.gecko.ACTION_CRASHED";
/**
* This is a key for extra data sent with {@link #ACTION_CRASHED}. It refers
* to a String with the path to a Breakpad minidump file containing information about
* the crash. Several crash reporters are able to ingest this in a
* crash report, including <a href="https://sentry.io">Sentry</a>
* and Mozilla's <a href="https://wiki.mozilla.org/Socorro">Socorro</a>.
* <br><br>
* Be aware, the minidump can contain personally identifiable information.
* Ensure you are obeying all applicable laws and policies before sending
* this to a remote server.
* @see GeckoRuntimeSettings.Builder#crashHandler(Class)
*/
public static final String EXTRA_MINIDUMP_PATH = "minidumpPath";
/**
* This is a key for extra data sent with {@link #ACTION_CRASHED}. It refers
* to a string with the path to a file containing extra metadata about the crash. The file
* contains key-value pairs in the form
* <pre>Key=Value</pre>
* Be aware, it may contain sensitive data such
* as the URI that was loaded at the time of the crash.
*/
public static final String EXTRA_EXTRAS_PATH = "extrasPath";
/**
* This is a key for extra data sent with {@link #ACTION_CRASHED}. The value is
* a boolean indicating whether or not the crash dump was succcessfully
* retrieved. If this is false, the dump file referred to in
* {@link #EXTRA_MINIDUMP_PATH} may be corrupted or incomplete.
*/
public static final String EXTRA_MINIDUMP_SUCCESS = "minidumpSuccess";
/**
* This is a key for extra data sent with {@link #ACTION_CRASHED}. The value is
* a boolean indicating whether or not the crash was fatal or not. If true, the
* main application process was affected by the crash. If false, only an internal
* process used by Gecko has crashed and the application may be able to recover.
* @see GeckoSession.ContentDelegate#onCrash(GeckoSession)
*/
public static final String EXTRA_CRASH_FATAL = "fatal";
private static GeckoRuntime sDefaultRuntime;
/**
@ -85,6 +139,17 @@ public final class GeckoRuntime implements Parcelable {
}
};
private static final String getProcessName(Context context) {
final ActivityManager manager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
for (final ActivityManager.RunningAppProcessInfo info : manager.getRunningAppProcesses()) {
if (info.pid == Process.myPid()) {
return info.processName;
}
}
return null;
}
/* package */ boolean init(final @NonNull Context context, final @NonNull GeckoRuntimeSettings settings) {
if (DEBUG) {
Log.d(LOGTAG, "init");
@ -98,9 +163,28 @@ public final class GeckoRuntime implements Parcelable {
flags |= GeckoThread.FLAG_DEBUGGING;
}
final Class<?> crashHandler = settings.getCrashHandler();
if (crashHandler != null) {
try {
final ServiceInfo info = context.getPackageManager().getServiceInfo(new ComponentName(context, crashHandler), 0);
if (info.processName.equals(getProcessName(context))) {
throw new IllegalArgumentException("Crash handler service must run in a separate process");
}
flags |= GeckoThread.FLAG_ENABLE_NATIVE_CRASHREPORTER;
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalArgumentException("Crash handler must be registered as a service");
}
}
if (GeckoAppShell.isFennec()) {
flags |= GeckoThread.FLAG_ENABLE_JAVA_CRASHREPORTER;
}
GeckoAppShell.setDisplayDensityOverride(settings.getDisplayDensityOverride());
GeckoAppShell.setDisplayDpiOverride(settings.getDisplayDpiOverride());
GeckoAppShell.setScreenSizeOverride(settings.getScreenSizeOverride());
GeckoAppShell.setCrashHandlerService(settings.getCrashHandler());
if (!GeckoThread.initMainProcess(/* profile */ null, settings.getArguments(),
settings.getExtras(), flags)) {

Просмотреть файл

@ -9,6 +9,7 @@ package org.mozilla.geckoview;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import android.app.Service;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
@ -252,6 +253,44 @@ public final class GeckoRuntimeSettings implements Parcelable {
mSettings.mScreenHeightOverride = height;
return this;
}
/**
* When set, the specified {@link android.app.Service} will be started by
* an {@link android.content.Intent} with action {@link GeckoRuntime#ACTION_CRASHED} when
* a crash is encountered. Crash details can be found in the Intent extras, such as
* {@link GeckoRuntime#EXTRA_MINIDUMP_PATH}.
* <br><br>
* The crash handler Service must be declared to run in a different process from
* the {@link GeckoRuntime}. Additionally, the handler will be run as a foreground service,
* so the normal rules about activating a foreground service apply.
* <br><br>
* In practice, you have one of three
* options once the crash handler is started:
* <ul>
* <li>Call {@link android.app.Service#startForeground(int, android.app.Notification)}. You can then
* take as much time as necessary to report the crash.</li>
* <li>Start an activity. Unless you also call {@link android.app.Service#startForeground(int, android.app.Notification)}
* this should be in a different process from the crash handler, since Android will
* kill the crash handler process as part of the background execution limitations.</li>
* <li>Schedule work via {@link android.app.job.JobScheduler}. This will allow you to
* do substantial work in the background without execution limits.</li>
* </ul><br>
* You can use {@link CrashReporter} to send the report to Mozilla, which provides Mozilla
* with data needed to fix the crash. Be aware that the minidump may contain
* personally identifiable information (PII). Consult Mozilla's
* <a href="https://www.mozilla.org/en-US/privacy/">privacy policy</a> for information
* on how this data will be handled.
*
* @param handler The class for the crash handler Service.
* @return This builder instance.
*
* @see <a href="https://developer.android.com/about/versions/oreo/background">Android Background Execution Limits</a>
* @see GeckoRuntime#ACTION_CRASHED
*/
public @NonNull Builder crashHandler(final Class<? extends Service> handler) {
mSettings.mCrashHandler = handler;
return this;
}
}
/* package */ GeckoRuntime runtime;
@ -320,6 +359,7 @@ public final class GeckoRuntimeSettings implements Parcelable {
/* package */ int mDisplayDpiOverride;
/* package */ int mScreenWidthOverride;
/* package */ int mScreenHeightOverride;
/* package */ Class<? extends Service> mCrashHandler;
private final Pref<?>[] mPrefs = new Pref<?>[] {
mCookieBehavior, mCookieLifetime, mConsoleOutput,
@ -361,6 +401,7 @@ public final class GeckoRuntimeSettings implements Parcelable {
mDisplayDpiOverride = settings.mDisplayDpiOverride;
mScreenWidthOverride = settings.mScreenWidthOverride;
mScreenHeightOverride = settings.mScreenHeightOverride;
mCrashHandler = settings.mCrashHandler;
}
/* package */ void flush() {
@ -487,6 +528,10 @@ public final class GeckoRuntimeSettings implements Parcelable {
return null;
}
public Class<? extends Service> getCrashHandler() {
return mCrashHandler;
}
/**
* Gets the screen size override value.
*
@ -707,6 +752,7 @@ public final class GeckoRuntimeSettings implements Parcelable {
out.writeInt(mDisplayDpiOverride);
out.writeInt(mScreenWidthOverride);
out.writeInt(mScreenHeightOverride);
out.writeString(mCrashHandler != null ? mCrashHandler.getName() : null);
}
// AIDL code may call readFromParcel even though it's not part of Parcelable.
@ -727,6 +773,18 @@ public final class GeckoRuntimeSettings implements Parcelable {
mDisplayDpiOverride = source.readInt();
mScreenWidthOverride = source.readInt();
mScreenHeightOverride = source.readInt();
final String crashHandlerName = source.readString();
if (crashHandlerName != null) {
try {
@SuppressWarnings("unchecked")
final Class<? extends Service> handler =
(Class<? extends Service>) Class.forName(crashHandlerName);
mCrashHandler = handler;
} catch (ClassNotFoundException e) {
}
}
}
public static final Parcelable.Creator<GeckoRuntimeSettings> CREATOR

Просмотреть файл

@ -227,9 +227,6 @@ static char* androidUserSerial = nullptr;
// Before Android 8 we needed to use "startservice" to start the crash reporting service.
// After Android 8 we need to use "start-foreground-service"
static const char* androidStartServiceCommand = nullptr;
// After targeting API 26 (Oreo) we ned to use a JobIntentService for the background
// work regarding crash reporting. That Service needs a unique Job Id.
static const char* androidCrashReporterJobId = nullptr;
#endif
// this holds additional data sent via the API
@ -838,35 +835,43 @@ LaunchProgram(const XP_CHAR* aProgramPath, const XP_CHAR* aMinidumpPath)
*/
static bool
LaunchCrashReporterActivity(XP_CHAR* aProgramPath, XP_CHAR* aMinidumpPath,
bool aSucceeded)
LaunchCrashHandlerService(XP_CHAR* aProgramPath, XP_CHAR* aMinidumpPath,
bool aSucceeded)
{
static XP_CHAR extrasPath[XP_PATH_MAX];
size_t size = XP_PATH_MAX;
XP_CHAR* p = Concat(extrasPath, aMinidumpPath, &size);
p = Concat(p - 3, "extra", &size);
pid_t pid = sys_fork();
if (pid == -1)
return false;
else if (pid == 0) {
// Invoke the reportCrash activity using am
// Invoke the crash handler service using am
if (androidUserSerial) {
Unused << execlp("/system/bin/am",
"/system/bin/am",
androidStartServiceCommand,
"--user", androidUserSerial,
"-a", "org.mozilla.gecko.reportCrash",
"-a", "org.mozilla.gecko.ACTION_CRASHED",
"-n", aProgramPath,
"--es", "minidumpPath", aMinidumpPath,
"--ei", "jobId", androidCrashReporterJobId,
"--es", "extrasPath", extrasPath,
"--ez", "minidumpSuccess", aSucceeded ? "true" : "false",
"--ez", "fatal", "true",
(char*)0);
} else {
Unused << execlp("/system/bin/am",
"/system/bin/am",
androidStartServiceCommand,
"-a", "org.mozilla.gecko.reportCrash",
"-a", "org.mozilla.gecko.ACTION_CRASHED",
"-n", aProgramPath,
"--es", "minidumpPath", aMinidumpPath,
"--ei", "jobId", androidCrashReporterJobId,
"--es", "extrasPath", extrasPath,
"--ez", "minidumpSuccess", aSucceeded ? "true" : "false",
"--ez", "fatal", "true",
(char*)0);
}
_exit(1);
@ -1132,8 +1137,8 @@ MinidumpCallback(
}
#if defined(MOZ_WIDGET_ANDROID) // Android
returnValue = LaunchCrashReporterActivity(crashReporterPath, minidumpPath,
succeeded);
returnValue = LaunchCrashHandlerService(crashReporterPath, minidumpPath,
succeeded);
#else // Windows, Mac, Linux, etc...
returnValue = LaunchProgram(crashReporterPath, minidumpPath);
#ifdef XP_WIN
@ -1551,17 +1556,12 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory,
#endif
#endif // XP_WIN32
#else
// On Android, we launch using the application package name instead of a
// filename, so use the dynamically set MOZ_ANDROID_PACKAGE_NAME, or fall
// back to the static ANDROID_PACKAGE_NAME.
const char* androidPackageName = PR_GetEnv("MOZ_ANDROID_PACKAGE_NAME");
if (androidPackageName != nullptr) {
nsCString package(androidPackageName);
package.AppendLiteral("/org.mozilla.gecko.CrashReporterService");
crashReporterPath = ToNewCString(package);
// On Android, we launch a service defined via MOZ_ANDROID_CRASH_HANDLER
const char* androidCrashHandler = PR_GetEnv("MOZ_ANDROID_CRASH_HANDLER");
if (androidCrashHandler) {
crashReporterPath = strdup(androidCrashHandler);
} else {
nsCString package(ANDROID_PACKAGE_NAME "/org.mozilla.gecko.CrashReporterService");
crashReporterPath = ToNewCString(package);
NS_WARNING("No Android crash handler set");
}
const char *deviceAndroidVersion = PR_GetEnv("MOZ_ANDROID_DEVICE_SDK_VERSION");
@ -1573,11 +1573,6 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory,
androidStartServiceCommand = (char*)"startservice";
}
}
const char *crashReporterJobId = PR_GetEnv("MOZ_ANDROID_CRASH_REPORTER_JOB_ID");
if (crashReporterJobId != nullptr) {
androidCrashReporterJobId = crashReporterJobId;
}
#endif // !defined(MOZ_WIDGET_ANDROID)
// get temp path to use for minidump path