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 <cstdio>
7 #include <fstream>
8 #include <string>
9 #include <sstream>
10 
11 #include "json/json.h"
12 #include "google_breakpad/processor/basic_source_line_resolver.h"
13 #include "google_breakpad/processor/call_stack.h"
14 #include "google_breakpad/processor/code_module.h"
15 #include "google_breakpad/processor/code_modules.h"
16 #include "google_breakpad/processor/minidump.h"
17 #include "google_breakpad/processor/minidump_processor.h"
18 #include "google_breakpad/processor/process_state.h"
19 #include "google_breakpad/processor/stack_frame.h"
20 #include "processor/pathname_stripper.h"
21 
22 #if defined(XP_WIN32)
23 
24 #include <windows.h>
25 
26 #elif defined(XP_UNIX) || defined(XP_MACOSX)
27 
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <unistd.h>
31 
32 #endif
33 
34 namespace CrashReporter {
35 
36 using std::ios;
37 using std::ios_base;
38 using std::hex;
39 using std::ofstream;
40 using std::map;
41 using std::showbase;
42 using std::string;
43 using std::stringstream;
44 using std::wstring;
45 
46 using google_breakpad::BasicSourceLineResolver;
47 using google_breakpad::CallStack;
48 using google_breakpad::CodeModule;
49 using google_breakpad::CodeModules;
50 using google_breakpad::Minidump;
51 using google_breakpad::MinidumpProcessor;
52 using google_breakpad::PathnameStripper;
53 using google_breakpad::ProcessResult;
54 using google_breakpad::ProcessState;
55 using google_breakpad::StackFrame;
56 
57 #ifdef XP_WIN
58 
UTF8ToWide(const string & aUtf8Str,bool * aSuccess=nullptr)59 static wstring UTF8ToWide(const string& aUtf8Str, bool *aSuccess = nullptr)
60 {
61   wchar_t* buffer = nullptr;
62   int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
63                                         -1, nullptr, 0);
64   if (buffer_size == 0) {
65     if (aSuccess) {
66       *aSuccess = false;
67     }
68 
69     return L"";
70   }
71 
72   buffer = new wchar_t[buffer_size];
73 
74   if (buffer == nullptr) {
75     if (aSuccess) {
76       *aSuccess = false;
77     }
78 
79     return L"";
80   }
81 
82   MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
83                       -1, buffer, buffer_size);
84   wstring str = buffer;
85   delete [] buffer;
86 
87   if (aSuccess) {
88     *aSuccess = true;
89   }
90 
91   return str;
92 }
93 
94 #endif
95 
96 struct ModuleCompare {
operator ()CrashReporter::ModuleCompare97   bool operator() (const CodeModule* aLhs, const CodeModule* aRhs) const {
98     return aLhs->base_address() < aRhs->base_address();
99   }
100 };
101 
102 typedef map<const CodeModule*, unsigned int, ModuleCompare> OrderedModulesMap;
103 
104 static const char kExtraDataExtension[] = ".extra";
105 
106 static string
ToHex(uint64_t aValue)107 ToHex(uint64_t aValue) {
108   stringstream output;
109 
110   output << hex << showbase << aValue;
111 
112   return output.str();
113 }
114 
115 // Convert the stack frame trust value into a readable string.
116 
117 static string
FrameTrust(const StackFrame::FrameTrust aTrust)118 FrameTrust(const StackFrame::FrameTrust aTrust) {
119   switch (aTrust) {
120   case StackFrame::FRAME_TRUST_NONE:
121     return "none";
122   case StackFrame::FRAME_TRUST_SCAN:
123     return "scan";
124   case StackFrame::FRAME_TRUST_CFI_SCAN:
125     return "cfi_scan";
126   case StackFrame::FRAME_TRUST_FP:
127     return "frame_pointer";
128   case StackFrame::FRAME_TRUST_CFI:
129     return "cfi";
130   case StackFrame::FRAME_TRUST_PREWALKED:
131     return "prewalked";
132   case StackFrame::FRAME_TRUST_CONTEXT:
133     return "context";
134   }
135 
136   return "none";
137 }
138 
139 // Convert the result value of the minidump processing step into a readable
140 // string.
141 
142 static string
ResultString(ProcessResult aResult)143 ResultString(ProcessResult aResult) {
144   switch (aResult) {
145   case google_breakpad::PROCESS_OK:
146     return "OK";
147   case google_breakpad::PROCESS_ERROR_MINIDUMP_NOT_FOUND:
148     return "ERROR_MINIDUMP_NOT_FOUND";
149   case google_breakpad::PROCESS_ERROR_NO_MINIDUMP_HEADER:
150     return "ERROR_NO_MINIDUMP_HEADER";
151   case google_breakpad::PROCESS_ERROR_NO_THREAD_LIST:
152     return "ERROR_NO_THREAD_LIST";
153   case google_breakpad::PROCESS_ERROR_GETTING_THREAD:
154     return "ERROR_GETTING_THREAD";
155   case google_breakpad::PROCESS_ERROR_GETTING_THREAD_ID:
156     return "ERROR_GETTING_THREAD_ID";
157   case google_breakpad::PROCESS_ERROR_DUPLICATE_REQUESTING_THREADS:
158     return "ERROR_DUPLICATE_REQUESTING_THREADS";
159   case google_breakpad::PROCESS_SYMBOL_SUPPLIER_INTERRUPTED:
160     return "SYMBOL_SUPPLIER_INTERRUPTED";
161   default:
162     return "";
163   }
164 }
165 
166 // Convert the list of stack frames to JSON and append them to the array
167 // specified in the |aNode| parameter.
168 
169 static void
ConvertStackToJSON(const ProcessState & aProcessState,const OrderedModulesMap & aOrderedModules,const CallStack * aStack,Json::Value & aNode)170 ConvertStackToJSON(const ProcessState& aProcessState,
171                    const OrderedModulesMap& aOrderedModules,
172                    const CallStack *aStack,
173                    Json::Value& aNode)
174 {
175   int frameCount = aStack->frames()->size();
176   unsigned int moduleIndex = 0;
177 
178   for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
179     const StackFrame *frame = aStack->frames()->at(frameIndex);
180     Json::Value frameNode;
181 
182     if (frame->module) {
183       auto itr = aOrderedModules.find(frame->module);
184 
185       if (itr != aOrderedModules.end()) {
186         moduleIndex = (*itr).second;
187         frameNode["module_index"] = moduleIndex;
188       }
189     }
190 
191     frameNode["trust"] = FrameTrust(frame->trust);
192     // The 'ip' field is equivalent to socorro's 'offset' field
193     frameNode["ip"] = ToHex(frame->instruction);
194 
195     aNode.append(frameNode);
196   }
197 }
198 
199 // Convert the list of modules to JSON and append them to the array specified
200 // in the |aNode| parameter.
201 
202 static int
ConvertModulesToJSON(const ProcessState & aProcessState,OrderedModulesMap & aOrderedModules,Json::Value & aNode)203 ConvertModulesToJSON(const ProcessState& aProcessState,
204                      OrderedModulesMap& aOrderedModules,
205                      Json::Value& aNode)
206 {
207   const CodeModules* modules = aProcessState.modules();
208 
209   if (!modules) {
210     return -1;
211   }
212 
213   // Create a sorted set of modules so that we'll be able to lookup the index
214   // of a particular module.
215   for (unsigned int i = 0; i < modules->module_count(); ++i) {
216     aOrderedModules.insert(
217       std::pair<const CodeModule*, unsigned int>(
218         modules->GetModuleAtSequence(i), i
219       )
220     );
221   }
222 
223   uint64_t mainAddress = 0;
224   const CodeModule *mainModule = modules->GetMainModule();
225 
226   if (mainModule) {
227     mainAddress = mainModule->base_address();
228   }
229 
230   unsigned int moduleCount = modules->module_count();
231   int mainModuleIndex = -1;
232 
233   for (unsigned int moduleSequence = 0;
234        moduleSequence < moduleCount;
235        ++moduleSequence)
236   {
237     const CodeModule *module = modules->GetModuleAtSequence(moduleSequence);
238 
239     if (module->base_address() == mainAddress) {
240       mainModuleIndex = moduleSequence;
241     }
242 
243     Json::Value moduleNode;
244     moduleNode["filename"] = PathnameStripper::File(module->code_file());
245     moduleNode["code_id"] = PathnameStripper::File(module->code_identifier());
246     moduleNode["version"] = module->version();
247     moduleNode["debug_file"] = PathnameStripper::File(module->debug_file());
248     moduleNode["debug_id"] = module->debug_identifier();
249     moduleNode["base_addr"] = ToHex(module->base_address());
250     moduleNode["end_addr"] = ToHex(module->base_address() + module->size());
251 
252     aNode.append(moduleNode);
253   }
254 
255   return mainModuleIndex;
256 }
257 
258 // Convert the process state to JSON, this includes information about the
259 // crash, the module list and stack traces for every thread
260 
261 static void
ConvertProcessStateToJSON(const ProcessState & aProcessState,Json::Value & aRoot)262 ConvertProcessStateToJSON(const ProcessState& aProcessState, Json::Value& aRoot)
263 {
264   // We use this map to get the index of a module when listed by address
265   OrderedModulesMap orderedModules;
266 
267   // Crash info
268   Json::Value crashInfo;
269   int requestingThread = aProcessState.requesting_thread();
270 
271   if (aProcessState.crashed()) {
272     crashInfo["type"] = aProcessState.crash_reason();
273     crashInfo["address"] = ToHex(aProcessState.crash_address());
274 
275     if (requestingThread != -1) {
276       crashInfo["crashing_thread"] = requestingThread;
277     }
278   } else {
279     crashInfo["type"] = Json::Value(Json::nullValue);
280     // Add assertion info, if available
281     string assertion = aProcessState.assertion();
282 
283     if (!assertion.empty()) {
284       crashInfo["assertion"] = assertion;
285     }
286   }
287 
288   aRoot["crash_info"] = crashInfo;
289 
290   // Modules
291   Json::Value modules(Json::arrayValue);
292   int mainModule = ConvertModulesToJSON(aProcessState, orderedModules, modules);
293 
294   if (mainModule != -1) {
295     aRoot["main_module"] = mainModule;
296   }
297 
298   aRoot["modules"] = modules;
299 
300   // Threads
301   Json::Value threads(Json::arrayValue);
302   int threadCount = aProcessState.threads()->size();
303 
304   for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex) {
305     Json::Value thread;
306     Json::Value stack(Json::arrayValue);
307     const CallStack* rawStack = aProcessState.threads()->at(threadIndex);
308 
309     ConvertStackToJSON(aProcessState, orderedModules, rawStack, stack);
310     thread["frames"] = stack;
311     threads.append(thread);
312   }
313 
314   aRoot["threads"] = threads;
315 }
316 
317 // Process the minidump file and append the JSON-formatted stack traces to
318 // the node specified in |aRoot|
319 
320 static bool
ProcessMinidump(Json::Value & aRoot,const string & aDumpFile)321 ProcessMinidump(Json::Value& aRoot, const string& aDumpFile) {
322   BasicSourceLineResolver resolver;
323   // We don't have a valid symbol resolver so we pass nullptr instead.
324   MinidumpProcessor minidumpProcessor(nullptr, &resolver);
325 
326   // Process the minidump.
327   Minidump dump(aDumpFile);
328   if (!dump.Read()) {
329     return false;
330   }
331 
332   ProcessResult rv;
333   ProcessState processState;
334   rv = minidumpProcessor.Process(&dump, &processState);
335   aRoot["status"] = ResultString(rv);
336 
337   ConvertProcessStateToJSON(processState, aRoot);
338 
339   return true;
340 }
341 
342 // Open the specified file in append mode
343 
344 static ofstream*
OpenAppend(const string & aFilename)345 OpenAppend(const string& aFilename)
346 {
347   ios_base::openmode mode = ios::out | ios::app;
348 
349 #if defined(XP_WIN)
350 #if defined(_MSC_VER)
351   ofstream* file = new ofstream();
352   file->open(UTF8ToWide(aFilename).c_str(), mode);
353 #else   // GCC
354   ofstream* file =
355     new ofstream(WideToMBCP(UTF8ToWide(aFilename), CP_ACP).c_str(), mode);
356 #endif  // _MSC_VER
357 #else // Non-Windows
358   ofstream* file = new ofstream(aFilename.c_str(), mode);
359 #endif // XP_WIN
360   return file;
361 }
362 
363 // Check if a file exists at the specified path
364 
365 static bool
FileExists(const string & aPath)366 FileExists(const string& aPath)
367 {
368 #if defined(XP_WIN)
369   DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str());
370   return (attrs != INVALID_FILE_ATTRIBUTES);
371 #else // Non-Windows
372   struct stat sb;
373   int ret = stat(aPath.c_str(), &sb);
374   if (ret == -1 || !(sb.st_mode & S_IFREG)) {
375     return false;
376   }
377 
378   return true;
379 #endif // XP_WIN
380 }
381 
382 // Update the extra data file by adding the StackTraces field holding the
383 // JSON output of this program.
384 
385 static void
UpdateExtraDataFile(const string & aDumpPath,const Json::Value & aRoot)386 UpdateExtraDataFile(const string &aDumpPath, const Json::Value& aRoot)
387 {
388   string extraDataPath(aDumpPath);
389   int dot = extraDataPath.rfind('.');
390 
391   if (dot < 0) {
392     return; // Not a valid dump path
393   }
394 
395   extraDataPath.replace(dot, extraDataPath.length() - dot, kExtraDataExtension);
396   ofstream* f = OpenAppend(extraDataPath.c_str());
397 
398   if (f->is_open()) {
399     Json::FastWriter writer;
400 
401     *f << "StackTraces=" << writer.write(aRoot);
402 
403     f->close();
404   }
405 
406   delete f;
407 }
408 
409 } // namespace CrashReporter
410 
411 using namespace CrashReporter;
412 
main(int argc,char ** argv)413 int main(int argc, char** argv)
414 {
415   string dumpPath;
416 
417   if (argc > 1) {
418     dumpPath = argv[1];
419   }
420 
421   if (dumpPath.empty()) {
422     exit(EXIT_FAILURE);
423   }
424 
425   if (!FileExists(dumpPath)) {
426     // The dump file does not exist
427     return 1;
428   }
429 
430   // Try processing the minidump
431   Json::Value root;
432   if (ProcessMinidump(root, dumpPath)) {
433     UpdateExtraDataFile(dumpPath, root);
434   }
435 
436   exit(EXIT_SUCCESS);
437 }
438