Bug 1307153 - Add stack traces to the crash pings in Fennec; r?jchen,ted.mielczarek draft
authorGabriele Svelto <gsvelto@mozilla.com>
Fri, 19 Jan 2018 16:48:00 +0100
changeset 747611 d6b23fbc0905f4a8ee474a8418f09b23a8ca776f
parent 747610 1089cb510d173dee2fe6d0e1a172af46808d52fa
push id96952
push usergsvelto@mozilla.com
push dateFri, 26 Jan 2018 13:28:22 +0000
reviewersjchen, ted.mielczarek
bugs1307153
milestone60.0a1
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/base/moz.build
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,17 @@ 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.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;
@@ -146,17 +147,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 {
+            System.loadLibrary("mozglue");
+
+            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);
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -249,16 +249,23 @@ mgjar.sources += [geckoview_source_dir +
     'mozglue/DirectBufferAllocator.java',
     'mozglue/GeckoLoader.java',
     'mozglue/JNIObject.java',
     'mozglue/NativeReference.java',
     'mozglue/NativeZip.java',
     'mozglue/SafeIntent.java',
     'mozglue/SharedMemory.java',
 ]]
+
+if CONFIG['MOZ_CRASHREPORTER']:
+    mgjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x for x in [
+        'mozglue/MinidumpAnalyzer.java',
+    ]]
+
+
 mgjar.generated_sources = [] # Keep it this way.
 mgjar.extra_jars += [
     CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],
     'constants.jar',
 ]
 mgjar.javac_flags += ['-Xlint:all']
 
 gujar = add_java_jar('gecko-util')
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
@@ -117,8 +121,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')