Bug 1359326 - Run the minidump analyzer directly from the CrashService code; r?bsmedberg draft
authorGabriele Svelto <gsvelto@mozilla.com>
Thu, 11 May 2017 14:03:50 +0200
changeset 584977 84174af9d3aa3a471fe7c5894803db7f090898d4
parent 578706 49365d675cbb2a8368cda3e4858a2a0d0634c249
child 584978 35266a2f3f1ad510dad3e550662dfa2c477f7f1d
push id60969
push usergsvelto@mozilla.com
push dateFri, 26 May 2017 09:58:29 +0000
reviewersbsmedberg
bugs1359326, 1322611, 1280477
milestone55.0a1
Bug 1359326 - Run the minidump analyzer directly from the CrashService code; r?bsmedberg This patch removes the C++ code used to run the minidump analyzer when a content process crashes, and replaces it with JS code within the CrashService object. This removes the need for a separate shutdown blocker in C++ code and allows end-to-end testing of the crash service functionality. Additionally the exception handler code can be simplified since it's now only used to run the crash reporter client. The test added to test_crash_service.js covers computing the minidump SHA256 hash (bug 1322611) and of the minidump analyzer itself (bug 1280477). MozReview-Commit-ID: LO5w839NHev
ipc/glue/CrashReporterHost.cpp
toolkit/components/crashes/CrashManagerTest.jsm
toolkit/components/crashes/CrashService.js
toolkit/components/crashes/nsICrashService.idl
toolkit/components/crashes/tests/xpcshell/crash.dmp
toolkit/components/crashes/tests/xpcshell/crash.extra
toolkit/components/crashes/tests/xpcshell/test_crash_service.js
toolkit/components/crashes/tests/xpcshell/xpcshell.ini
toolkit/crashreporter/nsExceptionHandler.cpp
toolkit/crashreporter/nsExceptionHandler.h
toolkit/crashreporter/test/unit/head_crashreporter.js
--- a/ipc/glue/CrashReporterHost.cpp
+++ b/ipc/glue/CrashReporterHost.cpp
@@ -1,22 +1,20 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CrashReporterHost.h"
 #include "CrashReporterMetadataShmem.h"
-#include "mozilla/LazyIdleThread.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/SyncRunnable.h"
 #include "mozilla/Telemetry.h"
 #ifdef MOZ_CRASHREPORTER
