Bug 1307153 - Add stack traces to the crash pings in Fennec; r=jchen,ted.mielczarek
authorGabriele Svelto <gsvelto@mozilla.com>
Fri, 19 Jan 2018 16:48:00 +0100
changeset 458112 81c0cc6fe4c46021c8f0c17b2ef6bd482ab95d7f
parent 458111 7a805b66dfcc73e00eea543252830efc1ff6eb81
child 458113 49cb3c5cd110a4e3e7e339bb9b724ef777fe578e
push id8799
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 16:46:23 +0000
treeherdermozilla-beta@15334014dc67 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjchen, ted.mielczarek
bugs1307153
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1307153 - Add stack traces to the crash pings in Fennec; r=jchen,ted.mielczarek MozReview-Commit-ID: ZJKUwHFsuK
mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/MinidumpAnalyzer.java
mozglue/android/moz.build
mozglue/android/nsGeckoUtils.cpp
toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
toolkit/crashreporter/minidump-analyzer/minidump-analyzer.h
toolkit/crashreporter/minidump-analyzer/moz.build
--- a/mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
@@ -24,16 +24,18 @@ 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.zip.GZIPOutputStream;
 
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.mozglue.GeckoLoader;
+import org.mozilla.gecko.mozglue.MinidumpAnalyzer;
 import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCrashPingBuilder;
 import org.mozilla.gecko.telemetry.TelemetryDispatcher;
 import org.mozilla.gecko.util.INIParser;
 import org.mozilla.gecko.util.INISection;
 import org.mozilla.gecko.util.ProxySelector;
 
 import android.annotation.SuppressLint;
 import android.app.AlertDialog;
@@ -148,17 +150,30 @@ 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);
 
+        // Compute the minidump hash and generate the stack traces
         computeMinidumpHash(mPendingExtrasFile, mPendingMinidumpFile);
+
+        try {
+            GeckoLoader.loadMozGlue(this);
+
+            if (!MinidumpAnalyzer.GenerateStacks(mPendingMinidumpFile.getPath(), /* fullStacks */ false)) {
+                Log.e(LOGTAG, "Could not generate stacks for this minidump: " + passedMinidumpPath);
+            }
+        } catch (UnsatisfiedLinkError e) {
+            Log.e(LOGTAG, "Could not load libmozglue.so, stacks for this crash won't be generated");
+        }
+
+        // Extract the annotations from the .extra file
         mExtrasStringMap = new HashMap<String, String>();
         readStringsFromFile(mPendingExtrasFile.getPath(), mExtrasStringMap);
 
         try {
             // Find the profile name and path. Since we don't have any other way of getting it within
             // this context we extract it from the crash dump path.
             final File profileDir = passedMinidumpFile.getParentFile().getParentFile();
             final String profileName = getProfileName(profileDir);
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/MinidumpAnalyzer.java
@@ -0,0 +1,31 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.mozglue;
+
+/**
+ * JNI wrapper for accessing the minidump analyzer tool. This is used to
+ * generate stack traces and other process information from a crash minidump.
+ */
+public final class MinidumpAnalyzer {
+    private MinidumpAnalyzer() {
+        // prevent instantiation
+    }
+
+    /**
+     * Generate the stacks from the minidump file specified in minidumpPath
+     * and adds the StackTraces annotation to the associated .extra file.
+     * If fullStacks is false then only the stack trace for the crashing thread
+     * will be generated, otherwise stacks will be generated for all threads.
+     *
+     * This JNI method is implemented in mozglue/android/nsGeckoUtils.cpp.
+     *
+     * @param minidumpPath The path to the minidump file to be analyzed.
+     * @param fullStacks Specifies if stacks must be generated for all threads.
+     * @return <code>true</code> if the operation was successful,
+     *         <code>false</code> otherwise.
+     */
+    public static native boolean GenerateStacks(String minidumpPath, boolean fullStacks);
+}
--- a/mozglue/android/moz.build
+++ b/mozglue/android/moz.build
@@ -13,16 +13,25 @@ SOURCES += [
     'NativeCrypto.cpp',
     'nsGeckoUtils.cpp',
     'NSSBridge.cpp',
     'pbkdf2_sha256.c',
     'SharedMemNatives.cpp',
     'SQLiteBridge.cpp',
 ]
 
+if CONFIG['MOZ_CRASHREPORTER']:
+    USE_LIBS += [
+        'minidump-analyzer',
+    ]
+
+    LOCAL_INCLUDES += [
+        '/toolkit/crashreporter/minidump-analyzer',
+    ]
+
 FINAL_LIBRARY = 'mozglue'
 
 for var in ('ANDROID_PACKAGE_NAME',
             'ANDROID_CPU_ARCH'):
     DEFINES[var] = '"%s"' % CONFIG[var]
 
 if CONFIG['MOZ_FOLD_LIBS']:
     DEFINES['MOZ_FOLD_LIBS'] = True
--- a/mozglue/android/nsGeckoUtils.cpp
+++ b/mozglue/android/nsGeckoUtils.cpp
@@ -6,16 +6,20 @@
 #include <jni.h>
 
 #include <stdlib.h>
 #include <fcntl.h>
 #include "APKOpen.h"
 #include "Zip.h"
 #include "mozilla/RefPtr.h"
 
+#ifdef MOZ_CRASHREPORTER
+# include "minidump-analyzer.h"
+#endif
+
 extern "C"
 __attribute__ ((visibility("default")))
 void MOZ_JNICALL
 Java_org_mozilla_gecko_mozglue_GeckoLoader_putenv(JNIEnv *jenv, jclass, jstring map)
 {
     const char* str;
     // XXX: java doesn't give us true UTF8, we should figure out something
     // better to do here
@@ -133,8 +137,26 @@ Java_org_mozilla_gecko_mozglue_NativeZip
     }
     jclass nativeZip = jenv->GetObjectClass(jzip);
     jmethodID method = jenv->GetMethodID(nativeZip, "createInputStream", "(Ljava/nio/ByteBuffer;I)Ljava/io/InputStream;");
     // Since this function is only expected to be called from Java, it is safe
     // to skip exception checking for the method call below, as long as no
     // other Native -> Java call doesn't happen before returning to Java.
     return jenv->CallObjectMethod(jzip, method, buf, (jint) stream.GetType());
 }
