--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -1221,23 +1221,19 @@ PluginModuleChromeParent::TakeFullMinidu
}
bool reportsReady = false;
// Check to see if we already have a browser dump id - with e10s plugin
// hangs we take this earlier (see ProcessHangMonitor) from a background
// thread. We do this before we message the main thread about the hang
// since the posted message will trash our browser stack state.
- bool exists;
nsCOMPtr<nsIFile> browserDumpFile;
- if (!aBrowserDumpId.IsEmpty() &&
- CrashReporter::GetMinidumpForID(aBrowserDumpId, getter_AddRefs(browserDumpFile)) &&
- browserDumpFile &&
- NS_SUCCEEDED(browserDumpFile->Exists(&exists)) && exists)
- {
+ if (CrashReporter::GetMinidumpForID(aBrowserDumpId,
+ getter_AddRefs(browserDumpFile))) {
// We have a single browser report, generate a new plugin process parent
// report and pair it up with the browser report handed in.
reportsReady = mCrashReporter->GenerateMinidumpAndPair(
this,
browserDumpFile,
NS_LITERAL_CSTRING("browser"));
if (!reportsReady) {
@@ -1259,18 +1255,17 @@ PluginModuleChromeParent::TakeFullMinidu
if (reportsReady) {
aDumpId = mCrashReporter->MinidumpID();
PLUGIN_LOG_DEBUG(
("generated paired browser/plugin minidumps: %s)",
NS_ConvertUTF16toUTF8(aDumpId).get()));
nsAutoCString additionalDumps("browser");
nsCOMPtr<nsIFile> pluginDumpFile;
- if (GetMinidumpForID(aDumpId, getter_AddRefs(pluginDumpFile)) &&
- pluginDumpFile) {
+ if (GetMinidumpForID(aDumpId, getter_AddRefs(pluginDumpFile))) {
#ifdef MOZ_CRASHREPORTER_INJECTOR
// If we have handles to the flash sandbox processes on Windows,
// include those minidumps as well.
if (CreatePluginMinidump(mFlashProcess1, 0, pluginDumpFile,
NS_LITERAL_CSTRING("flash1"))) {
additionalDumps.AppendLiteral(",flash1");
}
if (CreatePluginMinidump(mFlashProcess2, 0, pluginDumpFile,
--- a/mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
@@ -7,23 +7,25 @@ package org.mozilla.gecko;
import java.util.HashMap;
import java.util.Map;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
+import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
+import java.security.MessageDigest;
import java.util.zip.GZIPOutputStream;
import org.mozilla.gecko.AppConstants.Versions;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
@@ -133,16 +135,17 @@ public class CrashReporter extends AppCo
pendingDir.mkdirs();
mPendingMinidumpFile = new File(pendingDir, passedMinidumpFile.getName());
moveFile(passedMinidumpFile, mPendingMinidumpFile);
File extrasFile = new File(passedMinidumpPath.replaceAll("\\.dmp", ".extra"));
mPendingExtrasFile = new File(pendingDir, extrasFile.getName());
moveFile(extrasFile, mPendingExtrasFile);
+ computeMinidumpHash(mPendingExtrasFile, mPendingMinidumpFile);
mExtrasStringMap = new HashMap<String, String>();
readStringsFromFile(mPendingExtrasFile.getPath(), mExtrasStringMap);
// Notify GeckoApp that we've crashed, so it can react appropriately during the next start.
try {
File crashFlag = new File(GeckoProfileDirectories.getMozillaDirectory(this), "CRASHED");
crashFlag.createNewFile();
} catch (GeckoProfileDirectories.NoMozillaDirectoryException | IOException e) {
@@ -262,16 +265,56 @@ public class CrashReporter extends AppCo
backgroundSendReport();
}
public void onRestartClick(View v) { // bound via crash_reporter.xml
doRestart();
backgroundSendReport();
}
+ private 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 boolean readStringsFromFile(String filePath, Map<String, String> stringMap) {
try {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
return readStringsFromReader(reader, stringMap);
} catch (Exception e) {
Log.e(LOGTAG, "exception while reading strings: ", e);
return false;
}
--- a/toolkit/components/crashes/CrashManager.jsm
+++ b/toolkit/components/crashes/CrashManager.jsm
@@ -35,40 +35,54 @@ const MILLISECONDS_IN_DAY = 24 * 60 * 60
// Converts Date to days since UNIX epoch.
// This was copied from /services/metrics.storage.jsm. The implementation
// does not account for leap seconds.
function dateToDays(date) {
return Math.floor(date.getTime() / MILLISECONDS_IN_DAY);
}
/**
- * Parse the string stored in the specified field as JSON and then remove the
- * field from the object. The string might also be returned without parsing.
+ * Get a field from the specified object and remove it.
*
* @param obj {Object} The object holding the field
* @param field {String} The name of the field to be parsed and removed
- * @param [parseAsJson=true] {Boolean} If true parse the field's contents as if
- * it were JSON code, otherwise return the rew string.
*
- * @returns {Object|String} the parsed object or the raw string
+ * @returns {String} the field contents as a string, null if none was found
*/
-function parseAndRemoveField(obj, field, parseAsJson = true) {
+function getAndRemoveField(obj, field) {
let value = null;
if (field in obj) {
- if (!parseAsJson) {
- // We split extra files on LF characters but Windows-generated ones might
- // contain trailing CR characters so trim them here.
- value = obj[field].trim();
- } else {
- try {
- value = JSON.parse(obj[field]);
- } catch (e) {
- Cu.reportError(e);
- }
+ // We split extra files on LF characters but Windows-generated ones might
+ // contain trailing CR characters so trim them here.
+ value = obj[field].trim();
+
+ delete obj[field];
+ }
+
+ return value;
+}
+
+/**
+ * Parse the string stored in the specified field as JSON and then remove the
+ * field from the object.
+ *
+ * @param obj {Object} The object holding the field
+ * @param field {String} The name of the field to be parsed and removed
+ *
+ * @returns {Object} the parsed object, null if none was found
+ */
+function parseAndRemoveField(obj, field) {
+ let value = null;
+
+ if (field in obj) {
+ try {
+ value = JSON.parse(obj[field]);
+ } catch (e) {
+ Cu.reportError(e);
}
delete obj[field];
}
return value;
}
@@ -454,17 +468,17 @@ this.CrashManager.prototype = Object.fre
this._crashPromises.delete(id);
deferred.resolve();
}
// Send a telemetry ping for each content process crash
if (processType === this.PROCESS_TYPE_CONTENT) {
this._sendCrashPing(id, processType, date, metadata);
}
- }.bind(this));
+ }.bind(this));
return promise;
},
/**
* Returns a promise that is resolved only the crash with the specified id
* has been fully recorded.
*
@@ -632,32 +646,33 @@ this.CrashManager.prototype = Object.fre
},
_sendCrashPing(crashId, type, date, metadata = {}) {
// If we have a saved environment, use it. Otherwise report
// the current environment.
let reportMeta = Cu.cloneInto(metadata, myScope);
let crashEnvironment = parseAndRemoveField(reportMeta,
"TelemetryEnvironment");
- let sessionId = parseAndRemoveField(reportMeta, "TelemetrySessionId",
- /* parseAsJson */ false);
+ let sessionId = getAndRemoveField(reportMeta, "TelemetrySessionId");
let stackTraces = parseAndRemoveField(reportMeta, "StackTraces");
+ let minidumpSha256Hash = getAndRemoveField(reportMeta,
+ "MinidumpSha256Hash");
// If CrashPingUUID is not present then Telemetry will generate a new UUID
- let pingId = parseAndRemoveField(reportMeta, "CrashPingUUID",
- /* parseAsJson */ false);
+ let pingId = getAndRemoveField(reportMeta, "CrashPingUUID");
// Filter the remaining annotations to remove privacy-sensitive ones
reportMeta = this._filterAnnotations(reportMeta);
this._pingPromise = TelemetryController.submitExternalPing("crash",
{
version: 1,
crashDate: date.toISOString().slice(0, 10), // YYYY-MM-DD
sessionId,
crashId,
+ minidumpSha256Hash,
processType: type,
stackTraces,
metadata: reportMeta,
hasCrashEnvironment: (crashEnvironment !== null),
},
{
addClientId: true,
addEnvironment: true,
--- a/toolkit/components/crashes/CrashService.js
+++ b/toolkit/components/crashes/CrashService.js
@@ -10,41 +10,77 @@ Cu.import("resource://gre/modules/AsyncS
Cu.import("resource://gre/modules/KeyValueParser.jsm");
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
/**
+ * Computes the SHA256 hash of the minidump file associated with a crash
+ *
+ * @param crashID {string} Crash ID. Likely a UUID.
+ *
+ * @returns {Promise} A promise that resolves to the hash value of the
+ * minidump. If the hash could not be computed then null is returned
+ * instead.
+ */
+function computeMinidumpHash(id) {
+ let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]
+ .getService(Components.interfaces.nsICrashReporter);
+
+ return Task.spawn(function* () {
+ try {
+ let minidumpFile = cr.getMinidumpForID(id);
+ let minidumpData = yield OS.File.read(minidumpFile.path);
+ let hasher = Cc["@mozilla.org/security/hash;1"]
+ .createInstance(Ci.nsICryptoHash);
+ hasher.init(hasher.SHA256);
+ hasher.update(minidumpData, minidumpData.length);
+
+ let hashBin = hasher.finish(false);
+ let hash = "";
+
+ for (let i = 0; i < hashBin.length; i++) {
+ // Every character in the hash string contains a byte of the hash data
+ hash += ("0" + hashBin.charCodeAt(i).toString(16)).slice(-2);
+ }
+
+ return hash;
+ } catch (e) {
+ Cu.reportError(e);
+ return null;
+ }
+ });
+}
+
+/**
* Process the .extra file associated with the crash id and return the
* annotations it contains in an object.
*
* @param crashID {string} Crash ID. Likely a UUID.
*
* @return {Promise} A promise that resolves to an object holding the crash
* annotations, this object may be empty if no annotations were found.
*/
function processExtraFile(id) {
let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]
.getService(Components.interfaces.nsICrashReporter);
- let extraPath = OS.Path.join(cr.minidumpPath.path, id + ".extra");
return Task.spawn(function* () {
try {
+ let extraFile = cr.getExtraFileForID(id);
let decoder = new TextDecoder();
- let extraFile = yield OS.File.read(extraPath);
- let extraData = decoder.decode(extraFile);
+ let extraData = yield OS.File.read(extraFile.path);
- return parseKeyValuePairs(extraData);
+ return parseKeyValuePairs(decoder.decode(extraData));
} catch (e) {
Cu.reportError(e);
+ return {};
}
-
- return {};
});
}
/**
* This component makes crash data available throughout the application.
*
* It is a service because some background activity will eventually occur.
*/
@@ -84,23 +120,33 @@ CrashService.prototype = Object.freeze({
break;
case Ci.nsICrashService.CRASH_TYPE_HANG:
crashType = Services.crashmanager.CRASH_TYPE_HANG;
break;
default:
throw new Error("Unrecognized CRASH_TYPE: " + crashType);
}
+ let blocker = Task.spawn(function* () {
+ let metadata = yield processExtraFile(id);
+ let hash = yield computeMinidumpHash(id);
+
+ if (hash) {
+ metadata.MinidumpSha256Hash = hash;
+ }
+
+ yield Services.crashmanager.addCrash(processType, crashType, id,
+ new Date(), metadata);
+ });
+
AsyncShutdown.profileBeforeChange.addBlocker(
- "CrashService waiting for content crash ping to be sent",
- processExtraFile(id).then(metadata => {
- return Services.crashmanager.addCrash(processType, crashType, id,
- new Date(), metadata)
- })
+ "CrashService waiting for content crash ping to be sent", blocker
);
+
+ blocker.then(AsyncShutdown.profileBeforeChange.removeBlocker(blocker));
},
observe(subject, topic, data) {
switch (topic) {
case "profile-after-change":
// Side-effect is the singleton is instantiated.
Services.crashmanager;
break;
--- a/toolkit/components/crashes/nsICrashService.idl
+++ b/toolkit/components/crashes/nsICrashService.idl
@@ -11,16 +11,18 @@ interface nsICrashService : nsISupports
* Records the occurrence of a crash.
*
* @param processType
* One of the PROCESS_TYPE constants defined below.
* @param crashType
* One of the CRASH_TYPE constants defined below.
* @param id
* Crash ID. Likely a UUID.
+ *
+ * @return {Promise} A promise that resolves after the crash has been stored
*/
void addCrash(in long processType, in long crashType, in AString id);
const long PROCESS_TYPE_MAIN = 0;
const long PROCESS_TYPE_CONTENT = 1;
const long PROCESS_TYPE_PLUGIN = 2;
const long PROCESS_TYPE_GMPLUGIN = 3;
const long PROCESS_TYPE_GPU = 4;
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
@@ -205,18 +205,21 @@ add_task(function* test_schedule_mainten
yield m.scheduleMaintenance(25);
let crashes = yield m.getCrashes();
Assert.equal(crashes.length, 1);
Assert.equal(crashes[0].id, "id1");
});
const crashId = "3cb67eba-0dc7-6f78-6a569a0e-172287ec";
+const crashPingUuid = "103dbdf2-339b-4b9c-a7cc-5f9506ea9d08";
const productName = "Firefox";
const productId = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
+const sha256Hash =
+ "f8410c3ac4496cfa9191a1240f0e365101aef40c7bf34fc5bcb8ec511832ed79";
const stackTraces = "{\"status\":\"OK\"}";
add_task(function* test_main_crash_event_file() {
let ac = new TelemetryArchiveTesting.Checker();
yield ac.promiseInit();
let theEnvironment = TelemetryEnvironment.currentEnvironment;
const sessionId = "be66af2f-2ee5-4330-ae95-44462dfbdf0c";
@@ -225,39 +228,41 @@ add_task(function* test_main_crash_event
theEnvironment.testValue = "MyValue\"";
let m = yield getManager();
const fileContent = crashId + "\n" +
"ProductName=" + productName + "\n" +
"ProductID=" + productId + "\n" +
"TelemetryEnvironment=" + JSON.stringify(theEnvironment) + "\n" +
"TelemetrySessionId=" + sessionId + "\n" +
+ "MinidumpSha256Hash=" + sha256Hash + "\n" +
"StackTraces=" + stackTraces + "\n" +
"ThisShouldNot=end-up-in-the-ping\n";
yield m.createEventsFile(crashId, "crash.main.2", DUMMY_DATE, fileContent);
let count = yield m.aggregateEventsFiles();
Assert.equal(count, 1);
let crashes = yield m.getCrashes();
Assert.equal(crashes.length, 1);
Assert.equal(crashes[0].id, crashId);
Assert.equal(crashes[0].type, "main-crash");
Assert.equal(crashes[0].metadata.ProductName, productName);
Assert.equal(crashes[0].metadata.ProductID, productId);
Assert.ok(crashes[0].metadata.TelemetryEnvironment);
- Assert.equal(Object.getOwnPropertyNames(crashes[0].metadata).length, 6);
+ Assert.equal(Object.getOwnPropertyNames(crashes[0].metadata).length, 7);
Assert.equal(crashes[0].metadata.TelemetrySessionId, sessionId);
Assert.ok(crashes[0].metadata.StackTraces);
Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
let found = yield ac.promiseFindPing("crash", [
[["payload", "hasCrashEnvironment"], true],
[["payload", "metadata", "ProductName"], productName],
[["payload", "metadata", "ProductID"], productId],
+ [["payload", "minidumpSha256Hash"], sha256Hash],
[["payload", "crashId"], crashId],
[["payload", "stackTraces", "status"], "OK"],
[["payload", "sessionId"], sessionId],
]);
Assert.ok(found, "Telemetry ping submitted for found crash");
Assert.deepEqual(found.environment, theEnvironment,
"The saved environment should be present");
Assert.equal(found.payload.metadata.ThisShouldNot, undefined,
@@ -296,16 +301,55 @@ add_task(function* test_main_crash_event
]);
Assert.ok(found, "Telemetry ping submitted for found crash");
Assert.ok(found.environment, "There is an environment");
count = yield m.aggregateEventsFiles();
Assert.equal(count, 0);
});
+add_task(function* test_main_crash_event_file_override_ping_uuid() {
+ let ac = new TelemetryArchiveTesting.Checker();
+ yield ac.promiseInit();
+
+ let m = yield getManager();
+ const fileContent = crashId + "\n" +
+ "ProductName=" + productName + "\n" +
+ "ProductID=" + productId + "\n" +
+ "CrashPingUUID=" + crashPingUuid + "\n";
+
+ yield m.createEventsFile(crashId, "crash.main.2", DUMMY_DATE, fileContent);
+ let count = yield m.aggregateEventsFiles();
+ Assert.equal(count, 1);
+
+ let crashes = yield m.getCrashes();
+ Assert.equal(crashes.length, 1);
+ Assert.equal(crashes[0].id, crashId);
+ Assert.equal(crashes[0].type, "main-crash");
+ Assert.deepEqual(crashes[0].metadata, {
+ CrashPingUUID: crashPingUuid,
+ ProductName: productName,
+ ProductID: productId
+ });
+ Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
+
+ let found = yield ac.promiseFindPing("crash", [
+ [["id"], crashPingUuid],
+ [["payload", "hasCrashEnvironment"], false],
+ [["payload", "metadata", "ProductName"], productName],
+ [["payload", "metadata", "ProductID"], productId],
+ ]);
+ Assert.ok(found, "Telemetry ping submitted for found crash");
+ Assert.equal(found.payload.metadata.CrashPingUUID, undefined,
+ "Crash ping UUID should be filtered out");
+
+ count = yield m.aggregateEventsFiles();
+ Assert.equal(count, 0);
+});
+
add_task(function* test_crash_submission_event_file() {
let m = yield getManager();
yield m.createEventsFile("1", "crash.main.2", DUMMY_DATE, "crash1");
yield m.createEventsFile("1-submission", "crash.submission.1", DUMMY_DATE_2,
"crash1\nfalse\n");
// The line below has been intentionally commented out to make sure that
// the crash record is created when one does not exist.
@@ -461,22 +505,24 @@ add_task(function* test_addCrash() {
add_task(function* test_content_crash_ping() {
let ac = new TelemetryArchiveTesting.Checker();
yield ac.promiseInit();
let m = yield getManager();
let id = yield m.createDummyDump();
yield m.addCrash(m.PROCESS_TYPE_CONTENT, m.CRASH_TYPE_CRASH, id, DUMMY_DATE, {
StackTraces: stackTraces,
+ MinidumpSha256Hash: sha256Hash,
ThisShouldNot: "end-up-in-the-ping"
});
yield m._pingPromise;
let found = yield ac.promiseFindPing("crash", [
[["payload", "crashId"], id],
+ [["payload", "minidumpSha256Hash"], sha256Hash],
[["payload", "processType"], m.PROCESS_TYPE_CONTENT],
[["payload", "stackTraces", "status"], "OK"],
]);
Assert.ok(found, "Telemetry ping submitted for content crash");
Assert.equal(found.payload.metadata.ThisShouldNot, undefined,
"Non-whitelisted fields should be filtered out");
});
--- a/toolkit/components/telemetry/docs/data/crash-ping.rst
+++ b/toolkit/components/telemetry/docs/data/crash-ping.rst
@@ -31,16 +31,17 @@ Structure:
processType: <type>, // Type of process that crashed, see below for a list of types
payload: {
crashDate: "YYYY-MM-DD",
version: 1,
sessionId: <UUID>, // may be missing for crashes that happen early
// in startup. Added in Firefox 48 with the
// intention of uplifting to Firefox 46
crashId: <UUID>, // Optional, ID of the associated crash
+ minidumpSha256Hash: <hash>, // SHA256 hash of the minidump file
stackTraces: { ... }, // Optional, see below
metadata: { // Annotations saved while Firefox was running. See nsExceptionHandler.cpp for more information
ProductID: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
ProductName: "Firefox",
ReleaseChannel: <channel>,
Version: <version number>,
BuildID: "YYYYMMDDHHMMSS",
AvailablePageFile: <size>, // Windows-only, available paging file
--- a/toolkit/crashreporter/client/crashreporter.cpp
+++ b/toolkit/crashreporter/client/crashreporter.cpp
@@ -6,23 +6,31 @@
#include "crashreporter.h"
#ifdef _MSC_VER
// Disable exception handler warnings.
# pragma warning( disable : 4530 )
#endif
#include <fstream>
+#include <iomanip>
#include <sstream>
#include <memory>
#include <ctime>
#include <cstdlib>
#include <cstring>
#include <string>
+#ifdef XP_LINUX
+#include <dlfcn.h>
+#endif
+
+#include "nss.h"
+#include "sechash.h"
+
using std::string;
using std::istream;
using std::ifstream;
using std::istringstream;
using std::ostringstream;
using std::ostream;
using std::ofstream;
using std::vector;
@@ -431,16 +439,127 @@ void SendCompleted(bool success, const s
}
bool ShouldEnableSending()
{
srand(time(0));
return ((rand() % 100) < MOZ_CRASHREPORTER_ENABLE_PERCENT);
}
+static string ComputeDumpHash() {
+#ifdef XP_LINUX
+ // On Linux we rely on the system-provided libcurl which uses nss so we have
+ // to also use the system-provided nss instead of the ones we have bundled.
+ const char* libnssNames[] = {
+ "libnss3.so",
+#ifndef HAVE_64BIT_BUILD
+ // 32-bit versions on 64-bit hosts
+ "/usr/lib32/libnss3.so",
+#endif
+ };
+ void* lib = nullptr;
+
+ for (const char* libname : libnssNames) {
+ lib = dlopen(libname, RTLD_NOW);
+
+ if (lib) {
+ break;
+ }
+ }
+
+ if (!lib) {
+ return "";
+ }
+
+ SECStatus (*NSS_Initialize)(const char*, const char*, const char*,
+ const char*, PRUint32);
+ HASHContext* (*HASH_Create)(HASH_HashType);
+ void (*HASH_Destroy)(HASHContext*);
+ void (*HASH_Begin)(HASHContext*);
+ void (*HASH_Update)(HASHContext*, const unsigned char*, unsigned int);
+ void (*HASH_End)(HASHContext*, unsigned char*, unsigned int*, unsigned int);
+
+ *(void**) (&NSS_Initialize) = dlsym(lib, "NSS_Initialize");
+ *(void**) (&HASH_Create) = dlsym(lib, "HASH_Create");
+ *(void**) (&HASH_Destroy) = dlsym(lib, "HASH_Destroy");
+ *(void**) (&HASH_Begin) = dlsym(lib, "HASH_Begin");
+ *(void**) (&HASH_Update) = dlsym(lib, "HASH_Update");
+ *(void**) (&HASH_End) = dlsym(lib, "HASH_End");
+
+ if (!HASH_Create || !HASH_Destroy || !HASH_Begin || !HASH_Update ||
+ !HASH_End) {
+ return "";
+ }
+#endif
+ // Minimal NSS initialization so we can use the hash functions
+ const PRUint32 kNssFlags = NSS_INIT_READONLY | NSS_INIT_NOROOTINIT |
+ NSS_INIT_NOMODDB | NSS_INIT_NOCERTDB;
+ if (NSS_Initialize(nullptr, "", "", "", kNssFlags) != SECSuccess) {
+ return "";
+ }
+
+ HASHContext* hashContext = HASH_Create(HASH_AlgSHA256);
+
+ if (!hashContext) {
+ return "";
+ }
+
+ HASH_Begin(hashContext);
+
+ ifstream* f = UIOpenRead(gReporterDumpFile, /* binary */ true);
+ bool error = false;
+
+ // Read the minidump contents
+ if (f->is_open()) {
+ uint8_t buff[4096];
+
+ do {
+ f->read((char*) buff, sizeof(buff));
+
+ if (f->bad()) {
+ error = true;
+ break;
+ }
+
+ HASH_Update(hashContext, buff, f->gcount());
+ } while (!f->eof());
+
+ f->close();
+ } else {
+ error = true;
+ }
+
+ delete f;
+
+ // Finalize the hash computation
+ uint8_t result[SHA256_LENGTH];
+ uint32_t resultLen = 0;
+
+ HASH_End(hashContext, result, &resultLen, SHA256_LENGTH);
+
+ if (resultLen != SHA256_LENGTH) {
+ error = true;
+ }
+
+ HASH_Destroy(hashContext);
+
+ if (!error) {
+ ostringstream hash;
+
+ for (size_t i = 0; i < SHA256_LENGTH; i++) {
+ hash << std::setw(2) << std::setfill('0') << std::hex
+ << static_cast<unsigned int>(result[i]);
+ }
+
+ return hash.str();
+ } else {
+ return ""; // If we encountered an error, return an empty hash
+ }
+}
+
} // namespace CrashReporter
using namespace CrashReporter;
void RewriteStrings(StringTable& queryParameters)
{
// rewrite some UI strings with the values from the query parameters
string product = queryParameters["ProductName"];
@@ -636,19 +755,23 @@ int main(int argc, char** argv)
gEventsPath = eventsPath;
}
#endif
else {
gEventsPath.clear();
}
// Assemble and send the crash ping
+ string hash;
string pingUuid;
- if (SendCrashPing(queryParameters, pingUuid)) {
+ hash = ComputeDumpHash();
+ AppendToEventFile("MinidumpSha256Hash", hash);
+
+ if (SendCrashPing(queryParameters, hash, pingUuid)) {
AppendToEventFile("CrashPingUUID", pingUuid);
}
// Update the crash event with stacks if they are present
auto stackTracesItr = queryParameters.find("StackTraces");
if (stackTracesItr != queryParameters.end()) {
AppendToEventFile(stackTracesItr->first, stackTracesItr->second);
}
--- a/toolkit/crashreporter/client/crashreporter.h
+++ b/toolkit/crashreporter/client/crashreporter.h
@@ -113,17 +113,18 @@ namespace CrashReporter {
const std::string& header,
StringTable& strings,
bool escape);
void LogMessage(const std::string& message);
void DeleteDump();
bool ShouldEnableSending();
// Telemetry ping
- bool SendCrashPing(StringTable& strings, std::string& pingUuid);
+ bool SendCrashPing(StringTable& strings, const std::string& hash,
+ std::string& pingUuid);
static const unsigned int kSaveCount = 10;
}
//=============================================================================
// implemented in the platform-specific files
//=============================================================================
@@ -146,17 +147,17 @@ void UIError_impl(const std::string& mes
bool UIGetIniPath(std::string& path);
bool UIGetSettingsPath(const std::string& vendor,
const std::string& product,
std::string& settingsPath);
bool UIEnsurePathExists(const std::string& path);
bool UIFileExists(const std::string& path);
bool UIMoveFile(const std::string& oldfile, const std::string& newfile);
bool UIDeleteFile(const std::string& oldfile);
-std::ifstream* UIOpenRead(const std::string& filename);
+std::ifstream* UIOpenRead(const std::string& filename, bool binary = false);
std::ofstream* UIOpenWrite(const std::string& filename,
bool append=false,
bool binary=false);
void UIPruneSavedDumps(const std::string& directory);
// Run the program specified by exename, passing it the parameter in arg and
// then sending it the contents of data (if any) via a pipe tied to its stdin.
// If wait is true, wait for the program to terminate execution before
--- a/toolkit/crashreporter/client/crashreporter_gtk_common.cpp
+++ b/toolkit/crashreporter/client/crashreporter_gtk_common.cpp
@@ -425,19 +425,25 @@ bool UIMoveFile(const string& file, cons
return UIFileExists(newfile);
}
bool UIDeleteFile(const string& file)
{
return (unlink(file.c_str()) != -1);
}
-std::ifstream* UIOpenRead(const string& filename)
+std::ifstream* UIOpenRead(const string& filename, bool binary)
{
- return new std::ifstream(filename.c_str(), std::ios::in);
+ std::ios_base::openmode mode = std::ios::in;
+
+ if (binary) {
+ mode = mode | std::ios::binary;
+ }
+
+ return new std::ifstream(filename.c_str(), mode);
}
std::ofstream* UIOpenWrite(const string& filename,
bool append, // append=false
bool binary) // binary=false
{
std::ios_base::openmode mode = std::ios::out;
--- a/toolkit/crashreporter/client/crashreporter_osx.mm
+++ b/toolkit/crashreporter/client/crashreporter_osx.mm
@@ -894,19 +894,25 @@ bool UIMoveFile(const string& file, cons
return UIFileExists(newfile);
}
bool UIDeleteFile(const string& file)
{
return (unlink(file.c_str()) != -1);
}
-std::ifstream* UIOpenRead(const string& filename)
+std::ifstream* UIOpenRead(const string& filename, bool binary)
{
- return new std::ifstream(filename.c_str(), std::ios::in);
+ std::ios_base::openmode mode = std::ios::in;
+
+ if (binary) {
+ mode = mode | std::ios::binary;
+ }
+
+ return new std::ifstream(filename.c_str(), mode);
}
std::ofstream* UIOpenWrite(const string& filename,
bool append, // append=false
bool binary) // binary=false
{
std::ios_base::openmode mode = std::ios::out;
--- a/toolkit/crashreporter/client/crashreporter_win.cpp
+++ b/toolkit/crashreporter/client/crashreporter_win.cpp
@@ -1458,26 +1458,31 @@ bool UIMoveFile(const string& oldfile, c
== TRUE;
}
bool UIDeleteFile(const string& oldfile)
{
return DeleteFile(UTF8ToWide(oldfile).c_str()) == TRUE;
}
-ifstream* UIOpenRead(const string& filename)
+ifstream* UIOpenRead(const string& filename, bool binary)
{
// adapted from breakpad's src/common/windows/http_upload.cc
+ std::ios_base::openmode mode = ios::in;
+
+ if (binary) {
+ mode = mode | ios::binary;
+ }
#if defined(_MSC_VER)
ifstream* file = new ifstream();
- file->open(UTF8ToWide(filename).c_str(), ios::in);
+ file->open(UTF8ToWide(filename).c_str(), mode);
#else // GCC
ifstream* file = new ifstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(),
- ios::in);
+ mode);
#endif // _MSC_VER
return file;
}
ofstream* UIOpenWrite(const string& filename,
bool append, // append=false
bool binary) // binary=false
--- a/toolkit/crashreporter/client/moz.build
+++ b/toolkit/crashreporter/client/moz.build
@@ -23,16 +23,17 @@ if CONFIG['OS_TARGET'] != 'Android':
if CONFIG['OS_ARCH'] == 'WINNT':
UNIFIED_SOURCES += [
'crashreporter_win.cpp',
]
DEFINES['UNICODE'] = True
DEFINES['_UNICODE'] = True
USE_LIBS += [
'google_breakpad_libxul_s',
+ 'nss',
]
OS_LIBS += [
'comctl32',
'ole32',
'shell32',
'wininet',
'shlwapi',
]
@@ -43,16 +44,17 @@ elif CONFIG['OS_ARCH'] == 'Darwin':
]
LOCAL_INCLUDES += [
'../google-breakpad/src/common/mac',
]
OS_LIBS += ['-framework Cocoa']
USE_LIBS += [
'breakpad_common_s',
'breakpad_mac_common_s',
+ 'nss',
]
elif CONFIG['OS_ARCH'] == 'SunOS':
SOURCES += [
'crashreporter_linux.cpp',
'crashreporter_unix.cpp',
]
USE_LIBS += [
'breakpad_solaris_common_s',
--- a/toolkit/crashreporter/client/ping.cpp
+++ b/toolkit/crashreporter/client/ping.cpp
@@ -158,25 +158,27 @@ CreateMetadataNode(StringTable& strings)
}
}
return node;
}
// Create the payload node of the crash ping
static Json::Value
-CreatePayloadNode(StringTable& strings, const string& aSessionId)
+CreatePayloadNode(StringTable& strings, const string& aHash,
+ const string& aSessionId)
{
Json::Value payload;
payload["sessionId"] = aSessionId;
payload["version"] = 1;
payload["crashDate"] = CurrentDate(kISO8601Date);
payload["hasCrashEnvironment"] = true;
payload["crashId"] = GetDumpLocalID();
+ payload["minidumpSha256Hash"] = aHash;
payload["processType"] = "main"; // This is always a main crash
// Parse the stack traces
Json::Value stackTracesValue;
Json::Reader reader;
if (reader.parse(strings["StackTraces"], stackTracesValue,
/* collectComments */ false)) {
@@ -212,17 +214,17 @@ CreateApplicationNode(const string& aVen
application["xpcomAbi"] = aXpcomAbi;
}
return application;
}
// Create the root node of the crash ping
static Json::Value
-CreateRootNode(StringTable& strings, const string& aUuid,
+CreateRootNode(StringTable& strings, const string& aUuid, const string& aHash,
const string& aClientId, const string& aSessionId,
const string& aName, const string& aVersion,
const string& aChannel, const string& aBuildId)
{
Json::Value root;
root["type"] = "crash"; // This is a crash ping
root["id"] = aUuid;
root["version"] = kTelemetryVersion;
@@ -245,17 +247,17 @@ CreateRootNode(StringTable& strings, con
if (build.isMember("xpcomAbi") && build["xpcomAbi"].isString()) {
xpcomAbi = build["xpcomAbi"].asString();
}
}
root["environment"] = environment;
}
- root["payload"] = CreatePayloadNode(strings, aSessionId);
+ root["payload"] = CreatePayloadNode(strings, aHash, aSessionId);
root["application"] = CreateApplicationNode(strings["Vendor"], aName,
aVersion, aChannel, aBuildId,
architecture, xpcomAbi);
return root;
}
// Generates the URL used to submit the crash ping, see TelemetrySend.jsm
@@ -275,17 +277,17 @@ GenerateSubmissionUrl(const string& aUrl
// together with the crash report.
//
// Note that the crash ping sender is invoked in a fire-and-forget way so this
// won't block waiting for the ping to be delivered.
//
// Returns true if the ping was assembled and handed over to the pingsender
// correctly, false otherwise and populates the aUUID field with the ping UUID.
bool
-SendCrashPing(StringTable& strings, string& pingUuid)
+SendCrashPing(StringTable& strings, const string& aHash, string& pingUuid)
{
string clientId = strings[kTelemetryClientId];
string serverUrl = strings[kTelemetryUrl];
string sessionId = strings[kTelemetrySessionId];
// Remove the telemetry-related data from the crash annotations
strings.erase(kTelemetryClientId);
strings.erase(kTelemetryUrl);
@@ -298,17 +300,17 @@ SendCrashPing(StringTable& strings, stri
string uuid = GenerateUUID();
string url = GenerateSubmissionUrl(serverUrl, uuid, name, version,
channel, buildId);
if (serverUrl.empty() || uuid.empty()) {
return false;
}
- Json::Value root = CreateRootNode(strings, uuid, clientId, sessionId,
+ Json::Value root = CreateRootNode(strings, uuid, aHash, clientId, sessionId,
name, version, channel, buildId);
// Write out the result
Json::FastWriter writer;
string ping = writer.write(root);
// Hand over the ping to the sender
if (UIRunProgram(GetProgramPath(UI_PING_SENDER_FILENAME), url, ping)) {
--- a/toolkit/crashreporter/docs/index.rst
+++ b/toolkit/crashreporter/docs/index.rst
@@ -91,17 +91,18 @@ the time of the crash, memory statistics
If the *crash reporter client* is enabled, ``MinidumpCallback()`` invokes
it. It simply tries to create a new *crash reporter client* process (e.g.
*crashreporter.exe*) with the path to the written minidump file as an
argument.
The *crash reporter client* performs a number of roles. There's a lot going
on, so you may want to look at ``main()`` in ``crashreporter.cpp``. First,
stack traces are extracted from the dump via the *minidump analyzer* tool.
-The resulting traces are appended to the .extra file of the crash. Once this
+The resulting traces are appended to the .extra file of the crash together with
+the SHA256 hash of the minidump file. Once this
is done a crash ping is assembled holding the same information as the one
generated by the ```CrashManager``` and it's sent to the telemetry servers via
the *ping sender* program. The UUID of the ping is then stored in the extra
file; the ```CrashManager``` will later pick it up and generate a new ping
with the same UUID so that the telemetry server can deduplicate both pings.
Then, the
*crash reporter client* verifies that the dump data is sane. If it isn't
(e.g. required metadata is missing), the dump data is ignored. If dump data
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -182,16 +182,19 @@ static const char kCrashMainID[] = "cras
static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
static XP_CHAR* pendingDirectory;
static XP_CHAR* crashReporterPath;
static XP_CHAR* memoryReportPath;
#if !defined(MOZ_WIDGET_ANDROID)
static XP_CHAR* minidumpAnalyzerPath;
+#ifdef XP_MACOSX
+static XP_CHAR* libraryPath; // Path where the NSS library is
+#endif // XP_MACOSX
#endif // !defined(MOZ_WIDGET_ANDROID)
// Where crash events should go.
static XP_CHAR* eventsDirectory;
static char* eventsEnv = nullptr;
// The current telemetry session ID to write to the event file
static char* currentSessionId = nullptr;
@@ -387,30 +390,16 @@ JitExceptionHandler(void *exceptionRecor
*
* This size is bigger than xul.dll plus some extra for MinidumpWriteDump
* allocations.
*/
static const SIZE_T kReserveSize = 0x4000000; // 64 MB
static void* gBreakpadReservedVM;
#endif
-#ifdef XP_MACOSX
-static cpu_type_t pref_cpu_types[2] = {
-#if defined(__i386__)
- CPU_TYPE_X86,
-#elif defined(__x86_64__)
- CPU_TYPE_X86_64,
-#elif defined(__ppc__)
- CPU_TYPE_POWERPC,
-#endif
- CPU_TYPE_ANY };
-
-static posix_spawnattr_t spawnattr;
-#endif
-
#if defined(MOZ_WIDGET_ANDROID)
// Android builds use a custom library loader,
// so the embedding will provide a list of shared
// libraries that are mapped into anonymous mappings.
typedef struct {
std::string name;
uintptr_t start_address;
size_t length;
@@ -836,55 +825,37 @@ LaunchProgram(const XP_CHAR* aProgramPat
if (aWait) {
WaitForSingleObject(pi.hProcess, INFINITE);
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
#elif defined(XP_UNIX)
-#ifdef XP_MACOSX
- pid_t pid = 0;
- char* const my_argv[] = {
- const_cast<char*>(aProgramPath),
- const_cast<char*>(aMinidumpPath),
- nullptr
- };
-
- char **env = nullptr;
- char ***nsEnv = _NSGetEnviron();
- if (nsEnv)
- env = *nsEnv;
-
- int rv = posix_spawnp(&pid, my_argv[0], nullptr, &spawnattr, my_argv, env);
-
- if (rv != 0) {
- return false;
- } else if (aWait) {
- waitpid(pid, nullptr, 0);
- }
-
-#else // !XP_MACOSX
pid_t pid = sys_fork();
if (pid == -1) {
return false;
} else if (pid == 0) {
+#ifdef XP_LINUX
// need to clobber this, as libcurl might load NSS,
// and we want it to load the system NSS.
unsetenv("LD_LIBRARY_PATH");
+#else // XP_MACOSX
+ // Needed to locate NSS and its dependencies
+ setenv("DYLD_LIBRARY_PATH", libraryPath, /* overwrite */ 1);
+#endif
Unused << execl(aProgramPath,
aProgramPath, aMinidumpPath, (char*)0);
_exit(1);
} else {
if (aWait) {
- sys_waitpid(pid, nullptr, __WALL);
+ waitpid(pid, nullptr, 0);
}
}
-#endif // XP_MACOSX
#endif // XP_UNIX
return true;
}
#else
/**
@@ -1644,32 +1615,49 @@ nsresult SetExceptionHandler(nsIFile* aX
nsAutoString crashReporterPath_temp;
nsresult rv = LocateExecutable(aXREDirectory,
NS_LITERAL_CSTRING(CRASH_REPORTER_FILENAME),
crashReporterPath_temp);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
+#ifdef XP_MACOSX
+ nsCOMPtr<nsIFile> libPath;
+ rv = aXREDirectory->Clone(getter_AddRefs(libPath));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString libraryPath_temp;
+ rv = libPath->GetPath(libraryPath_temp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+#endif // XP_MACOSX
+
nsAutoString minidumpAnalyzerPath_temp;
rv = LocateExecutable(aXREDirectory,
NS_LITERAL_CSTRING(MINIDUMP_ANALYZER_FILENAME),
minidumpAnalyzerPath_temp);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
#ifdef XP_WIN32
crashReporterPath =
reinterpret_cast<wchar_t*>(ToNewUnicode(crashReporterPath_temp));
minidumpAnalyzerPath =
reinterpret_cast<wchar_t*>(ToNewUnicode(minidumpAnalyzerPath_temp));
#else
crashReporterPath = ToNewCString(crashReporterPath_temp);
minidumpAnalyzerPath = ToNewCString(minidumpAnalyzerPath_temp);
+#ifdef XP_MACOSX
+ libraryPath = ToNewCString(libraryPath_temp);
+#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);
@@ -1687,35 +1675,16 @@ nsresult SetExceptionHandler(nsIFile* aX
nsString tempPath;
#else
nsCString tempPath;
#endif
if (!BuildTempPath(tempPath)) {
return NS_ERROR_FAILURE;
}
-#ifdef XP_MACOSX
- // Initialize spawn attributes, since this calls malloc.
- if (posix_spawnattr_init(&spawnattr) != 0) {
- return NS_ERROR_FAILURE;
- }
-
- // Set spawn attributes.
- size_t attr_count = ArrayLength(pref_cpu_types);
- size_t attr_ocount = 0;
- if (posix_spawnattr_setbinpref_np(&spawnattr,
- attr_count,
- pref_cpu_types,
- &attr_ocount) != 0 ||
- attr_ocount != attr_count) {
- posix_spawnattr_destroy(&spawnattr);
- return NS_ERROR_FAILURE;
- }
-#endif
-
#ifdef XP_WIN32
ReserveBreakpadVM();
#endif // XP_WIN32
#ifdef MOZ_WIDGET_ANDROID
androidUserSerial = getenv("MOZ_ANDROID_USER_SERIAL_NUMBER");
#endif
@@ -2136,35 +2105,44 @@ nsresult UnsetExceptionHandler()
pendingDirectory = nullptr;
}
if (crashReporterPath) {
free(crashReporterPath);
crashReporterPath = nullptr;
}
+#if !defined(MOZ_WIDGET_ANDROID)
+ if (minidumpAnalyzerPath) {
+ free(minidumpAnalyzerPath);
+ minidumpAnalyzerPath = nullptr;
+ }
+#ifdef XP_MACOSX
+ if (libraryPath) {
+ free(libraryPath);
+ libraryPath = nullptr;
+ }
+#endif // XP_MACOSX
+#endif // !defined(MOZ_WIDGET_ANDROID)
+
if (eventsDirectory) {
free(eventsDirectory);
eventsDirectory = nullptr;
}
if (currentSessionId) {
free(currentSessionId);
currentSessionId = nullptr;
}
if (memoryReportPath) {
free(memoryReportPath);
memoryReportPath = nullptr;
}
-#ifdef XP_MACOSX
- posix_spawnattr_destroy(&spawnattr);
-#endif
-
if (!gExceptionHandler)
return NS_ERROR_NOT_INITIALIZED;
gExceptionHandler = nullptr;
OOPDeinit();
delete dumpSafetyLock;
@@ -2950,53 +2928,67 @@ GetMinidumpLimboDir(nsIFile** dir)
return nullptr != *dir;
}
}
void
DeleteMinidumpFilesForID(const nsAString& id)
{
nsCOMPtr<nsIFile> minidumpFile;
- GetMinidumpForID(id, getter_AddRefs(minidumpFile));
- bool exists = false;
- if (minidumpFile && NS_SUCCEEDED(minidumpFile->Exists(&exists)) && exists) {
+ if (GetMinidumpForID(id, getter_AddRefs(minidumpFile))) {
nsCOMPtr<nsIFile> childExtraFile;
GetExtraFileForMinidump(minidumpFile, getter_AddRefs(childExtraFile));
if (childExtraFile) {
childExtraFile->Remove(false);
}
minidumpFile->Remove(false);
}
}
bool
GetMinidumpForID(const nsAString& id, nsIFile** minidump)
{
- if (!GetMinidumpLimboDir(minidump))
+ if (!GetMinidumpLimboDir(minidump)) {
return false;
+ }
+
(*minidump)->Append(id + NS_LITERAL_STRING(".dmp"));
+
+ bool exists;
+ if (NS_FAILED((*minidump)->Exists(&exists)) || !exists) {
+ return false;
+ }
+
return true;
}
bool
GetIDFromMinidump(nsIFile* minidump, nsAString& id)
{
if (minidump && NS_SUCCEEDED(minidump->GetLeafName(id))) {
id.Replace(id.Length() - 4, 4, NS_LITERAL_STRING(""));
return true;
}
return false;
}
bool
GetExtraFileForID(const nsAString& id, nsIFile** extraFile)
{
- if (!GetMinidumpLimboDir(extraFile))
+ if (!GetMinidumpLimboDir(extraFile)) {
return false;
+ }
+
(*extraFile)->Append(id + NS_LITERAL_STRING(".extra"));
+
+ bool exists;
+ if (NS_FAILED((*extraFile)->Exists(&exists)) || !exists) {
+ return false;
+ }
+
return true;
}
bool
GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile)
{
nsAutoString leafName;
nsresult rv = minidump->GetLeafName(leafName);
@@ -3037,17 +3029,17 @@ AppendExtraData(const nsAString& id, con
* do not call it from the main thread!
*/
void
RunMinidumpAnalyzer(const nsAString& id)
{
#if !defined(MOZ_WIDGET_ANDROID)
nsCOMPtr<nsIFile> file;
- if (CrashReporter::GetMinidumpForID(id, getter_AddRefs(file)) && file) {
+ if (CrashReporter::GetMinidumpForID(id, getter_AddRefs(file))) {
#ifdef XP_WIN
nsAutoString path;
file->GetPath(path);
#else
nsAutoCString path;
file->GetNativePath(path);
#endif
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -1235,16 +1235,36 @@ nsXULAppInfo::SetMinidumpPath(nsIFile* a
{
nsAutoString path;
nsresult rv = aMinidumpPath->GetPath(path);
NS_ENSURE_SUCCESS(rv, rv);
return CrashReporter::SetMinidumpPath(path);
}
NS_IMETHODIMP
+nsXULAppInfo::GetMinidumpForID(const nsAString& aId, nsIFile** aMinidump)
+{
+ if (!CrashReporter::GetMinidumpForID(aId, aMinidump)) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULAppInfo::GetExtraFileForID(const nsAString& aId, nsIFile** aExtraFile)
+{
+ if (!CrashReporter::GetExtraFileForID(aId, aExtraFile)) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
nsXULAppInfo::AnnotateCrashReport(const nsACString& key,
const nsACString& data)
{
return CrashReporter::AnnotateCrashReport(key, data);
}
NS_IMETHODIMP
nsXULAppInfo::AppendAppNotesToCrashReport(const nsACString& data)
--- a/xpcom/system/nsICrashReporter.idl
+++ b/xpcom/system/nsICrashReporter.idl
@@ -44,16 +44,40 @@ interface nsICrashReporter : nsISupports
* Get or set the path on the local system to which minidumps will be
* written when a crash happens.
*
* @throw NS_ERROR_NOT_INITIALIZED if crash reporting is not initialized
*/
attribute nsIFile minidumpPath;
/**
+ * Get the minidump file corresponding to the specified ID.
+ *
+ * @param id
+ * ID of the crash. Likely a UUID.
+ *
+ * @return The minidump file associated with the ID.
+ *
+ * @throw NS_ERROR_FILE_NOT_FOUND if the minidump could not be found
+ */
+ nsIFile getMinidumpForID(in AString id);
+
+ /**
+ * Get the extra file corresponding to the specified ID.
+ *
+ * @param id
+ * ID of the crash. Likely a UUID.
+ *
+ * @return The extra file associated with the ID.
+ *
+ * @throw NS_ERROR_FILE_NOT_FOUND if the extra file could not be found
+ */
+ nsIFile getExtraFileForID(in AString id);
+
+ /**
* Add some extra data to be submitted with a crash report.
*
* @param key
* Name of the data to be added.
* @param data
* Data to be added.
*
* @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized