Support MTE and GWP-ASan features in proto tombstones.

Proto tombstones were missing tagged fault addresses, tagged_addr_ctrl,
tags in memory dumps and Scudo and GWP-ASan error reports. Since text
tombstones now go via protos, all of these features broke when we
switched to text tombstones generated from protos by default. Fix
the features by adding support for them to the proto format,
tombstone_proto and tombstone_proto_to_text.

Bug: 135772972
Bug: 182489365
Change-Id: I3ca854546c38755b1f6410a1f6198a44d25ed1c5
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index ab95768..dcb5395 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -423,12 +423,11 @@
 
   ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
   ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" +
-                           std::to_string(GetParam()) + R"(-byte allocation.*
-
-allocated by thread .*
-      #00 pc)");
+                           std::to_string(GetParam()) + R"(-byte allocation)");
   ASSERT_MATCH(result, R"(deallocated by thread .*
       #00 pc)");
+  ASSERT_MATCH(result, R"(allocated by thread .*
+      #00 pc)");
 #else
   GTEST_SKIP() << "Requires aarch64";
 #endif
@@ -460,9 +459,8 @@
 
   ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
   ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
-                           std::to_string(GetParam()) + R"(-byte allocation.*
-
-allocated by thread .*
+                           std::to_string(GetParam()) + R"(-byte allocation)");
+  ASSERT_MATCH(result, R"(allocated by thread .*
       #00 pc)");
 #else
   GTEST_SKIP() << "Requires aarch64";
@@ -495,9 +493,8 @@
 
   ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
   ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" +
-                           std::to_string(GetParam()) + R"(-byte allocation.*
-
-allocated by thread .*
+                           std::to_string(GetParam()) + R"(-byte allocation)");
+  ASSERT_MATCH(result, R"(allocated by thread .*
       #00 pc)");
 #else
   GTEST_SKIP() << "Requires aarch64";
diff --git a/debuggerd/libdebuggerd/gwp_asan.cpp b/debuggerd/libdebuggerd/gwp_asan.cpp
index 9750fc4..6b8af7f 100644
--- a/debuggerd/libdebuggerd/gwp_asan.cpp
+++ b/debuggerd/libdebuggerd/gwp_asan.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "libdebuggerd/gwp_asan.h"
+#include "libdebuggerd/tombstone.h"
 #include "libdebuggerd/utility.h"
 
 #include "gwp_asan/common.h"
@@ -25,6 +26,8 @@
 #include <unwindstack/Regs.h>
 #include <unwindstack/Unwinder.h>
 
+#include "tombstone.pb.h"
+
 // Retrieve GWP-ASan state from `state_addr` inside the process at
 // `process_memory`. Place the state into `*state`.
 static bool retrieve_gwp_asan_state(unwindstack::Memory* process_memory, uintptr_t state_addr,
@@ -98,6 +101,67 @@
   return is_gwp_asan_responsible_;
 }
 
+constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;
+
+void GwpAsanCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const {
+  if (!CrashIsMine()) {
+    ALOGE("Internal Error: AddCauseProtos() on a non-GWP-ASan crash.");
+    return;
+  }
+
+  Cause* cause = tombstone->add_causes();
+  MemoryError* memory_error = cause->mutable_memory_error();
+  HeapObject* heap_object = memory_error->mutable_heap();
+
+  memory_error->set_tool(MemoryError_Tool_GWP_ASAN);
+  switch (error_) {
+    case gwp_asan::Error::USE_AFTER_FREE:
+      memory_error->set_type(MemoryError_Type_USE_AFTER_FREE);
+      break;
+    case gwp_asan::Error::DOUBLE_FREE:
+      memory_error->set_type(MemoryError_Type_DOUBLE_FREE);
+      break;
+    case gwp_asan::Error::INVALID_FREE:
+      memory_error->set_type(MemoryError_Type_INVALID_FREE);
+      break;
+    case gwp_asan::Error::BUFFER_OVERFLOW:
+      memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW);
+      break;
+    case gwp_asan::Error::BUFFER_UNDERFLOW:
+      memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW);
+      break;
+    default:
+      memory_error->set_type(MemoryError_Type_UNKNOWN);
+      break;
+  }
+
+  heap_object->set_address(__gwp_asan_get_allocation_address(responsible_allocation_));
+  heap_object->set_size(__gwp_asan_get_allocation_size(responsible_allocation_));
+  unwinder->SetDisplayBuildID(true);
+
+  std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
+
+  heap_object->set_allocation_tid(__gwp_asan_get_allocation_thread_id(responsible_allocation_));
+  size_t num_frames =
+      __gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
+  for (size_t i = 0; i != num_frames; ++i) {
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
+    BacktraceFrame* f = heap_object->add_allocation_backtrace();
+    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
+  }
+
+  heap_object->set_deallocation_tid(__gwp_asan_get_deallocation_thread_id(responsible_allocation_));
+  num_frames =
+      __gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
+  for (size_t i = 0; i != num_frames; ++i) {
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
+    BacktraceFrame* f = heap_object->add_deallocation_backtrace();
+    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
+  }
+
+  set_human_readable_cause(cause, crash_address_);
+}
+
 void GwpAsanCrashData::DumpCause(log_t* log) const {
   if (!CrashIsMine()) {
     ALOGE("Internal Error: DumpCause() on a non-GWP-ASan crash.");
@@ -157,8 +221,6 @@
        error_string_, diff, byte_suffix, location_str, alloc_size, alloc_address);
 }
 
-constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;
-
 bool GwpAsanCrashData::HasDeallocationTrace() const {
   assert(CrashIsMine() && "HasDeallocationTrace(): Crash is not mine!");
   if (!responsible_allocation_ || !__gwp_asan_is_deallocated(responsible_allocation_)) {
@@ -171,7 +233,7 @@
   assert(HasDeallocationTrace() && "DumpDeallocationTrace(): No dealloc trace!");
   uint64_t thread_id = __gwp_asan_get_deallocation_thread_id(responsible_allocation_);
 
-  std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
+  std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
   size_t num_frames =
       __gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
 
@@ -183,7 +245,7 @@
 
   unwinder->SetDisplayBuildID(true);
   for (size_t i = 0; i < num_frames; ++i) {
-    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]);
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
     frame_data.num = i;
     _LOG(log, logtype::BACKTRACE, "    %s\n", unwinder->FormatFrame(frame_data).c_str());
   }
@@ -198,7 +260,7 @@
   assert(HasAllocationTrace() && "DumpAllocationTrace(): No dealloc trace!");
   uint64_t thread_id = __gwp_asan_get_allocation_thread_id(responsible_allocation_);
 
-  std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
+  std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
   size_t num_frames =
       __gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
 
@@ -210,7 +272,7 @@
 
   unwinder->SetDisplayBuildID(true);
   for (size_t i = 0; i < num_frames; ++i) {
-    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]);
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
     frame_data.num = i;
     _LOG(log, logtype::BACKTRACE, "    %s\n", unwinder->FormatFrame(frame_data).c_str());
   }
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h b/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
index 6c88733..f9c2481 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
@@ -26,6 +26,9 @@
 #include "types.h"
 #include "utility.h"
 
+class Cause;
+class Tombstone;
+
 class GwpAsanCrashData {
  public:
   GwpAsanCrashData() = delete;
@@ -69,6 +72,8 @@
   // HasAllocationTrace() returns true.
   void DumpAllocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const;
 
+  void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const;
+
  protected:
   // Is GWP-ASan responsible for this crash.
   bool is_gwp_asan_responsible_ = false;
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
index 4d00ece..c3b95d6 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
@@ -23,6 +23,9 @@
 
 #include "scudo/interface.h"
 
+class Cause;
+class Tombstone;
+
 class ScudoCrashData {
  public:
   ScudoCrashData() = delete;
@@ -32,6 +35,7 @@
   bool CrashIsMine() const;
 
   void DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const;
+  void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const;
 
  private:
   scudo_error_info error_info_ = {};
@@ -39,4 +43,7 @@
 
   void DumpReport(const scudo_error_report* report, log_t* log,
                   unwindstack::Unwinder* unwinder) const;
+
+  void FillInCause(Cause* cause, const scudo_error_report* report,
+                   unwindstack::Unwinder* unwinder) const;
 };
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
index bf2cbb3..2331f1e 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
@@ -31,9 +31,13 @@
 #include "types.h"
 
 // Forward declarations
+class BacktraceFrame;
+class Cause;
 class Tombstone;
 
 namespace unwindstack {
+struct FrameData;
+class Maps;
 class Unwinder;
 }
 
@@ -64,4 +68,8 @@
     const Tombstone& tombstone,
     std::function<void(const std::string& line, bool should_log)> callback);
 
+void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame,
+                             unwindstack::Maps* maps);
+void set_human_readable_cause(Cause* cause, uint64_t fault_addr);
+
 #endif  // _DEBUGGERD_TOMBSTONE_H
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index d71b76f..c490fb1 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -81,7 +81,7 @@
 
 void log_backtrace(log_t* log, unwindstack::Unwinder* unwinder, const char* prefix);
 
-ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr,
+ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr,
                     unwindstack::Memory* memory);
 void dump_memory(log_t* log, unwindstack::Memory* backtrace, uint64_t addr, const std::string&);
 
@@ -93,4 +93,7 @@
 const char* get_signame(const siginfo_t*);
 const char* get_sigcode(const siginfo_t*);
 
+// Number of bytes per MTE granule.
+constexpr size_t kTagGranuleSize = 16;
+
 #endif // _DEBUGGERD_UTILITY_H
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index 1c3437f..f4690ba 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -15,13 +15,16 @@
  */
 
 #include "libdebuggerd/scudo.h"
-#include "libdebuggerd/gwp_asan.h"
+#include "libdebuggerd/tombstone.h"
 
 #include "unwindstack/Memory.h"
 #include "unwindstack/Unwinder.h"
 
+#include <android-base/macros.h>
 #include <bionic/macros.h>
 
+#include "tombstone.pb.h"
+
 std::unique_ptr<char[]> AllocAndReadFully(unwindstack::Memory* process_memory, uint64_t addr,
                                           size_t size) {
   auto buf = std::make_unique<char[]>(size);
@@ -31,8 +34,6 @@
   return buf;
 }
 
