• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <libdebuggerd/tombstone_proto_to_text.h>
18 #include <libdebuggerd/utility_host.h>
19 
20 #include <ctype.h>
21 #include <inttypes.h>
22 
23 #include <algorithm>
24 #include <functional>
25 #include <optional>
26 #include <set>
27 #include <string>
28 #include <unordered_set>
29 #include <utility>
30 #include <vector>
31 
32 #include <android-base/stringprintf.h>
33 #include <android-base/strings.h>
34 #include <android-base/unique_fd.h>
35 
36 #include "libdebuggerd/utility_host.h"
37 #include "tombstone.pb.h"
38 
39 using android::base::StringAppendF;
40 using android::base::StringPrintf;
41 
42 #define CB(log, ...) callback(StringPrintf(__VA_ARGS__), log)
43 #define CBL(...) CB(true, __VA_ARGS__)
44 #define CBS(...) CB(false, __VA_ARGS__)
45 using CallbackType = std::function<void(const std::string& line, bool should_log)>;
46 using SymbolizeCallbackType = std::function<void(const BacktraceFrame& frame)>;
47 
48 #define DESCRIBE_FLAG(flag) \
49   if (value & flag) {       \
50     desc += ", ";           \
51     desc += #flag;          \
52     value &= ~flag;         \
53   }
54 
describe_end(long value,std::string & desc)55 static std::string describe_end(long value, std::string& desc) {
56   if (value) {
57     desc += StringPrintf(", unknown 0x%lx", value);
58   }
59   return desc.empty() ? "" : " (" + desc.substr(2) + ")";
60 }
61 
abi_string(const Architecture & arch)62 static const char* abi_string(const Architecture& arch) {
63   switch (arch) {
64     case Architecture::ARM32:
65       return "arm";
66     case Architecture::ARM64:
67       return "arm64";
68     case Architecture::RISCV64:
69       return "riscv64";
70     case Architecture::X86:
71       return "x86";
72     case Architecture::X86_64:
73       return "x86_64";
74     default:
75       return "<unknown>";
76   }
77 }
78 
pointer_width(const Tombstone & tombstone)79 static int pointer_width(const Tombstone& tombstone) {
80   switch (tombstone.arch()) {
81     case Architecture::ARM32:
82       return 4;
83     case Architecture::ARM64:
84       return 8;
85     case Architecture::RISCV64:
86       return 8;
87     case Architecture::X86:
88       return 4;
89     case Architecture::X86_64:
90       return 8;
91     default:
92       return 8;
93   }
94 }
95 
untag_address(Architecture arch,uint64_t addr)96 static uint64_t untag_address(Architecture arch, uint64_t addr) {
97   if (arch == Architecture::ARM64) {
98     return addr & ((1ULL << 56) - 1);
99   }
100   return addr;
101 }
102 
print_thread_header(CallbackType callback,const Tombstone & tombstone,const Thread & thread,bool should_log)103 static void print_thread_header(CallbackType callback, const Tombstone& tombstone,
104                                 const Thread& thread, bool should_log) {
105   const char* process_name = "<unknown>";
106   if (!tombstone.command_line().empty()) {
107     process_name = tombstone.command_line()[0].c_str();
108     CB(should_log, "Cmdline: %s", android::base::Join(tombstone.command_line(), " ").c_str());
109   } else {
110     CB(should_log, "Cmdline: <unknown>");
111   }
112   CB(should_log, "pid: %d, tid: %d, name: %s  >>> %s <<<", tombstone.pid(), thread.id(),
113      thread.name().c_str(), process_name);
114   CB(should_log, "uid: %d", tombstone.uid());
115   if (thread.tagged_addr_ctrl() != -1) {
116     CB(should_log, "tagged_addr_ctrl: %016" PRIx64 "%s", thread.tagged_addr_ctrl(),
117        describe_tagged_addr_ctrl(thread.tagged_addr_ctrl()).c_str());
118   }
119   if (thread.pac_enabled_keys() != -1) {
120     CB(should_log, "pac_enabled_keys: %016" PRIx64 "%s", thread.pac_enabled_keys(),
121        describe_pac_enabled_keys(thread.pac_enabled_keys()).c_str());
122   }
123 }
124 
print_register_row(CallbackType callback,int word_size,std::vector<std::pair<std::string,uint64_t>> row,bool should_log)125 static void print_register_row(CallbackType callback, int word_size,
126                                std::vector<std::pair<std::string, uint64_t>> row, bool should_log) {
127   std::string output = "  ";
128   for (const auto& [name, value] : row) {
129     output += android::base::StringPrintf("  %-3s %0*" PRIx64, name.c_str(), 2 * word_size,
130                                           static_cast<uint64_t>(value));
131   }
132   callback(output, should_log);
133 }
134 
print_thread_registers(CallbackType callback,const Tombstone & tombstone,const Thread & thread,bool should_log)135 static void print_thread_registers(CallbackType callback, const Tombstone& tombstone,
136                                    const Thread& thread, bool should_log) {
137   static constexpr size_t column_count = 4;
138   std::vector<std::pair<std::string, uint64_t>> current_row;
139   std::vector<std::pair<std::string, uint64_t>> special_row;
140   std::unordered_set<std::string> special_registers;
141 
142   int word_size = pointer_width(tombstone);
143 
144   switch (tombstone.arch()) {
145     case Architecture::ARM32:
146       special_registers = {"ip", "lr", "sp", "pc", "pst"};
147       break;
148 
149     case Architecture::ARM64:
150       special_registers = {"ip", "lr", "sp", "pc", "pst"};
151       break;
152 
153     case Architecture::RISCV64:
154       special_registers = {"ra", "sp", "pc"};
155       break;
156 
157     case Architecture::X86:
158       special_registers = {"ebp", "esp", "eip"};
159       break;
160 
161     case Architecture::X86_64:
162       special_registers = {"rbp", "rsp", "rip"};
163       break;
164 
165     default:
166       CBL("Unknown architecture %d printing thread registers", tombstone.arch());
167       return;
168   }
169 
170   for (const auto& reg : thread.registers()) {
171     auto row = &current_row;
172     if (special_registers.count(reg.name()) == 1) {
173       row = &special_row;
174     }
175 
176     row->emplace_back(reg.name(), reg.u64());
177     if (current_row.size() == column_count) {
178       print_register_row(callback, word_size, current_row, should_log);
179       current_row.clear();
180     }
181   }
182 
183   if (!current_row.empty()) {
184     print_register_row(callback, word_size, current_row, should_log);
185   }
186 
187   print_register_row(callback, word_size, special_row, should_log);
188 }
189 
print_backtrace(CallbackType callback,SymbolizeCallbackType symbolize,const Tombstone & tombstone,const google::protobuf::RepeatedPtrField<BacktraceFrame> & backtrace,bool should_log)190 static void print_backtrace(CallbackType callback, SymbolizeCallbackType symbolize,
191                             const Tombstone& tombstone,
192                             const google::protobuf::RepeatedPtrField<BacktraceFrame>& backtrace,
193                             bool should_log) {
194   int index = 0;
195   for (const auto& frame : backtrace) {
196     std::string function;
197 
198     if (!frame.function_name().empty()) {
199       function =
200           StringPrintf(" (%s+%" PRId64 ")", frame.function_name().c_str(), frame.function_offset());
201     }
202 
203     std::string build_id;
204     if (!frame.build_id().empty()) {
205       build_id = StringPrintf(" (BuildId: %s)", frame.build_id().c_str());
206     }
207 
208     std::string line =
209         StringPrintf("      #%02d pc %0*" PRIx64 "  %s", index++, pointer_width(tombstone) * 2,
210                      frame.rel_pc(), frame.file_name().c_str());
211     if (frame.file_map_offset() != 0) {
212       line += StringPrintf(" (offset 0x%" PRIx64 ")", frame.file_map_offset());
213     }
214     line += function + build_id;
215     CB(should_log, "%s", line.c_str());
216 
217     symbolize(frame);
218   }
219 }
220 
print_thread_backtrace(CallbackType callback,SymbolizeCallbackType symbolize,const Tombstone & tombstone,const Thread & thread,bool should_log)221 static void print_thread_backtrace(CallbackType callback, SymbolizeCallbackType symbolize,
222                                    const Tombstone& tombstone, const Thread& thread,
223                                    bool should_log) {
224   CBS("");
225   CB(should_log, "%d total frames", thread.current_backtrace().size());
226   CB(should_log, "backtrace:");
227   if (!thread.backtrace_note().empty()) {
228     CB(should_log, "  NOTE: %s",
229        android::base::Join(thread.backtrace_note(), "\n  NOTE: ").c_str());
230   }
231   print_backtrace(callback, symbolize, tombstone, thread.current_backtrace(), should_log);
232 }
233 
print_thread_memory_dump(CallbackType callback,const Tombstone & tombstone,const Thread & thread)234 static void print_thread_memory_dump(CallbackType callback, const Tombstone& tombstone,
235                                      const Thread& thread) {
236   static constexpr size_t bytes_per_line = 16;
237   static_assert(bytes_per_line == kTagGranuleSize);
238   int word_size = pointer_width(tombstone);
239   for (const auto& mem : thread.memory_dump()) {
240     CBS("");
241     if (mem.mapping_name().empty()) {
242       CBS("memory near %s:", mem.register_name().c_str());
243     } else {
244       CBS("memory near %s (%s):", mem.register_name().c_str(), mem.mapping_name().c_str());
245     }
246     uint64_t addr = mem.begin_address();
247     for (size_t offset = 0; offset < mem.memory().size(); offset += bytes_per_line) {
248       uint64_t tagged_addr = addr;
249       if (mem.has_arm_mte_metadata() &&
250           mem.arm_mte_metadata().memory_tags().size() > offset / kTagGranuleSize) {
251         tagged_addr |=
252             static_cast<uint64_t>(mem.arm_mte_metadata().memory_tags()[offset / kTagGranuleSize])
253             << 56;
254       }
255       std::string line = StringPrintf("    %0*" PRIx64, word_size * 2, tagged_addr + offset);
256 
257       size_t bytes = std::min(bytes_per_line, mem.memory().size() - offset);
258       for (size_t i = 0; i < bytes; i += word_size) {
259         uint64_t word = 0;
260 
261         // Assumes little-endian, but what doesn't?
262         memcpy(&word, mem.memory().data() + offset + i, word_size);
263 
264         StringAppendF(&line, " %0*" PRIx64, word_size * 2, word);
265       }
266 
267       char ascii[bytes_per_line + 1];
268 
269       memset(ascii, '.', sizeof(ascii));
270       ascii[bytes_per_line] = '\0';
271 
272       for (size_t i = 0; i < bytes; ++i) {
273         uint8_t byte = mem.memory()[offset + i];
274         if (byte >= 0x20 && byte < 0x7f) {
275           ascii[i] = byte;
276         }
277       }
278 
279       CBS("%s  %s", line.c_str(), ascii);
280     }
281   }
282 }
283 
print_thread(CallbackType callback,SymbolizeCallbackType symbolize,const Tombstone & tombstone,const Thread & thread)284 static void print_thread(CallbackType callback, SymbolizeCallbackType symbolize,
285                          const Tombstone& tombstone, const Thread& thread) {
286   print_thread_header(callback, tombstone, thread, false);
287   print_thread_registers(callback, tombstone, thread, false);
288   print_thread_backtrace(callback, symbolize, tombstone, thread, false);
289   print_thread_memory_dump(callback, tombstone, thread);
290 }
291 
print_tag_dump(CallbackType callback,const Tombstone & tombstone)292 static void print_tag_dump(CallbackType callback, const Tombstone& tombstone) {
293   if (!tombstone.has_signal_info()) return;
294 
295   const Signal& signal = tombstone.signal_info();
296 
297   if (!signal.has_fault_address() || !signal.has_fault_adjacent_metadata()) {
298     return;
299   }
300 
301   const MemoryDump& memory_dump = signal.fault_adjacent_metadata();
302 
303   if (!memory_dump.has_arm_mte_metadata() || memory_dump.arm_mte_metadata().memory_tags().empty()) {
304     return;
305   }
306 
307   const std::string& tags = memory_dump.arm_mte_metadata().memory_tags();
308 
309   CBS("");
310   CBS("Memory tags around the fault address (0x%" PRIx64 "), one tag per %zu bytes:",
311       signal.fault_address(), kTagGranuleSize);
312   constexpr uintptr_t kRowStartMask = ~(kNumTagColumns * kTagGranuleSize - 1);
313 
314   size_t tag_index = 0;
315   size_t num_tags = tags.length();
316   uintptr_t fault_granule =
317       untag_address(tombstone.arch(), signal.fault_address()) & ~(kTagGranuleSize - 1);
318   for (size_t row = 0; tag_index < num_tags; ++row) {
319     uintptr_t row_addr =
320         (memory_dump.begin_address() + row * kNumTagColumns * kTagGranuleSize) & kRowStartMask;
321     std::string row_contents;
322     bool row_has_fault = false;
323 
324     for (size_t column = 0; column < kNumTagColumns; ++column) {
325       uintptr_t granule_addr = row_addr + column * kTagGranuleSize;
326       if (granule_addr < memory_dump.begin_address() ||
327           granule_addr >= memory_dump.begin_address() + num_tags * kTagGranuleSize) {
328         row_contents += " . ";
329       } else if (granule_addr == fault_granule) {
330         row_contents += StringPrintf("[%1hhx]", tags[tag_index++]);
331         row_has_fault = true;
332       } else {
333         row_contents += StringPrintf(" %1hhx ", tags[tag_index++]);
334       }
335     }
336 
337     if (row_contents.back() == ' ') row_contents.pop_back();
338 
339     if (row_has_fault) {
340       CBS("    =>0x%" PRIxPTR ":%s", row_addr, row_contents.c_str());
341     } else {
342       CBS("      0x%" PRIxPTR ":%s", row_addr, row_contents.c_str());
343     }
344   }
345 }
346 
print_memory_maps(CallbackType callback,const Tombstone & tombstone)347 static void print_memory_maps(CallbackType callback, const Tombstone& tombstone) {
348   int word_size = pointer_width(tombstone);
349   const auto format_pointer = [word_size](uint64_t ptr) -> std::string {
350     if (word_size == 8) {
351       uint64_t top = ptr >> 32;
352       uint64_t bottom = ptr & 0xFFFFFFFF;
353       return StringPrintf("%08" PRIx64 "'%08" PRIx64, top, bottom);
354     }
355 
356     return StringPrintf("%0*" PRIx64, word_size * 2, ptr);
357   };
358 
359   std::string memory_map_header =
360       StringPrintf("memory map (%d %s):", tombstone.memory_mappings().size(),
361                    tombstone.memory_mappings().size() == 1 ? "entry" : "entries");
362 
363   const Signal& signal_info = tombstone.signal_info();
364   bool has_fault_address = signal_info.has_fault_address();
365   uint64_t fault_address = untag_address(tombstone.arch(), signal_info.fault_address());
366   bool preamble_printed = false;
367   bool printed_fault_address_marker = false;
368   for (const auto& map : tombstone.memory_mappings()) {
369     if (!preamble_printed) {
370       preamble_printed = true;
371       if (has_fault_address) {
372         if (fault_address < map.begin_address()) {
373           memory_map_header +=
374               StringPrintf("\n--->Fault address falls at %s before any mapped regions",
375                            format_pointer(fault_address).c_str());
376           printed_fault_address_marker = true;
377         } else {
378           memory_map_header += " (fault address prefixed with --->)";
379         }
380       }
381       CBS("%s", memory_map_header.c_str());
382     }
383 
384     std::string line = "    ";
385     if (has_fault_address && !printed_fault_address_marker) {
386       if (fault_address < map.begin_address()) {
387         printed_fault_address_marker = true;
388         CBS("--->Fault address falls at %s between mapped regions",
389             format_pointer(fault_address).c_str());
390       } else if (fault_address >= map.begin_address() && fault_address < map.end_address()) {
391         printed_fault_address_marker = true;
392         line = "--->";
393       }
394     }
395     StringAppendF(&line, "%s-%s", format_pointer(map.begin_address()).c_str(),
396                   format_pointer(map.end_address() - 1).c_str());
397     StringAppendF(&line, " %s%s%s", map.read() ? "r" : "-", map.write() ? "w" : "-",
398                   map.execute() ? "x" : "-");
399     StringAppendF(&line, "  %8" PRIx64 "  %8" PRIx64, map.offset(),
400                   map.end_address() - map.begin_address());
401 
402     if (!map.mapping_name().empty()) {
403       StringAppendF(&line, "  %s", map.mapping_name().c_str());
404 
405       if (!map.build_id().empty()) {
406         StringAppendF(&line, " (BuildId: %s)", map.build_id().c_str());
407       }
408 
409       if (map.load_bias() != 0) {
410         StringAppendF(&line, " (load bias 0x%" PRIx64 ")", map.load_bias());
411       }
412     }
413 
414     CBS("%s", line.c_str());
415   }
416 
417   if (has_fault_address && !printed_fault_address_marker) {
418     CBS("--->Fault address falls at %s after any mapped regions",
419         format_pointer(fault_address).c_str());
420   }
421 }
422 
print_main_thread(CallbackType callback,SymbolizeCallbackType symbolize,const Tombstone & tombstone,const Thread & thread)423 static void print_main_thread(CallbackType callback, SymbolizeCallbackType symbolize,
424                               const Tombstone& tombstone, const Thread& thread) {
425   print_thread_header(callback, tombstone, thread, true);
426 
427   const Signal& signal_info = tombstone.signal_info();
428   std::string sender_desc;
429 
430   if (signal_info.has_sender()) {
431     sender_desc =
432         StringPrintf(" from pid %d, uid %d", signal_info.sender_pid(), signal_info.sender_uid());
433   }
434 
435   bool is_async_mte_crash = false;
436   bool is_mte_crash = false;
437   if (!tombstone.has_signal_info()) {
438     CBL("signal information missing");
439   } else {
440     std::string fault_addr_desc;
441     if (signal_info.has_fault_address()) {
442       fault_addr_desc =
443           StringPrintf("0x%0*" PRIx64, 2 * pointer_width(tombstone), signal_info.fault_address());
444     } else {
445       fault_addr_desc = "--------";
446     }
447 
448     CBL("signal %d (%s), code %d (%s%s), fault addr %s", signal_info.number(),
449         signal_info.name().c_str(), signal_info.code(), signal_info.code_name().c_str(),
450         sender_desc.c_str(), fault_addr_desc.c_str());
451 #ifdef SEGV_MTEAERR
452     is_async_mte_crash = signal_info.number() == SIGSEGV && signal_info.code() == SEGV_MTEAERR;
453     is_mte_crash = is_async_mte_crash ||
454                    (signal_info.number() == SIGSEGV && signal_info.code() == SEGV_MTESERR);
455 #endif
456   }
457 
458   if (tombstone.causes_size() == 1) {
459     CBL("Cause: %s", tombstone.causes(0).human_readable().c_str());
460   }
461 
462   if (!tombstone.abort_message().empty()) {
463     CBL("Abort message: '%s'", tombstone.abort_message().c_str());
464   }
465 
466   for (const auto& crash_detail : tombstone.crash_details()) {
467     std::string oct_encoded_name = oct_encode_non_printable(crash_detail.name());
468     std::string oct_encoded_data = oct_encode_non_printable(crash_detail.data());
469     CBL("Extra crash detail: %s: '%s'", oct_encoded_name.c_str(), oct_encoded_data.c_str());
470   }
471 
472   print_thread_registers(callback, tombstone, thread, true);
473   if (is_async_mte_crash) {
474     CBL("Note: This crash is a delayed async MTE crash. Memory corruption has occurred");
475     CBL("      in this process. The stack trace below is the first system call or context");
476     CBL("      switch that was executed after the memory corruption happened.");
477   }
478   print_thread_backtrace(callback, symbolize, tombstone, thread, true);
479 
480   if (tombstone.causes_size() > 1) {
481     CBS("");
482     CBL("Note: multiple potential causes for this crash were detected, listing them in decreasing "
483         "order of likelihood.");
484   }
485 
486   if (tombstone.has_stack_history_buffer()) {
487     for (const StackHistoryBufferEntry& shbe : tombstone.stack_history_buffer().entries()) {
488       std::string stack_record_str = StringPrintf(
489           "stack_record fp:0x%" PRIx64 " tag:0x%" PRIx64 " pc:%s+0x%" PRIx64, shbe.fp(), shbe.tag(),
490           shbe.addr().file_name().c_str(), shbe.addr().rel_pc());
491       if (!shbe.addr().build_id().empty()) {
492         StringAppendF(&stack_record_str, " (BuildId: %s)", shbe.addr().build_id().c_str());
493       }
494 
495       CBL("%s", stack_record_str.c_str());
496     }
497   }
498 
499   for (const Cause& cause : tombstone.causes()) {
500     if (tombstone.causes_size() > 1) {
501       CBS("");
502       CBL("Cause: %s", cause.human_readable().c_str());
503     }
504 
505     if (cause.has_memory_error() && cause.memory_error().has_heap()) {
506       const HeapObject& heap_object = cause.memory_error().heap();
507 
508       if (heap_object.deallocation_backtrace_size() != 0) {
509         CBS("");
510         CBL("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid());
511         print_backtrace(callback, symbolize, tombstone, heap_object.deallocation_backtrace(), true);
512       }
513 
514       if (heap_object.allocation_backtrace_size() != 0) {
515         CBS("");
516         CBL("allocated by thread %" PRIu64 ":", heap_object.allocation_tid());
517         print_backtrace(callback, symbolize, tombstone, heap_object.allocation_backtrace(), true);
518       }
519     }
520   }
521 
522   print_tag_dump(callback, tombstone);
523 
524   if (is_mte_crash) {
525     CBS("");
526     CBL("Learn more about MTE reports: "
527         "https://source.android.com/docs/security/test/memory-safety/mte-reports");
528   }
529 
530   print_thread_memory_dump(callback, tombstone, thread);
531 
532   CBS("");
533 
534   // No memory maps to print.
535   if (!tombstone.memory_mappings().empty()) {
536     print_memory_maps(callback, tombstone);
537   } else {
538     CBS("No memory maps found");
539   }
540 }
541 
print_logs(CallbackType callback,const Tombstone & tombstone,int tail)542 void print_logs(CallbackType callback, const Tombstone& tombstone, int tail) {
543   for (const auto& buffer : tombstone.log_buffers()) {
544     if (tail) {
545       CBS("--------- tail end of log %s", buffer.name().c_str());
546     } else {
547       CBS("--------- log %s", buffer.name().c_str());
548     }
549 
550     int begin = 0;
551     if (tail != 0) {
552       begin = std::max(0, buffer.logs().size() - tail);
553     }
554 
555     for (int i = begin; i < buffer.logs().size(); ++i) {
556       const LogMessage& msg = buffer.logs(i);
557 
558       static const char* kPrioChars = "!.VDIWEFS";
559       char priority = (msg.priority() < strlen(kPrioChars) ? kPrioChars[msg.priority()] : '?');
560       CBS("%s %5u %5u %c %-8s: %s", msg.timestamp().c_str(), msg.pid(), msg.tid(), priority,
561           msg.tag().c_str(), msg.message().c_str());
562     }
563   }
564 }
565 
print_guest_thread(CallbackType callback,SymbolizeCallbackType symbolize,const Tombstone & tombstone,const Thread & guest_thread,pid_t tid,bool should_log)566 static void print_guest_thread(CallbackType callback, SymbolizeCallbackType symbolize,
567                                const Tombstone& tombstone, const Thread& guest_thread, pid_t tid,
568                                bool should_log) {
569   CBS("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---");
570   CBS("Guest thread information for tid: %d", tid);
571   print_thread_registers(callback, tombstone, guest_thread, should_log);
572 
573   CBS("");
574   CB(true, "%d total frames", guest_thread.current_backtrace().size());
575   CB(true, "backtrace:");
576   print_backtrace(callback, symbolize, tombstone, guest_thread.current_backtrace(), should_log);
577 
578   print_thread_memory_dump(callback, tombstone, guest_thread);
579 }
580 
tombstone_proto_to_text(const Tombstone & tombstone,CallbackType callback,SymbolizeCallbackType symbolize)581 bool tombstone_proto_to_text(const Tombstone& tombstone, CallbackType callback,
582                              SymbolizeCallbackType symbolize) {
583   CBL("*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***");
584   CBL("Build fingerprint: '%s'", tombstone.build_fingerprint().c_str());
585   CBL("Revision: '%s'", tombstone.revision().c_str());
586   CBL("ABI: '%s'", abi_string(tombstone.arch()));
587   if (tombstone.guest_arch() != Architecture::NONE) {
588     CBL("Guest architecture: '%s'", abi_string(tombstone.guest_arch()));
589   }
590   CBL("Timestamp: %s", tombstone.timestamp().c_str());
591   CBL("Process uptime: %ds", tombstone.process_uptime());
592 
593   // only print this info if the page size is not 4k or has been in 16k mode
594   if (tombstone.page_size() != 4096) {
595     CBL("Page size: %d bytes", tombstone.page_size());
596   } else if (tombstone.has_been_16kb_mode()) {
597     CBL("Has been in 16 KB mode before: yes");
598   }
599 
600   // Process header
601   const auto& threads = tombstone.threads();
602   auto main_thread_it = threads.find(tombstone.tid());
603   if (main_thread_it == threads.end()) {
604     CBL("failed to find entry for main thread in tombstone");
605     return false;
606   }
607 
608   const auto& main_thread = main_thread_it->second;
609 
610   print_main_thread(callback, symbolize, tombstone, main_thread);
611 
612   print_logs(callback, tombstone, 50);
613 
614   const auto& guest_threads = tombstone.guest_threads();
615   auto main_guest_thread_it = guest_threads.find(tombstone.tid());
616   if (main_guest_thread_it != threads.end()) {
617     print_guest_thread(callback, symbolize, tombstone, main_guest_thread_it->second,
618                        tombstone.tid(), true);
619   }
620 
621   // protobuf's map is unordered, so sort the keys first.
622   std::set<int> thread_ids;
623   for (const auto& [tid, _] : threads) {
624     if (tid != tombstone.tid()) {
625       thread_ids.insert(tid);
626     }
627   }
628 
629   for (const auto& tid : thread_ids) {
630     CBS("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---");
631     print_thread(callback, symbolize, tombstone, threads.find(tid)->second);
632     auto guest_thread_it = guest_threads.find(tid);
633     if (guest_thread_it != guest_threads.end()) {
634       print_guest_thread(callback, symbolize, tombstone, guest_thread_it->second, tid, false);
635     }
636   }
637 
638   if (tombstone.open_fds().size() > 0) {
639     CBS("");
640     CBS("open files:");
641     for (const auto& fd : tombstone.open_fds()) {
642       std::optional<std::string> owner;
643       if (!fd.owner().empty()) {
644         owner = StringPrintf("owned by %s 0x%" PRIx64, fd.owner().c_str(), fd.tag());
645       }
646 
647       CBS("    fd %d: %s (%s)", fd.fd(), fd.path().c_str(), owner ? owner->c_str() : "unowned");
648     }
649   }
650 
651   print_logs(callback, tombstone, 0);
652 
653   return true;
654 }
655