/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tombstone.pb.h" using android::base::StringAppendF; using android::base::StringPrintf; #define CB(log, ...) callback(StringPrintf(__VA_ARGS__), log) #define CBL(...) CB(true, __VA_ARGS__) #define CBS(...) CB(false, __VA_ARGS__) using CallbackType = std::function; #define DESCRIBE_FLAG(flag) \ if (value & flag) { \ desc += ", "; \ desc += #flag; \ value &= ~flag; \ } static std::string describe_end(long value, std::string& desc) { if (value) { desc += StringPrintf(", unknown 0x%lx", value); } return desc.empty() ? "" : " (" + desc.substr(2) + ")"; } static std::string describe_tagged_addr_ctrl(long value) { std::string desc; DESCRIBE_FLAG(PR_TAGGED_ADDR_ENABLE); DESCRIBE_FLAG(PR_MTE_TCF_SYNC); DESCRIBE_FLAG(PR_MTE_TCF_ASYNC); if (value & PR_MTE_TAG_MASK) { desc += StringPrintf(", mask 0x%04lx", (value & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT); value &= ~PR_MTE_TAG_MASK; } return describe_end(value, desc); } static std::string describe_pac_enabled_keys(long value) { std::string desc; DESCRIBE_FLAG(PR_PAC_APIAKEY); DESCRIBE_FLAG(PR_PAC_APIBKEY); DESCRIBE_FLAG(PR_PAC_APDAKEY); DESCRIBE_FLAG(PR_PAC_APDBKEY); DESCRIBE_FLAG(PR_PAC_APGAKEY); return describe_end(value, desc); } static const char* abi_string(const Architecture& arch) { switch (arch) { case Architecture::ARM32: return "arm"; case Architecture::ARM64: return "arm64"; case Architecture::RISCV64: return "riscv64"; case Architecture::X86: return "x86"; case Architecture::X86_64: return "x86_64"; default: return ""; } } static int pointer_width(const Tombstone& tombstone) { switch (tombstone.arch()) { case Architecture::ARM32: return 4; case Architecture::ARM64: return 8; case Architecture::RISCV64: return 8; case Architecture::X86: return 4; case Architecture::X86_64: return 8; default: return 8; } } static void print_thread_header(CallbackType callback, const Tombstone& tombstone, const Thread& thread, bool should_log) { const char* process_name = ""; if (!tombstone.command_line().empty()) { process_name = tombstone.command_line()[0].c_str(); CB(should_log, "Cmdline: %s", android::base::Join(tombstone.command_line(), " ").c_str()); } else { CB(should_log, "Cmdline: "); } CB(should_log, "pid: %d, tid: %d, name: %s >>> %s <<<", tombstone.pid(), thread.id(), thread.name().c_str(), process_name); CB(should_log, "uid: %d", tombstone.uid()); if (thread.tagged_addr_ctrl() != -1) { CB(should_log, "tagged_addr_ctrl: %016" PRIx64 "%s", thread.tagged_addr_ctrl(), describe_tagged_addr_ctrl(thread.tagged_addr_ctrl()).c_str()); } if (thread.pac_enabled_keys() != -1) { CB(should_log, "pac_enabled_keys: %016" PRIx64 "%s", thread.pac_enabled_keys(), describe_pac_enabled_keys(thread.pac_enabled_keys()).c_str()); } } static void print_register_row(CallbackType callback, int word_size, std::vector> row, bool should_log) { std::string output = " "; for (const auto& [name, value] : row) { output += android::base::StringPrintf(" %-3s %0*" PRIx64, name.c_str(), 2 * word_size, static_cast(value)); } callback(output, should_log); } static void print_thread_registers(CallbackType callback, const Tombstone& tombstone, const Thread& thread, bool should_log) { static constexpr size_t column_count = 4; std::vector> current_row; std::vector> special_row; std::unordered_set special_registers; int word_size = pointer_width(tombstone); switch (tombstone.arch()) { case Architecture::ARM32: special_registers = {"ip", "lr", "sp", "pc", "pst"}; break; case Architecture::ARM64: special_registers = {"ip", "lr", "sp", "pc", "pst"}; break; case Architecture::RISCV64: special_registers = {"ra", "sp", "pc"}; break; case Architecture::X86: special_registers = {"ebp", "esp", "eip"}; break; case Architecture::X86_64: special_registers = {"rbp", "rsp", "rip"}; break; default: CBL("Unknown architecture %d printing thread registers", tombstone.arch()); return; } for (const auto& reg : thread.registers()) { auto row = ¤t_row; if (special_registers.count(reg.name()) == 1) { row = &special_row; } row->emplace_back(reg.name(), reg.u64()); if (current_row.size() == column_count) { print_register_row(callback, word_size, current_row, should_log); current_row.clear(); } } if (!current_row.empty()) { print_register_row(callback, word_size, current_row, should_log); } print_register_row(callback, word_size, special_row, should_log); } static void print_backtrace(CallbackType callback, const Tombstone& tombstone, const google::protobuf::RepeatedPtrField& backtrace, bool should_log) { int index = 0; for (const auto& frame : backtrace) { std::string function; if (!frame.function_name().empty()) { function = StringPrintf(" (%s+%" PRId64 ")", frame.function_name().c_str(), frame.function_offset()); } std::string build_id; if (!frame.build_id().empty()) { build_id = StringPrintf(" (BuildId: %s)", frame.build_id().c_str()); } std::string line = StringPrintf(" #%02d pc %0*" PRIx64 " %s", index++, pointer_width(tombstone) * 2, frame.rel_pc(), frame.file_name().c_str()); if (frame.file_map_offset() != 0) { line += StringPrintf(" (offset 0x%" PRIx64 ")", frame.file_map_offset()); } line += function + build_id; CB(should_log, "%s", line.c_str()); } } static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone, const Thread& thread, bool should_log) { CBS(""); CB(should_log, "%d total frames", thread.current_backtrace().size()); CB(should_log, "backtrace:"); if (!thread.backtrace_note().empty()) { CB(should_log, " NOTE: %s", android::base::Join(thread.backtrace_note(), "\n NOTE: ").c_str()); } print_backtrace(callback, tombstone, thread.current_backtrace(), should_log); } static void print_thread_memory_dump(CallbackType callback, const Tombstone& tombstone, const Thread& thread) { static constexpr size_t bytes_per_line = 16; static_assert(bytes_per_line == kTagGranuleSize); int word_size = pointer_width(tombstone); for (const auto& mem : thread.memory_dump()) { CBS(""); if (mem.mapping_name().empty()) { CBS("memory near %s:", mem.register_name().c_str()); } else { CBS("memory near %s (%s):", mem.register_name().c_str(), mem.mapping_name().c_str()); } uint64_t addr = mem.begin_address(); for (size_t offset = 0; offset < mem.memory().size(); offset += bytes_per_line) { uint64_t tagged_addr = addr; if (mem.has_arm_mte_metadata() && mem.arm_mte_metadata().memory_tags().size() > offset / kTagGranuleSize) { tagged_addr |= static_cast(mem.arm_mte_metadata().memory_tags()[offset / kTagGranuleSize]) << 56; } std::string line = StringPrintf(" %0*" PRIx64, word_size * 2, tagged_addr + offset); size_t bytes = std::min(bytes_per_line, mem.memory().size() - offset); for (size_t i = 0; i < bytes; i += word_size) { uint64_t word = 0; // Assumes little-endian, but what doesn't? memcpy(&word, mem.memory().data() + offset + i, word_size); StringAppendF(&line, " %0*" PRIx64, word_size * 2, word); } char ascii[bytes_per_line + 1]; memset(ascii, '.', sizeof(ascii)); ascii[bytes_per_line] = '\0'; for (size_t i = 0; i < bytes; ++i) { uint8_t byte = mem.memory()[offset + i]; if (byte >= 0x20 && byte < 0x7f) { ascii[i] = byte; } } CBS("%s %s", line.c_str(), ascii); } } } static void print_thread(CallbackType callback, const Tombstone& tombstone, const Thread& thread) { print_thread_header(callback, tombstone, thread, false); print_thread_registers(callback, tombstone, thread, false); print_thread_backtrace(callback, tombstone, thread, false); print_thread_memory_dump(callback, tombstone, thread); } static void print_tag_dump(CallbackType callback, const Tombstone& tombstone) { if (!tombstone.has_signal_info()) return; const Signal& signal = tombstone.signal_info(); if (!signal.has_fault_address() || !signal.has_fault_adjacent_metadata()) { return; } const MemoryDump& memory_dump = signal.fault_adjacent_metadata(); if (!memory_dump.has_arm_mte_metadata() || memory_dump.arm_mte_metadata().memory_tags().empty()) { return; } const std::string& tags = memory_dump.arm_mte_metadata().memory_tags(); CBS(""); CBS("Memory tags around the fault address (0x%" PRIx64 "), one tag per %zu bytes:", signal.fault_address(), kTagGranuleSize); constexpr uintptr_t kRowStartMask = ~(kNumTagColumns * kTagGranuleSize - 1); size_t tag_index = 0; size_t num_tags = tags.length(); uintptr_t fault_granule = untag_address(signal.fault_address()) & ~(kTagGranuleSize - 1); for (size_t row = 0; tag_index < num_tags; ++row) { uintptr_t row_addr = (memory_dump.begin_address() + row * kNumTagColumns * kTagGranuleSize) & kRowStartMask; std::string row_contents; bool row_has_fault = false; for (size_t column = 0; column < kNumTagColumns; ++column) { uintptr_t granule_addr = row_addr + column * kTagGranuleSize; if (granule_addr < memory_dump.begin_address() || granule_addr >= memory_dump.begin_address() + num_tags * kTagGranuleSize) { row_contents += " . "; } else if (granule_addr == fault_granule) { row_contents += StringPrintf("[%1hhx]", tags[tag_index++]); row_has_fault = true; } else { row_contents += StringPrintf(" %1hhx ", tags[tag_index++]); } } if (row_contents.back() == ' ') row_contents.pop_back(); if (row_has_fault) { CBS(" =>0x%" PRIxPTR ":%s", row_addr, row_contents.c_str()); } else { CBS(" 0x%" PRIxPTR ":%s", row_addr, row_contents.c_str()); } } } static void print_memory_maps(CallbackType callback, const Tombstone& tombstone) { int word_size = pointer_width(tombstone); const auto format_pointer = [word_size](uint64_t ptr) -> std::string { if (word_size == 8) { uint64_t top = ptr >> 32; uint64_t bottom = ptr & 0xFFFFFFFF; return StringPrintf("%08" PRIx64 "'%08" PRIx64, top, bottom); } return StringPrintf("%0*" PRIx64, word_size * 2, ptr); }; std::string memory_map_header = StringPrintf("memory map (%d %s):", tombstone.memory_mappings().size(), tombstone.memory_mappings().size() == 1 ? "entry" : "entries"); const Signal& signal_info = tombstone.signal_info(); bool has_fault_address = signal_info.has_fault_address(); uint64_t fault_address = untag_address(signal_info.fault_address()); bool preamble_printed = false; bool printed_fault_address_marker = false; for (const auto& map : tombstone.memory_mappings()) { if (!preamble_printed) { preamble_printed = true; if (has_fault_address) { if (fault_address < map.begin_address()) { memory_map_header += StringPrintf("\n--->Fault address falls at %s before any mapped regions", format_pointer(fault_address).c_str()); printed_fault_address_marker = true; } else { memory_map_header += " (fault address prefixed with --->)"; } } CBS("%s", memory_map_header.c_str()); } std::string line = " "; if (has_fault_address && !printed_fault_address_marker) { if (fault_address < map.begin_address()) { printed_fault_address_marker = true; CBS("--->Fault address falls at %s between mapped regions", format_pointer(fault_address).c_str()); } else if (fault_address >= map.begin_address() && fault_address < map.end_address()) { printed_fault_address_marker = true; line = "--->"; } } StringAppendF(&line, "%s-%s", format_pointer(map.begin_address()).c_str(), format_pointer(map.end_address() - 1).c_str()); StringAppendF(&line, " %s%s%s", map.read() ? "r" : "-", map.write() ? "w" : "-", map.execute() ? "x" : "-"); StringAppendF(&line, " %8" PRIx64 " %8" PRIx64, map.offset(), map.end_address() - map.begin_address()); if (!map.mapping_name().empty()) { StringAppendF(&line, " %s", map.mapping_name().c_str()); if (!map.build_id().empty()) { StringAppendF(&line, " (BuildId: %s)", map.build_id().c_str()); } if (map.load_bias() != 0) { StringAppendF(&line, " (load bias 0x%" PRIx64 ")", map.load_bias()); } } CBS("%s", line.c_str()); } if (has_fault_address && !printed_fault_address_marker) { CBS("--->Fault address falls at %s after any mapped regions", format_pointer(fault_address).c_str()); } } static std::string oct_encode(const std::string& data) { std::string oct_encoded; oct_encoded.reserve(data.size()); // N.B. the unsigned here is very important, otherwise e.g. \255 would render as // \-123 (and overflow our buffer). for (unsigned char c : data) { if (isprint(c)) { oct_encoded += c; } else { std::string oct_digits("\\\0\0\0", 4); // char is encodable in 3 oct digits static_assert(std::numeric_limits::max() <= 8 * 8 * 8); auto [ptr, ec] = std::to_chars(oct_digits.data() + 1, oct_digits.data() + 4, c, 8); oct_digits.resize(ptr - oct_digits.data()); oct_encoded += oct_digits; } } return oct_encoded; } static void print_main_thread(CallbackType callback, const Tombstone& tombstone, const Thread& thread) { print_thread_header(callback, tombstone, thread, true); const Signal& signal_info = tombstone.signal_info(); std::string sender_desc; if (signal_info.has_sender()) { sender_desc = StringPrintf(" from pid %d, uid %d", signal_info.sender_pid(), signal_info.sender_uid()); } bool is_async_mte_crash = false; bool is_mte_crash = false; if (!tombstone.has_signal_info()) { CBL("signal information missing"); } else { std::string fault_addr_desc; if (signal_info.has_fault_address()) { fault_addr_desc = StringPrintf("0x%0*" PRIx64, 2 * pointer_width(tombstone), signal_info.fault_address()); } else { fault_addr_desc = "--------"; } CBL("signal %d (%s), code %d (%s%s), fault addr %s", signal_info.number(), signal_info.name().c_str(), signal_info.code(), signal_info.code_name().c_str(), sender_desc.c_str(), fault_addr_desc.c_str()); #ifdef SEGV_MTEAERR is_async_mte_crash = signal_info.number() == SIGSEGV && signal_info.code() == SEGV_MTEAERR; is_mte_crash = is_async_mte_crash || (signal_info.number() == SIGSEGV && signal_info.code() == SEGV_MTESERR); #endif } if (tombstone.causes_size() == 1) { CBL("Cause: %s", tombstone.causes(0).human_readable().c_str()); } if (!tombstone.abort_message().empty()) { CBL("Abort message: '%s'", tombstone.abort_message().c_str()); } for (const auto& crash_detail : tombstone.crash_details()) { std::string oct_encoded_name = oct_encode(crash_detail.name()); std::string oct_encoded_data = oct_encode(crash_detail.data()); CBL("Extra crash detail: %s: '%s'", oct_encoded_name.c_str(), oct_encoded_data.c_str()); } print_thread_registers(callback, tombstone, thread, true); if (is_async_mte_crash) { CBL("Note: This crash is a delayed async MTE crash. Memory corruption has occurred"); CBL(" in this process. The stack trace below is the first system call or context"); CBL(" switch that was executed after the memory corruption happened."); } print_thread_backtrace(callback, tombstone, thread, true); if (tombstone.causes_size() > 1) { CBS(""); CBL("Note: multiple potential causes for this crash were detected, listing them in decreasing " "order of likelihood."); } for (const Cause& cause : tombstone.causes()) { if (tombstone.causes_size() > 1) { CBS(""); CBL("Cause: %s", cause.human_readable().c_str()); } if (cause.has_memory_error() && cause.memory_error().has_heap()) { const HeapObject& heap_object = cause.memory_error().heap(); if (heap_object.deallocation_backtrace_size() != 0) { CBS(""); CBL("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid()); print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), true); } if (heap_object.allocation_backtrace_size() != 0) { CBS(""); CBL("allocated by thread %" PRIu64 ":", heap_object.allocation_tid()); print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), true); } } } print_tag_dump(callback, tombstone); if (is_mte_crash) { CBS(""); CBL("Learn more about MTE reports: " "https://source.android.com/docs/security/test/memory-safety/mte-reports"); } print_thread_memory_dump(callback, tombstone, thread); CBS(""); // No memory maps to print. if (!tombstone.memory_mappings().empty()) { print_memory_maps(callback, tombstone); } else { CBS("No memory maps found"); } } void print_logs(CallbackType callback, const Tombstone& tombstone, int tail) { for (const auto& buffer : tombstone.log_buffers()) { if (tail) { CBS("--------- tail end of log %s", buffer.name().c_str()); } else { CBS("--------- log %s", buffer.name().c_str()); } int begin = 0; if (tail != 0) { begin = std::max(0, buffer.logs().size() - tail); } for (int i = begin; i < buffer.logs().size(); ++i) { const LogMessage& msg = buffer.logs(i); static const char* kPrioChars = "!.VDIWEFS"; char priority = (msg.priority() < strlen(kPrioChars) ? kPrioChars[msg.priority()] : '?'); CBS("%s %5u %5u %c %-8s: %s", msg.timestamp().c_str(), msg.pid(), msg.tid(), priority, msg.tag().c_str(), msg.message().c_str()); } } } static void print_guest_thread(CallbackType callback, const Tombstone& tombstone, const Thread& guest_thread, pid_t tid, bool should_log) { CBS("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---"); CBS("Guest thread information for tid: %d", tid); print_thread_registers(callback, tombstone, guest_thread, should_log); CBS(""); CB(true, "%d total frames", guest_thread.current_backtrace().size()); CB(true, "backtrace:"); print_backtrace(callback, tombstone, guest_thread.current_backtrace(), should_log); print_thread_memory_dump(callback, tombstone, guest_thread); } bool tombstone_proto_to_text(const Tombstone& tombstone, CallbackType callback) { CBL("*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***"); CBL("Build fingerprint: '%s'", tombstone.build_fingerprint().c_str()); CBL("Revision: '%s'", tombstone.revision().c_str()); CBL("ABI: '%s'", abi_string(tombstone.arch())); if (tombstone.guest_arch() != Architecture::NONE) { CBL("Guest architecture: '%s'", abi_string(tombstone.guest_arch())); } CBL("Timestamp: %s", tombstone.timestamp().c_str()); CBL("Process uptime: %ds", tombstone.process_uptime()); // only print this info if the page size is not 4k or has been in 16k mode if (tombstone.page_size() != 4096) { CBL("Page size: %d bytes", tombstone.page_size()); } else if (tombstone.has_been_16kb_mode()) { CBL("Has been in 16kb mode: yes"); } // Process header const auto& threads = tombstone.threads(); auto main_thread_it = threads.find(tombstone.tid()); if (main_thread_it == threads.end()) { CBL("failed to find entry for main thread in tombstone"); return false; } const auto& main_thread = main_thread_it->second; print_main_thread(callback, tombstone, main_thread); print_logs(callback, tombstone, 50); const auto& guest_threads = tombstone.guest_threads(); auto main_guest_thread_it = guest_threads.find(tombstone.tid()); if (main_guest_thread_it != threads.end()) { print_guest_thread(callback, tombstone, main_guest_thread_it->second, tombstone.tid(), true); } // protobuf's map is unordered, so sort the keys first. std::set thread_ids; for (const auto& [tid, _] : threads) { if (tid != tombstone.tid()) { thread_ids.insert(tid); } } for (const auto& tid : thread_ids) { CBS("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---"); print_thread(callback, tombstone, threads.find(tid)->second); auto guest_thread_it = guest_threads.find(tid); if (guest_thread_it != guest_threads.end()) { print_guest_thread(callback, tombstone, guest_thread_it->second, tid, false); } } if (tombstone.open_fds().size() > 0) { CBS(""); CBS("open files:"); for (const auto& fd : tombstone.open_fds()) { std::optional owner; if (!fd.owner().empty()) { owner = StringPrintf("owned by %s 0x%" PRIx64, fd.owner().c_str(), fd.tag()); } CBS(" fd %d: %s (%s)", fd.fd(), fd.path().c_str(), owner ? owner->c_str() : "unowned"); } } print_logs(callback, tombstone, 0); return true; }