Bug 1333126: use win64 PE unwind metadata to improve client-side stack walking; r?gsvelto draft
authorCarl Corcoran <carlco@gmail.com>
Sun, 06 Aug 2017 08:45:58 +0200
changeset 648854 682da624d32593f330b17ab0de6676fbb27f5513
parent 648046 932388b8c22c9775264e543697ce918415db9e23
child 648855 a61eca4a65740e88d3ef642f758ec93711e104bf
push id74910
push userbmo:ccorcoran@mozilla.com
push dateFri, 18 Aug 2017 12:14:35 +0000
reviewersgsvelto
bugs1333126
milestone57.0a1
Bug 1333126: use win64 PE unwind metadata to improve client-side stack walking; r?gsvelto MozReview-Commit-ID: GDARnPSemyu
toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp
toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h
toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp
toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h
toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
toolkit/crashreporter/minidump-analyzer/moz.build
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
@@ -0,0 +1,108 @@
+/* -*- 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 MinidumpAnalyzerUtils_h
+#define MinidumpAnalyzerUtils_h
+
+#ifdef XP_WIN
+#include <windows.h>
+#endif
+
+#include <vector>
+#include <map>
+#include <string>
+#include <algorithm>
+
+namespace CrashReporter {
+
+struct MinidumpAnalyzerOptions {
+  std::string forceUseModule;
+};
+
+extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
+
+#ifdef XP_WIN
+
+inline std::wstring
+UTF8ToWide(const std::string& aUtf8Str, bool *aSuccess = nullptr) {
+  wchar_t* buffer = nullptr;
+  int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
+                                        -1, nullptr, 0);
+  if (buffer_size == 0) {
+    if (aSuccess) {
+      *aSuccess = false;
+    }
+
+    return L"";
+  }
+
+  buffer = new wchar_t[buffer_size];
+  if (buffer == nullptr) {
+    if (aSuccess) {
+      *aSuccess = false;
+    }
+
+    return L"";
+  }
+
+  buffer[0] = 0;
+  MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
+                      -1, buffer, buffer_size);
+  std::wstring str = buffer;
+  delete [] buffer;
+
+  if (aSuccess) {
+    *aSuccess = true;
+  }
+
+  return str;
+}
+
+inline std::string
+WideToMBCS(const std::wstring &inp) {
+  int buffer_size = WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1,
+    nullptr, 0, NULL, NULL);
+  if (buffer_size == 0)
+    return "";
+
+  std::vector<char> buffer(buffer_size);
+  buffer[0] = 0;
+
+  WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1,
+    buffer.data(), buffer_size, NULL, NULL);
+
+  return buffer.data();
+}
+
+inline std::string UTF8toMBCS(const std::string &inp) {
+  std::wstring wide = UTF8ToWide(inp);
+  std::string ret = WideToMBCS(wide);
+  return ret;
+}
+
+#endif
+
+// Check if a file exists at the specified path
+
+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
+
+#endif // MinidumpAnalyzerUtils_h
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp
@@ -0,0 +1,120 @@
+/* -*- 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/. */
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+#include "MozStackFrameSymbolizer.h"
+
+#include "MinidumpAnalyzerUtils.h"
+
+#include "processor/cfi_frame_info.h"
+
+#include <iostream>
+#include <sstream>
+#include <fstream>
+
+namespace CrashReporter {
+
+  extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
+
+  using google_breakpad::CFIFrameInfo;
+
+MozStackFrameSymbolizer::MozStackFrameSymbolizer() :
+  StackFrameSymbolizer(nullptr, nullptr)
+{
+}
+
+MozStackFrameSymbolizer::SymbolizerResult
+MozStackFrameSymbolizer::FillSourceLineInfo(const CodeModules* modules,
+                                            const SystemInfo* system_info,
+                                            StackFrame* stack_frame)
+{
+  SymbolizerResult ret = StackFrameSymbolizer::FillSourceLineInfo(
+    modules, system_info, stack_frame);
+
+  if (ret == kNoError && this->HasImplementation() &&
+    stack_frame->function_name.empty()) {
+    // Breakpad's Stackwalker::InstructionAddressSeemsValid only considers an
+    // address valid if it has associated symbols.
+    //
+    // This makes sense for complete & accurate symbols, but ours may be
+    // incomplete or wrong. Returning a function name tells Breakpad we
+    // recognize this address as code, so it's OK to use in stack scanning.
+    // This function is only called with addresses that land in this module.
+    //
+    // This allows us to fall back to stack scanning in the case where we were
+    // unable to provide CFI.
+    stack_frame->function_name = "<unknown code>";
+  }
+  return ret;
+}
+
+CFIFrameInfo*
+MozStackFrameSymbolizer::FindCFIFrameInfo(const StackFrame* frame)
+{
+  std::string modulePath;
+
+  // For unit testing, support loading a specified module instead of
+  // the real one.
+  bool moduleHasBeenReplaced = false;
+  if (gMinidumpAnalyzerOptions.forceUseModule.size() > 0) {
+    modulePath = gMinidumpAnalyzerOptions.forceUseModule;
+    moduleHasBeenReplaced = true;
+  } else {
+    if (!frame->module) {
+      return nullptr;
+    }
+    modulePath = frame->module->code_file();
+  }
+
+  // Get/create the unwind parser.
+  auto itMod = mModuleMap.find(modulePath);
+  std::shared_ptr<ModuleUnwindParser> unwindParser;
+  if (itMod != mModuleMap.end()) {
+    unwindParser = itMod->second;
+  } else {
+    unwindParser.reset(new ModuleUnwindParser(modulePath));
+    mModuleMap[modulePath] = unwindParser;
+  }
+
+  UnwindCFI cfi;
+  DWORD offsetAddr;
+
+  if (moduleHasBeenReplaced) {
+    // If we are replacing a module, addresses will never line up.
+    // So just act like the 1st entry is correct.
+    offsetAddr = unwindParser->GetAnyOffsetAddr();
+  } else {
+    offsetAddr = frame->instruction - frame->module->base_address();
+  }
+
+  if (!unwindParser->GetCFI(offsetAddr, cfi)) {
+    return nullptr;
+  }
+
+  std::unique_ptr<CFIFrameInfo> rules(new CFIFrameInfo());
+
+  static const size_t exprSize = 50;
+  char expr[exprSize];
+  if (cfi.stackSize == 0) {
+    snprintf(expr, exprSize, "$rsp");
+  } else {
+    snprintf(expr, exprSize, "$rsp %d +", cfi.stackSize);
+  }
+  rules->SetCFARule(expr);
+
+  if (cfi.ripOffset == 0) {
+    snprintf(expr, exprSize, ".cfa ^");
+  } else {
+    snprintf(expr, exprSize, ".cfa %d - ^", cfi.ripOffset);
+  }
+  rules->SetRARule(expr);
+
+  return rules.release();
+}
+
+} // namespace CrashReporter
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h
@@ -0,0 +1,48 @@
+/* -*- 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 MozStackFrameSymbolizer_h
+#define MozStackFrameSymbolizer_h
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+#include "Win64ModuleUnwindMetadata.h"
+
+#include "google_breakpad/processor/stack_frame_symbolizer.h"
+#include "google_breakpad/processor/stack_frame.h"
+
+#include <memory>
+
+namespace CrashReporter {
+
+using google_breakpad::CodeModule;
+using google_breakpad::CodeModules;
+using google_breakpad::SourceLineResolverInterface;
+using google_breakpad::StackFrame;
+using google_breakpad::StackFrameSymbolizer;
+using google_breakpad::SymbolSupplier;
+using google_breakpad::SystemInfo;
+
+class MozStackFrameSymbolizer : public StackFrameSymbolizer {
+  using google_breakpad::StackFrameSymbolizer::SymbolizerResult;
+
+  std::map<std::string, std::shared_ptr<ModuleUnwindParser>> mModuleMap;
+
+public:
+  MozStackFrameSymbolizer();
+
+  virtual SymbolizerResult FillSourceLineInfo(const CodeModules* modules,
+    const SystemInfo* system_info,
+    StackFrame* stack_frame);
+
+  virtual class google_breakpad::CFIFrameInfo* FindCFIFrameInfo(
+    const StackFrame* frame);
+};
+
+} // namespace CrashReporter
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
+
+#endif // MozStackFrameSymbolizer_h
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp
@@ -0,0 +1,265 @@
+/* -*- 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/. */
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+#include "Win64ModuleUnwindMetadata.h"
+
+#include "MinidumpAnalyzerUtils.h"
+
+#include <windows.h>
+#include <winnt.h>
+#include <ImageHlp.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+namespace CrashReporter {
+
+union UnwindCode {
+  struct {
+    uint8_t offset_in_prolog;
+    uint8_t unwind_operation_code : 4;
+    uint8_t operation_info        : 4;
+  };
+  USHORT frame_offset;
+};
+
+enum UnwindOperationCodes {
+  UWOP_PUSH_NONVOL = 0,     // info == register number
+  UWOP_ALLOC_LARGE = 1,     // no info, alloc size in next 2 slots
+  UWOP_ALLOC_SMALL = 2,     // info == size of allocation / 8 - 1
+  UWOP_SET_FPREG = 3,       // no info, FP = RSP + UNWIND_INFO.FPRegOffset*16
+  UWOP_SAVE_NONVOL = 4,     // info == register number, offset in next slot
+  UWOP_SAVE_NONVOL_FAR = 5, // info == register number, offset in next 2 slots
+  UWOP_SAVE_XMM = 6,        // Version 1; undocumented
+  UWOP_EPILOG = 6,          // Version 2; undocumented
+  UWOP_SAVE_XMM_FAR = 7,    // Version 1; undocumented
+  UWOP_SPARE = 7,           // Version 2; undocumented
+  UWOP_SAVE_XMM128 = 8,     // info == XMM reg number, offset in next slot
+  UWOP_SAVE_XMM128_FAR = 9, // info == XMM reg number, offset in next 2 slots
+  UWOP_PUSH_MACHFRAME = 10  // info == 0: no error-code, 1: error-code
+};
+
+struct UnwindInfo {
+  uint8_t version       : 3;
+  uint8_t flags         : 5;
+  uint8_t size_of_prolog;
+  uint8_t count_of_codes;
+  uint8_t frame_register : 4;
+  uint8_t frame_offset   : 4;
+  UnwindCode unwind_code[1];
+};
+
+ModuleUnwindParser::~ModuleUnwindParser()
+{
+  if (mImg) {
+    ImageUnload(mImg);
+  }
+}
+
+void*
+ModuleUnwindParser::RvaToVa(ULONG aRva)
+{
+  return ImageRvaToVa(
+    mImg->FileHeader, mImg->MappedAddress, aRva, &mImg->LastRvaSection);
+}
+
+ModuleUnwindParser::ModuleUnwindParser(const std::string& aPath)
+  : mPath(aPath)
+{
+  // Convert wchar to native charset because ImageLoad only takes
+  // a PSTR as input.
+  std::string code_file = UTF8toMBCS(aPath);
+
+  mImg = ImageLoad((PSTR)code_file.c_str(), NULL);
+  if (!mImg || !mImg->FileHeader) {
+    return;
+  }
+
+  PIMAGE_OPTIONAL_HEADER64 optional_header = &mImg->FileHeader->OptionalHeader;
+  if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
+    return;
+  }
+
+  DWORD exception_rva = optional_header->
+    DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress;
+
+  DWORD exception_size = optional_header->
+    DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
+
+  auto funcs = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(exception_rva);
+  if (!funcs) {
+    return;
+  }
+
+  for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) {
+    mUnwindMap[funcs[i].BeginAddress] = &funcs[i];
+  }
+}
+
+bool
+ModuleUnwindParser::GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc,
+                                           UnwindCFI& aRet)
+{
+  DWORD unwind_rva = aFunc.UnwindInfoAddress;
+  // Holds RVA to all visited IMAGE_RUNTIME_FUNCTION_ENTRY, to avoid
+  // circular references.
+  std::set<DWORD> visited;
+
+  // Follow chained function entries
+  while (unwind_rva & 0x1) {
+    unwind_rva ^= 0x1;
+
+    if (visited.end() != visited.find(unwind_rva)) {
+      return false;
+    }
+    visited.insert(unwind_rva);
+
+    auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(unwind_rva);
+    if (!chained_func) {
+      return false;
+    }
+    unwind_rva = chained_func->UnwindInfoAddress;
+  }
+
+  visited.insert(unwind_rva);
+
+  auto unwind_info = (UnwindInfo*)RvaToVa(unwind_rva);
+  if (!unwind_info) {
+    return false;
+  }
+
+  DWORD stack_size = 8; // minimal stack size is 8 for RIP
+  DWORD rip_offset = 8;
+  do {
+    for (uint8_t c = 0; c < unwind_info->count_of_codes; c++) {
+      UnwindCode* unwind_code = &unwind_info->unwind_code[c];
+      switch (unwind_code->unwind_operation_code) {
+        case UWOP_PUSH_NONVOL: {
+          stack_size += 8;
+          break;
+        }
+        case UWOP_ALLOC_LARGE: {
+          if (unwind_code->operation_info == 0) {
+            c++;
+            if (c < unwind_info->count_of_codes) {
+              stack_size += (unwind_code + 1)->frame_offset * 8;
+            }
+          } else {
+            c += 2;
+            if (c < unwind_info->count_of_codes) {
+              stack_size += (unwind_code + 1)->frame_offset |
+                            ((unwind_code + 2)->frame_offset << 16);
+            }
+          }
+          break;
+        }
+        case UWOP_ALLOC_SMALL: {
+          stack_size += unwind_code->operation_info * 8 + 8;
+          break;
+        }
+        case UWOP_SET_FPREG:
+          // To correctly track RSP when it's been transferred to another
+          // register, we would need to emit CFI records for every unwind op.
+          // For simplicity, don't emit CFI records for this function as
+          // we know it will be incorrect after this point.
+          return false;
+        case UWOP_SAVE_NONVOL:
+        case UWOP_SAVE_XMM: // also v2 UWOP_EPILOG
+        case UWOP_SAVE_XMM128: {
+          c++; // skip slot with offset
+          break;
+        }
+        case UWOP_SAVE_NONVOL_FAR:
+        case UWOP_SAVE_XMM_FAR: // also v2 UWOP_SPARE
+        case UWOP_SAVE_XMM128_FAR: {
+          c += 2; // skip 2 slots with offset
+          break;
+        }
+        case UWOP_PUSH_MACHFRAME: {
+          if (unwind_code->operation_info) {
+            stack_size += 88;
+          } else {
+            stack_size += 80;
+          }
+          rip_offset += 80;
+          break;
+        }
+        default: {
+          return false;
+        }
+      }
+    }
+
+    if (unwind_info->flags & UNW_FLAG_CHAININFO) {
+      auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)(
+            (unwind_info->unwind_code +
+            ((unwind_info->count_of_codes + 1) & ~1)));
+
+      if (visited.end() != visited.find(chained_func->UnwindInfoAddress)) {
+        return false; // Circular reference
+      }
+
+      visited.insert(chained_func->UnwindInfoAddress);
+
+      unwind_info = (UnwindInfo*)RvaToVa(chained_func->UnwindInfoAddress);
+    } else {
+      unwind_info = nullptr;
+    }
+  } while (unwind_info);
+
+  aRet.beginAddress = aFunc.BeginAddress;
+  aRet.size = aFunc.EndAddress - aFunc.BeginAddress;
+  aRet.stackSize = stack_size;
+  aRet.ripOffset = rip_offset;
+  return true;
+}
+
+// For unit testing we sometimes need any address that's valid in this module.
+// Just return the first address we know of.
+DWORD
+ModuleUnwindParser::GetAnyOffsetAddr() const {
+  if (mUnwindMap.size() < 1) {
+    return 0;
+  }
+  return mUnwindMap.begin()->first;
+}
+
+bool
+ModuleUnwindParser::GetCFI(DWORD aAddress, UnwindCFI& aRet)
+{
+  // Figure out the begin address of the requested address.
+  auto itUW = mUnwindMap.lower_bound(aAddress + 1);
+  if (itUW == mUnwindMap.begin()) {
+   return false; // address before this module.
+  }
+  --itUW;
+
+  // Ensure that the function entry is big enough to contain this address.
+  IMAGE_RUNTIME_FUNCTION_ENTRY& func = *itUW->second;
+  if (aAddress > func.EndAddress) {
+    return false;
+  }
+
+  // Do we have CFI for this function already?
+  auto itCFI = mCFIMap.find(aAddress);
+  if (itCFI != mCFIMap.end()) {
+    aRet = itCFI->second;
+    return true;
+  }
+
+  // No, generate it.
+  if (!GenerateCFIForFunction(func, aRet)) {
+    return false;
+  }
+
+  mCFIMap[func.BeginAddress] = aRet;
+  return true;
+}
+
+} // namespace
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h
@@ -0,0 +1,57 @@
+/* -*- 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 Win64ModuleUnwindMetadata_h
+#define Win64ModuleUnwindMetadata_h
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+#include <functional>
+#include <map>
+#include <string>
+
+#include <windows.h>
+#include <winnt.h>
+#include <ImageHlp.h>
+
+namespace CrashReporter {
+
+struct UnwindCFI
+{
+  uint32_t beginAddress;
+  uint32_t size;
+  uint32_t stackSize;
+  uint32_t ripOffset;
+};
+
+// Does lazy-parsing of unwind info.
+class ModuleUnwindParser {
+  PLOADED_IMAGE mImg;
+  std::string mPath;
+
+  // Maps begin address to exception record.
+  // Populated upon construction.
+  std::map<DWORD, PIMAGE_RUNTIME_FUNCTION_ENTRY> mUnwindMap;
+
+  // Maps begin address to CFI.
+  // Populated as needed.
+  std::map<DWORD, UnwindCFI> mCFIMap;
+
+  bool GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc,
+                              UnwindCFI& aRet);
+  void* RvaToVa(ULONG aRva);
+
+public:
+  explicit ModuleUnwindParser(const std::string& aPath);
+  ~ModuleUnwindParser();
+  bool GetCFI(DWORD aAddress, UnwindCFI& aRet);
+  DWORD GetAnyOffsetAddr() const;
+};
+
+} // namespace
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
+
+#endif // Win64ModuleUnwindMetadata_h
--- a/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
+++ b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
@@ -1,16 +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/. */
 
 #include <cstdio>
 #include <fstream>
 #include <string>