-static const uintptr_t kTagGranuleSize = 16;
-
 ScudoCrashData::ScudoCrashData(unwindstack::Memory* process_memory,
                                const ProcessInfo& process_info) {
   if (!process_info.has_fault_address) {
@@ -78,6 +79,58 @@
   return error_info_.reports[0].error_type != UNKNOWN;
 }
 
+void ScudoCrashData::FillInCause(Cause* cause, const scudo_error_report* report,
+                                 unwindstack::Unwinder* unwinder) const {
+  MemoryError* memory_error = cause->mutable_memory_error();
+  HeapObject* heap_object = memory_error->mutable_heap();
+
+  memory_error->set_tool(MemoryError_Tool_SCUDO);
+  switch (report->error_type) {
+    case USE_AFTER_FREE:
+      memory_error->set_type(MemoryError_Type_USE_AFTER_FREE);
+      break;
+    case BUFFER_OVERFLOW:
+      memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW);
+      break;
+    case BUFFER_UNDERFLOW:
+      memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW);
+      break;
+    default:
+      memory_error->set_type(MemoryError_Type_UNKNOWN);
+      break;
+  }
+
+  heap_object->set_address(report->allocation_address);
+  heap_object->set_size(report->allocation_size);
+  unwinder->SetDisplayBuildID(true);
+
+  heap_object->set_allocation_tid(report->allocation_tid);
+  for (size_t i = 0; i < arraysize(report->allocation_trace) && report->allocation_trace[i]; ++i) {
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(report->allocation_trace[i]);
+    BacktraceFrame* f = heap_object->add_allocation_backtrace();
+    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
+  }
+
+  heap_object->set_deallocation_tid(report->deallocation_tid);
+  for (size_t i = 0; i < arraysize(report->deallocation_trace) && report->deallocation_trace[i];
+       ++i) {
+    unwindstack::FrameData frame_data =
+        unwinder->BuildFrameFromPcOnly(report->deallocation_trace[i]);
+    BacktraceFrame* f = heap_object->add_deallocation_backtrace();
+    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
+  }
+
+  set_human_readable_cause(cause, untagged_fault_addr_);
+}
+
+void ScudoCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const {
+  size_t report_num = 0;
+  while (report_num < sizeof(error_info_.reports) / sizeof(error_info_.reports[0]) &&
+         error_info_.reports[report_num].error_type != UNKNOWN) {
+    FillInCause(tombstone->add_causes(), &error_info_.reports[report_num++], unwinder);
+  }
+}
+
 void ScudoCrashData::DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const {
   if (error_info_.reports[1].error_type != UNKNOWN) {
     _LOG(log, logtype::HEADER,
@@ -140,7 +193,8 @@
   if (report->allocation_trace[0]) {
     _LOG(log, logtype::BACKTRACE, "\nallocated by thread %u:\n", report->allocation_tid);
     unwinder->SetDisplayBuildID(true);
-    for (size_t i = 0; i < 64 && report->allocation_trace[i]; ++i) {
+    for (size_t i = 0; i < arraysize(report->allocation_trace) && report->allocation_trace[i];
+         ++i) {
       unwindstack::FrameData frame_data =
           unwinder->BuildFrameFromPcOnly(report->allocation_trace[i]);
       frame_data.num = i;
@@ -151,7 +205,8 @@
   if (report->deallocation_trace[0]) {
     _LOG(log, logtype::BACKTRACE, "\ndeallocated by thread %u:\n", report->deallocation_tid);
     unwinder->SetDisplayBuildID(true);
-    for (size_t i = 0; i < 64 && report->deallocation_trace[i]; ++i) {
+    for (size_t i = 0; i < arraysize(report->deallocation_trace) && report->deallocation_trace[i];
+         ++i) {
       unwindstack::FrameData frame_data =
           unwinder->BuildFrameFromPcOnly(report->deallocation_trace[i]);
       frame_data.num = i;
diff --git a/debuggerd/libdebuggerd/test/dump_memory_test.cpp b/debuggerd/libdebuggerd/test/dump_memory_test.cpp
index f16f578..5be145a 100644
--- a/debuggerd/libdebuggerd/test/dump_memory_test.cpp
+++ b/debuggerd/libdebuggerd/test/dump_memory_test.cpp
@@ -73,34 +73,14 @@
 "    0000000012345600 2726252423222120 2f2e2d2c2b2a2928   !\"#$%&'()*+,-./\n"
 "    0000000012345610 3736353433323130 3f3e3d3c3b3a3938  0123456789:;<=>?\n"
 "    0000000012345620 4746454443424140 4f4e4d4c4b4a4948  @ABCDEFGHIJKLMNO\n"
-"    0000000012345630 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n"
-"    0000000012345640 6766656463626160 ----------------  `abcdefg........\n"
-"    0000000012345650 ---------------- ----------------  ................\n"
-"    0000000012345660 ---------------- ----------------  ................\n"
-"    0000000012345670 ---------------- ----------------  ................\n"
-"    0000000012345680 ---------------- ----------------  ................\n"
-"    0000000012345690 ---------------- ----------------  ................\n"
-"    00000000123456a0 ---------------- ----------------  ................\n"
-"    00000000123456b0 ---------------- ----------------  ................\n"
-"    00000000123456c0 ---------------- ----------------  ................\n"
-"    00000000123456d0 ---------------- ----------------  ................\n";
+"    0000000012345630 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n";
 #else
 "    123455e0 03020100 07060504 0b0a0908 0f0e0d0c  ................\n"
 "    123455f0 13121110 17161514 1b1a1918 1f1e1d1c  ................\n"
 "    12345600 23222120 27262524 2b2a2928 2f2e2d2c   !\"#$%&'()*+,-./\n"
 "    12345610 33323130 37363534 3b3a3938 3f3e3d3c  0123456789:;<=>?\n"
 "    12345620 43424140 47464544 4b4a4948 4f4e4d4c  @ABCDEFGHIJKLMNO\n"
-"    12345630 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n"
-"    12345640 63626160 67666564 -------- --------  `abcdefg........\n"
-"    12345650 -------- -------- -------- --------  ................\n"
-"    12345660 -------- -------- -------- --------  ................\n"
-"    12345670 -------- -------- -------- --------  ................\n"
-"    12345680 -------- -------- -------- --------  ................\n"
-"    12345690 -------- -------- -------- --------  ................\n"
-"    123456a0 -------- -------- -------- --------  ................\n"
-"    123456b0 -------- -------- -------- --------  ................\n"
-"    123456c0 -------- -------- -------- --------  ................\n"
-"    123456d0 -------- -------- -------- --------  ................\n";
+"    12345630 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n";
 #endif
 
 class MemoryMock : public unwindstack::Memory {
@@ -513,15 +493,7 @@
   const char* expected_dump = \
 "\nmemory near r4:\n"
 #if defined(__LP64__)
-R"(    0000000010000f80 ---------------- ----------------  ................
-    0000000010000f90 ---------------- ----------------  ................
-    0000000010000fa0 ---------------- ----------------  ................
-    0000000010000fb0 ---------------- ----------------  ................
-    0000000010000fc0 ---------------- ----------------  ................
-    0000000010000fd0 ---------------- ----------------  ................
-    0000000010000fe0 ---------------- ----------------  ................
-    0000000010000ff0 ---------------- ----------------  ................
-    0000000010001000 8786858483828180 8f8e8d8c8b8a8988  ................
+R"(    0000000010001000 8786858483828180 8f8e8d8c8b8a8988  ................
     0000000010001010 9796959493929190 9f9e9d9c9b9a9998  ................
     0000000010001020 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................
     0000000010001030 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................
@@ -531,15 +503,7 @@
     0000000010001070 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................
 )";
 #else
-R"(    10000f80 -------- -------- -------- --------  ................
-    10000f90 -------- -------- -------- --------  ................
-    10000fa0 -------- -------- -------- --------  ................
-    10000fb0 -------- -------- -------- --------  ................
-    10000fc0 -------- -------- -------- --------  ................
-    10000fd0 -------- -------- -------- --------  ................
-    10000fe0 -------- -------- -------- --------  ................
-    10000ff0 -------- -------- -------- --------  ................
-    10001000 83828180 87868584 8b8a8988 8f8e8d8c  ................
+R"(    10001000 83828180 87868584 8b8a8988 8f8e8d8c  ................
     10001010 93929190 97969594 9b9a9998 9f9e9d9c  ................
     10001020 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac  ................
     10001030 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc  ................
@@ -574,39 +538,11 @@
   const char* expected_dump = \
 "\nmemory near r4:\n"
 #if defined(__LP64__)
-"    0000000010000f40 ---------------- ----------------  ................\n"
-"    0000000010000f50 ---------------- ----------------  ................\n"
-"    0000000010000f60 ---------------- ----------------  ................\n"
-"    0000000010000f70 ---------------- ----------------  ................\n"
-"    0000000010000f80 ---------------- ----------------  ................\n"
-"    0000000010000f90 ---------------- ----------------  ................\n"
-"    0000000010000fa0 ---------------- ----------------  ................\n"
-"    0000000010000fb0 ---------------- ----------------  ................\n"
-"    0000000010000fc0 ---------------- ----------------  ................\n"
-"    0000000010000fd0 ---------------- ----------------  ................\n"
-"    0000000010000fe0 ---------------- ----------------  ................\n"
-"    0000000010000ff0 ---------------- ----------------  ................\n"
 "    0000000010001000 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
-"    0000000010001010 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n"
-"    0000000010001020 ---------------- ----------------  ................\n"
-"    0000000010001030 ---------------- ----------------  ................\n";
+"    0000000010001010 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n";
 #else
-"    10000f40 -------- -------- -------- --------  ................\n"
-"    10000f50 -------- -------- -------- --------  ................\n"
-"    10000f60 -------- -------- -------- --------  ................\n"
-"    10000f70 -------- -------- -------- --------  ................\n"
-"    10000f80 -------- -------- -------- --------  ................\n"
-"    10000f90 -------- -------- -------- --------  ................\n"
-"    10000fa0 -------- -------- -------- --------  ................\n"
-"    10000fb0 -------- -------- -------- --------  ................\n"
-"    10000fc0 -------- -------- -------- --------  ................\n"
-"    10000fd0 -------- -------- -------- --------  ................\n"
-"    10000fe0 -------- -------- -------- --------  ................\n"
-"    10000ff0 -------- -------- -------- --------  ................\n"
 "    10001000 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................\n"
-"    10001010 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n"
-"    10001020 -------- -------- -------- --------  ................\n"
-"    10001030 -------- -------- -------- --------  ................\n";
+"    10001010 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n";
 #endif
   ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
 
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index 23ca070..1ab93fe 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -17,6 +17,8 @@
 #define LOG_TAG "DEBUG"
 
 #include "libdebuggerd/tombstone.h"
+#include "libdebuggerd/gwp_asan.h"
+#include "libdebuggerd/scudo.h"
 
 #include <errno.h>
 #include <fcntl.h>
@@ -106,32 +108,120 @@
   return {};
 }
 
-static void dump_probable_cause(Tombstone* tombstone, const siginfo_t* si, unwindstack::Maps* maps,
-                                unwindstack::Regs* regs) {
+void set_human_readable_cause(Cause* cause, uint64_t fault_addr) {
+  if (!cause->has_memory_error() || !cause->memory_error().has_heap()) {
+    return;
+  }
+
+  const MemoryError& memory_error = cause->memory_error();
+  const HeapObject& heap_object = memory_error.heap();
+
+  const char *tool_str;
+  switch (memory_error.tool()) {
+    case MemoryError_Tool_GWP_ASAN:
+      tool_str = "GWP-ASan";
+      break;
+    case MemoryError_Tool_SCUDO:
+      tool_str = "MTE";
+      break;
+    default:
+      tool_str = "Unknown";
+      break;
+  }
+
+  const char *error_type_str;
+  switch (memory_error.type()) {
+    case MemoryError_Type_USE_AFTER_FREE:
+      error_type_str = "Use After Free";
+      break;
+    case MemoryError_Type_DOUBLE_FREE:
+      error_type_str = "Double Free";
+      break;
+    case MemoryError_Type_INVALID_FREE:
+      error_type_str = "Invalid (Wild) Free";
+      break;
+    case MemoryError_Type_BUFFER_OVERFLOW:
+      error_type_str = "Buffer Overflow";
+      break;
+    case MemoryError_Type_BUFFER_UNDERFLOW:
+      error_type_str = "Buffer Underflow";
+      break;
+    default:
+      cause->set_human_readable(
+          StringPrintf("[%s]: Unknown error occurred at 0x%" PRIx64 ".", tool_str, fault_addr));
+      return;
+  }
+
+  uint64_t diff;
+  const char* location_str;
+
+  if (fault_addr < heap_object.address()) {
+    // Buffer Underflow, 6 bytes left of a 41-byte allocation at 0xdeadbeef.
+    location_str = "left of";
+    diff = heap_object.address() - fault_addr;
+  } else if (fault_addr - heap_object.address() < heap_object.size()) {
+    // Use After Free, 40 bytes into a 41-byte allocation at 0xdeadbeef.
+    location_str = "into";
+    diff = fault_addr - heap_object.address();
+  } else {
+    // Buffer Overflow, 6 bytes right of a 41-byte allocation at 0xdeadbeef.
+    location_str = "right of";
+    diff = fault_addr - heap_object.address() - heap_object.size();
+  }
+
+  // Suffix of 'bytes', i.e. 4 bytes' vs. '1 byte'.
+  const char* byte_suffix = "s";
+  if (diff == 1) {
+    byte_suffix = "";
+  }
+
+  cause->set_human_readable(StringPrintf(
+      "[%s]: %s, %" PRIu64 " byte%s %s a %" PRIu64 "-byte allocation at 0x%" PRIx64, tool_str,
+      error_type_str, diff, byte_suffix, location_str, heap_object.size(), heap_object.address()));
+}
+
+static void dump_probable_cause(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
+                                const ProcessInfo& process_info, const ThreadInfo& main_thread) {
+  ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
+  if (scudo_crash_data.CrashIsMine()) {
+    scudo_crash_data.AddCauseProtos(tombstone, unwinder);
+    return;
+  }
+
+  GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
+                                       main_thread);
+  if (gwp_asan_crash_data.CrashIsMine()) {
+    gwp_asan_crash_data.AddCauseProtos(tombstone, unwinder);
+    return;
+  }
+
+  const siginfo *si = main_thread.siginfo;
+  auto fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
+  unwindstack::Maps* maps = unwinder->GetMaps();
+
   std::optional<std::string> cause;
   if (si->si_signo == SIGSEGV && si->si_code == SEGV_MAPERR) {
-    if (si->si_addr < reinterpret_cast<void*>(4096)) {
+    if (fault_addr < 4096) {
       cause = "null pointer dereference";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0ffc)) {
+    } else if (fault_addr == 0xffff0ffc) {
       cause = "call to kuser_helper_version";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fe0)) {
+    } else if (fault_addr == 0xffff0fe0) {
       cause = "call to kuser_get_tls";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fc0)) {
+    } else if (fault_addr == 0xffff0fc0) {
       cause = "call to kuser_cmpxchg";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fa0)) {
+    } else if (fault_addr == 0xffff0fa0) {
       cause = "call to kuser_memory_barrier";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0f60)) {
+    } else if (fault_addr == 0xffff0f60) {
       cause = "call to kuser_cmpxchg64";
     } else {
-      cause = get_stack_overflow_cause(reinterpret_cast<uint64_t>(si->si_addr), regs->sp(), maps);
+      cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
     }
   } else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
-    uint64_t fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
     unwindstack::MapInfo* map_info = maps->Find(fault_addr);
     if (map_info != nullptr && map_info->flags == PROT_EXEC) {
       cause = "execute-only (no-read) memory access error; likely due to data in .text.";
     } else {
-      cause = get_stack_overflow_cause(fault_addr, regs->sp(), maps);
+      cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
     }
   } else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) {
     cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING,
@@ -139,7 +229,8 @@
   }
 
   if (cause) {
-    tombstone->mutable_cause()->set_human_readable(*cause);
+    Cause *cause_proto = tombstone->add_causes();
+    cause_proto->set_human_readable(*cause);
   }
 }
 
@@ -205,12 +296,49 @@
   }
 }
 