-# include "nsIAsyncShutdown.h"
 # include "nsICrashService.h"
 #endif
 
 namespace mozilla {
 namespace ipc {
 
 CrashReporterHost::CrashReporterHost(GeckoProcessType aProcessType,
                                      const Shmem& aShmem,
@@ -100,177 +98,38 @@ CrashReporterHost::FinalizeCrashReport()
   // Use mExtraNotes, since NotifyCrashService looks for "PluginHang" which is
   // set in the parent process.
   NotifyCrashService(mProcessType, mDumpID, &mExtraNotes);
 
   mFinalized = true;
   return true;
 }
 
-/**
- * Runnable used to execute the minidump analyzer program asynchronously after
- * a crash. This should run on a background thread not to block the main thread
- * over the potentially long minidump analysis. Once analysis is over, the
- * crash information will be relayed to the crash manager via another runnable
- * sent to the main thread. Shutdown will be blocked for the duration of the
- * entire process to ensure this information is sent.
- */
-class AsyncMinidumpAnalyzer final : public nsIRunnable
-                                  , public nsIAsyncShutdownBlocker
-{
-public:
-  /**
-   * Create a new minidump analyzer runnable, this will also block shutdown
-   * until the associated crash has been added to the crash manager.
-   */
-  AsyncMinidumpAnalyzer(int32_t aProcessType,
-                        int32_t aCrashType,
-                        const nsString& aChildDumpID)
-    : mProcessType(aProcessType)
-    , mCrashType(aCrashType)
-    , mChildDumpID(aChildDumpID)
-    , mName(NS_LITERAL_STRING("Crash reporter: blocking on minidump analysis"))
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    nsresult rv = GetShutdownBarrier()->AddBlocker(
-      this, NS_LITERAL_STRING(__FILE__), __LINE__,
-      NS_LITERAL_STRING("Minidump analysis"));
-    Unused << NS_WARN_IF(NS_FAILED(rv));
-  }
-
-  NS_IMETHOD Run() override
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-
-    if (mProcessType == nsICrashService::PROCESS_TYPE_CONTENT ||
-        mProcessType == nsICrashService::PROCESS_TYPE_GPU) {
-      CrashReporter::RunMinidumpAnalyzer(mChildDumpID);
-    }
-
-    // Make a copy of these so we can copy them into the runnable lambda
-    int32_t processType = mProcessType;
-    int32_t crashType = mCrashType;
-    nsString childDumpID(mChildDumpID);
-    nsCOMPtr<nsIAsyncShutdownBlocker> self(this);
-
-    NS_DispatchToMainThread(NS_NewRunnableFunction([
-      self, processType, crashType, childDumpID
-    ] {
-      nsCOMPtr<nsICrashService> crashService =
-        do_GetService("@mozilla.org/crashservice;1");
-      if (crashService) {
-        crashService->AddCrash(processType, crashType, childDumpID);
-      }
-
-      nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
-
-      if (barrier) {
-        barrier->RemoveBlocker(self);
-      }
-    }));
-
-    return NS_OK;
-  }
-
-  NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient* aBarrierClient) override
-  {
-    return NS_OK;
-  }
-
-  NS_IMETHOD GetName(nsAString& aName) override
-  {
-    aName = mName;
-    return NS_OK;
-  }
-
-  NS_IMETHOD GetState(nsIPropertyBag**) override
-  {
-    return NS_OK;
-  }
-
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-private:
-  ~AsyncMinidumpAnalyzer() {}
-
-  // Returns the profile-before-change shutdown barrier
-  static nsCOMPtr<nsIAsyncShutdownClient> GetShutdownBarrier()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown();
-    nsCOMPtr<nsIAsyncShutdownClient> barrier;
-    nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(barrier));
-
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return nullptr;
-    }
-
-    return barrier.forget();
-  }
-
-  int32_t mProcessType;
-  int32_t mCrashType;
-  const nsString mChildDumpID;
-  const nsString mName;
-};
-
-NS_IMPL_ISUPPORTS(AsyncMinidumpAnalyzer, nsIRunnable, nsIAsyncShutdownBlocker)
-
-/**
- * Add information about a crash to the crash manager. This method runs the
- * various activities required to gather additional information and notify the
- * crash manager asynchronously, since many of them involve I/O or potentially
- * long processing.
- *
- * @param aProcessType The type of process that crashed
- * @param aCrashType The type of crash (crash or hang)
- * @param aChildDumpID A string holding the ID of the dump associated with this
- *        crash
- */
-/* static */ void
-CrashReporterHost::AsyncAddCrash(int32_t aProcessType,
-                                 int32_t aCrashType,
-                                 const nsString& aChildDumpID)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  static StaticRefPtr<LazyIdleThread> sBackgroundThread;
-
-  if (!sBackgroundThread) {
-    // Background thread used for running minidump analysis. It will be
-    // destroyed immediately after it's done with the task.
-    sBackgroundThread =
-      new LazyIdleThread(0, NS_LITERAL_CSTRING("CrashReporterHost"));
-    ClearOnShutdown(&sBackgroundThread);
-  }
-
-  RefPtr<AsyncMinidumpAnalyzer> task =
-    new AsyncMinidumpAnalyzer(aProcessType, aCrashType, aChildDumpID);
-
-  Unused << sBackgroundThread->Dispatch(task, NS_DISPATCH_NORMAL);
-}
-
 /* static */ void
 CrashReporterHost::NotifyCrashService(GeckoProcessType aProcessType,
                                       const nsString& aChildDumpID,
                                       const AnnotationTable* aNotes)
 {
   if (!NS_IsMainThread()) {
     RefPtr<Runnable> runnable = NS_NewRunnableFunction([=] () -> void {
       CrashReporterHost::NotifyCrashService(aProcessType, aChildDumpID, aNotes);
     });
     RefPtr<nsIThread> mainThread = do_GetMainThread();
     SyncRunnable::DispatchToThread(mainThread, runnable);
     return;
   }
 
   MOZ_ASSERT(!aChildDumpID.IsEmpty());
 
+  nsCOMPtr<nsICrashService> crashService =
+    do_GetService("@mozilla.org/crashservice;1");
+  if (!crashService) {
+    return;
+  }
+
   int32_t processType;
   int32_t crashType = nsICrashService::CRASH_TYPE_CRASH;
 
   nsCString telemetryKey;
 
   switch (aProcessType) {
     case GeckoProcessType_Content:
       processType = nsICrashService::PROCESS_TYPE_CONTENT;
@@ -295,17 +154,18 @@ CrashReporterHost::NotifyCrashService(Ge
       processType = nsICrashService::PROCESS_TYPE_GPU;
       telemetryKey.AssignLiteral("gpu");
       break;
     default:
       NS_ERROR("unknown process type");
       return;
   }
 
-  AsyncAddCrash(processType, crashType, aChildDumpID);
+  nsCOMPtr<nsISupports> promise;
+  crashService->AddCrash(processType, crashType, aChildDumpID, getter_AddRefs(promise));
   Telemetry::Accumulate(Telemetry::SUBPROCESS_CRASHES_WITH_DUMP, telemetryKey, 1);
 }
 
 void
 CrashReporterHost::AddNote(const nsCString& aKey, const nsCString& aValue)
 {
   mExtraNotes.Put(aKey, aValue);
 }
--- a/toolkit/components/crashes/CrashManagerTest.jsm
+++ b/toolkit/components/crashes/CrashManagerTest.jsm
@@ -173,13 +173,14 @@ this.getManager = function() {
     // paths are triggered.
     let storeD = await makeDir(false);
 
     let m = new TestingCrashManager({
       pendingDumpsDir: pendingD,
       submittedDumpsDir: submittedD,
       eventsDirs: [eventsD1, eventsD2],
       storeDir: storeD,
+      telemetryStoreSizeKey: "CRASH_STORE_COMPRESSED_BYTES",
     });
 
     return m;
   })();
 };
