[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 {