[GWP-ASan] Add generic unwinders and structure backtrace output.

Summary:
Adds two flavours of generic unwinder and all the supporting cruft. If the
supporting allocator is okay with bringing in sanitizer_common, they can use
the fast frame-pointer based unwinder from sanitizer_common. Otherwise, we also
provide the backtrace() libc-based unwinder as well. Of course, the allocator
can always specify its own unwinder and unwinder-symbolizer.

The slightly changed output format is exemplified in the first comment on this
patch. It now better incorporates backtrace information, and displays
allocation details on the second line.

Reviewers: eugenis, vlad.tsyrklevich

Reviewed By: eugenis, vlad.tsyrklevich

Subscribers: srhines, kubamracek, mgorny, cryptoad, #sanitizers, llvm-commits, morehouse

Tags: #sanitizers, #llvm

Differential Revision: https://reviews.llvm.org/D63841

llvm-svn: 364941
GitOrigin-RevId: 7339ca278c3fdb1d932b776005b2687ec11eadae
diff --git a/guarded_pool_allocator.cpp b/guarded_pool_allocator.cpp
index 68ec00d..3180684 100644
--- a/guarded_pool_allocator.cpp
+++ b/guarded_pool_allocator.cpp
@@ -11,6 +11,8 @@
 #include "gwp_asan/options.h"
 
 #include <assert.h>
+#include <inttypes.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
@@ -26,6 +28,25 @@
 // referenced by users outside this translation unit, in order to avoid
 // init-order-fiasco.
 GuardedPoolAllocator *SingletonPtr = nullptr;
+
+class ScopedBoolean {
+public:
+  ScopedBoolean(bool &B) : Bool(B) { Bool = true; }
+  ~ScopedBoolean() { Bool = false; }
+
+private:
+  bool &Bool;
+};
+
+void defaultPrintStackTrace(uintptr_t *Trace, options::Printf_t Printf) {
+  if (Trace[0] == 0)
+    Printf("  <unknown (does your allocator support backtracing?)>\n");
+
+  for (size_t i = 0; Trace[i] != 0; ++i) {
+    Printf("  #%zu 0x%zx in <unknown>\n", i, Trace[i]);
+  }
+  Printf("\n");
+}
 } // anonymous namespace
 
 // Gets the singleton implementation of this class. Thread-compatible until
@@ -33,25 +54,33 @@
 GuardedPoolAllocator *getSingleton() { return SingletonPtr; }
 
 void GuardedPoolAllocator::AllocationMetadata::RecordAllocation(
-    uintptr_t AllocAddr, size_t AllocSize) {
+    uintptr_t AllocAddr, size_t AllocSize, options::Backtrace_t Backtrace) {
   Addr = AllocAddr;
   Size = AllocSize;
   IsDeallocated = false;
 
-  // TODO(hctim): Implement stack trace collection.
   // TODO(hctim): Ask the caller to provide the thread ID, so we don't waste
   // other thread's time getting the thread ID under lock.
   AllocationTrace.ThreadID = getThreadID();
   DeallocationTrace.ThreadID = kInvalidThreadID;
-  AllocationTrace.Trace[0] = 0;
+  if (Backtrace)
+    Backtrace(AllocationTrace.Trace, kMaximumStackFrames);
+  else
+    AllocationTrace.Trace[0] = 0;
   DeallocationTrace.Trace[0] = 0;
 }
 
-void GuardedPoolAllocator::AllocationMetadata::RecordDeallocation() {
+void GuardedPoolAllocator::AllocationMetadata::RecordDeallocation(
+    options::Backtrace_t Backtrace) {
   IsDeallocated = true;
-  // TODO(hctim): Implement stack trace collection. Ensure that the unwinder is
-  // not called if we have our recursive flag called, otherwise non-reentrant
-  // unwinders may deadlock.
+  // Ensure that the unwinder is not called if the recursive flag is set,
+  // otherwise non-reentrant unwinders may deadlock.
+  if (Backtrace && !ThreadLocals.RecursiveGuard) {
+    ScopedBoolean B(ThreadLocals.RecursiveGuard);
+    Backtrace(DeallocationTrace.Trace, kMaximumStackFrames);
+  } else {
+    DeallocationTrace.Trace[0] = 0;
+  }
   DeallocationTrace.ThreadID = getThreadID();
 }
 
@@ -93,6 +122,11 @@
 
   PerfectlyRightAlign = Opts.PerfectlyRightAlign;
   Printf = Opts.Printf;
+  Backtrace = Opts.Backtrace;
+  if (Opts.PrintBacktrace)
+    PrintBacktrace = Opts.PrintBacktrace;
+  else
+    PrintBacktrace = defaultPrintStackTrace;
 
   size_t PoolBytesRequired =
       PageSize * (1 + MaxSimultaneousAllocations) +
@@ -126,17 +160,6 @@
     installSignalHandlers();
 }
 