--- a/toolkit/components/crashes/CrashService.js
+++ b/toolkit/components/crashes/CrashService.js
@@ -1,85 +1,101 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
+Cu.import("resource://gre/modules/AppConstants.jsm", this);
 Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
 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/XPCOMUtils.jsm", this);
 
 /**
- * Computes the SHA256 hash of the minidump file associated with a crash
+ * Run the minidump analyzer tool to gather stack traces from the minidump. The
+ * stack traces will be stored in the .extra file under the StackTraces= entry.
+ *
+ * @param minidumpPath {string} The path to the minidump file
  *
- * @param crashID {string} Crash ID. Likely a UUID.
+ * @returns {Promise} A promise that gets resolved once minidump analysis has
+ *          finished.
+ */
+function runMinidumpAnalyzer(minidumpPath) {
+  return new Promise((resolve, reject) => {
+    const binSuffix = AppConstants.platform === "win" ? ".exe" : "";
+    const exeName = "minidump-analyzer" + binSuffix;
+
+    let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);
+    exe.append(exeName);
+
+    let args = [ minidumpPath ];
+    let process = Cc["@mozilla.org/process/util;1"]
+                    .createInstance(Ci.nsIProcess);
+    process.init(exe);
+    process.runAsync(args, args.length, (subject, topic, data) => {
+      switch (topic) {
+        case "process-finished":
+          resolve();
+          break;
+        default:
+          reject(topic);
+          break;
+      }
+    });
+  });
+}
+
+/**
+ * Computes the SHA256 hash of a minidump file
+ *
+ * @param minidumpPath {string} The path to the minidump file
  *
  * @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.
+ *          minidump.
  */
