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