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