-function computeMinidumpHash(id) {
-  let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]
-             .getService(Components.interfaces.nsICrashReporter);
-
+function computeMinidumpHash(minidumpPath) {
   return (async function() {
-    try {
-      let minidumpFile = cr.getMinidumpForID(id);
-      let minidumpData = await 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 = "";
+    let minidumpData = await OS.File.read(minidumpPath);
+    let hasher = Cc["@mozilla.org/security/hash;1"]
+                   .createInstance(Ci.nsICryptoHash);
+    hasher.init(hasher.SHA256);
+    hasher.update(minidumpData, minidumpData.length);
 
-      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);
-      }
+    let hashBin = hasher.finish(false);
+    let hash = "";
 
-      return hash;
-    } catch (e) {
-      Cu.reportError(e);
-      return null;
+    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;
   })();
 }
 
 /**
- * Process the .extra file associated with the crash id and return the
- * annotations it contains in an object.
+ * Process the given .extra file and return the annotations it contains in an
+ * object.
  *
- * @param crashID {string} Crash ID. Likely a UUID.
+ * @param extraPath {string} The path to the .extra file
  *
  * @return {Promise} A promise that resolves to an object holding the crash
- *         annotations, this object may be empty if no annotations were found.
+ *         annotations.
  */
-function processExtraFile(id) {
-  let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]
-             .getService(Components.interfaces.nsICrashReporter);
-
+function processExtraFile(extraPath) {
   return (async function() {
-    try {
-      let extraFile = cr.getExtraFileForID(id);
-      let decoder = new TextDecoder();
-      let extraData = await OS.File.read(extraFile.path);
+    let decoder = new TextDecoder();
+    let extraData = await OS.File.read(extraPath);
 
-      return parseKeyValuePairs(decoder.decode(extraData));
-    } catch (e) {
-      Cu.reportError(e);
-      return {};
-    }
+    return parseKeyValuePairs(decoder.decode(extraData));
   })();
 }
 
 /**
  * This component makes crash data available throughout the application.
  *
  * It is a service because some background activity will eventually occur.
  */
@@ -120,32 +136,47 @@ CrashService.prototype = Object.freeze({
     case Ci.nsICrashService.CRASH_TYPE_HANG:
       crashType = Services.crashmanager.CRASH_TYPE_HANG;
       break;
     default:
       throw new Error("Unrecognized CRASH_TYPE: " + crashType);
     }
 
     let blocker = (async function() {
-      let metadata = await processExtraFile(id);
-      let hash = await computeMinidumpHash(id);
+      let metadata = {};
+      let hash = null;
+
+      try {
+        let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]
+                   .getService(Components.interfaces.nsICrashReporter);
+        let minidumpPath = cr.getMinidumpForID(id).path;
+        let extraPath = cr.getExtraFileForID(id).path;
+
+        await runMinidumpAnalyzer(minidumpPath);
+        metadata = await processExtraFile(extraPath);
+        hash = await computeMinidumpHash(minidumpPath);
+      } catch (e) {
+        Cu.reportError(e);
+      }
 
       if (hash) {
         metadata.MinidumpSha256Hash = hash;
       }
 
       await Services.crashmanager.addCrash(processType, crashType, id,
                                            new Date(), metadata);
     })();
 
     AsyncShutdown.profileBeforeChange.addBlocker(
       "CrashService waiting for content crash ping to be sent", blocker
     );
 
     blocker.then(AsyncShutdown.profileBeforeChange.removeBlocker(blocker));
+
+    return 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
@@ -14,17 +14,17 @@ interface nsICrashService : nsISupports
    *        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);
+  nsISupports 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;
 
   const long CRASH_TYPE_CRASH = 0;
