Loading debuggerd/debuggerd_test.cpp +129 −10 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ #include <scoped_minijail.h> #include "debuggerd/handler.h" #include "libdebuggerd/utility.h" #include "protocol.h" #include "tombstoned/tombstoned.h" #include "util.h" Loading Loading @@ -526,6 +527,8 @@ TEST_P(SizeParamCrasherTest, mte_uaf) { std::vector<std::string> log_sources(2); ConsumeFd(std::move(output_fd), &log_sources[0]); logcat_collector.Collect(&log_sources[1]); // Tag dump only available in the tombstone, not logcat. ASSERT_MATCH(log_sources[0], "Memory tags around the fault address"); for (const auto& result : log_sources) { ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); Loading Loading @@ -597,6 +600,12 @@ TEST_P(SizeParamCrasherTest, mte_overflow) { ConsumeFd(std::move(output_fd), &log_sources[0]); logcat_collector.Collect(&log_sources[1]); // Tag dump only in tombstone, not logcat, and tagging is not used for // overflow protection in the scudo secondary (guard pages are used instead). if (GetParam() < 0x10000) { ASSERT_MATCH(log_sources[0], "Memory tags around the fault address"); } for (const auto& result : log_sources) { ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" + Loading Loading @@ -637,6 +646,7 @@ TEST_P(SizeParamCrasherTest, mte_underflow) { std::to_string(GetParam()) + R"(-byte allocation)"); ASSERT_MATCH(result, R"((^|\s)allocated by thread .* #00 pc)"); ASSERT_MATCH(result, "Memory tags around the fault address"); #else GTEST_SKIP() << "Requires aarch64"; #endif Loading Loading @@ -686,6 +696,9 @@ TEST_F(CrasherTest, mte_multiple_causes) { ConsumeFd(std::move(output_fd), &log_sources[0]); logcat_collector.Collect(&log_sources[1]); // Tag dump only in the tombstone, not logcat. ASSERT_MATCH(log_sources[0], "Memory tags around the fault address"); for (const auto& result : log_sources) { ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); ASSERT_THAT(result, HasSubstr("Note: multiple potential causes for this crash were detected, " Loading @@ -706,21 +719,26 @@ TEST_F(CrasherTest, mte_multiple_causes) { #if defined(__aarch64__) static uintptr_t CreateTagMapping() { uintptr_t mapping = reinterpret_cast<uintptr_t>(mmap(nullptr, getpagesize(), PROT_READ | PROT_WRITE | PROT_MTE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); if (reinterpret_cast<void*>(mapping) == MAP_FAILED) { // Some of the MTE tag dump tests assert that there is an inaccessible page to the left and right // of the PROT_MTE page, so map three pages and set the two guard pages to PROT_NONE. size_t page_size = getpagesize(); void* mapping = mmap(nullptr, page_size * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); uintptr_t mapping_uptr = reinterpret_cast<uintptr_t>(mapping); if (mapping == MAP_FAILED) { return 0; } __asm__ __volatile__(".arch_extension mte; stg %0, [%0]" : : "r"(mapping + (1ULL << 56)) : "memory"); return mapping; mprotect(reinterpret_cast<void*>(mapping_uptr + page_size), page_size, PROT_READ | PROT_WRITE | PROT_MTE); // Stripe the mapping, where even granules get tag '1', and odd granules get tag '0'. for (uintptr_t offset = 0; offset < page_size; offset += 2 * kTagGranuleSize) { uintptr_t tagged_addr = mapping_uptr + page_size + offset + (1ULL << 56); __asm__ __volatile__(".arch_extension mte; stg %0, [%0]" : : "r"(tagged_addr) : "memory"); } return mapping_uptr + page_size; } #endif TEST_F(CrasherTest, mte_tag_dump) { TEST_F(CrasherTest, mte_register_tag_dump) { #if defined(__aarch64__) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; Loading Loading @@ -753,6 +771,107 @@ TEST_F(CrasherTest, mte_tag_dump) { #endif } TEST_F(CrasherTest, mte_fault_tag_dump_front_truncated) { #if defined(__aarch64__) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); volatile char* p = reinterpret_cast<char*>(CreateTagMapping()); p[0] = 0; // Untagged pointer, tagged memory. }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Memory tags around the fault address.* \s*=>0x[0-9a-f]+000:\[1\] 0 1 0)"); #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_F(CrasherTest, mte_fault_tag_dump) { #if defined(__aarch64__) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); volatile char* p = reinterpret_cast<char*>(CreateTagMapping()); p[320] = 0; // Untagged pointer, tagged memory. }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Memory tags around the fault address.* \s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 \s*=>0x[0-9a-f]+: 1 0 1 0 \[1\] 0 1 0 1 0 1 0 1 0 1 0 \s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 )"); #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_F(CrasherTest, mte_fault_tag_dump_rear_truncated) { #if defined(__aarch64__) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); size_t page_size = getpagesize(); volatile char* p = reinterpret_cast<char*>(CreateTagMapping()); p[page_size - kTagGranuleSize * 2] = 0; // Untagged pointer, tagged memory. }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Memory tags around the fault address)"); ASSERT_MATCH(result, R"(\s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 \s*=>0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 \[1\] 0 )"); // Ensure truncation happened and there's a newline after the tag fault. #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_F(CrasherTest, LD_PRELOAD) { int intercept_result; unique_fd output_fd; Loading debuggerd/libdebuggerd/include/libdebuggerd/utility.h +4 −0 Original line number Diff line number Diff line Loading @@ -96,4 +96,8 @@ const char* get_sigcode(const siginfo_t*); // Number of bytes per MTE granule. constexpr size_t kTagGranuleSize = 16; // Number of rows and columns to display in an MTE tag dump. constexpr size_t kNumTagColumns = 16; constexpr size_t kNumTagRows = 16; #endif // _DEBUGGERD_UTILITY_H debuggerd/libdebuggerd/tombstone_proto.cpp +64 −4 Original line number Diff line number Diff line Loading @@ -362,8 +362,10 @@ static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder, dump.set_mapping_name(map_info->name()); } char buf[256]; uint8_t tags[256 / kTagGranuleSize]; constexpr size_t kNumBytesAroundRegister = 256; constexpr size_t kNumTagsAroundRegister = kNumBytesAroundRegister / kTagGranuleSize; char buf[kNumBytesAroundRegister]; uint8_t tags[kNumTagsAroundRegister]; size_t start_offset = 0; ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory); if (bytes == -1) { Loading @@ -377,7 +379,19 @@ static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder, } dump.set_memory(buf, bytes); dump.set_tags(tags, bytes / kTagGranuleSize); bool has_tags = false; #if defined(__aarch64__) for (size_t i = 0; i < kNumTagsAroundRegister; ++i) { if (tags[i] != 0) { has_tags = true; } } #endif // defined(__aarch64__) if (has_tags) { dump.mutable_arm_mte_metadata()->set_memory_tags(tags, kNumTagsAroundRegister); } *thread.add_memory_dump() = std::move(dump); } Loading Loading @@ -531,6 +545,50 @@ static void dump_logcat(Tombstone* tombstone, pid_t pid) { dump_log_file(tombstone, "main", pid); } static void dump_tags_around_fault_addr(Signal* signal, const Tombstone& tombstone, unwindstack::Unwinder* unwinder, uintptr_t fault_addr) { if (tombstone.arch() != Architecture::ARM64) return; fault_addr = untag_address(fault_addr); constexpr size_t kNumGranules = kNumTagRows * kNumTagColumns; constexpr size_t kBytesToRead = kNumGranules * kTagGranuleSize; // If the low part of the tag dump would underflow to the high address space, it's probably not // a valid address for us to dump tags from. if (fault_addr < kBytesToRead / 2) return; unwindstack::Memory* memory = unwinder->GetProcessMemory().get(); constexpr uintptr_t kRowStartMask = ~(kNumTagColumns * kTagGranuleSize - 1); size_t start_address = (fault_addr & kRowStartMask) - kBytesToRead / 2; MemoryDump tag_dump; size_t granules_to_read = kNumGranules; // Attempt to read the first tag. If reading fails, this likely indicates the // lowest touched page is inaccessible or not marked with PROT_MTE. // Fast-forward over pages until one has tags, or we exhaust the search range. while (memory->ReadTag(start_address) < 0) { size_t page_size = sysconf(_SC_PAGE_SIZE); size_t bytes_to_next_page = page_size - (start_address % page_size); if (bytes_to_next_page >= granules_to_read * kTagGranuleSize) return; start_address += bytes_to_next_page; granules_to_read -= bytes_to_next_page / kTagGranuleSize; } tag_dump.set_begin_address(start_address); std::string* mte_tags = tag_dump.mutable_arm_mte_metadata()->mutable_memory_tags(); for (size_t i = 0; i < granules_to_read; ++i) { long tag = memory->ReadTag(start_address + i * kTagGranuleSize); if (tag < 0) break; mte_tags->push_back(static_cast<uint8_t>(tag)); } if (!mte_tags->empty()) { *signal->mutable_fault_adjacent_metadata() = tag_dump; } } static std::optional<uint64_t> read_uptime_secs() { std::string uptime; if (!android::base::ReadFileToString("/proc/uptime", &uptime)) { Loading Loading @@ -594,7 +652,9 @@ void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwind if (process_info.has_fault_address) { sig.set_has_fault_address(true); sig.set_fault_address(process_info.maybe_tagged_fault_address); uintptr_t fault_addr = process_info.maybe_tagged_fault_address; sig.set_fault_address(fault_addr); dump_tags_around_fault_addr(&sig, result, unwinder, fault_addr); } *result.mutable_signal_info() = sig; Loading debuggerd/libdebuggerd/tombstone_proto_to_text.cpp +62 −2 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <async_safe/log.h> #include <bionic/macros.h> #include "tombstone.pb.h" Loading Loading @@ -193,8 +194,11 @@ static void print_thread_memory_dump(CallbackType callback, const Tombstone& tom 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.tags().size() > offset / kTagGranuleSize) { tagged_addr |= static_cast<uint64_t>(mem.tags()[offset / kTagGranuleSize]) << 56; if (mem.has_arm_mte_metadata() && mem.arm_mte_metadata().memory_tags().size() > offset / kTagGranuleSize) { tagged_addr |= static_cast<uint64_t>(mem.arm_mte_metadata().memory_tags()[offset / kTagGranuleSize]) << 56; } std::string line = StringPrintf(" %0*" PRIx64, word_size * 2, tagged_addr + offset); Loading Loading @@ -232,6 +236,60 @@ static void print_thread(CallbackType callback, const Tombstone& tombstone, cons 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_main_thread(CallbackType callback, const Tombstone& tombstone, const Thread& thread) { print_thread_header(callback, tombstone, thread, true); Loading Loading @@ -299,6 +357,8 @@ static void print_main_thread(CallbackType callback, const Tombstone& tombstone, } } print_tag_dump(callback, tombstone); print_thread_memory_dump(callback, tombstone, thread); CBS(""); Loading debuggerd/proto/tombstone.proto +14 −3 Original line number Diff line number Diff line Loading @@ -56,8 +56,11 @@ message Signal { bool has_fault_address = 8; uint64 fault_address = 9; // Note, may or may not contain the dump of the actual memory contents. Currently, on arm64, we // only include metadata, and not the contents. MemoryDump fault_adjacent_metadata = 10; reserved 10 to 999; reserved 11 to 999; } message HeapObject { Loading Loading @@ -142,14 +145,22 @@ message BacktraceFrame { reserved 9 to 999; } message ArmMTEMetadata { // One memory tag per granule (e.g. every 16 bytes) of regular memory. bytes memory_tags = 1; reserved 2 to 999; } message MemoryDump { string register_name = 1; string mapping_name = 2; uint64 begin_address = 3; bytes memory = 4; bytes tags = 5; oneof metadata { ArmMTEMetadata arm_mte_metadata = 6; } reserved 6 to 999; reserved 5, 7 to 999; } message MemoryMapping { Loading Loading
debuggerd/debuggerd_test.cpp +129 −10 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ #include <scoped_minijail.h> #include "debuggerd/handler.h" #include "libdebuggerd/utility.h" #include "protocol.h" #include "tombstoned/tombstoned.h" #include "util.h" Loading Loading @@ -526,6 +527,8 @@ TEST_P(SizeParamCrasherTest, mte_uaf) { std::vector<std::string> log_sources(2); ConsumeFd(std::move(output_fd), &log_sources[0]); logcat_collector.Collect(&log_sources[1]); // Tag dump only available in the tombstone, not logcat. ASSERT_MATCH(log_sources[0], "Memory tags around the fault address"); for (const auto& result : log_sources) { ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); Loading Loading @@ -597,6 +600,12 @@ TEST_P(SizeParamCrasherTest, mte_overflow) { ConsumeFd(std::move(output_fd), &log_sources[0]); logcat_collector.Collect(&log_sources[1]); // Tag dump only in tombstone, not logcat, and tagging is not used for // overflow protection in the scudo secondary (guard pages are used instead). if (GetParam() < 0x10000) { ASSERT_MATCH(log_sources[0], "Memory tags around the fault address"); } for (const auto& result : log_sources) { ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" + Loading Loading @@ -637,6 +646,7 @@ TEST_P(SizeParamCrasherTest, mte_underflow) { std::to_string(GetParam()) + R"(-byte allocation)"); ASSERT_MATCH(result, R"((^|\s)allocated by thread .* #00 pc)"); ASSERT_MATCH(result, "Memory tags around the fault address"); #else GTEST_SKIP() << "Requires aarch64"; #endif Loading Loading @@ -686,6 +696,9 @@ TEST_F(CrasherTest, mte_multiple_causes) { ConsumeFd(std::move(output_fd), &log_sources[0]); logcat_collector.Collect(&log_sources[1]); // Tag dump only in the tombstone, not logcat. ASSERT_MATCH(log_sources[0], "Memory tags around the fault address"); for (const auto& result : log_sources) { ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); ASSERT_THAT(result, HasSubstr("Note: multiple potential causes for this crash were detected, " Loading @@ -706,21 +719,26 @@ TEST_F(CrasherTest, mte_multiple_causes) { #if defined(__aarch64__) static uintptr_t CreateTagMapping() { uintptr_t mapping = reinterpret_cast<uintptr_t>(mmap(nullptr, getpagesize(), PROT_READ | PROT_WRITE | PROT_MTE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); if (reinterpret_cast<void*>(mapping) == MAP_FAILED) { // Some of the MTE tag dump tests assert that there is an inaccessible page to the left and right // of the PROT_MTE page, so map three pages and set the two guard pages to PROT_NONE. size_t page_size = getpagesize(); void* mapping = mmap(nullptr, page_size * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); uintptr_t mapping_uptr = reinterpret_cast<uintptr_t>(mapping); if (mapping == MAP_FAILED) { return 0; } __asm__ __volatile__(".arch_extension mte; stg %0, [%0]" : : "r"(mapping + (1ULL << 56)) : "memory"); return mapping; mprotect(reinterpret_cast<void*>(mapping_uptr + page_size), page_size, PROT_READ | PROT_WRITE | PROT_MTE); // Stripe the mapping, where even granules get tag '1', and odd granules get tag '0'. for (uintptr_t offset = 0; offset < page_size; offset += 2 * kTagGranuleSize) { uintptr_t tagged_addr = mapping_uptr + page_size + offset + (1ULL << 56); __asm__ __volatile__(".arch_extension mte; stg %0, [%0]" : : "r"(tagged_addr) : "memory"); } return mapping_uptr + page_size; } #endif TEST_F(CrasherTest, mte_tag_dump) { TEST_F(CrasherTest, mte_register_tag_dump) { #if defined(__aarch64__) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; Loading Loading @@ -753,6 +771,107 @@ TEST_F(CrasherTest, mte_tag_dump) { #endif } TEST_F(CrasherTest, mte_fault_tag_dump_front_truncated) { #if defined(__aarch64__) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); volatile char* p = reinterpret_cast<char*>(CreateTagMapping()); p[0] = 0; // Untagged pointer, tagged memory. }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Memory tags around the fault address.* \s*=>0x[0-9a-f]+000:\[1\] 0 1 0)"); #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_F(CrasherTest, mte_fault_tag_dump) { #if defined(__aarch64__) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); volatile char* p = reinterpret_cast<char*>(CreateTagMapping()); p[320] = 0; // Untagged pointer, tagged memory. }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Memory tags around the fault address.* \s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 \s*=>0x[0-9a-f]+: 1 0 1 0 \[1\] 0 1 0 1 0 1 0 1 0 1 0 \s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 )"); #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_F(CrasherTest, mte_fault_tag_dump_rear_truncated) { #if defined(__aarch64__) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); size_t page_size = getpagesize(); volatile char* p = reinterpret_cast<char*>(CreateTagMapping()); p[page_size - kTagGranuleSize * 2] = 0; // Untagged pointer, tagged memory. }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Memory tags around the fault address)"); ASSERT_MATCH(result, R"(\s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 \s*=>0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 \[1\] 0 )"); // Ensure truncation happened and there's a newline after the tag fault. #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_F(CrasherTest, LD_PRELOAD) { int intercept_result; unique_fd output_fd; Loading
debuggerd/libdebuggerd/include/libdebuggerd/utility.h +4 −0 Original line number Diff line number Diff line Loading @@ -96,4 +96,8 @@ const char* get_sigcode(const siginfo_t*); // Number of bytes per MTE granule. constexpr size_t kTagGranuleSize = 16; // Number of rows and columns to display in an MTE tag dump. constexpr size_t kNumTagColumns = 16; constexpr size_t kNumTagRows = 16; #endif // _DEBUGGERD_UTILITY_H
debuggerd/libdebuggerd/tombstone_proto.cpp +64 −4 Original line number Diff line number Diff line Loading @@ -362,8 +362,10 @@ static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder, dump.set_mapping_name(map_info->name()); } char buf[256]; uint8_t tags[256 / kTagGranuleSize]; constexpr size_t kNumBytesAroundRegister = 256; constexpr size_t kNumTagsAroundRegister = kNumBytesAroundRegister / kTagGranuleSize; char buf[kNumBytesAroundRegister]; uint8_t tags[kNumTagsAroundRegister]; size_t start_offset = 0; ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory); if (bytes == -1) { Loading @@ -377,7 +379,19 @@ static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder, } dump.set_memory(buf, bytes); dump.set_tags(tags, bytes / kTagGranuleSize); bool has_tags = false; #if defined(__aarch64__) for (size_t i = 0; i < kNumTagsAroundRegister; ++i) { if (tags[i] != 0) { has_tags = true; } } #endif // defined(__aarch64__) if (has_tags) { dump.mutable_arm_mte_metadata()->set_memory_tags(tags, kNumTagsAroundRegister); } *thread.add_memory_dump() = std::move(dump); } Loading Loading @@ -531,6 +545,50 @@ static void dump_logcat(Tombstone* tombstone, pid_t pid) { dump_log_file(tombstone, "main", pid); } static void dump_tags_around_fault_addr(Signal* signal, const Tombstone& tombstone, unwindstack::Unwinder* unwinder, uintptr_t fault_addr) { if (tombstone.arch() != Architecture::ARM64) return; fault_addr = untag_address(fault_addr); constexpr size_t kNumGranules = kNumTagRows * kNumTagColumns; constexpr size_t kBytesToRead = kNumGranules * kTagGranuleSize; // If the low part of the tag dump would underflow to the high address space, it's probably not // a valid address for us to dump tags from. if (fault_addr < kBytesToRead / 2) return; unwindstack::Memory* memory = unwinder->GetProcessMemory().get(); constexpr uintptr_t kRowStartMask = ~(kNumTagColumns * kTagGranuleSize - 1); size_t start_address = (fault_addr & kRowStartMask) - kBytesToRead / 2; MemoryDump tag_dump; size_t granules_to_read = kNumGranules; // Attempt to read the first tag. If reading fails, this likely indicates the // lowest touched page is inaccessible or not marked with PROT_MTE. // Fast-forward over pages until one has tags, or we exhaust the search range. while (memory->ReadTag(start_address) < 0) { size_t page_size = sysconf(_SC_PAGE_SIZE); size_t bytes_to_next_page = page_size - (start_address % page_size); if (bytes_to_next_page >= granules_to_read * kTagGranuleSize) return; start_address += bytes_to_next_page; granules_to_read -= bytes_to_next_page / kTagGranuleSize; } tag_dump.set_begin_address(start_address); std::string* mte_tags = tag_dump.mutable_arm_mte_metadata()->mutable_memory_tags(); for (size_t i = 0; i < granules_to_read; ++i) { long tag = memory->ReadTag(start_address + i * kTagGranuleSize); if (tag < 0) break; mte_tags->push_back(static_cast<uint8_t>(tag)); } if (!mte_tags->empty()) { *signal->mutable_fault_adjacent_metadata() = tag_dump; } } static std::optional<uint64_t> read_uptime_secs() { std::string uptime; if (!android::base::ReadFileToString("/proc/uptime", &uptime)) { Loading Loading @@ -594,7 +652,9 @@ void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwind if (process_info.has_fault_address) { sig.set_has_fault_address(true); sig.set_fault_address(process_info.maybe_tagged_fault_address); uintptr_t fault_addr = process_info.maybe_tagged_fault_address; sig.set_fault_address(fault_addr); dump_tags_around_fault_addr(&sig, result, unwinder, fault_addr); } *result.mutable_signal_info() = sig; Loading
debuggerd/libdebuggerd/tombstone_proto_to_text.cpp +62 −2 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <async_safe/log.h> #include <bionic/macros.h> #include "tombstone.pb.h" Loading Loading @@ -193,8 +194,11 @@ static void print_thread_memory_dump(CallbackType callback, const Tombstone& tom 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.tags().size() > offset / kTagGranuleSize) { tagged_addr |= static_cast<uint64_t>(mem.tags()[offset / kTagGranuleSize]) << 56; if (mem.has_arm_mte_metadata() && mem.arm_mte_metadata().memory_tags().size() > offset / kTagGranuleSize) { tagged_addr |= static_cast<uint64_t>(mem.arm_mte_metadata().memory_tags()[offset / kTagGranuleSize]) << 56; } std::string line = StringPrintf(" %0*" PRIx64, word_size * 2, tagged_addr + offset); Loading Loading @@ -232,6 +236,60 @@ static void print_thread(CallbackType callback, const Tombstone& tombstone, cons 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_main_thread(CallbackType callback, const Tombstone& tombstone, const Thread& thread) { print_thread_header(callback, tombstone, thread, true); Loading Loading @@ -299,6 +357,8 @@ static void print_main_thread(CallbackType callback, const Tombstone& tombstone, } } print_tag_dump(callback, tombstone); print_thread_memory_dump(callback, tombstone, thread); CBS(""); Loading
debuggerd/proto/tombstone.proto +14 −3 Original line number Diff line number Diff line Loading @@ -56,8 +56,11 @@ message Signal { bool has_fault_address = 8; uint64 fault_address = 9; // Note, may or may not contain the dump of the actual memory contents. Currently, on arm64, we // only include metadata, and not the contents. MemoryDump fault_adjacent_metadata = 10; reserved 10 to 999; reserved 11 to 999; } message HeapObject { Loading Loading @@ -142,14 +145,22 @@ message BacktraceFrame { reserved 9 to 999; } message ArmMTEMetadata { // One memory tag per granule (e.g. every 16 bytes) of regular memory. bytes memory_tags = 1; reserved 2 to 999; } message MemoryDump { string register_name = 1; string mapping_name = 2; uint64 begin_address = 3; bytes memory = 4; bytes tags = 5; oneof metadata { ArmMTEMetadata arm_mte_metadata = 6; } reserved 6 to 999; reserved 5, 7 to 999; } message MemoryMapping { Loading