[GWP-ASan] Add generic unwinders and structure backtrace output. am: 50c1d8ed3a am: ce3972a006 am: 2dcc2bea53
am: 7d184206f5
Change-Id: Ic4e93b140c57b9eaf28cb0e54f57b8dd51ca4359
diff --git a/gwp_asan/guarded_pool_allocator.cpp b/gwp_asan/guarded_pool_allocator.cpp
index 68ec00d..3180684 100644
--- a/gwp_asan/guarded_pool_allocator.cpp
+++ b/gwp_asan/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/gwp_asan/guarded_pool_allocator.h b/gwp_asan/guarded_pool_allocator.h
index 9b77c00..400d50c 100644
--- a/gwp_asan/guarded_pool_allocator.h
+++ b/gwp_asan/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/gwp_asan/optional/backtrace.h b/gwp_asan/optional/backtrace.h
new file mode 100644
index 0000000..2700970
--- /dev/null
+++ b/gwp_asan/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/gwp_asan/optional/backtrace_linux_libc.cpp b/gwp_asan/optional/backtrace_linux_libc.cpp
new file mode 100644
index 0000000..f20a310
--- /dev/null
+++ b/gwp_asan/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/gwp_asan/optional/backtrace_sanitizer_common.cpp b/gwp_asan/optional/backtrace_sanitizer_common.cpp
new file mode 100644
index 0000000..7d17eec
--- /dev/null
+++ b/gwp_asan/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/gwp_asan/optional/options_parser.cpp b/gwp_asan/optional/options_parser.cpp
index ba9af49..6c21672 100644
--- a/gwp_asan/optional/options_parser.cpp
+++ b/gwp_asan/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/gwp_asan/optional/options_parser.h b/gwp_asan/optional/options_parser.h
index 7a1d3b0..7a6bfaf 100644
--- a/gwp_asan/optional/options_parser.h
+++ b/gwp_asan/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/gwp_asan/options.h b/gwp_asan/options.h
index c1b6e67..6423e16 100644
--- a/gwp_asan/options.h
+++ b/gwp_asan/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/gwp_asan/tests/backtrace.cpp b/gwp_asan/tests/backtrace.cpp
new file mode 100644
index 0000000..9ed16a5
--- /dev/null
+++ b/gwp_asan/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/gwp_asan/tests/harness.h b/gwp_asan/tests/harness.h
index 987564d..136e566 100644
--- a/gwp_asan/tests/harness.h
+++ b/gwp_asan/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_