+#include <cstring>
 #include <sstream>
 
 #include "json/json.h"
 #include "google_breakpad/processor/basic_source_line_resolver.h"
 #include "google_breakpad/processor/call_stack.h"
 #include "google_breakpad/processor/code_module.h"
 #include "google_breakpad/processor/code_modules.h"
 #include "google_breakpad/processor/minidump.h"
@@ -26,16 +27,22 @@
 #elif defined(XP_UNIX) || defined(XP_MACOSX)
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
 #endif
 
+#include "MinidumpAnalyzerUtils.h"
+
+#if XP_WIN && HAVE_64BIT_BUILD
+#include "MozStackFrameSymbolizer.h"
+#endif
+
 namespace CrashReporter {
 
 using std::ios;
 using std::ios_base;
 using std::hex;
 using std::ofstream;
 using std::map;
 using std::showbase;
@@ -49,54 +56,17 @@ using google_breakpad::CodeModule;
 using google_breakpad::CodeModules;
 using google_breakpad::Minidump;
 using google_breakpad::MinidumpProcessor;
 using google_breakpad::PathnameStripper;
 using google_breakpad::ProcessResult;
 using google_breakpad::ProcessState;
 using google_breakpad::StackFrame;
 
-#ifdef XP_WIN
-
-static wstring UTF8ToWide(const string& aUtf8Str, bool *aSuccess = nullptr)
-{
-  wchar_t* buffer = nullptr;
-  int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
-                                        -1, nullptr, 0);
-  if (buffer_size == 0) {
-    if (aSuccess) {
-      *aSuccess = false;
-    }
-
-    return L"";
-  }
-
-  buffer = new wchar_t[buffer_size];
-
-  if (buffer == nullptr) {
-    if (aSuccess) {
-      *aSuccess = false;
-    }
-
-    return L"";
-  }
-
-  MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
-                      -1, buffer, buffer_size);
-  wstring str = buffer;
-  delete [] buffer;
-
-  if (aSuccess) {
-    *aSuccess = true;
-  }
-
-  return str;
-}
-
-#endif
+MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
 
 struct ModuleCompare {
   bool operator() (const CodeModule* aLhs, const CodeModule* aRhs) const {
     return aLhs->base_address() < aRhs->base_address();
   }
 };
 
 typedef map<const CodeModule*, unsigned int, ModuleCompare> OrderedModulesMap;
