diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp index 0c5543ed9..c365cac52 100644 --- a/debuggerd/Android.bp +++ b/debuggerd/Android.bp @@ -359,6 +359,7 @@ cc_test { "libdebuggerd/test/dump_memory_test.cpp", "libdebuggerd/test/elf_fake.cpp", "libdebuggerd/test/log_fake.cpp", + "libdebuggerd/test/mte_stack_record_test.cpp", "libdebuggerd/test/open_files_list_test.cpp", "libdebuggerd/test/tombstone_proto_to_text_test.cpp", ], diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp index 8dd2b0d73..c9235eeff 100644 --- a/debuggerd/crash_dump.cpp +++ b/debuggerd/crash_dump.cpp @@ -662,6 +662,15 @@ int main(int argc, char** argv) { info.pac_enabled_keys = -1; } +#if defined(__aarch64__) + struct iovec tls_iov = { + &info.tls, + sizeof(info.tls), + }; + if (ptrace(PTRACE_GETREGSET, thread, NT_ARM_TLS, reinterpret_cast(&tls_iov)) == -1) { + info.tls = 0; + } +#endif if (thread == g_target_thread) { // Read the thread's registers along with the rest of the crash info out of the pipe. ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info, &recoverable_crash); diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h index dfdfabdff..074b0957a 100644 --- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h +++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h @@ -73,5 +73,8 @@ bool tombstone_proto_to_text( void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame); void set_human_readable_cause(Cause* cause, uint64_t fault_addr); - +#if defined(__aarch64__) +void dump_stack_history(unwindstack::AndroidUnwinder* unwinder, uintptr_t target_tls, + StackHistoryBuffer& shb_ob, bool nounwind = false); +#endif #endif // _DEBUGGERD_TOMBSTONE_H diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h index c799f2448..f7fc2a3b7 100644 --- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h +++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h @@ -41,6 +41,9 @@ struct ThreadInfo { siginfo_t* siginfo = nullptr; std::unique_ptr guest_registers; +#if defined(__aarch64__) + uintptr_t tls; // This is currently used for MTE stack history buffer. +#endif }; // This struct is written into a pipe from inside the crashing process. diff --git a/debuggerd/libdebuggerd/test/mte_stack_record_test.cpp b/debuggerd/libdebuggerd/test/mte_stack_record_test.cpp new file mode 100644 index 000000000..4b788f3b7 --- /dev/null +++ b/debuggerd/libdebuggerd/test/mte_stack_record_test.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2024 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. + */ +#if defined(__aarch64__) + +#include +#include + +#include + +#include "bionic/mte.h" +#include "bionic/page.h" +#include "unwindstack/AndroidUnwinder.h" +#include "unwindstack/Memory.h" + +#include +#include "gtest/gtest.h" + +#include "libdebuggerd/tombstone.h" + +struct ScopedUnmap { + void* ptr; + size_t size; + ~ScopedUnmap() { munmap(ptr, size); } +}; + +class MteStackHistoryTest : public ::testing::TestWithParam { + void SetUp() override { +#if !defined(__aarch64__) + GTEST_SKIP(); +#endif + SKIP_WITH_HWASAN; + unwinder.emplace(); + unwindstack::ErrorData E; + ASSERT_TRUE(unwinder->Initialize(E)); + } + + protected: + // std::optional so we don't construct it for the SKIP cases. + std::optional unwinder; +}; + +TEST(MteStackHistoryUnwindTest, TestOne) { +#if !defined(__aarch64__) + GTEST_SKIP(); +#endif + SKIP_WITH_HWASAN; + size_t size = stack_mte_ringbuffer_size(0); + char* data = static_cast(stack_mte_ringbuffer_allocate(0, nullptr)); + ScopedUnmap s{data, size}; + + uintptr_t taggedfp = (1ULL << 56) | 1; + uintptr_t pc = reinterpret_cast(&memcpy); + memcpy(data, &pc, sizeof(pc)); + memcpy(data + 8, &taggedfp, sizeof(taggedfp)); + + // The MTE TLS is at TLS - 3, so we allocate 3 placeholders. + void* tls[4] = {data + 16}; + + unwindstack::AndroidLocalUnwinder unwinder; + unwindstack::ErrorData E; + unwinder.Initialize(E); + StackHistoryBuffer shb; + dump_stack_history(&unwinder, reinterpret_cast(&tls[3]), shb, /* nounwind= */ false); + ASSERT_EQ(shb.entries_size(), 1); + const StackHistoryBufferEntry& e = shb.entries(0); + EXPECT_EQ(e.addr().pc(), pc); + EXPECT_EQ(e.addr().file_name(), "/apex/com.android.runtime/lib64/bionic/libc.so"); + EXPECT_EQ(e.fp(), 1ULL); + EXPECT_EQ(e.tag(), 1ULL); +} + +TEST_P(MteStackHistoryTest, TestEmpty) { + int size_cls = GetParam(); + size_t size = stack_mte_ringbuffer_size(size_cls); + void* data = stack_mte_ringbuffer_allocate(size_cls, nullptr); + ScopedUnmap s{data, size}; + // The MTE TLS is at TLS - 3, so we allocate 3 placeholders. + void* tls[4] = {data}; + + StackHistoryBuffer shb; + dump_stack_history(&*unwinder, reinterpret_cast(&tls[3]), shb, /* nounwind= */ true); + EXPECT_EQ(shb.entries_size(), 0); +} + +TEST_P(MteStackHistoryTest, TestFull) { + int size_cls = GetParam(); + size_t size = stack_mte_ringbuffer_size(size_cls); + char* data = static_cast(stack_mte_ringbuffer_allocate(size_cls, nullptr)); + ScopedUnmap s{data, size}; + uintptr_t itr = 1; + for (char* d = data; d < &data[size]; d += 16) { + uintptr_t taggedfp = ((itr & 15) << 56) | itr; + uintptr_t pc = itr; + memcpy(d, &pc, sizeof(pc)); + memcpy(d + 8, &taggedfp, sizeof(taggedfp)); + ++itr; + } + // The MTE TLS is at TLS - 3, so we allocate 3 placeholders. + // Because the buffer is full, and we point at one past the last inserted element, + // due to wrap-around we point at the beginning of the buffer. + void* tls[4] = {data}; + + StackHistoryBuffer shb; + dump_stack_history(&*unwinder, reinterpret_cast(&tls[3]), shb, /* nounwind= */ true); + EXPECT_EQ(static_cast(shb.entries_size()), size / 16); + for (const auto& entry : shb.entries()) { + EXPECT_EQ(entry.addr().pc(), --itr); + EXPECT_EQ(entry.addr().pc(), entry.fp()); + EXPECT_EQ(entry.addr().pc() & 15, entry.tag()); + } +} + +TEST_P(MteStackHistoryTest, TestHalfFull) { + int size_cls = GetParam(); + size_t size = stack_mte_ringbuffer_size(size_cls); + size_t half_size = size / 2; + + char* data = static_cast(stack_mte_ringbuffer_allocate(size_cls, nullptr)); + ScopedUnmap s{data, size}; + + uintptr_t itr = 1; + for (char* d = data; d < &data[half_size]; d += 16) { + uintptr_t taggedfp = ((itr & 15) << 56) | itr; + uintptr_t pc = itr; + memcpy(d, &pc, sizeof(pc)); + memcpy(d + 8, &taggedfp, sizeof(taggedfp)); + ++itr; + } + // The MTE TLS is at TLS - 3, so we allocate 3 placeholders. + void* tls[4] = {&data[half_size]}; + + StackHistoryBuffer shb; + dump_stack_history(&*unwinder, reinterpret_cast(&tls[3]), shb, /* nounwind= */ true); + EXPECT_EQ(static_cast(shb.entries_size()), half_size / 16); + for (const auto& entry : shb.entries()) { + EXPECT_EQ(entry.addr().pc(), --itr); + EXPECT_EQ(entry.addr().pc(), entry.fp()); + EXPECT_EQ(entry.addr().pc() & 15, entry.tag()); + } +} + +INSTANTIATE_TEST_SUITE_P(MteStackHistoryTestInstance, MteStackHistoryTest, testing::Range(0, 8)); + +#endif diff --git a/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp index a4c08a450..4fd264301 100644 --- a/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp +++ b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp @@ -134,3 +134,31 @@ TEST_F(TombstoneProtoToTextTest, crash_detail_bytes) { ProtoToString(); EXPECT_MATCH(text_, R"(CRASH_DETAIL_NAME: 'helloworld\\1\\255\\3')"); } + +TEST_F(TombstoneProtoToTextTest, stack_record) { + auto* cause = tombstone_->add_causes(); + cause->set_human_readable("stack tag-mismatch on thread 123"); + auto* stack = tombstone_->mutable_stack_history_buffer(); + stack->set_tid(123); + { + auto* shb_entry = stack->add_entries(); + shb_entry->set_fp(0x1); + shb_entry->set_tag(0xb); + auto* addr = shb_entry->mutable_addr(); + addr->set_rel_pc(0x567); + addr->set_file_name("foo.so"); + addr->set_build_id("ABC123"); + } + { + auto* shb_entry = stack->add_entries(); + shb_entry->set_fp(0x2); + shb_entry->set_tag(0xc); + auto* addr = shb_entry->mutable_addr(); + addr->set_rel_pc(0x678); + addr->set_file_name("bar.so"); + } + ProtoToString(); + EXPECT_MATCH(text_, "stack tag-mismatch on thread 123"); + EXPECT_MATCH(text_, "stack_record fp:0x1 tag:0xb pc:foo\\.so\\+0x567 \\(BuildId: ABC123\\)"); + EXPECT_MATCH(text_, "stack_record fp:0x2 tag:0xc pc:bar\\.so\\+0x678"); +} diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp index 3e8ab6ea5..b6fc4e2dc 100644 --- a/debuggerd/libdebuggerd/tombstone_proto.cpp +++ b/debuggerd/libdebuggerd/tombstone_proto.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -49,9 +50,11 @@ #include #include -#include -#include #include +#include +#include +#include +#include #include #include #include @@ -202,8 +205,117 @@ void set_human_readable_cause(Cause* cause, uint64_t fault_addr) { error_type_str, diff, byte_suffix, location_str, heap_object.size(), heap_object.address())); } +#if defined(__aarch64__) +void dump_stack_history(unwindstack::AndroidUnwinder* unwinder, uintptr_t target_tls, + StackHistoryBuffer& shb_obj, bool nounwind) { + auto process_memory = unwinder->GetProcessMemory(); + target_tls += sizeof(void*) * TLS_SLOT_STACK_MTE; + uintptr_t stack_mte; + if (!process_memory->ReadFully(target_tls, &stack_mte, sizeof(stack_mte))) { + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, + "dump_stack_history: failed to read TLS_SLOT_STACK_MTE: %m"); + return; + } + if (stack_mte == 0) { + async_safe_format_log(ANDROID_LOG_DEBUG, LOG_TAG, + "dump_stack_history: stack history buffer is null"); + return; + } + uintptr_t untagged_stack_mte = untag_address(stack_mte); + uintptr_t buf_size = stack_mte_ringbuffer_size_from_pointer(stack_mte); + if (buf_size == 0) { + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "dump_stack_history: empty size"); + return; + } + uintptr_t buf_start = untagged_stack_mte & ~(buf_size - 1ULL); + std::vector buf(buf_size); + if (!process_memory->ReadFully(buf_start, buf.data(), buf.size())) { + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, + "dump_stack_history: failed to read stack history: %m"); + return; + } + uintptr_t original_off = untagged_stack_mte - buf_start; + if (original_off % 16 || original_off > buf_size) { + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, + "dump_stack_history: invalid offset: %" PRIuPTR, original_off); + return; + } + + // The original_off is the next slot that would have been written, so the last + // slot that was written is the previous one. + for (uintptr_t idx = 16; idx <= buf_size; idx += 16) { + int64_t off = original_off - idx; + if (off < 0) off += buf_size; + uintptr_t pc, taggedfp; + memcpy(&pc, &(buf[off]), sizeof(pc)); + memcpy(&taggedfp, &(buf[off + sizeof(pc)]), sizeof(taggedfp)); + + if (pc == 0) break; + uintptr_t fp = untag_address(taggedfp); + uintptr_t tag = taggedfp >> 56; + + unwindstack::FrameData frame_data; + + if (nounwind) { + frame_data.pc = pc; + } else { + // +4 is to counteract the "pc adjustment" in BuildFrameFromPcOnly. + // BuildFrameFromPcOnly assumes we are unwinding, so it needs to correct for that + // the PC is the return address. That is not the case here. + // It doesn't really matter, because either should be in the correct function, but + // this is more correct (and consistent with the nounwind case). + frame_data = unwinder->BuildFrameFromPcOnly(pc); + frame_data.pc += 4; + frame_data.rel_pc += 4; + } + + StackHistoryBufferEntry* entry = shb_obj.add_entries(); + fill_in_backtrace_frame(entry->mutable_addr(), frame_data); + entry->set_fp(fp); + entry->set_tag(tag); + } +} + +static pid_t get_containing_thread(unwindstack::MapInfo* map_info, pid_t main_tid) { + if (map_info == nullptr) return 0; + + std::string name = map_info->name(); + if (name == "[stack]") { + return main_tid; + } + int tid; + if (sscanf(name.c_str(), "[anon:stack_and_tls:%d", &tid) != 1) { + return 0; + } + return tid; +} + +static std::optional maybe_stack_mte_cause( + Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder, const ThreadInfo& target_thread, + [[maybe_unused]] const std::map& threads, uint64_t fault_addr) { + unwindstack::Maps* maps = unwinder->GetMaps(); + auto map_info = maps->Find(untag_address(fault_addr)); + pid_t tid = get_containing_thread(map_info.get(), target_thread.tid); + if (!tid) { + return std::nullopt; + } + auto it = threads.find(tid); + if (it != threads.end()) { + StackHistoryBuffer* shb = tombstone->mutable_stack_history_buffer(); + shb->set_tid(tid); + dump_stack_history(unwinder, it->second.tls, *shb); + } else { + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, + "dump_probable_cause: unknown target thread %d", tid); + } + return StringPrintf("stack tag-mismatch on thread %u", tid); +} + +#endif + static void dump_probable_cause(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder, - const ProcessInfo& process_info, const ThreadInfo& target_thread) { + const ProcessInfo& process_info, const ThreadInfo& target_thread, + [[maybe_unused]] const std::map& threads) { #if defined(USE_SCUDO) ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info); if (scudo_crash_data.CrashIsMine()) { @@ -247,7 +359,15 @@ static void dump_probable_cause(Tombstone* tombstone, unwindstack::AndroidUnwind } else { cause = get_stack_overflow_cause(fault_addr, target_thread.registers->sp(), maps); } - } else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) { + } +#if defined(__aarch64__) && defined(SEGV_MTESERR) + else if (si->si_signo == SIGSEGV && si->si_code == SEGV_MTESERR) { + // If this was a heap MTE crash, it would have been handled by scudo. Checking whether it + // is a stack one. + cause = maybe_stack_mte_cause(tombstone, unwinder, target_thread, threads, fault_addr); + } +#endif + else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) { cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING, si->si_syscall); } @@ -787,7 +907,7 @@ void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::AndroidUnwinder* } } - dump_probable_cause(&result, unwinder, process_info, target_thread); + dump_probable_cause(&result, unwinder, process_info, target_thread, threads); dump_mappings(&result, unwinder->GetMaps(), unwinder->GetProcessMemory()); diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp index 19007194e..c3f94700f 100644 --- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp +++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp @@ -511,6 +511,19 @@ static void print_main_thread(CallbackType callback, const Tombstone& tombstone, "order of likelihood."); } + if (tombstone.has_stack_history_buffer()) { + for (const StackHistoryBufferEntry& shbe : tombstone.stack_history_buffer().entries()) { + std::string stack_record_str = StringPrintf( + "stack_record fp:0x%" PRIx64 " tag:0x%" PRIx64 " pc:%s+0x%" PRIx64, shbe.fp(), shbe.tag(), + shbe.addr().file_name().c_str(), shbe.addr().rel_pc()); + if (!shbe.addr().build_id().empty()) { + StringAppendF(&stack_record_str, " (BuildId: %s)", shbe.addr().build_id().c_str()); + } + + CBL("%s", stack_record_str.c_str()); + } + } + for (const Cause& cause : tombstone.causes()) { if (tombstone.causes_size() > 1) { CBS(""); diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto index b662d3680..444c9732f 100644 --- a/debuggerd/proto/tombstone.proto +++ b/debuggerd/proto/tombstone.proto @@ -22,6 +22,21 @@ message CrashDetail { reserved 3 to 999; } +message StackHistoryBufferEntry { + BacktraceFrame addr = 1; + uint64 fp = 2; + uint64 tag = 3; + + reserved 4 to 999; +} + +message StackHistoryBuffer { + uint64 tid = 1; + repeated StackHistoryBufferEntry entries = 2; + + reserved 3 to 999; +} + message Tombstone { Architecture arch = 1; Architecture guest_arch = 24; @@ -53,7 +68,9 @@ message Tombstone { uint32 page_size = 22; bool has_been_16kb_mode = 23; - reserved 26 to 999; + StackHistoryBuffer stack_history_buffer = 26; + + reserved 27 to 999; } enum Architecture {