+
+#ifdef MOZ_CRASHREPORTER
+
+extern "C"
+__attribute__ ((visibility("default")))
+jboolean MOZ_JNICALL
+Java_org_mozilla_gecko_mozglue_MinidumpAnalyzer_GenerateStacks(JNIEnv *jenv, jclass, jstring minidumpPath, jboolean fullStacks)
+{
+    const char* str;
+    str = jenv->GetStringUTFChars(minidumpPath, nullptr);
+
+    bool res = CrashReporter::GenerateStacks(str, fullStacks);
+
+    jenv->ReleaseStringUTFChars(minidumpPath, str);
+    return res;
+}
+
+#endif // MOZ_CRASHREPORTER
--- a/toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
+++ b/toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
@@ -120,30 +120,11 @@ static inline std::string
 UTF8toMBCS(const std::string &inp) {
   std::wstring wide = UTF8ToWide(inp);
   std::string ret = WideToMBCS(wide);
   return ret;
 }
 
 #endif // XP_WIN
 
-// Check if a file exists at the specified path
-
-static inline bool
-FileExists(const std::string& aPath)
-{
-#if defined(XP_WIN)
-  DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str());
-  return (attrs != INVALID_FILE_ATTRIBUTES);
-#else // Non-Windows
-  struct stat sb;
-  int ret = stat(aPath.c_str(), &sb);
-  if (ret == -1 || !(sb.st_mode & S_IFREG)) {
-    return false;
-  }
-
-  return true;
-#endif // XP_WIN
-}
-
-} // namespace
+} // namespace CrashReporter
 
 #endif // MinidumpAnalyzerUtils_h
--- a/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
+++ b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
@@ -1,13 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "minidump-analyzer.h"
+
 #include <cstdio>
 #include <cstring>
 #include <fstream>
 #include <string>
 #include <sstream>
 
 #include "json/json.h"
 #include "google_breakpad/processor/basic_source_line_resolver.h"
@@ -227,35 +229,35 @@ ConvertModulesToJSON(const ProcessState&
 
   return mainModuleIndex;
 }
 
 // Convert the process state to JSON, this includes information about the
 // crash, the module list and stack traces for every thread
 
 static void