@@ -184,16 +154,17 @@ ConvertStackToJSON(const ProcessState& a
 
       if (itr != aOrderedModules.end()) {
         moduleIndex = (*itr).second;
         frameNode["module_index"] = moduleIndex;
       }
     }
 
     frameNode["trust"] = FrameTrust(frame->trust);
+
     // The 'ip' field is equivalent to socorro's 'offset' field
     frameNode["ip"] = ToHex(frame->instruction);
 
     aNode.append(frameNode);
   }
 }
 
 // Convert the list of modules to JSON and append them to the array specified
@@ -314,31 +285,35 @@ 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) {
+#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
 
   // Process the minidump.
   Minidump dump(aDumpFile);
   if (!dump.Read()) {
     return false;
   }
 
   ProcessResult rv;
   ProcessState processState;
   rv = minidumpProcessor.Process(&dump, &processState);
   aRoot["status"] = ResultString(rv);
-
   ConvertProcessStateToJSON(processState, aRoot);
 
   return true;
 }
 
 // Open the specified file in append mode
 
 static ofstream*
@@ -355,35 +330,16 @@ OpenAppend(const string& aFilename)
     new ofstream(WideToMBCP(UTF8ToWide(aFilename), CP_ACP).c_str(), mode);
 #endif  // _MSC_VER
 #else // Non-Windows
   ofstream* file = new ofstream(aFilename.c_str(), mode);
 #endif // XP_WIN
   return file;
 }
 
