[MTE] write stack history into tombstone
We will change the symbolizer to use this information to output
something like:
Potentially referenced stack object:
0 bytes inside a stack variable "variableName" in stack frame of function functionName
at source.cc:1234
Bug: 309446520
Change-Id: I1163ac81ac6b5e184387eb9e058d97a7227e3671
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 0c5543e..c365cac 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -359,6 +359,7 @@
"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 8dd2b0d..c9235ee 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -662,6 +662,15 @@
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<void*>(&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 dfdfabd..074b095 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
@@ -73,5 +73,8 @@
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 c799f24..f7fc2a3 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -41,6 +41,9 @@
siginfo_t* siginfo = nullptr;
std::unique_ptr<unwindstack::Regs> 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 0000000..4b788f3
--- /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 <stdint.h>
+#include <sys/mman.h>
+
+#include <optional>
+
+#include "bionic/mte.h"
+#include "bionic/page.h"
+#include "unwindstack/AndroidUnwinder.h"
+#include "unwindstack/Memory.h"
+
+#include <android-base/test_utils.h>
+#include "gtest/gtest.h"
+
+#include "libdebuggerd/tombstone.h"
+
+struct ScopedUnmap {
+ void* ptr;
+ size_t size;
+ ~ScopedUnmap() { munmap(ptr, size); }
+};
+
+class MteStackHistoryTest : public ::testing::TestWithParam<int> {
+ 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<unwindstack::AndroidLocalUnwinder> 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<char*>(stack_mte_ringbuffer_allocate(0, nullptr));
+ ScopedUnmap s{data, size};
+
+ uintptr_t taggedfp = (1ULL << 56) | 1;
+ uintptr_t pc = reinterpret_cast<uintptr_t>(&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<uintptr_t>(&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<uintptr_t>(&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<char*>(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<uintptr_t>(&tls[3]), shb, /* nounwind= */ true);
+ EXPECT_EQ(static_cast<size_t>(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<char*>(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<uintptr_t>(&tls[3]), shb, /* nounwind= */ true);
+ EXPECT_EQ(static_cast<size_t>(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 a4c08a4..4fd2643 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 @@
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 3e8ab6e..b6fc4e2 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -27,6 +27,7 @@
#include <inttypes.h>
#include <signal.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
@@ -49,9 +50,11 @@
#include <android/log.h>
#include <android/set_abort_message.h>
-#include <bionic/macros.h>
-#include <bionic/reserved_signals.h>
#include <bionic/crash_detail_internal.h>
+#include <bionic/macros.h>
+#include <bionic/mte.h>
+#include <bionic/reserved_signals.h>
+#include <bionic/tls_defines.h>
#include <log/log.h>
#include <log/log_read.h>
#include <log/logprint.h>
@@ -202,8 +205,117 @@
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<char> 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<std::string> maybe_stack_mte_cause(
+ Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder, const ThreadInfo& target_thread,
+ [[maybe_unused]] const std::map<pid_t, ThreadInfo>& 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<pid_t, ThreadInfo>& threads) {
#if defined(USE_SCUDO)
ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
if (scudo_crash_data.CrashIsMine()) {
@@ -247,7 +359,15 @@
} 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 @@
}
}
- 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 1900719..c3f9470 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -511,6 +511,19 @@
"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 b662d36..444c973 100644
--- a/debuggerd/proto/tombstone.proto
+++ b/debuggerd/proto/tombstone.proto
@@ -22,6 +22,21 @@
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 @@
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 {