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 = ¤t_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