-// Check if a file exists at the specified path
-
-static bool
-FileExists(const 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
-}
-
 // Update the extra data file by adding the StackTraces field holding the
 // JSON output of this program.
 
 static void
 UpdateExtraDataFile(const string &aDumpPath, const Json::Value& aRoot)
 {
   string extraDataPath(aDumpPath);
   int dot = extraDataPath.rfind('.');
@@ -422,16 +378,27 @@ int main(int argc, char** argv)
     exit(EXIT_FAILURE);
   }
 
   if (!FileExists(dumpPath)) {
     // The dump file does not exist
     return 1;
   }
 
+  // Process command-line arguments
+  for (int i = 2; i < argc; ++i) {
+    if (std::strcmp(argv[i], "--force-use-module") == 0) {
+      if ((++ i) < argc) {
+        gMinidumpAnalyzerOptions.forceUseModule = argv[i];
+      } else {
+        return 1; // The module name wasn't specified on the command line.
+      }
+    }
+  }
+
   // Try processing the minidump
   Json::Value root;
   if (ProcessMinidump(root, dumpPath)) {
     UpdateExtraDataFile(dumpPath, root);
   }
 
   exit(EXIT_SUCCESS);
 }
--- a/toolkit/crashreporter/minidump-analyzer/moz.build
+++ b/toolkit/crashreporter/minidump-analyzer/moz.build
@@ -22,13 +22,25 @@ if CONFIG['OS_TARGET'] != 'Android':
     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',
+    ]
+
+    OS_LIBS += [
+        'Dbghelp',
+        'Imagehlp'
+    ]
+
+
 # Don't use the STL wrappers in the crashreporter clients; they don't
 # link with -lmozalloc, and it really doesn't matter here anyway.
 DISABLE_STL_WRAPPING = True
 
 include('/toolkit/crashreporter/crashreporter.mozbuild')