+void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame,
+                             unwindstack::Maps* maps) {
+  f->set_rel_pc(frame.rel_pc);
+  f->set_pc(frame.pc);
+  f->set_sp(frame.sp);
+
+  if (!frame.function_name.empty()) {
+    // TODO: Should this happen here, or on the display side?
+    char* demangled_name = __cxa_demangle(frame.function_name.c_str(), nullptr, nullptr, nullptr);
+    if (demangled_name) {
+      f->set_function_name(demangled_name);
+      free(demangled_name);
+    } else {
+      f->set_function_name(frame.function_name);
+    }
+  }
+
+  f->set_function_offset(frame.function_offset);
+
+  if (frame.map_start == frame.map_end) {
+    // No valid map associated with this frame.
+    f->set_file_name("<unknown>");
+  } else if (!frame.map_name.empty()) {
+    f->set_file_name(frame.map_name);
+  } else {
+    f->set_file_name(StringPrintf("<anonymous:%" PRIx64 ">", frame.map_start));
+  }
+
+  f->set_file_map_offset(frame.map_elf_start_offset);
+
+  unwindstack::MapInfo* map_info = maps->Find(frame.map_start);
+  if (map_info) {
+    f->set_build_id(map_info->GetPrintableBuildID());
+  }
+}
+
 static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
                         const ThreadInfo& thread_info, bool memory_dump = false) {
   Thread thread;
 
   thread.set_id(thread_info.tid);
   thread.set_name(thread_info.thread_name);
+  thread.set_tagged_addr_ctrl(thread_info.tagged_addr_ctrl);
 
   unwindstack::Maps* maps = unwinder->GetMaps();
   unwindstack::Memory* memory = unwinder->GetProcessMemory().get();
@@ -225,20 +353,19 @@
         if (memory_dump) {
           MemoryDump dump;
 
-          char buf[256];
-          size_t start_offset = 0;
-          ssize_t bytes = dump_memory(buf, sizeof(buf), &start_offset, &value, memory);
-          if (bytes == -1) {
-            return;
-          }
-
           dump.set_register_name(name);
-
           unwindstack::MapInfo* map_info = maps->Find(untag_address(value));
           if (map_info) {
             dump.set_mapping_name(map_info->name);
           }
 
+          char buf[256];
+          uint8_t tags[256 / kTagGranuleSize];
+          size_t start_offset = 0;
+          ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory);
+          if (bytes == -1) {
+            return;
+          }
           dump.set_begin_address(value);
 
           if (start_offset + bytes > sizeof(buf)) {
@@ -246,7 +373,8 @@
                              start_offset, bytes);
           }
 