new file mode 100644
index 0000000000000000000000000000000000000000..1a6feee7cc1f6a988c3810c325dc43df70562799
GIT binary patch
literal 11317
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
Vme*-9k@F4vkeDd$78Rb;{{lqd;6nfa
new file mode 100644
--- /dev/null
+++ b/toolkit/components/crashes/tests/xpcshell/crash.extra
@@ -0,0 +1,32 @@
+E10SCohort=unsupportedChannel
+ContentSandboxLevel=2
+TelemetryEnvironment={}
+EMCheckCompatibility=true
+ProductName=Firefox
+ContentSandboxCapabilities=119
+TelemetryClientId=
+Vendor=Mozilla
+InstallTime=1000000000
+Theme=classic/1.0
+ReleaseChannel=default
+AddonsShouldHaveBlockedE10s=1
+ServerURL=https://crash-reports.mozilla.com
+SafeMode=0
+ContentSandboxCapable=1
+useragent_locale=en-US
+Version=55.0a1
+BuildID=20170512114708
+ProductID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+TelemetryServerURL=
+DOMIPCEnabled=1
+Add-ons=
+CrashTime=1494582646
+UptimeTS=14.9179586
+ThreadIdNameMapping=
+ContentSandboxLevel=2
+ContentSandboxEnabled=1
+ProcessType=content
+DOMIPCEnabled=1
+StartupTime=1000000000
+URL=about:home
+ContentSandboxCapabilities=119
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_service.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_service.js
@@ -1,17 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
+Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://testing-common/AppData.jsm", this);
+Cu.import("resource://testing-common/CrashManagerTest.jsm", this);
 var bsp = Cu.import("resource://gre/modules/CrashManager.jsm", {});
 
 function run_test() {
   run_next_test();
 }
 
 add_task(async function test_instantiation() {
   Assert.ok(!bsp.gCrashManager, "CrashManager global instance not initially defined.");
@@ -24,8 +26,56 @@ add_task(async function test_instantiati
     .getService(Ci.nsIObserver)
     .observe(null, "profile-after-change", null);
 
   Assert.ok(bsp.gCrashManager, "Profile creation makes it available.");
   Assert.ok(Services.crashmanager, "CrashManager available via Services.");
   Assert.strictEqual(bsp.gCrashManager, Services.crashmanager,
                      "The objects are the same.");
 });
+
+add_task(async function test_addCrash() {
+  const crashId = "56cd87bc-bb26-339b-3a8e-f00c0f11380e";
+
+  let cwd = await OS.File.getCurrentDirectory();
+  let minidump = OS.Path.join(cwd, "crash.dmp");
+  let extra = OS.Path.join(cwd, "crash.extra");
+  let dir = do_get_tempdir();
+
+  // Make a copy of the files because the .extra file will be modified
+  await OS.File.copy(minidump, OS.Path.join(dir.path, crashId + ".dmp"));
+  await OS.File.copy(extra, OS.Path.join(dir.path, crashId + ".extra"));
+
+  // Ensure that the nsICrashReporter methods can find the dump
+  let crashReporter =
+      Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
+                .getService(Components.interfaces.nsICrashReporter);
+  crashReporter.minidumpPath = dir;
+
+  let cs = Cc["@mozilla.org/crashservice;1"].getService(Ci.nsICrashService);
+  await cs.addCrash(Ci.nsICrashService.PROCESS_TYPE_CONTENT,
+                    Ci.nsICrashService.CRASH_TYPE_CRASH, crashId);
+  let crashes = await Services.crashmanager.getCrashes();
+  let crash = crashes.find(c => { return c.id === crashId; });
+  Assert.ok(crash, "Crash " + crashId + " has been stored successfully.");
+  Assert.equal(crash.metadata.ProcessType, "content");
+  Assert.equal(crash.metadata.MinidumpSha256Hash,
+    "24b0ea7794b2d2523c46c9aea72c03ccbb0ab88ad76d8258d3752c7b71d233ff");
+  Assert.ok(crash.metadata.StackTraces, "The StackTraces field is present.\n")
+
+  try {
+    let stackTraces = JSON.parse(crash.metadata.StackTraces);
+    Assert.equal(stackTraces.status, "OK");
+    Assert.ok(stackTraces.crash_info, "The crash_info field is populated.");
+    Assert.ok(stackTraces.modules && (stackTraces.modules.length > 0),
+              "The module list is populated.");
+    Assert.ok(stackTraces.threads && (stackTraces.modules.length > 0),
+              "The thread list is populated.");
+
+    let frames = stackTraces.threads[0].frames;
+    Assert.ok(frames && (frames.length > 0), "The stack trace is present.\n");
+  } catch (e) {
+    Assert.ok(false, "StackTraces does not contain valid JSON.");
+  }
+
+  // Remove the minidumps to prevent the test harness from thinking we crashed
+  await OS.File.removeDir(dir.path);
+});
--- a/toolkit/components/crashes/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/crashes/tests/xpcshell/xpcshell.ini
@@ -1,7 +1,10 @@
 [DEFAULT]
 head =
 skip-if = toolkit == 'android'
+support-files =
+  crash.dmp
+  crash.extra
 
 [test_crash_manager.js]
 [test_crash_service.js]
 [test_crash_store.js]
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -192,22 +192,19 @@ static xpstring *defaultMemoryReportPath
 
 static const char kCrashMainID[] = "crash.main.2\n";
 
 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;
 
@@ -814,21 +811,19 @@ WriteGlobalMemoryStatus(PlatformWriter* 
 
 /**
  * Launches the program specified in aProgramPath with aMinidumpPath as its
  * sole argument.
  *
  * @param aProgramPath The path of the program to be launched
  * @param aMinidumpPath The path of the minidump file, passed as an argument
  *        to the launched program
- * @param aWait If true wait for the program termination
  */
 static bool
-LaunchProgram(const XP_CHAR* aProgramPath, const XP_CHAR* aMinidumpPath,
-              bool aWait = false)
+LaunchProgram(const XP_CHAR* aProgramPath, const XP_CHAR* aMinidumpPath)
 {
 #ifdef XP_WIN
   XP_CHAR cmdLine[CMDLINE_SIZE];
   XP_CHAR* p;
 
   size_t size = CMDLINE_SIZE;
   p = Concat(cmdLine, L"\"", &size);
   p = Concat(p, aProgramPath, &size);
@@ -839,20 +834,16 @@ LaunchProgram(const XP_CHAR* aProgramPat
   PROCESS_INFORMATION pi = {};
   STARTUPINFO si = {};
   si.cb = sizeof(si);
 
   // If CreateProcess() fails don't do anything
   if (CreateProcess(nullptr, (LPWSTR)cmdLine, nullptr, nullptr, FALSE,
                     NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
                     nullptr, nullptr, &si, &pi)) {
-    if (aWait) {
-      WaitForSingleObject(pi.hProcess, INFINITE);
-    }
-
     CloseHandle(pi.hProcess);
     CloseHandle(pi.hThread);
   }
 #elif defined(XP_UNIX)
   pid_t pid = sys_fork();
 
   if (pid == -1) {
     return false;
@@ -863,20 +854,16 @@ LaunchProgram(const XP_CHAR* aProgramPat
     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) {
-      waitpid(pid, nullptr, 0);
-    }
   }
 #endif // XP_UNIX
 
   return true;
 }
 
 #else
 
@@ -1692,32 +1679,21 @@ nsresult SetExceptionHandler(nsIFile* aX
 
     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.
@@ -2202,28 +2178,22 @@ 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);
@@ -3114,44 +3084,16 @@ bool
 AppendExtraData(const nsAString& id, const AnnotationTable& data)
 {
   nsCOMPtr<nsIFile> extraFile;
   if (!GetExtraFileForID(id, getter_AddRefs(extraFile)))
     return false;
   return AppendExtraData(extraFile, data);
 }
 
-/**
- * Runs the minidump analyzer program on the specified crash dump. The analyzer
- * will extract the stack traces from the dump and store them in JSON format as
- * an annotation in the extra file associated with the crash.
- *
- * This method waits synchronously for the program to have finished executing,
- * 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))) {
-#ifdef XP_WIN
-    nsAutoString path;
-    file->GetPath(path);
-#else
-    nsAutoCString path;
-    file->GetNativePath(path);
-#endif
-
-    LaunchProgram(minidumpAnalyzerPath, path.get(), /* aWait */ true);
-  }
-#endif // !defined(MOZ_WIDGET_ANDROID)
-}
-
 //-----------------------------------------------------------------------------
 // Helpers for AppendExtraData()
 //
 struct Blacklist {
   Blacklist() : mItems(nullptr), mLen(0) { }
   Blacklist(const char** items, int len) : mItems(items), mLen(len) { }
 
   bool Contains(const nsACString& key) const {
--- a/toolkit/crashreporter/nsExceptionHandler.h
+++ b/toolkit/crashreporter/nsExceptionHandler.h
@@ -98,17 +98,16 @@ typedef nsDataHashtable<nsCStringHashKey
 
 void DeleteMinidumpFilesForID(const nsAString& id);
 bool GetMinidumpForID(const nsAString& id, nsIFile** minidump);
 bool GetIDFromMinidump(nsIFile* minidump, nsAString& id);
 bool GetExtraFileForID(const nsAString& id, nsIFile** extraFile);
 bool GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile);
 bool AppendExtraData(const nsAString& id, const AnnotationTable& data);
 bool AppendExtraData(nsIFile* extraFile, const AnnotationTable& data);
-void RunMinidumpAnalyzer(const nsAString& id);
 
 /*
  * Renames the stand alone dump file aDumpFile to:
  *  |aOwnerDumpFile-aDumpFileProcessType.dmp|
  * and moves it into the same directory as aOwnerDumpFile. Does not
  * modify aOwnerDumpFile in any way.
  *
  * @param aDumpFile - the dump file to associate with aOwnerDumpFile.
--- a/toolkit/crashreporter/test/unit/head_crashreporter.js
+++ b/toolkit/crashreporter/test/unit/head_crashreporter.js
@@ -164,34 +164,34 @@ function do_content_crash(setup, callbac
   if (setup) {
     if (typeof(setup) == "function") {
       // funky, but convenient
       setup = "(" + setup.toSource() + ")();";
     }
   }
 
   let handleCrash = function() {
-    do_get_profile();
-    makeFakeAppDir().then(() => {
-      let id = getMinidump().leafName.slice(0, -4);
-      return Services.crashmanager.ensureCrashIsPresent(id);
-    }).then(() => {
+    let id = getMinidump().leafName.slice(0, -4);
+    Services.crashmanager.ensureCrashIsPresent(id).then(() => {
       try {
         handleMinidump(callback);
       } catch (x) {
         do_report_unexpected_exception(x);
       }
       do_test_finished();
     });
   };
 
-  sendCommand("load(\"" + headfile.path.replace(/\\/g, "/") + "\");", () =>
-    sendCommand(setup, () =>
-      sendCommand("load(\"" + tailfile.path.replace(/\\/g, "/") + "\");", () =>
-        do_execute_soon(handleCrash)
+  do_get_profile();
+  makeFakeAppDir().then(() => {
+    sendCommand("load(\"" + headfile.path.replace(/\\/g, "/") + "\");", () =>
+      sendCommand(setup, () =>
+        sendCommand("load(\"" + tailfile.path.replace(/\\/g, "/") + "\");", () =>
+          do_execute_soon(handleCrash)
+        )
       )
-    )
-  );
+    );
+  });
 }
 
 // Import binary APIs via js-ctypes.
 Components.utils.import("resource://test/CrashTestUtils.jsm");
 Components.utils.import("resource://gre/modules/KeyValueParser.jsm");