-namespace {
-class ScopedBoolean {
-public:
-  ScopedBoolean(bool &B) : Bool(B) { Bool = true; }
-  ~ScopedBoolean() { Bool = false; }
-
-private:
-  bool &Bool;
-};
-} // anonymous namespace
-
 void *GuardedPoolAllocator::allocate(size_t Size) {
   // GuardedPagePoolEnd == 0 when GWP-ASan is disabled. If we are disabled, fall
   // back to the supporting allocator.
@@ -169,7 +192,7 @@
   // unmapped.
   markReadWrite(reinterpret_cast<void *>(getPageAddr(Ptr)), Size);
 
-  Meta->RecordAllocation(Ptr, Size);
+  Meta->RecordAllocation(Ptr, Size, Backtrace);
 
   return reinterpret_cast<void *>(Ptr);
 }
@@ -196,7 +219,7 @@
     // Ensure that the deallocation is recorded before marking the page as
     // inaccessible. Otherwise, a racy use-after-free will have inconsistent
     // metadata.
-    Meta->RecordDeallocation();
+    Meta->RecordDeallocation(Backtrace);
   }
 
   markInaccessible(reinterpret_cast<void *>(SlotStart),
@@ -328,78 +351,101 @@
 
   // If we have reached here, the error is still unknown. There is no metadata
   // available.
+  *Meta = nullptr;
   return Error::UNKNOWN;
 }
 