-          dump.set_memory(buf, start_offset + bytes);
+          dump.set_memory(buf, bytes);
+          dump.set_tags(tags, bytes / kTagGranuleSize);
 
           *thread.add_memory_dump() = std::move(dump);
         }
@@ -267,39 +395,7 @@
     unwinder->SetDisplayBuildID(true);
     for (const auto& frame : unwinder->frames()) {
       BacktraceFrame* f = thread.add_current_backtrace();
-      f->set_rel_pc(frame.rel_pc);
-      f->set_pc(frame.pc);
-      f->set_sp(frame.sp);
-
-      if (!frame.function_name.empty()) {
-        // TODO: Should this happen here, or on the display side?
-        char* demangled_name =
-            __cxa_demangle(frame.function_name.c_str(), nullptr, nullptr, nullptr);
-        if (demangled_name) {
-          f->set_function_name(demangled_name);
-          free(demangled_name);
-        } else {
-          f->set_function_name(frame.function_name);
-        }
-      }
-
-      f->set_function_offset(frame.function_offset);
-
-      if (frame.map_start == frame.map_end) {
-        // No valid map associated with this frame.
-        f->set_file_name("<unknown>");
-      } else if (!frame.map_name.empty()) {
-        f->set_file_name(frame.map_name);
-      } else {
-        f->set_file_name(StringPrintf("<anonymous:%" PRIx64 ">", frame.map_start));
-      }
-
-      f->set_file_map_offset(frame.map_elf_start_offset);
-
-      unwindstack::MapInfo* map_info = maps->Find(frame.map_start);
-      if (map_info) {
-        f->set_build_id(map_info->GetPrintableBuildID());
-      }
+      fill_in_backtrace_frame(f, frame, maps);
     }
   }
 
