1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "minidump-analyzer.h"
7 
8 #include <cstdio>
9 #include <cstring>
10 #include <string>
11 #include <sstream>
12 
13 #include "json/json.h"
14 #include "google_breakpad/processor/basic_source_line_resolver.h"
15 #include "google_breakpad/processor/call_stack.h"
16 #include "google_breakpad/processor/code_module.h"
17 #include "google_breakpad/processor/code_modules.h"
18 #include "google_breakpad/processor/minidump.h"
19 #include "google_breakpad/processor/minidump_processor.h"
20 #include "google_breakpad/processor/process_state.h"
21 #include "google_breakpad/processor/stack_frame.h"
22 #include "processor/pathname_stripper.h"
23 
24 #include "mozilla/FStream.h"
25 #include "mozilla/Unused.h"
26 
27 #if defined(XP_WIN)
28 
29 #  include <windows.h>
30 #  include "mozilla/glue/WindowsDllServices.h"
31 
32 #elif defined(XP_UNIX) || defined(XP_MACOSX)
33 
34 #  include <sys/types.h>
35 #  include <sys/stat.h>
36 #  include <unistd.h>
37 
38 #endif
39 
40 #include "MinidumpAnalyzerUtils.h"
41 
42 #if XP_WIN && HAVE_64BIT_BUILD && defined(_M_X64)
43 #  include "MozStackFrameSymbolizer.h"
44 #endif
45 
46 namespace CrashReporter {
47 
48 #if defined(XP_WIN)
49 
50 static mozilla::glue::BasicDllServices gDllServices;
51 
52 #endif
53 
54 using std::hex;
55 using std::ios;
56 using std::ios_base;
57 using std::map;
58 using std::showbase;
59 using std::string;
60 using std::stringstream;
61 using std::wstring;
62 
63 using google_breakpad::BasicSourceLineResolver;
64 using google_breakpad::CallStack;
65 using google_breakpad::CodeModule;
66 using google_breakpad::CodeModules;
67 using google_breakpad::Minidump;
68 using google_breakpad::MinidumpProcessor;
69 using google_breakpad::PathnameStripper;
70 using google_breakpad::ProcessResult;
71 using google_breakpad::ProcessState;
72 using google_breakpad::StackFrame;
73 
74 using mozilla::IFStream;
75 using mozilla::OFStream;
76 using mozilla::Unused;
77 
78 MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
79 
80 // Path of the minidump to be analyzed.
81 static string gMinidumpPath;
82 
83 struct ModuleCompare {
operator ()CrashReporter::ModuleCompare84   bool operator()(const CodeModule* aLhs, const CodeModule* aRhs) const {
85     return aLhs->base_address() < aRhs->base_address();
86   }
87 };
88 
89 typedef map<const CodeModule*, unsigned int, ModuleCompare> OrderedModulesMap;
90 
AddModulesFromCallStack(OrderedModulesMap & aOrderedModules,const CallStack * aStack)91 static void AddModulesFromCallStack(OrderedModulesMap& aOrderedModules,
92                                     const CallStack* aStack) {
93   int frameCount = aStack->frames()->size();
94 
95   for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
96     const StackFrame* frame = aStack->frames()->at(frameIndex);
97 
98     if (frame->module) {
99       aOrderedModules.insert(
100           std::pair<const CodeModule*, unsigned int>(frame->module, 0));
101     }
102   }
103 }
104 
PopulateModuleList(const ProcessState & aProcessState,OrderedModulesMap & aOrderedModules,bool aFullStacks)105 static void PopulateModuleList(const ProcessState& aProcessState,
106                                OrderedModulesMap& aOrderedModules,
107                                bool aFullStacks) {
108   int threadCount = aProcessState.threads()->size();
109   int requestingThread = aProcessState.requesting_thread();
110 
111   if (!aFullStacks && (requestingThread != -1)) {
112     AddModulesFromCallStack(aOrderedModules,
113                             aProcessState.threads()->at(requestingThread));
114   } else {
115     for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex) {
116       AddModulesFromCallStack(aOrderedModules,
117                               aProcessState.threads()->at(threadIndex));
118     }
119   }
120 
121   int moduleCount = 0;
122   for (auto& itr : aOrderedModules) {
123     itr.second = moduleCount++;
124   }
125 }
126 
127 static const char kExtraDataExtension[] = ".extra";
128 
ToHex(uint64_t aValue)129 static string ToHex(uint64_t aValue) {
130   stringstream output;
131 
132   output << hex << showbase << aValue;
133 
134   return output.str();
135 }
136 
137 // Convert the stack frame trust value into a readable string.
138 
FrameTrust(const StackFrame::FrameTrust aTrust)139 static string FrameTrust(const StackFrame::FrameTrust aTrust) {
140   switch (aTrust) {
141     case StackFrame::FRAME_TRUST_NONE:
142       return "none";
143     case StackFrame::FRAME_TRUST_SCAN:
144       return "scan";
145     case StackFrame::FRAME_TRUST_CFI_SCAN:
146       return "cfi_scan";
147     case StackFrame::FRAME_TRUST_FP:
148       return "frame_pointer";
149     case StackFrame::FRAME_TRUST_CFI:
150       return "cfi";
151     case StackFrame::FRAME_TRUST_PREWALKED:
152       return "prewalked";
153     case StackFrame::FRAME_TRUST_CONTEXT:
154       return "context";
155   }
156 
157   return "none";
158 }
159 
160 // Convert the result value of the minidump processing step into a readable
161 // string.
162 
ResultString(ProcessResult aResult)163 static string ResultString(ProcessResult aResult) {
164   switch (aResult) {
165     case google_breakpad::PROCESS_OK:
166       return "OK";
167     case google_breakpad::PROCESS_ERROR_MINIDUMP_NOT_FOUND:
168       return "ERROR_MINIDUMP_NOT_FOUND";
169     case google_breakpad::PROCESS_ERROR_NO_MINIDUMP_HEADER:
170       return "ERROR_NO_MINIDUMP_HEADER";
171     case google_breakpad::PROCESS_ERROR_NO_THREAD_LIST:
172       return "ERROR_NO_THREAD_LIST";
173     case google_breakpad::PROCESS_ERROR_GETTING_THREAD:
174       return "ERROR_GETTING_THREAD";
175     case google_breakpad::PROCESS_ERROR_GETTING_THREAD_ID:
176       return "ERROR_GETTING_THREAD_ID";
177     case google_breakpad::PROCESS_ERROR_DUPLICATE_REQUESTING_THREADS:
178       return "ERROR_DUPLICATE_REQUESTING_THREADS";
179     case google_breakpad::PROCESS_SYMBOL_SUPPLIER_INTERRUPTED:
180       return "SYMBOL_SUPPLIER_INTERRUPTED";
181     default:
182       return "";
183   }
184 }
185 
186 // Convert the list of stack frames to JSON and append them to the array
187 // specified in the |aNode| parameter.
188 
ConvertStackToJSON(const ProcessState & aProcessState,const OrderedModulesMap & aOrderedModules,const CallStack * aStack,Json::Value & aNode)189 static void ConvertStackToJSON(const ProcessState& aProcessState,
190                                const OrderedModulesMap& aOrderedModules,
191                                const CallStack* aStack, Json::Value& aNode) {
192   int frameCount = aStack->frames()->size();
193 
194   for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
195     const StackFrame* frame = aStack->frames()->at(frameIndex);
196     Json::Value frameNode;
197 
198     if (frame->module) {
199       const auto& itr = aOrderedModules.find(frame->module);
200 
201       if (itr != aOrderedModules.end()) {
202         frameNode["module_index"] = (*itr).second;
203       }
204     }
205 
206     frameNode["trust"] = FrameTrust(frame->trust);
207     // The 'ip' field is equivalent to socorro's 'offset' field
208     frameNode["ip"] = ToHex(frame->instruction);
209 
210     aNode.append(frameNode);
211   }
212 }
213 
214 // Extract the list of certifications subjects from the list of modules and
215 // store it in the |aCertSubjects| parameter
216 
RetrieveCertSubjects(const CodeModules * modules,Json::Value & aCertSubjects)217 static void RetrieveCertSubjects(const CodeModules* modules,
218                                  Json::Value& aCertSubjects) {
219 #if defined(XP_WIN)
220   if (modules) {
221     for (size_t i = 0; i < modules->module_count(); i++) {
222       const CodeModule* module = modules->GetModuleAtIndex(i);
223       auto certSubject = gDllServices.GetBinaryOrgName(
224           UTF8ToWide(module->code_file()).c_str());
225       if (certSubject) {
226         string strSubject(WideToUTF8(certSubject.get()));
227         // Json::Value::operator[] creates and returns a null member if the key
228         // does not exist.
229         Json::Value& subjectNode = aCertSubjects[strSubject];
230         if (!subjectNode) {
231           // If the member is null, we want to convert that to an array.
232           subjectNode = Json::Value(Json::arrayValue);
233         }
234 
235         // Now we're guaranteed that subjectNode is an array. Add the new entry.
236         subjectNode.append(PathnameStripper::File(module->code_file()));
237       }
238     }
239   }
240 #endif  // defined(XP_WIN)
241 }
242 
243 // Convert the list of modules to JSON and append them to the array specified
244 // in the |aNode| parameter.
245 
ConvertModulesToJSON(const ProcessState & aProcessState,const OrderedModulesMap & aOrderedModules,Json::Value & aNode)246 static int ConvertModulesToJSON(const ProcessState& aProcessState,
247                                 const OrderedModulesMap& aOrderedModules,
248                                 Json::Value& aNode) {
249   const CodeModules* modules = aProcessState.modules();
250 
251   if (!modules) {
252     return -1;
253   }
254 
255   uint64_t mainAddress = 0;
256   const CodeModule* mainModule = modules->GetMainModule();
257 
258   if (mainModule) {
259     mainAddress = mainModule->base_address();
260   }
261 
262   int mainModuleIndex = -1;
263 
264   for (const auto& itr : aOrderedModules) {
265     const CodeModule* module = itr.first;
266 
267     if ((module->base_address() == mainAddress) && mainModule) {
268       mainModuleIndex = itr.second;
269     }
270 
271     Json::Value moduleNode;
272     moduleNode["filename"] = PathnameStripper::File(module->code_file());
273     moduleNode["code_id"] = PathnameStripper::File(module->code_identifier());
274     moduleNode["version"] = module->version();
275     moduleNode["debug_file"] = PathnameStripper::File(module->debug_file());
276     moduleNode["debug_id"] = module->debug_identifier();
277     moduleNode["base_addr"] = ToHex(module->base_address());
278     moduleNode["end_addr"] = ToHex(module->base_address() + module->size());
279 
280     aNode.append(moduleNode);
281   }
282 
283   return mainModuleIndex;
284 }
285 
286 // Convert the list of unloaded modules to JSON and append them to the array
287 // specified in the |aNode| parameter. Return the number of unloaded modules
288 // that were found.
289 
ConvertUnloadedModulesToJSON(const ProcessState & aProcessState,Json::Value & aNode)290 static size_t ConvertUnloadedModulesToJSON(const ProcessState& aProcessState,
291                                            Json::Value& aNode) {
292   const CodeModules* unloadedModules = aProcessState.unloaded_modules();
293   if (!unloadedModules) {
294     return 0;
295   }
296 
297   const size_t unloadedModulesLen = unloadedModules->module_count();
298   for (size_t i = 0; i < unloadedModulesLen; i++) {
299     const CodeModule* unloadedModule = unloadedModules->GetModuleAtIndex(i);
300 
301     Json::Value unloadedModuleNode;
302     unloadedModuleNode["filename"] =
303         PathnameStripper::File(unloadedModule->code_file());
304     unloadedModuleNode["code_id"] =
305         PathnameStripper::File(unloadedModule->code_identifier());
306     unloadedModuleNode["base_addr"] = ToHex(unloadedModule->base_address());
307     unloadedModuleNode["end_addr"] =
308         ToHex(unloadedModule->base_address() + unloadedModule->size());
309 
310     aNode.append(unloadedModuleNode);
311   }
312 
313   return unloadedModulesLen;
314 }
315 
316 // Convert the process state to JSON, this includes information about the
317 // crash, the module list and stack traces for every thread
318 
ConvertProcessStateToJSON(const ProcessState & aProcessState,Json::Value & aStackTraces,const bool aFullStacks,Json::Value & aCertSubjects)319 static void ConvertProcessStateToJSON(const ProcessState& aProcessState,
320                                       Json::Value& aStackTraces,
321                                       const bool aFullStacks,
322                                       Json::Value& aCertSubjects) {
323   // Crash info
324   Json::Value crashInfo;
325   int requestingThread = aProcessState.requesting_thread();
326 
327   if (aProcessState.crashed()) {
328     crashInfo["type"] = aProcessState.crash_reason();
329     crashInfo["address"] = ToHex(aProcessState.crash_address());
330 
331     if (requestingThread != -1) {
332       // Record the crashing thread index only if this is a full minidump
333       // and all threads' stacks are present, otherwise only the crashing
334       // thread stack is written out and this field is set to 0.
335       crashInfo["crashing_thread"] = aFullStacks ? requestingThread : 0;
336     }
337   } else {
338     crashInfo["type"] = Json::Value(Json::nullValue);
339     // Add assertion info, if available
340     string assertion = aProcessState.assertion();
341 
342     if (!assertion.empty()) {
343       crashInfo["assertion"] = assertion;
344     }
345   }
346 
347   aStackTraces["crash_info"] = crashInfo;
348 
349   // Modules
350   OrderedModulesMap orderedModules;
351   PopulateModuleList(aProcessState, orderedModules, aFullStacks);
352 
353   Json::Value modules(Json::arrayValue);
354   int mainModule = ConvertModulesToJSON(aProcessState, orderedModules, modules);
355 
356   if (mainModule != -1) {
357     aStackTraces["main_module"] = mainModule;
358   }
359 
360   aStackTraces["modules"] = modules;
361 
362   Json::Value unloadedModules(Json::arrayValue);
363   size_t unloadedModulesLen =
364       ConvertUnloadedModulesToJSON(aProcessState, unloadedModules);
365 
366   if (unloadedModulesLen > 0) {
367     aStackTraces["unloaded_modules"] = unloadedModules;
368   }
369 
370   RetrieveCertSubjects(aProcessState.modules(), aCertSubjects);
371   RetrieveCertSubjects(aProcessState.unloaded_modules(), aCertSubjects);
372 
373   // Threads
374   Json::Value threads(Json::arrayValue);
375   int threadCount = aProcessState.threads()->size();
376 
377   if (!aFullStacks && (requestingThread != -1)) {
378     // Only add the crashing thread
379     Json::Value thread;
380     Json::Value stack(Json::arrayValue);
381     const CallStack* rawStack = aProcessState.threads()->at(requestingThread);
382 
383     ConvertStackToJSON(aProcessState, orderedModules, rawStack, stack);
384     thread["frames"] = stack;
385     threads.append(thread);
386   } else {
387     for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex) {
388       Json::Value thread;
389       Json::Value stack(Json::arrayValue);
390       const CallStack* rawStack = aProcessState.threads()->at(threadIndex);
391 
392       ConvertStackToJSON(aProcessState, orderedModules, rawStack, stack);
393       thread["frames"] = stack;
394       threads.append(thread);
395     }
396   }
397 
398   aStackTraces["threads"] = threads;
399 }
400 
401 // Process the minidump file and append the JSON-formatted stack traces to
402 // the node specified in |aStackTraces|. We also populate |aCertSubjects| with
403 // information about the certificates used to sign modules, when present and
404 // supported by the underlying OS.
ProcessMinidump(Json::Value & aStackTraces,Json::Value & aCertSubjects,const string & aDumpFile,const bool aFullStacks)405 static bool ProcessMinidump(Json::Value& aStackTraces,
406                             Json::Value& aCertSubjects, const string& aDumpFile,
407                             const bool aFullStacks) {
408 #if XP_WIN && HAVE_64BIT_BUILD && defined(_M_X64)
409   MozStackFrameSymbolizer symbolizer;
410   MinidumpProcessor minidumpProcessor(&symbolizer, false);
411 #else
412   BasicSourceLineResolver resolver;
413   // We don't have a valid symbol resolver so we pass nullptr instead.
414   MinidumpProcessor minidumpProcessor(nullptr, &resolver);
415 #endif
416 
417   // Process the minidump.
418 #if defined(XP_WIN)
419   // Breakpad invokes std::ifstream directly, so this path needs to be ANSI
420   Minidump dump(UTF8ToMBCS(aDumpFile));
421 #else
422   Minidump dump(aDumpFile);
423 #endif  // defined(XP_WIN)
424   if (!dump.Read()) {
425     return false;
426   }
427 
428   ProcessResult rv;
429   ProcessState processState;
430   rv = minidumpProcessor.Process(&dump, &processState);
431   aStackTraces["status"] = ResultString(rv);
432 
433   ConvertProcessStateToJSON(processState, aStackTraces, aFullStacks,
434                             aCertSubjects);
435 
436   return true;
437 }
438 
ReadExtraFile(const string & aExtraDataPath,Json::Value & aExtra)439 static bool ReadExtraFile(const string& aExtraDataPath, Json::Value& aExtra) {
440   IFStream f(
441 #if defined(XP_WIN)
442       UTF8ToWide(aExtraDataPath).c_str(),
443 #else
444       aExtraDataPath.c_str(),
445 #endif  // defined(XP_WIN)
446       ios::in);
447   if (!f.is_open()) {
448     return false;
449   }
450 
451   Json::CharReaderBuilder builder;
452   return parseFromStream(builder, f, &aExtra, nullptr);
453 }
454 
455 // Update the extra data file by adding the StackTraces and ModuleSignatureInfo
456 // fields that contain the JSON outputs of this program.
UpdateExtraDataFile(const string & aDumpPath,const Json::Value & aStackTraces,const Json::Value & aCertSubjects)457 static bool UpdateExtraDataFile(const string& aDumpPath,
458                                 const Json::Value& aStackTraces,
459                                 const Json::Value& aCertSubjects) {
460   string extraDataPath(aDumpPath);
461   int dot = extraDataPath.rfind('.');
462 
463   if (dot < 0) {
464     return false;  // Not a valid dump path
465   }
466 
467   extraDataPath.replace(dot, extraDataPath.length() - dot, kExtraDataExtension);
468 
469   Json::Value extra;
470   if (!ReadExtraFile(extraDataPath, extra)) {
471     return false;
472   }
473 
474   OFStream f(
475 #if defined(XP_WIN)
476       UTF8ToWide(extraDataPath).c_str(),
477 #else
478       extraDataPath.c_str(),
479 #endif  // defined(XP_WIN)
480       ios::out | ios::trunc);
481 
482   bool res = false;
483   if (f.is_open()) {
484     Json::StreamWriterBuilder builder;
485     builder["indentation"] = "";
486 
487     // The StackTraces field is not stored as a string because it's not a
488     // crash annotation. It's only used by the crash reporter client which
489     // strips it before submitting the other annotations to Socorro.
490     extra["StackTraces"] = aStackTraces;
491 
492     if (!!aCertSubjects) {
493       extra["ModuleSignatureInfo"] = Json::writeString(builder, aCertSubjects);
494     }
495 
496     std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
497     writer->write(extra, &f);
498     f << "\n";
499     res = !f.fail();
500     f.close();
501   }
502 
503   return res;
504 }
505 
GenerateStacks(const string & aDumpPath,const bool aFullStacks)506 bool GenerateStacks(const string& aDumpPath, const bool aFullStacks) {
507   Json::Value stackTraces;
508   Json::Value certSubjects;
509 
510   if (!ProcessMinidump(stackTraces, certSubjects, aDumpPath, aFullStacks)) {
511     return false;
512   }
513 
514   return UpdateExtraDataFile(aDumpPath, stackTraces, certSubjects);
515 }
516 
517 }  // namespace CrashReporter
518 
519 using namespace CrashReporter;
520 
521 #if defined(XP_WIN)
522 #  define XP_LITERAL(s) L##s
523 #else
524 #  define XP_LITERAL(s) s
525 #endif
526 
527 template <typename CharT>
528 struct CharTraits;
529 
530 template <>
531 struct CharTraits<char> {
compareCharTraits532   static int compare(const char* left, const char* right) {
533     return strcmp(left, right);
534   }
535 
assignCharTraits536   static string& assign(string& left, const char* right) {
537     left = right;
538     return left;
539   }
540 };
541 
542 #if defined(XP_WIN)
543 
544 template <>
545 struct CharTraits<wchar_t> {
compareCharTraits546   static int compare(const wchar_t* left, const wchar_t* right) {
547     return wcscmp(left, right);
548   }
549 
assignCharTraits550   static string& assign(string& left, const wchar_t* right) {
551     left = WideToUTF8(right);
552     return left;
553   }
554 };
555 
556 #endif  // defined(XP_WIN)
557 
LowerPriority()558 static void LowerPriority() {
559 #if defined(XP_WIN)
560   Unused << SetPriorityClass(GetCurrentProcess(),
561                              PROCESS_MODE_BACKGROUND_BEGIN);
562 #else  // Linux, MacOS X, etc...
563   Unused << nice(20);
564 #endif
565 }
566 
567 template <typename CharT, typename Traits = CharTraits<CharT>>
ParseArguments(int argc,CharT ** argv)568 static void ParseArguments(int argc, CharT** argv) {
569   if (argc <= 1) {
570     exit(EXIT_FAILURE);
571   }
572 
573   for (int i = 1; i < argc - 1; i++) {
574     if (!Traits::compare(argv[i], XP_LITERAL("--full"))) {
575       gMinidumpAnalyzerOptions.fullMinidump = true;
576     } else if (!Traits::compare(argv[i], XP_LITERAL("--force-use-module")) &&
577                (i < argc - 2)) {
578       Traits::assign(gMinidumpAnalyzerOptions.forceUseModule, argv[i + 1]);
579       ++i;
580     } else {
581       exit(EXIT_FAILURE);
582     }
583   }
584 
585   Traits::assign(gMinidumpPath, argv[argc - 1]);
586 }
587 
588 #if defined(XP_WIN)
589 // WARNING: Windows does *NOT* use UTF8 for char strings off the command line!
590 // Using wmain here so that the CRT doesn't need to perform a wasteful and
591 // lossy UTF-16 to MBCS conversion; ParseArguments will convert to UTF8
592 // directly.
wmain(int argc,wchar_t ** argv)593 extern "C" int wmain(int argc, wchar_t** argv)
594 #else
595 int main(int argc, char** argv)
596 #endif
597 {
598   LowerPriority();
599   ParseArguments(argc, argv);
600 
601   if (!GenerateStacks(gMinidumpPath, gMinidumpAnalyzerOptions.fullMinidump)) {
602     exit(EXIT_FAILURE);
603   }
604 
605   exit(EXIT_SUCCESS);
606 }
607