-// Prints the provided error and metadata information. Returns true if there is
-// additional context that can be provided, false otherwise (i.e. returns false
-// if Error == {UNKNOWN, INVALID_FREE without metadata}).
-bool printErrorType(Error E, uintptr_t AccessPtr, AllocationMetadata *Meta,
-                    options::Printf_t Printf) {
+namespace {
+// Prints the provided error and metadata information.
+void printErrorType(Error E, uintptr_t AccessPtr, AllocationMetadata *Meta,
+                    options::Printf_t Printf, uint64_t ThreadID) {
+  // Print using intermediate strings. Platforms like Android don't like when
+  // you print multiple times to the same line, as there may be a newline
+  // appended to a log file automatically per Printf() call.
+  const char *ErrorString;
   switch (E) {
   case Error::UNKNOWN:
-    Printf("GWP-ASan couldn't automatically determine the source of the "
-           "memory error when accessing 0x%zx. It was likely caused by a wild "
-           "memory access into the GWP-ASan pool.\n",
-           AccessPtr);
-    return false;
+    ErrorString = "GWP-ASan couldn't automatically determine the source of "
+                  "the memory error. It was likely caused by a wild memory "
+                  "access into the GWP-ASan pool. The error occured";
+    break;
   case Error::USE_AFTER_FREE:
-    Printf("Use after free occurred when accessing memory at: 0x%zx\n",
-           AccessPtr);
+    ErrorString = "Use after free";
     break;
   case Error::DOUBLE_FREE:
-    Printf("Double free occurred when trying to free memory at: 0x%zx\n",
-           AccessPtr);
+    ErrorString = "Double free";
     break;
   case Error::INVALID_FREE:
-    Printf(
-        "Invalid (wild) free occurred when trying to free memory at: 0x%zx\n",
-        AccessPtr);
-    // It's possible for an invalid free to fall onto a slot that has never been
-    // allocated. If this is the case, there is no valid metadata.
-    if (Meta == nullptr)
-      return false;
+    ErrorString = "Invalid (wild) free";
     break;
   case Error::BUFFER_OVERFLOW:
-    Printf("Buffer overflow occurred when accessing memory at: 0x%zx\n",
-           AccessPtr);
+    ErrorString = "Buffer overflow";
     break;
   case Error::BUFFER_UNDERFLOW:
-    Printf("Buffer underflow occurred when accessing memory at: 0x%zx\n",
-           AccessPtr);
+    ErrorString = "Buffer underflow";
     break;
   }
 
-  Printf("0x%zx is ", AccessPtr);
-  if (AccessPtr < Meta->Addr)
-    Printf("located %zu bytes to the left of a %zu-byte allocation located at "
-           "0x%zx\n",
-           Meta->Addr - AccessPtr, Meta->Size, Meta->Addr);
-  else if (AccessPtr > Meta->Addr)
-    Printf("located %zu bytes to the right of a %zu-byte allocation located at "
-           "0x%zx\n",
-           AccessPtr - Meta->Addr, Meta->Size, Meta->Addr);
+  constexpr size_t kDescriptionBufferLen = 128;
+  char DescriptionBuffer[kDescriptionBufferLen];
+  if (Meta) {
+    if (E == Error::USE_AFTER_FREE) {
+      snprintf(DescriptionBuffer, kDescriptionBufferLen,
+               "(%zu byte%s into a %zu-byte allocation at 0x%zx)",
+               AccessPtr - Meta->Addr, (AccessPtr - Meta->Addr == 1) ? "" : "s",
+               Meta->Size, Meta->Addr);
+    } else if (AccessPtr < Meta->Addr) {
+      snprintf(DescriptionBuffer, kDescriptionBufferLen,
+               "(%zu byte%s to the left of a %zu-byte allocation at 0x%zx)",
+               Meta->Addr - AccessPtr, (Meta->Addr - AccessPtr == 1) ? "" : "s",
+               Meta->Size, Meta->Addr);
+    } else if (AccessPtr > Meta->Addr) {
+      snprintf(DescriptionBuffer, kDescriptionBufferLen,
+               "(%zu byte%s to the right of a %zu-byte allocation at 0x%zx)",
+               AccessPtr - Meta->Addr, (AccessPtr - Meta->Addr == 1) ? "" : "s",
+               Meta->Size, Meta->Addr);
+    } else {
+      snprintf(DescriptionBuffer, kDescriptionBufferLen,
+               "(a %zu-byte allocation)", Meta->Size);
+    }
+  }
+
+  // Possible number of digits of a 64-bit number: ceil(log10(2^64)) == 20. Add
+  // a null terminator, and round to the nearest 8-byte boundary.
+  constexpr size_t kThreadBufferLen = 24;
+  char ThreadBuffer[kThreadBufferLen];
+  if (ThreadID == GuardedPoolAllocator::kInvalidThreadID)
+    snprintf(ThreadBuffer, kThreadBufferLen, "<unknown>");
   else
-    Printf("a %zu-byte allocation\n", Meta->Size);
-  return true;
+    snprintf(ThreadBuffer, kThreadBufferLen, "%" PRIu64, ThreadID);
+
+  Printf("%s at 0x%zx %s by thread %s here:\n", ErrorString, AccessPtr,
+         DescriptionBuffer, ThreadBuffer);
 }
 
-void printThreadInformation(Error E, uintptr_t AccessPtr,
-                            AllocationMetadata *Meta,
-                            options::Printf_t Printf) {
-  Printf("0x%zx was allocated by thread ", AccessPtr);
-  if (Meta->AllocationTrace.ThreadID == UINT64_MAX)
-    Printf("UNKNOWN.\n");
-  else
-    Printf("%zu.\n", Meta->AllocationTrace.ThreadID);
+void printAllocDeallocTraces(uintptr_t AccessPtr, AllocationMetadata *Meta,
+                             options::Printf_t Printf,
+                             options::PrintBacktrace_t PrintBacktrace) {
+  assert(Meta != nullptr && "Metadata is non-null for printAllocDeallocTraces");
 
-  if (E == Error::USE_AFTER_FREE || E == Error::DOUBLE_FREE) {
-    Printf("0x%zx was freed by thread ", AccessPtr);
-    if (Meta->AllocationTrace.ThreadID == UINT64_MAX)
-      Printf("UNKNOWN.\n");
+  if (Meta->IsDeallocated) {
+    if (Meta->DeallocationTrace.ThreadID ==
+        GuardedPoolAllocator::kInvalidThreadID)
+      Printf("0x%zx was deallocated by thread <unknown> here:\n", AccessPtr);
     else
-      Printf("%zu.\n", Meta->AllocationTrace.ThreadID);
+      Printf("0x%zx was deallocated by thread %zu here:\n", AccessPtr,
+             Meta->DeallocationTrace.ThreadID);
+
+    PrintBacktrace(Meta->DeallocationTrace.Trace, Printf);
   }
+
+  if (Meta->AllocationTrace.ThreadID == GuardedPoolAllocator::kInvalidThreadID)
+    Printf("0x%zx was allocated by thread <unknown> here:\n", Meta->Addr);
+  else
+    Printf("0x%zx was allocated by thread %zu here:\n", Meta->Addr,
+           Meta->AllocationTrace.ThreadID);
+
+  PrintBacktrace(Meta->AllocationTrace.Trace, Printf);
 }
 
 struct ScopedEndOfReportDecorator {
@@ -407,6 +453,7 @@
   ~ScopedEndOfReportDecorator() { Printf("*** End GWP-ASan report ***\n"); }
   options::Printf_t Printf;
 };
+} // anonymous namespace
 
 void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr, Error E) {
   if (!pointerIsMine(reinterpret_cast<void *>(AccessPtr))) {
@@ -434,22 +481,21 @@
       Meta = nullptr;
   }
 
-  // Print the error information, and if there is no valid metadata, stop here.
-  if (!printErrorType(E, AccessPtr, Meta, Printf)) {
-    return;
+  // Print the error information.
+  uint64_t ThreadID = getThreadID();
+  printErrorType(E, AccessPtr, Meta, Printf, ThreadID);
+  if (Backtrace) {
+    static constexpr unsigned kMaximumStackFramesForCrashTrace = 128;
+    uintptr_t Trace[kMaximumStackFramesForCrashTrace];
+    Backtrace(Trace, kMaximumStackFramesForCrashTrace);
+
+    PrintBacktrace(Trace, Printf);
+  } else {
+    Printf("  <unknown (does your allocator support backtracing?)>\n\n");
   }
 
-  // Ensure that we have a valid metadata pointer from this point forward.
-  if (Meta == nullptr) {
-    Printf("GWP-ASan internal unreachable error. Metadata is not null.\n");
-    return;
-  }
-
-  printThreadInformation(E, AccessPtr, Meta, Printf);
-  // TODO(hctim): Implement stack unwinding here. Ask the caller to provide us
-  // with the base pointer, and we unwind the stack to give a stack trace for
-  // the access.
-  // TODO(hctim): Implement dumping here of allocation/deallocation traces.
+  if (Meta)
+    printAllocDeallocTraces(AccessPtr, Meta, Printf, PrintBacktrace);
 }
 
 TLS_INITIAL_EXEC
diff --git a/guarded_pool_allocator.h b/guarded_pool_allocator.h
index 9b77c00..400d50c 100644
--- a/guarded_pool_allocator.h
+++ b/guarded_pool_allocator.h
@@ -41,16 +41,14 @@
   struct AllocationMetadata {
     // Maximum number of stack trace frames to collect for allocations + frees.
     // TODO(hctim): Implement stack frame compression, a-la Chromium.
-    // Currently the maximum stack frames is one, as we don't collect traces.
-    static constexpr size_t kMaximumStackFrames = 1;
+    static constexpr size_t kMaximumStackFrames = 64;
 
-    // Records the given allocation metadata into this struct. In the future,
-    // this will collect the allocation trace as well.
-    void RecordAllocation(uintptr_t Addr, size_t Size);
+    // Records the given allocation metadata into this struct.
+    void RecordAllocation(uintptr_t Addr, size_t Size,
+                          options::Backtrace_t Backtrace);
 
-    // Record that this allocation is now deallocated. In future, this will
-    // collect the deallocation trace as well.
-    void RecordDeallocation();
+    // Record that this allocation is now deallocated.
+    void RecordDeallocation(options::Backtrace_t Backtrace);
 
     struct CallSiteInfo {
       // The backtrace to the allocation/deallocation. If the first value is
@@ -234,6 +232,8 @@
   // general) use printf() from the cstdlib as it may malloc(), causing infinite
   // recursion.
   options::Printf_t Printf = nullptr;
+  options::Backtrace_t Backtrace = nullptr;
+  options::PrintBacktrace_t PrintBacktrace = nullptr;
 
   // The adjusted sample rate for allocation sampling. Default *must* be
   // nonzero, as dynamic initialisation may call malloc (e.g. from libstdc++)
diff --git a/optional/backtrace.h b/optional/backtrace.h
new file mode 100644
index 0000000..2700970
--- /dev/null
+++ b/optional/backtrace.h
@@ -0,0 +1,23 @@
+//===-- backtrace.h ---------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef GWP_ASAN_OPTIONAL_BACKTRACE_H_
+#define GWP_ASAN_OPTIONAL_BACKTRACE_H_
+
+#include "gwp_asan/options.h"
+
+namespace gwp_asan {
+namespace options {
+// Functions to get the platform-specific and implementation-specific backtrace
+// and backtrace printing functions.
+Backtrace_t getBacktraceFunction();
+PrintBacktrace_t getPrintBacktraceFunction();
+} // namespace options
+} // namespace gwp_asan
+
+#endif // GWP_ASAN_OPTIONAL_BACKTRACE_H_
diff --git a/optional/backtrace_linux_libc.cpp b/optional/backtrace_linux_libc.cpp
new file mode 100644
index 0000000..f20a310
--- /dev/null
+++ b/optional/backtrace_linux_libc.cpp
@@ -0,0 +1,64 @@
+//===-- backtrace_linux_libc.cpp --------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include <assert.h>
+#include <execinfo.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gwp_asan/optional/backtrace.h"
+#include "gwp_asan/options.h"
+
+namespace {
+void Backtrace(uintptr_t *TraceBuffer, size_t Size) {
+  // Grab (what seems to be) one more trace than we need. TraceBuffer needs to
+  // be null-terminated, but we wish to remove the frame of this function call.
+  static_assert(sizeof(uintptr_t) == sizeof(void *), "uintptr_t is not void*");
+  int NumTraces =
+      backtrace(reinterpret_cast<void **>(TraceBuffer), Size);
+
+  // Now shift the entire trace one place to the left and null-terminate.
+  memmove(TraceBuffer, TraceBuffer + 1, NumTraces * sizeof(void *));
+  TraceBuffer[NumTraces - 1] = 0;
+}
+
+static void PrintBacktrace(uintptr_t *Trace,
+                           gwp_asan::options::Printf_t Printf) {
+  size_t NumTraces = 0;
+  for (; Trace[NumTraces] != 0; ++NumTraces) {
+  }
+
+  if (NumTraces == 0) {
+    Printf("  <not found (does your allocator support backtracing?)>\n\n");
+    return;
+  }
+
+  char **BacktraceSymbols =
+      backtrace_symbols(reinterpret_cast<void **>(Trace), NumTraces);
+
+  for (size_t i = 0; i < NumTraces; ++i) {
+    if (!BacktraceSymbols)
+      Printf("  #%zu %p\n", i, Trace[i]);
+    else
+      Printf("  #%zu %s\n", i, BacktraceSymbols[i]);
+  }
+
+  Printf("\n");
+  if (BacktraceSymbols)
+    free(BacktraceSymbols);
+}
+} // anonymous namespace
+
+namespace gwp_asan {
+namespace options {
+Backtrace_t getBacktraceFunction() { return Backtrace; }
+PrintBacktrace_t getPrintBacktraceFunction() { return PrintBacktrace; }
+} // namespace options
+} // namespace gwp_asan
diff --git a/optional/backtrace_sanitizer_common.cpp b/optional/backtrace_sanitizer_common.cpp
new file mode 100644
index 0000000..7d17eec
--- /dev/null
+++ b/optional/backtrace_sanitizer_common.cpp
@@ -0,0 +1,69 @@
+//===-- backtrace_sanitizer_common.cpp --------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "gwp_asan/optional/backtrace.h"
+#include "gwp_asan/options.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+
+void __sanitizer::BufferedStackTrace::UnwindImpl(uptr pc, uptr bp,
+                                                 void *context,
+                                                 bool request_fast,
+                                                 u32 max_depth) {
+  if (!StackTrace::WillUseFastUnwind(request_fast)) {
+    return Unwind(max_depth, pc, bp, context, 0, 0, request_fast);
+  }
+  Unwind(max_depth, pc, 0, context, 0, 0, false);
+}
+
+namespace {
+void Backtrace(uintptr_t *TraceBuffer, size_t Size) {
+  __sanitizer::BufferedStackTrace Trace;
+  Trace.Reset();
+  if (Size > __sanitizer::kStackTraceMax)
+    Size = __sanitizer::kStackTraceMax;
+
+  Trace.Unwind((__sanitizer::uptr)__builtin_return_address(0),
+               (__sanitizer::uptr)__builtin_frame_address(0),
+               /* ucontext */ nullptr,
+               /* fast unwind */ true, Size - 1);
+
+  memcpy(TraceBuffer, Trace.trace, Trace.size * sizeof(uintptr_t));
+  TraceBuffer[Trace.size] = 0;
+}
+
+static void PrintBacktrace(uintptr_t *Trace,
+                           gwp_asan::options::Printf_t Printf) {
+  __sanitizer::StackTrace StackTrace;
+  StackTrace.trace = reinterpret_cast<__sanitizer::uptr *>(Trace);
+
+  for (StackTrace.size = 0; StackTrace.size < __sanitizer::kStackTraceMax;
+       ++StackTrace.size) {
+    if (Trace[StackTrace.size] == 0)
+      break;
+  }
+
+  if (StackTrace.size == 0) {
+    Printf("  <unknown (does your allocator support backtracing?)>\n\n");
+    return;
+  }
+
+  StackTrace.Print();
+}
+} // anonymous namespace
+
+namespace gwp_asan {
+namespace options {
+Backtrace_t getBacktraceFunction() { return Backtrace; }
+PrintBacktrace_t getPrintBacktraceFunction() { return PrintBacktrace; }
+} // namespace options
+} // namespace gwp_asan
diff --git a/optional/options_parser.cpp b/optional/options_parser.cpp
index ba9af49..6c21672 100644
--- a/optional/options_parser.cpp
+++ b/optional/options_parser.cpp
@@ -47,6 +47,8 @@
 } // anonymous namespace
 
 void initOptions() {
+  __sanitizer::SetCommonFlagsDefaults();
+
   Options *o = getOptionsInternal();
   o->setDefaults();
 
@@ -85,7 +87,7 @@
   o->Printf = __sanitizer::Printf;
 }
 
-const Options &getOptions() { return *getOptionsInternal(); }
+Options &getOptions() { return *getOptionsInternal(); }
 
 } // namespace options
 } // namespace gwp_asan
diff --git a/optional/options_parser.h b/optional/options_parser.h
index 7a1d3b0..7a6bfaf 100644
--- a/optional/options_parser.h
+++ b/optional/options_parser.h
@@ -9,18 +9,17 @@
 #ifndef GWP_ASAN_OPTIONAL_OPTIONS_PARSER_H_
 #define GWP_ASAN_OPTIONAL_OPTIONS_PARSER_H_
 
+#include "gwp_asan/optional/backtrace.h"
 #include "gwp_asan/options.h"
 #include "sanitizer_common/sanitizer_common.h"
 
 namespace gwp_asan {
 namespace options {
-
 // Parse the options from the GWP_ASAN_FLAGS environment variable.
 void initOptions();
-// Returns a pointer to the initialised options. Call initOptions() prior to
-// calling this function.
-const Options &getOptions();
-
+// Returns the initialised options. Call initOptions() prior to calling this
+// function.
+Options &getOptions();
 } // namespace options
 } // namespace gwp_asan
 
diff --git a/options.h b/options.h
index c1b6e67..6423e16 100644
--- a/options.h
+++ b/options.h
@@ -9,6 +9,9 @@
 #ifndef GWP_ASAN_OPTIONS_H_
 #define GWP_ASAN_OPTIONS_H_
 
+#include <stddef.h>
+#include <stdint.h>
+
 namespace gwp_asan {
 namespace options {
 // The function pointer type for printf(). Follows the standard format from the
@@ -17,8 +20,21 @@
 // printf() signature, and pass the wrapper instead.
 typedef void (*Printf_t)(const char *Format, ...);
 
+// The function pointer type for backtrace information. Required to be
+// implemented by the supporting allocator. The callee should elide itself and
+// all frames below itself from TraceBuffer, i.e. the caller's frame should be
+// in TraceBuffer[0], and subsequent frames 1..n into TraceBuffer[1..n], where a
+// maximum of `MaximumDepth - 1` frames are stored. TraceBuffer should be
+// nullptr-terminated (i.e. if there are 5 frames; TraceBuffer[5] == nullptr).
+// If the allocator cannot supply backtrace information, it should set
+// TraceBuffer[0] == nullptr.
+typedef void (*Backtrace_t)(uintptr_t *TraceBuffer, size_t Size);
+typedef void (*PrintBacktrace_t)(uintptr_t *TraceBuffer, Printf_t Print);
+
 struct Options {
   Printf_t Printf = nullptr;
+  Backtrace_t Backtrace = nullptr;
+  PrintBacktrace_t PrintBacktrace = nullptr;
 
   // Read the options from the included definitions file.
 #define GWP_ASAN_OPTION(Type, Name, DefaultValue, Description)                 \
@@ -33,6 +49,8 @@
 #undef GWP_ASAN_OPTION
 
     Printf = nullptr;
+    Backtrace = nullptr;
+    PrintBacktrace = nullptr;
   }
 };
 } // namespace options
diff --git a/tests/backtrace.cpp b/tests/backtrace.cpp
new file mode 100644
index 0000000..9ed16a5
--- /dev/null
+++ b/tests/backtrace.cpp
@@ -0,0 +1,41 @@
+//===-- backtrace.cc --------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include <string>
+
+#include "gwp_asan/tests/harness.h"
+
+TEST_F(BacktraceGuardedPoolAllocator, DoubleFree) {
+  void *Ptr = GPA.allocate(1);
+  GPA.deallocate(Ptr);
+
+  std::string DeathRegex = "Double free.*";
+  DeathRegex.append("backtrace\\.cpp:25.*");
+
+  DeathRegex.append("was deallocated.*");
+  DeathRegex.append("backtrace\\.cpp:15.*");
+
+  DeathRegex.append("was allocated.*");
+  DeathRegex.append("backtrace\\.cpp:14.*");
+  ASSERT_DEATH(GPA.deallocate(Ptr), DeathRegex);
+}
+
+TEST_F(BacktraceGuardedPoolAllocator, UseAfterFree) {
+  char *Ptr = static_cast<char *>(GPA.allocate(1));
+  GPA.deallocate(Ptr);
+
+  std::string DeathRegex = "Use after free.*";
+  DeathRegex.append("backtrace\\.cpp:40.*");
+
+  DeathRegex.append("was deallocated.*");
+  DeathRegex.append("backtrace\\.cpp:30.*");
+
+  DeathRegex.append("was allocated.*");
+  DeathRegex.append("backtrace\\.cpp:29.*");
+  ASSERT_DEATH({ *Ptr = 7; }, DeathRegex);
+}
diff --git a/tests/harness.h b/tests/harness.h
index 987564d..136e566 100644
--- a/tests/harness.h
+++ b/tests/harness.h
@@ -17,6 +17,8 @@
 #include "sanitizer_common/sanitizer_common.h"
 
 #include "gwp_asan/guarded_pool_allocator.h"
+#include "gwp_asan/optional/backtrace.h"
+#include "gwp_asan/optional/options_parser.h"
 #include "gwp_asan/options.h"
 
 class DefaultGuardedPoolAllocator : public ::testing::Test {
@@ -57,4 +59,25 @@
       MaxSimultaneousAllocations;
 };
 
+class BacktraceGuardedPoolAllocator : public ::testing::Test {
+public:
+  BacktraceGuardedPoolAllocator() {
+    // Call initOptions to initialise the internal sanitizer_common flags. These
+    // flags are referenced by the sanitizer_common unwinder, and if left
+    // uninitialised, they'll unintentionally crash the program.
+    gwp_asan::options::initOptions();
+
+    gwp_asan::options::Options Opts;
+    Opts.setDefaults();
+
+    Opts.Printf = __sanitizer::Printf;
+    Opts.Backtrace = gwp_asan::options::getBacktraceFunction();
+    Opts.PrintBacktrace = gwp_asan::options::getPrintBacktraceFunction();
+    GPA.init(Opts);
+  }
+
+protected:
+  gwp_asan::GuardedPoolAllocator GPA;
+};
+
 #endif // GWP_ASAN_TESTS_HARNESS_H_