@@ -458,7 +554,7 @@
 
   if (process_info.has_fault_address) {
     sig.set_has_fault_address(true);
-    sig.set_fault_address(process_info.untagged_fault_address);
+    sig.set_fault_address(process_info.maybe_tagged_fault_address);
   }
 
   *result.mutable_signal_info() = sig;
@@ -473,8 +569,7 @@
     }
   }
 
-  dump_probable_cause(&result, main_thread.siginfo, unwinder->GetMaps(),
-                      main_thread.registers.get());
+  dump_probable_cause(&result, unwinder, process_info, main_thread);
 
   dump_mappings(&result, unwinder);
 
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 187379d..e4dd6df 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -74,6 +74,9 @@
   CB(should_log, "pid: %d, tid: %d, name: %s  >>> %s <<<", tombstone.pid(), thread.id(),
      thread.name().c_str(), tombstone.process_name().c_str());
   CB(should_log, "uid: %d", tombstone.uid());
+  if (thread.tagged_addr_ctrl() != -1) {
+    CB(should_log, "tagged_addr_ctrl: %016" PRIx64, thread.tagged_addr_ctrl());
+  }
 }
 
 static void print_register_row(CallbackType callback, int word_size,
@@ -136,12 +139,11 @@
   print_register_row(callback, word_size, special_row, should_log);
 }
 
