1# HG changeset patch
2# User Steven Michaud <smichaud@pobox.com>
3# Date 1619800781 18000
4#      Fri Apr 30 11:39:41 2021 -0500
5# Node ID 9f89eb3d68316e8c3a469d1c058ad40c1807d7bc
6# Parent  0db412525773fff333e8d338551021e083c25619
7Bug 1577886 - Add support to for macOS __crash_info data to Breakpad. r=gsvelto
8Differential Revision: https://phabricator.services.mozilla.com/D112871
9
10diff --git a/src/google_breakpad/common/minidump_format.h b/src/google_breakpad/common/minidump_format.h
11--- a/src/google_breakpad/common/minidump_format.h
12+++ b/src/google_breakpad/common/minidump_format.h
13@@ -351,6 +351,10 @@ typedef enum {
14   /* Crashpad extension types. 0x4350 = "CP"
15    * See Crashpad's minidump/minidump_extensions.h. */
16   MD_CRASHPAD_INFO_STREAM        = 0x43500001,  /* MDRawCrashpadInfo  */
17+
18+  /* Data from the __DATA,__crash_info section of every module which contains
19+   * one that has useful data. Only available on macOS. 0x4D7A = "Mz". */
20+  MOZ_MACOS_CRASH_INFO_STREAM    = 0x4d7a0001,
21 } MDStreamType;  /* MINIDUMP_STREAM_TYPE */
22
23
24@@ -1094,6 +1098,52 @@ typedef struct {
25   MDLocationDescriptor module_list;  /* MDRawModuleCrashpadInfoList */
26 } MDRawCrashpadInfo;
27
28+/* macOS __DATA,__crash_info data */
29+
30+typedef struct {
31+  uint64_t stream_type; /* MOZ_MACOS_CRASH_INFO_STREAM */
32+  uint64_t version;
33+  uint64_t thread;
34+  uint64_t dialog_mode;
35+  uint64_t abort_cause; /* Only valid when 'version' > 4 */
36+  /* If/when Apple adds more fields to crashreporter_annotations_t, add
37+   * numerical fields here and change (MDRawMacCrashInfo).record_start_size
38+   * accordingly. Make them all uint64_t, to keep this structure the same size
39+   * on all platforms. 'data' should always be the last field. Add new string
40+   * fields to the end of 'data'. */
41+  /* 'data' currently contains five null-terminated uint8_t arrays, each
42+   * possibly empty (containing only a single terminal null), stored one after
43+   * the other:
44+   *   module_path;
45+   *   message;
46+   *   signature_string;
47+   *   backtrace;
48+   *   message2; */
49+  uint8_t data[0];
50+} MDRawMacCrashInfoRecord;
51+
52+/* This is the maximum supported size for each string in
53+ * (MDRawMacCrashInfoRecord).data. If we encounter a string in the
54+ * __crash_info section which seems larger than this, that's a sign of data
55+ * corruption. */
56+#define MACCRASHINFO_STRING_MAXSIZE 8192
57+
58+/* In principle there should only be one or two non-empty __DATA,__crash_info
59+ * sections per process. But the __crash_info section is almost entirely
60+ * undocumented, so just in case we set a large maximum. */
61+#define MAC_CRASH_INFOS_MAX 20
62+
63+typedef struct {
64+  uint32_t stream_type; /* MOZ_MACOS_CRASH_INFO_STREAM */
65+  uint32_t record_count;
66+  /* The size of the "fixed-size" part of MDRawMacCrashInfoRecord, before the
67+   * 'data' field. This will always be 'sizeof(MDRawMacCrashInfoRecord)'. But
68+   * that value may change if more numerical fields are added to
69+   * MDRawMacCrashInfoRecord in the future. */
70+  uint32_t record_start_size;
71+  MDLocationDescriptor records[MAC_CRASH_INFOS_MAX];
72+} MDRawMacCrashInfo;
73+
74 #if defined(_MSC_VER)
75 #pragma warning(pop)
76 #endif  /* _MSC_VER */
77diff --git a/src/google_breakpad/processor/minidump.h b/src/google_breakpad/processor/minidump.h
78--- a/src/google_breakpad/processor/minidump.h
79+++ b/src/google_breakpad/processor/minidump.h
80@@ -1151,6 +1151,57 @@ class MinidumpCrashpadInfo : public Mini
81   std::map<std::string, std::string> simple_annotations_;
82 };
83
84+// MinidumpMacCrashInfo wraps MDRawMacCrashInfo. It's an optional stream
85+// in a minidump that records information from the __DATA,__crash_info
86+// section of every module in the crashing process that contains one, and
87+// which isn't empty of useful information. Only present on macOS.
88+
89+// Friendly wrapper for the information in MDRawMacCrashInfoRecord.
90+typedef struct crash_info_record {
91+  string module_path;
92+  unsigned long version;
93+  string message;
94+  string signature_string;
95+  string backtrace;
96+  string message2;
97+  unsigned long long thread;
98+  unsigned int dialog_mode;
99+  long long abort_cause; // Only valid when 'version' > 4
100+  crash_info_record()
101+      : version(0), thread(0), dialog_mode(0), abort_cause(0)
102+    {}
103+} crash_info_record_t;
104+
105+class MinidumpMacCrashInfo : public MinidumpStream {
106+ public:
107+  // A human-readable representation of the data from the __DATA,__crash_info
108+  // sections in all of the crashing process's modules that have one, if
109+  // it's not empty of useful data. Suitable for use by "minidump_stackwalk".
110+  string description() const { return description_; }
111+  // A "machine-readable" copy of the same information, suitable for use by
112+  // "minidump_stalkwalk -m".
113+  vector<crash_info_record_t> const records() {
114+    return records_;
115+  }
116+
117+  // Print a human-readable representation of the object to stdout.
118+  void Print();
119+
120+ private:
121+  friend class Minidump;
122+
123+  static const uint32_t kStreamType = MOZ_MACOS_CRASH_INFO_STREAM;
124+
125+  explicit MinidumpMacCrashInfo(Minidump* minidump_);
126+
127+  bool ReadCrashInfoRecord(MDLocationDescriptor location,
128+                           uint32_t record_start_size);
129+  bool Read(uint32_t expected_size);
130+
131+  string description_;
132+  vector<crash_info_record_t> records_;
133+};
134+
135
136 // Minidump is the user's interface to a minidump file.  It wraps MDRawHeader
137 // and provides access to the minidump's top-level stream directory.
138@@ -1214,6 +1265,7 @@ class Minidump {
139   virtual MinidumpBreakpadInfo* GetBreakpadInfo();
140   virtual MinidumpMemoryInfoList* GetMemoryInfoList();
141   MinidumpCrashpadInfo* GetCrashpadInfo();
142+  MinidumpMacCrashInfo* GetMacCrashInfo();
143
144   // The next method also calls GetStream, but is exclusive for Linux dumps.
145   virtual MinidumpLinuxMapsList *GetLinuxMapsList();
146diff --git a/src/google_breakpad/processor/process_state.h b/src/google_breakpad/processor/process_state.h
147--- a/src/google_breakpad/processor/process_state.h
148+++ b/src/google_breakpad/processor/process_state.h
149@@ -112,6 +112,14 @@ class ProcessState {
150     return &thread_memory_regions_;
151   }
152   const SystemInfo* system_info() const { return &system_info_; }
153+  string mac_crash_info() const { return mac_crash_info_; }
154+  size_t mac_crash_info_records_count() const {
155+    return mac_crash_info_records_.size();
156+  }
157+  const crash_info_record_t* mac_crash_info_records() const {
158+    return reinterpret_cast<const crash_info_record_t*>(
159+      &mac_crash_info_records_[0]);
160+  }
161   const CodeModules* modules() const { return modules_; }
162   const CodeModules* unloaded_modules() const { return unloaded_modules_; }
163   const vector<linked_ptr<const CodeModule> >* shrunk_range_modules() const {
164@@ -179,6 +187,10 @@ class ProcessState {
165   // OS and CPU information.
166   SystemInfo system_info_;
167
168+  // Information from __DATA,__crash_info sections.  Only present on macOS.
169+  string mac_crash_info_;
170+  vector<crash_info_record_t> mac_crash_info_records_;
171+
172   // The modules that were loaded into the process represented by the
173   // ProcessState.
174   const CodeModules *modules_;
175diff --git a/src/processor/minidump.cc b/src/processor/minidump.cc
176--- a/src/processor/minidump.cc
177+++ b/src/processor/minidump.cc
178@@ -5116,6 +5116,230 @@ void MinidumpCrashpadInfo::Print() {
179   printf("\n");
180 }
181
182+//
183+// MinidumpMacCrashInfo
184+//
185+
186+MinidumpMacCrashInfo::MinidumpMacCrashInfo(Minidump* minidump)
187+    : MinidumpStream(minidump),
188+      description_(),
189+      records_() {
190+}
191+
192+bool MinidumpMacCrashInfo::ReadCrashInfoRecord(MDLocationDescriptor location,
193+                                               uint32_t record_start_size) {
194+  if (!minidump_->SeekSet(location.rva)) {
195+    BPLOG(ERROR) << "ReadCrashInfoRecord could not seek to record";
196+    return false;
197+  }
198+
199+  // We may be reading a minidump 1) created by (newer) code that defines more
200+  // fields than we do in the fixed-size part of MDRawMacCrashInfoRecord
201+  // (before 'data'), or 2) created by (older) code that defines fewer fields.
202+  // In the first case we read in the newer fields but ignore them. In the
203+  // second case we read in only the older fields, and leave the newer fields
204+  // (in 'raw_record_start') set to zero.
205+  uint32_t raw_record_size = sizeof(MDRawMacCrashInfoRecord);
206+  if (record_start_size > raw_record_size) {
207+    raw_record_size = record_start_size;
208+  }
209+  scoped_ptr< vector<uint8_t> > raw_record(
210+    new vector<uint8_t>(raw_record_size));
211+  if (!minidump_->ReadBytes(&(*raw_record)[0], record_start_size)) {
212+     BPLOG(ERROR) << "ReadCrashInfoRecord could not read " <<
213+                     record_start_size << " bytes of record";
214+     return false;
215+  }
216+  MDRawMacCrashInfoRecord* raw_record_start =
217+    (MDRawMacCrashInfoRecord*) &(*raw_record)[0];
218+
219+  if (minidump_->swap()) {
220+    Swap(&raw_record_start->stream_type);
221+    Swap(&raw_record_start->version);
222+    Swap(&raw_record_start->thread);
223+    Swap(&raw_record_start->dialog_mode);
224+    Swap(&raw_record_start->abort_cause);
225+  }
226+
227+  if (raw_record_start->stream_type != MOZ_MACOS_CRASH_INFO_STREAM) {
228+    BPLOG(ERROR) << "ReadCrashInfoRecord stream type mismatch, " <<
229+                    raw_record_start->stream_type << " != " <<
230+                    MOZ_MACOS_CRASH_INFO_STREAM;
231+    return false;
232+  }
233+
234+  uint32_t string_data_size = location.data_size - record_start_size;
235+  scoped_ptr< vector<uint8_t> > data(new vector<uint8_t>(string_data_size));
236+  if (!minidump_->ReadBytes(&(*data)[0], string_data_size)) {
237+     BPLOG(ERROR) << "ReadCrashInfoRecord could not read " <<
238+                     string_data_size << " bytes of record data";
239+     return false;
240+  }
241+
242+  crash_info_record_t record;
243+
244+  record.version = (unsigned long) raw_record_start->version;
245+  record.thread = (unsigned long long) raw_record_start->thread;
246+  record.dialog_mode = (unsigned int) raw_record_start->dialog_mode;
247+  record.abort_cause = (long long) raw_record_start->abort_cause;
248+
249+  // Once again, we may be reading a minidump created by newer code that
250+  // stores more strings than we expect in (MDRawMacCrashInfoRecord).data,
251+  // or one created by older code that contains fewer strings than we
252+  // expect. In the first case we ignore the "extra" strings. To deal with
253+  // the second case we bail when 'offset >= string_data_size'.
254+  const char* string_data = (const char*) &(*data)[0];
255+  size_t offset = 0;
256+  for (int i = 1; i <= 5; ++i) {
257+    switch (i) {
258+      case 1:
259+        record.module_path.append(string_data);
260+        break;
261+      case 2:
262+        record.message.append(string_data);
263+        break;
264+      case 3:
265+        record.signature_string.append(string_data);
266+        break;
267+      case 4:
268+        record.backtrace.append(string_data);
269+        break;
270+      case 5:
271+        record.message2.append(string_data);
272+        break;
273+    }
274+    size_t char_array_size = strlen(string_data) + 1;
275+    offset += char_array_size;
276+    if (offset >= string_data_size) {
277+      break;
278+    }
279+    string_data += char_array_size;
280+  }
281+
282+  records_.push_back(record);
283+
284+  description_.append(" Module \"");
285+  description_.append(record.module_path);
286+  description_.append("\":\n");
287+
288+  int num_fields = 6;
289+  if (record.version > 4) {
290+    num_fields = 7;
291+  }
292+  for (int i = 1; i <= num_fields; ++i) {
293+    switch (i) {
294+      case 1:
295+        if (!record.message.empty()) {
296+          description_.append("  message: \"");
297+          description_.append(record.message);
298+          description_.append("\"\n");
299+        }
300+        break;
301+      case 2:
302+        if (!record.signature_string.empty()) {
303+          description_.append("  signature_string: \"");
304+          description_.append(record.signature_string);
305+          description_.append("\"\n");
306+        }
307+        break;
308+      case 3:
309+        if (!record.backtrace.empty()) {
310+          description_.append("  backtrace: \"");
311+          description_.append(record.backtrace);
312+          description_.append("\"\n");
313+        }
314+        break;
315+      case 4:
316+        if (!record.message2.empty()) {
317+          description_.append("  message2: \"");
318+          description_.append(record.message2);
319+          description_.append("\"\n");
320+        }
321+        break;
322+      case 5:
323+        if (record.thread) {
324+          char thread[128];
325+          snprintf(thread, sizeof(thread), "  thread: 0x%llx\n",
326+                   record.thread);
327+          description_.append(thread);
328+        }
329+        break;
330+      case 6:
331+        if (record.dialog_mode) {
332+          char dialog_mode[128];
333+          snprintf(dialog_mode, sizeof(dialog_mode), "  dialog_mode: 0x%x\n",
334+                   record.dialog_mode);
335+          description_.append(dialog_mode);
336+        }
337+        break;
338+      case 7:
339+        if (record.abort_cause) {
340+          char abort_cause[128];
341+          snprintf(abort_cause, sizeof(abort_cause), "  abort_cause: %lld\n",
342+                   record.abort_cause);
343+          description_.append(abort_cause);
344+        }
345+        break;
346+      default:
347+        break;
348+    }
349+  }
350+
351+  return true;
352+}
353+
354+bool MinidumpMacCrashInfo::Read(uint32_t expected_size) {
355+  description_.clear();
356+  records_.clear();
357+  valid_ = false;
358+
359+  MDRawMacCrashInfo crash_info;
360+  if (expected_size != sizeof(crash_info)) {
361+    BPLOG(ERROR) << "MinidumpMacCrashInfo size mismatch, " <<
362+                    expected_size << " != " << sizeof(crash_info);
363+    return false;
364+  }
365+  if (!minidump_->ReadBytes(&crash_info, sizeof(crash_info))) {
366+    BPLOG(ERROR) << "MinidumpMacCrashInfo could not read " <<
367+                    sizeof(crash_info) << " bytes";
368+    return false;
369+  }
370+  if (minidump_->swap()) {
371+    Swap(&crash_info.stream_type);
372+    Swap(&crash_info.record_count);
373+    Swap(&crash_info.record_start_size);
374+    for (uint32_t i = 0; i < crash_info.record_count; ++i) {
375+      Swap(&crash_info.records[i].data_size);
376+      Swap(&crash_info.records[i].rva);
377+    }
378+  }
379+  if (crash_info.stream_type != MOZ_MACOS_CRASH_INFO_STREAM) {
380+    BPLOG(ERROR) << "MinidumpMacCrashInfo stream type mismatch, " <<
381+                    crash_info.stream_type << " != " <<
382+                    MOZ_MACOS_CRASH_INFO_STREAM;
383+    return false;
384+  }
385+
386+  for (uint32_t i = 0; i < crash_info.record_count; ++i) {
387+    if (!ReadCrashInfoRecord(crash_info.records[i],
388+                             crash_info.record_start_size)) {
389+      return false;
390+    }
391+  }
392+
393+  valid_ = true;
394+  return true;
395+}
396+
397+void MinidumpMacCrashInfo::Print() {
398+  if (!valid_) {
399+    BPLOG(ERROR) << "MinidumpMacCrashInfo cannot print invalid data";
400+    return;
401+  }
402+
403+  printf("MinidumpMacCrashInfo:\n\n");
404+  printf("%s", description_.c_str());
405+}
406
407 //
408 // Minidump
409@@ -5378,7 +5602,8 @@ bool Minidump::Read() {
410         case MD_SYSTEM_INFO_STREAM:
411         case MD_MISC_INFO_STREAM:
412         case MD_BREAKPAD_INFO_STREAM:
413-        case MD_CRASHPAD_INFO_STREAM: {
414+        case MD_CRASHPAD_INFO_STREAM:
415+        case MOZ_MACOS_CRASH_INFO_STREAM: {
416           if (stream_map_->find(stream_type) != stream_map_->end()) {
417             // Another stream with this type was already found.  A minidump
418             // file should contain at most one of each of these stream types.
419@@ -5499,6 +5724,11 @@ MinidumpCrashpadInfo* Minidump::GetCrash
420   return GetStream(&crashpad_info);
421 }
422
423+MinidumpMacCrashInfo* Minidump::GetMacCrashInfo() {
424+  MinidumpMacCrashInfo* mac_crash_info;
425+  return GetStream(&mac_crash_info);
426+}
427+
428 static const char* get_stream_name(uint32_t stream_type) {
429   switch (stream_type) {
430   case MD_UNUSED_STREAM:
431@@ -5571,6 +5801,8 @@ static const char* get_stream_name(uint3
432     return "MD_LINUX_DSO_DEBUG";
433   case MD_CRASHPAD_INFO_STREAM:
434     return "MD_CRASHPAD_INFO_STREAM";
435+  case MOZ_MACOS_CRASH_INFO_STREAM:
436+    return "MOZ_MACOS_CRASH_INFO_STREAM";
437   default:
438     return "unknown";
439   }
440diff --git a/src/processor/minidump_processor.cc b/src/processor/minidump_processor.cc
441--- a/src/processor/minidump_processor.cc
442+++ b/src/processor/minidump_processor.cc
443@@ -137,6 +137,12 @@ ProcessResult MinidumpProcessor::Process
444     }
445   }
446
447+  MinidumpMacCrashInfo *crash_info = dump->GetMacCrashInfo();
448+  if (crash_info) {
449+    process_state->mac_crash_info_ = crash_info->description();
450+    process_state->mac_crash_info_records_ = crash_info->records();
451+  }
452+
453   // This will just return an empty string if it doesn't exist.
454   process_state->assertion_ = GetAssertion(dump);
455
456diff --git a/src/processor/stackwalk_common.cc b/src/processor/stackwalk_common.cc
457--- a/src/processor/stackwalk_common.cc
458+++ b/src/processor/stackwalk_common.cc
459@@ -872,6 +872,12 @@ void PrintProcessState(const ProcessStat
460     printf("Process uptime: not available\n");
461   }
462
463+  if (!process_state.mac_crash_info().empty()) {
464+    printf("\n");
465+    printf("Application-specific information:\n");
466+    printf("%s", process_state.mac_crash_info().c_str());
467+  }
468+
469   // If the thread that requested the dump is known, print it first.
470   int requesting_thread = process_state.requesting_thread();
471   if (requesting_thread != -1) {
472@@ -955,6 +961,44 @@ void PrintProcessStateMachineReadable(co
473     printf("\n");
474   }
475
476+  const crash_info_record_t* crash_info_records =
477+    process_state.mac_crash_info_records();
478+  size_t num_records =
479+    process_state.mac_crash_info_records_count();
480+  for (size_t i = 0; i < num_records; ++i) {
481+    char thread_str[32];
482+    if (crash_info_records[i].thread) {
483+      snprintf(thread_str, sizeof(thread_str), "0x%llx",
484+               crash_info_records[i].thread);
485+    } else {
486+      strncpy(thread_str, "0", sizeof(thread_str));
487+    }
488+    char dialog_mode_str[32];
489+    if (crash_info_records[i].dialog_mode) {
490+      snprintf(dialog_mode_str, sizeof(dialog_mode_str), "0x%x",
491+               crash_info_records[i].dialog_mode);
492+    } else {
493+      strncpy(dialog_mode_str, "0", sizeof(dialog_mode_str));
494+    }
495+    char abort_cause_str[32];
496+    if (crash_info_records[i].abort_cause) {
497+      snprintf(abort_cause_str, sizeof(abort_cause_str), "%lld",
498+               crash_info_records[i].abort_cause);
499+    } else {
500+      strncpy(abort_cause_str, "0", sizeof(abort_cause_str));
501+    }
502+    printf("MacCrashInfo%c%s%c%lu%c%s%c%s%c%s%c%s%c%s%c%s%c%s\n",
503+           kOutputSeparator, crash_info_records[i].module_path.c_str(),
504+           kOutputSeparator, crash_info_records[i].version,
505+           kOutputSeparator, crash_info_records[i].message.c_str(),
506+           kOutputSeparator, crash_info_records[i].signature_string.c_str(),
507+           kOutputSeparator, crash_info_records[i].backtrace.c_str(),
508+           kOutputSeparator, crash_info_records[i].message2.c_str(),
509+           kOutputSeparator, thread_str,
510+           kOutputSeparator, dialog_mode_str,
511+           kOutputSeparator, abort_cause_str);
512+  }
513+
514   PrintModulesMachineReadable(process_state.modules());
515   PrintUnloadedModulesMachineReadable(process_state.unloaded_modules());
516
517