-ConvertProcessStateToJSON(const ProcessState& aProcessState, Json::Value& aRoot)
+ConvertProcessStateToJSON(const ProcessState& aProcessState, Json::Value& aRoot,
+                          const bool aFullStacks)
 {
   // We use this map to get the index of a module when listed by address
   OrderedModulesMap orderedModules;
 
   // Crash info
   Json::Value crashInfo;
   int requestingThread = aProcessState.requesting_thread();
 
   if (aProcessState.crashed()) {
     crashInfo["type"] = aProcessState.crash_reason();
     crashInfo["address"] = ToHex(aProcessState.crash_address());
 
     if (requestingThread != -1) {
       // Record the crashing thread index only if this is a full minidump
       // and all threads' stacks are present, otherwise only the crashing
       // thread stack is written out and this field is set to 0.
-      crashInfo["crashing_thread"] =
-        gMinidumpAnalyzerOptions.fullMinidump ? requestingThread : 0;
+      crashInfo["crashing_thread"] = aFullStacks ? requestingThread : 0;
     }
   } else {
     crashInfo["type"] = Json::Value(Json::nullValue);
     // Add assertion info, if available
     string assertion = aProcessState.assertion();
 
     if (!assertion.empty()) {
       crashInfo["assertion"] = assertion;
@@ -273,17 +275,17 @@ ConvertProcessStateToJSON(const ProcessS
   }
 
   aRoot["modules"] = modules;
 
   // Threads
   Json::Value threads(Json::arrayValue);
   int threadCount = aProcessState.threads()->size();
 
-  if (!gMinidumpAnalyzerOptions.fullMinidump && (requestingThread != -1)) {
+  if (!aFullStacks && (requestingThread != -1)) {
     // Only add the crashing thread
     Json::Value thread;
     Json::Value stack(Json::arrayValue);
     const CallStack* rawStack = aProcessState.threads()->at(requestingThread);
 
     ConvertStackToJSON(aProcessState, orderedModules, rawStack, stack);
     thread["frames"] = stack;
     threads.append(thread);
@@ -301,17 +303,17 @@ ConvertProcessStateToJSON(const ProcessS
 
   aRoot["threads"] = threads;
 }
 
 // Process the minidump file and append the JSON-formatted stack traces to
 // the node specified in |aRoot|
 
 static bool
-ProcessMinidump(Json::Value& aRoot, const string& aDumpFile) {
+ProcessMinidump(Json::Value& aRoot, const string& aDumpFile, const bool aFullStacks) {
 #if XP_WIN && HAVE_64BIT_BUILD
   MozStackFrameSymbolizer symbolizer;
   MinidumpProcessor minidumpProcessor(&symbolizer, false);
 #else
   BasicSourceLineResolver resolver;
   // We don't have a valid symbol resolver so we pass nullptr instead.
   MinidumpProcessor minidumpProcessor(nullptr, &resolver);
 #endif
@@ -322,17 +324,17 @@ ProcessMinidump(Json::Value& aRoot, cons
     return false;
   }
 
   ProcessResult rv;
   ProcessState processState;
   rv = minidumpProcessor.Process(&dump, &processState);
   aRoot["status"] = ResultString(rv);
 
-  ConvertProcessStateToJSON(processState, aRoot);
+  ConvertProcessStateToJSON(processState, aRoot, aFullStacks);
 
   return true;
 }
 
 // Open the specified file in append mode
 
 static ofstream*
 OpenAppend(const string& aFilename)
@@ -351,38 +353,53 @@ OpenAppend(const string& aFilename)
   ofstream* file = new ofstream(aFilename.c_str(), mode);
 #endif // XP_WIN
   return file;
 }
 
 // Update the extra data file by adding the StackTraces field holding the
 // JSON output of this program.
 
-static void
+static bool
 UpdateExtraDataFile(const string &aDumpPath, const Json::Value& aRoot)
 {
   string extraDataPath(aDumpPath);
   int dot = extraDataPath.rfind('.');
 
   if (dot < 0) {
-    return; // Not a valid dump path
+    return false; // Not a valid dump path
   }
 
   extraDataPath.replace(dot, extraDataPath.length() - dot, kExtraDataExtension);
   ofstream* f = OpenAppend(extraDataPath.c_str());
+  bool res = false;
 
   if (f->is_open()) {
     Json::FastWriter writer;
 
     *f << "StackTraces=" << writer.write(aRoot);
+    res = !f->fail();
 
     f->close();
   }
 
   delete f;
+
+  return res;
+}
+
+bool
+GenerateStacks(const string& aDumpPath, const bool aFullStacks) {
+  Json::Value root;
+
+  if (!ProcessMinidump(root, aDumpPath, aFullStacks)) {
+    return false;
+  }
+
+  return UpdateExtraDataFile(aDumpPath , root);
 }
 
 } // namespace CrashReporter
 
 using namespace CrashReporter;
 
 static void
 ParseArguments(int argc, char** argv) {
@@ -403,21 +420,14 @@ ParseArguments(int argc, char** argv) {
 
   gMinidumpPath = argv[argc - 1];
 }
 
 int main(int argc, char** argv)
 {
   ParseArguments(argc, argv);
 
-  if (!FileExists(gMinidumpPath)) {
-    // The dump file does not exist
+  if (!GenerateStacks(gMinidumpPath, gMinidumpAnalyzerOptions.fullMinidump)) {
     exit(EXIT_FAILURE);
   }
 
-  // Try processing the minidump
-  Json::Value root;
-  if (ProcessMinidump(root, gMinidumpPath)) {
-    UpdateExtraDataFile(gMinidumpPath, root);
-  }
-
   exit(EXIT_SUCCESS);
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.h
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef MINIDUMP_ANALYZER_H__
+#define MINIDUMP_ANALYZER_H__
+
+#include <string>
+
+namespace CrashReporter {
+
+bool GenerateStacks(const std::string& aDumpPath, const bool aFullStacks);
+
+}
+
+#endif // MINIDUMP_ANALYZER_H__
--- a/toolkit/crashreporter/minidump-analyzer/moz.build
+++ b/toolkit/crashreporter/minidump-analyzer/moz.build
@@ -2,45 +2,50 @@
 # vim: set filetype=python:
 # 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/.
 
 if CONFIG['OS_TARGET'] != 'Android':
     Program('minidump-analyzer')
 
-    DEFINES['UNICODE'] = True
-    DEFINES['_UNICODE'] = True
-
-    UNIFIED_SOURCES += [
-        'minidump-analyzer.cpp',
-    ]
-
-    USE_LIBS += [
-        'breakpad_processor',
-        'jsoncpp',
-    ]
-
-    LOCAL_INCLUDES += [
-        '/toolkit/components/jsoncpp/include',
-    ]
-
-
     if CONFIG['OS_TARGET'] == 'Darwin':
         DIST_SUBDIR = 'crashreporter.app/Contents/MacOS'
 
-if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64':
-    UNIFIED_SOURCES += [
-        'MozStackFrameSymbolizer.cpp',
-        'Win64ModuleUnwindMetadata.cpp',
+    if CONFIG['OS_TARGET'] == 'WINNT':
+        DEFINES['UNICODE'] = True
+        DEFINES['_UNICODE'] = True
+
+        if CONFIG['CPU_ARCH'] == 'x86_64':
+            UNIFIED_SOURCES += [
+                'MozStackFrameSymbolizer.cpp',
+                'Win64ModuleUnwindMetadata.cpp',
+            ]
+
+            OS_LIBS += [
+                'Dbghelp',
+                'Imagehlp'
+            ]
+else:
+    Library('minidump-analyzer')
+
+    EXPORTS += [
+        'minidump-analyzer.h',
     ]
 
-    OS_LIBS += [
-        'Dbghelp',
-        'Imagehlp'
-    ]
+UNIFIED_SOURCES += [
+    'minidump-analyzer.cpp',
+]
 
+USE_LIBS += [
+    'breakpad_processor',
+    'jsoncpp',
+]
+
+LOCAL_INCLUDES += [
+    '/toolkit/components/jsoncpp/include',
+]
 
 # Don't use the STL wrappers in the crashreporter clients; they don't
 # link with -lmozalloc, and it really doesn't matter here anyway.
 DisableStlWrapping()
 
 include('/toolkit/crashreporter/crashreporter.mozbuild')