-static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone,
-                                   const Thread& thread, bool should_log) {
-  CBS("");
-  CB(should_log, "backtrace:");
+static void print_backtrace(CallbackType callback, const Tombstone& tombstone,
+                            const google::protobuf::RepeatedPtrField<BacktraceFrame>& backtrace,
+                            bool should_log) {
   int index = 0;
-  for (const auto& frame : thread.current_backtrace()) {
+  for (const auto& frame : backtrace) {
     std::string function;
 
     if (!frame.function_name().empty()) {
@@ -159,16 +161,32 @@
   }
 }
 
+static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone,
+                                   const Thread& thread, bool should_log) {
+  CBS("");
+  CB(should_log, "backtrace:");
+  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("");
-    CBS("memory near %s (%s):", mem.register_name().c_str(), mem.mapping_name().c_str());
+    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) {
-      std::string line = StringPrintf("    %0*" PRIx64, word_size * 2, addr + offset);
+      uint64_t tagged_addr = addr;
+      if (mem.tags().size() > offset / kTagGranuleSize) {
+        tagged_addr |= static_cast<uint64_t>(mem.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) {
@@ -231,9 +249,8 @@
         sender_desc.c_str(), fault_addr_desc.c_str());
   }
 
-  if (tombstone.has_cause()) {
-    const Cause& cause = tombstone.cause();
-    CBL("Cause: %s", cause.human_readable().c_str());
+  if (tombstone.causes_size() == 1) {
+    CBL("Cause: %s", tombstone.causes(0).human_readable().c_str());
   }
 
   if (!tombstone.abort_message().empty()) {
@@ -242,6 +259,36 @@
 
   print_thread_registers(callback, tombstone, thread, true);
   print_thread_backtrace(callback, tombstone, thread, true);
+
+  if (tombstone.causes_size() > 1) {
+    CBS("");
+    CBS("Note: multiple potential causes for this crash were detected, listing them in decreasing "
+        "order of probability.");
+  }
+
+  for (const Cause& cause : tombstone.causes()) {
+    if (tombstone.causes_size() > 1) {
+      CBS("");
+      CBS("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("");
+        CBS("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid());
+        print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), false);
+      }
+
+      if (heap_object.allocation_backtrace_size() != 0) {
+        CBS("");
+        CBS("allocated by thread %" PRIu64 ":", heap_object.allocation_tid());
+        print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), false);
+      }
+    }
+  }
+
   print_thread_memory_dump(callback, tombstone, thread);
 
   CBS("");
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 6f13ed4..2c645b5 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -125,8 +125,9 @@
 
 #define MEMORY_BYTES_TO_DUMP 256
 #define MEMORY_BYTES_PER_LINE 16
+static_assert(MEMORY_BYTES_PER_LINE == kTagGranuleSize);
 
-ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr,
+ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr,
                     unwindstack::Memory* memory) {
   // Align the address to the number of bytes per line to avoid confusing memory tag output if
   // memory is tagged and we start from a misaligned address. Start 32 bytes before the address.
@@ -154,17 +155,17 @@
     bytes &= ~(sizeof(uintptr_t) - 1);
   }
 
-  *start_offset = 0;
   bool skip_2nd_read = false;
   if (bytes == 0) {
     // In this case, we might want to try another read at the beginning of
     // the next page only if it's within the amount of memory we would have
     // read.
     size_t page_size = sysconf(_SC_PAGE_SIZE);
-    *start_offset = ((*addr + (page_size - 1)) & ~(page_size - 1)) - *addr;
-    if (*start_offset == 0 || *start_offset >= len) {
+    uint64_t next_page = (*addr + (page_size - 1)) & ~(page_size - 1);
+    if (next_page == *addr || next_page >= *addr + len) {
       skip_2nd_read = true;
     }
+    *addr = next_page;
   }
 
   if (bytes < len && !skip_2nd_read) {
@@ -174,8 +175,7 @@
     // into a readable map. Only requires one extra read because a map has
     // to contain at least one page, and the total number of bytes to dump
     // is smaller than a page.
-    size_t bytes2 = memory->Read(*addr + *start_offset + bytes, static_cast<uint8_t*>(out) + bytes,
-                                 len - bytes - *start_offset);
+    size_t bytes2 = memory->Read(*addr + bytes, static_cast<uint8_t*>(out) + bytes, len - bytes);
     bytes += bytes2;
     if (bytes2 > 0 && bytes % sizeof(uintptr_t) != 0) {
       // This should never happen, but we'll try and continue any way.
@@ -190,15 +190,24 @@
     return -1;
   }
 
+  for (uint64_t tag_granule = 0; tag_granule < bytes / kTagGranuleSize; ++tag_granule) {
+    long tag = memory->ReadTag(*addr + kTagGranuleSize * tag_granule);
+    if (tag_granule < tags_len) {
+      tags[tag_granule] = tag >= 0 ? tag : 0;
+    } else {
+      ALOGE("Insufficient space for tags");
+    }
+  }
+
   return bytes;
 }
 
 void dump_memory(log_t* log, unwindstack::Memory* memory, uint64_t addr, const std::string& label) {
   // Dump 256 bytes
   uintptr_t data[MEMORY_BYTES_TO_DUMP / sizeof(uintptr_t)];
-  size_t start_offset = 0;
+  uint8_t tags[MEMORY_BYTES_TO_DUMP / kTagGranuleSize];
 
-  ssize_t bytes = dump_memory(data, sizeof(data), &start_offset, &addr, memory);
+  ssize_t bytes = dump_memory(data, sizeof(data), tags, sizeof(tags), &addr, memory);
   if (bytes == -1) {
     return;
   }
@@ -212,38 +221,27 @@
   // On 32-bit machines, there are still 16 bytes per line but addresses and
   // words are of course presented differently.
   uintptr_t* data_ptr = data;
-  size_t current = 0;
-  size_t total_bytes = start_offset + bytes;
-  for (size_t line = 0; line < MEMORY_BYTES_TO_DUMP / MEMORY_BYTES_PER_LINE; line++) {
-    uint64_t tagged_addr = addr;
-    long tag = memory->ReadTag(addr);
-    if (tag >= 0) {
-      tagged_addr |= static_cast<uint64_t>(tag) << 56;
-    }
+  uint8_t* tags_ptr = tags;
+  for (size_t line = 0; line < static_cast<size_t>(bytes) / MEMORY_BYTES_PER_LINE; line++) {
+    uint64_t tagged_addr = addr | static_cast<uint64_t>(*tags_ptr++) << 56;
     std::string logline;
     android::base::StringAppendF(&logline, "    %" PRIPTR, tagged_addr);
 
     addr += MEMORY_BYTES_PER_LINE;
     std::string ascii;
     for (size_t i = 0; i < MEMORY_BYTES_PER_LINE / sizeof(uintptr_t); i++) {
-      if (current >= start_offset && current + sizeof(uintptr_t) <= total_bytes) {
-        android::base::StringAppendF(&logline, " %" PRIPTR, static_cast<uint64_t>(*data_ptr));
+      android::base::StringAppendF(&logline, " %" PRIPTR, static_cast<uint64_t>(*data_ptr));
 
-        // Fill out the ascii string from the data.
-        uint8_t* ptr = reinterpret_cast<uint8_t*>(data_ptr);
-        for (size_t val = 0; val < sizeof(uintptr_t); val++, ptr++) {
-          if (*ptr >= 0x20 && *ptr < 0x7f) {
-            ascii += *ptr;
-          } else {
-            ascii += '.';
-          }
+      // Fill out the ascii string from the data.
+      uint8_t* ptr = reinterpret_cast<uint8_t*>(data_ptr);
+      for (size_t val = 0; val < sizeof(uintptr_t); val++, ptr++) {
+        if (*ptr >= 0x20 && *ptr < 0x7f) {
+          ascii += *ptr;
+        } else {
+          ascii += '.';
         }
-        data_ptr++;
-      } else {
-        logline += ' ' + std::string(sizeof(uintptr_t) * 2, '-');
-        ascii += std::string(sizeof(uintptr_t), '.');
       }
-      current += sizeof(uintptr_t);
+      data_ptr++;
     }
     _LOG(log, logtype::MEMORY, "%s  %s\n", logline.c_str(), ascii.c_str());
   }
diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto
index 2c7156b..433c406 100644
--- a/debuggerd/proto/tombstone.proto
+++ b/debuggerd/proto/tombstone.proto
@@ -21,7 +21,7 @@
 
   Signal signal_info = 10;
   string abort_message = 14;
-  Cause cause = 15;
+  repeated Cause causes = 15;
 
   map<uint32, Thread> threads = 16;
   repeated MemoryMapping memory_mappings = 17;
@@ -57,10 +57,52 @@
   reserved 10 to 999;
 }
 
+message HeapObject {
+  uint64 address = 1;
+  uint64 size = 2;
+
+  uint64 allocation_tid = 3;
+  repeated BacktraceFrame allocation_backtrace = 4;
+
+  uint64 deallocation_tid = 5;
+  repeated BacktraceFrame deallocation_backtrace = 6;
+}
+
+message MemoryError {
+  enum Tool {
+    GWP_ASAN = 0;
+    SCUDO = 1;
+
+    reserved 2 to 999;
+  }
+  Tool tool = 1;
+
+  enum Type {
+    UNKNOWN = 0;
+    USE_AFTER_FREE = 1;
+    DOUBLE_FREE = 2;
+    INVALID_FREE = 3;
+    BUFFER_OVERFLOW = 4;
+    BUFFER_UNDERFLOW = 5;
+
+    reserved 6 to 999;
+  }
+  Type type = 2;
+
+  oneof location {
+    HeapObject heap = 3;
+  }
+
+  reserved 4 to 999;
+}
+
 message Cause {
   string human_readable = 1;
+  oneof details {
+    MemoryError memory_error = 2;
+  }
 
-  reserved 2 to 999;
+  reserved 3 to 999;
 }
 
 message Register {
@@ -76,8 +118,9 @@
   repeated Register registers = 3;
   repeated BacktraceFrame current_backtrace = 4;
   repeated MemoryDump memory_dump = 5;
+  int64 tagged_addr_ctrl = 6;
 
-  reserved 6 to 999;
+  reserved 7 to 999;
 }
 
 message BacktraceFrame {
@@ -100,8 +143,9 @@
   string mapping_name = 2;
   uint64 begin_address = 3;
   bytes memory = 4;
+  bytes tags = 5;
 
-  reserved 5 to 999;
+  reserved 6 to 999;
 }
 
 message MemoryMapping {