[GWP-ASan] Guard against recursive allocs. Pack TLS for perf. am: 000701f219 am: 29b34e25e9 am: 26bd8a98ef
am: 76562aca7d
Change-Id: Ied322aefd48f046ee325facdeeb43ff8e23c5f42
diff --git a/gwp_asan/guarded_pool_allocator.cpp b/gwp_asan/guarded_pool_allocator.cpp
index 93743cd..68ec00d 100644
--- a/gwp_asan/guarded_pool_allocator.cpp
+++ b/gwp_asan/guarded_pool_allocator.cpp
@@ -49,7 +49,9 @@
void GuardedPoolAllocator::AllocationMetadata::RecordDeallocation() {
IsDeallocated = true;
- // TODO(hctim): Implement stack trace collection.
+ // 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.
DeallocationTrace.ThreadID = getThreadID();
}
@@ -124,7 +126,28 @@
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.
+ if (GuardedPagePoolEnd == 0)
+ return nullptr;
+
+ // Protect against recursivity.
+ if (ThreadLocals.RecursiveGuard)
+ return nullptr;
+ ScopedBoolean SB(ThreadLocals.RecursiveGuard);
+
if (Size == 0 || Size > maximumAllocationSize())
return nullptr;
@@ -385,8 +408,7 @@
options::Printf_t Printf;
};
-void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr,
- Error E) {
+void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr, Error E) {
if (!pointerIsMine(reinterpret_cast<void *>(AccessPtr))) {
return;
}
@@ -395,6 +417,7 @@
// This does not guarantee that there are no races, because another thread can
// take the locks during the time that the signal handler is being called.
PoolMutex.tryLock();
+ ThreadLocals.RecursiveGuard = true;
Printf("*** GWP-ASan detected a memory error ***\n");
ScopedEndOfReportDecorator Decorator(Printf);
@@ -429,5 +452,7 @@
// TODO(hctim): Implement dumping here of allocation/deallocation traces.
}
-TLS_INITIAL_EXEC uint64_t GuardedPoolAllocator::NextSampleCounter = 0;
+TLS_INITIAL_EXEC
+GuardedPoolAllocator::ThreadLocalPackedVariables
+ GuardedPoolAllocator::ThreadLocals;
} // namespace gwp_asan
diff --git a/gwp_asan/guarded_pool_allocator.h b/gwp_asan/guarded_pool_allocator.h
index 50d9baf..9b77c00 100644
--- a/gwp_asan/guarded_pool_allocator.h
+++ b/gwp_asan/guarded_pool_allocator.h
@@ -96,14 +96,11 @@
ALWAYS_INLINE bool shouldSample() {
// NextSampleCounter == 0 means we "should regenerate the counter".
// == 1 means we "should sample this allocation".
- if (UNLIKELY(NextSampleCounter == 0)) {
- // GuardedPagePoolEnd == 0 if GWP-ASan is disabled.
- if (UNLIKELY(GuardedPagePoolEnd == 0))
- return false;
- NextSampleCounter = (getRandomUnsigned32() % AdjustedSampleRate) + 1;
- }
+ if (UNLIKELY(ThreadLocals.NextSampleCounter == 0))
+ ThreadLocals.NextSampleCounter =
+ (getRandomUnsigned32() % AdjustedSampleRate) + 1;
- return UNLIKELY(--NextSampleCounter == 0);
+ return UNLIKELY(--ThreadLocals.NextSampleCounter == 0);
}
// Returns whether the provided pointer is a current sampled allocation that
@@ -245,9 +242,23 @@
// GWP-ASan is disabled, we wish to never spend wasted cycles recalculating
// the sample rate.
uint32_t AdjustedSampleRate = UINT32_MAX;
- // Thread-local decrementing counter that indicates that a given allocation
- // should be sampled when it reaches zero.
- static TLS_INITIAL_EXEC uint64_t NextSampleCounter;
+
+ // Pack the thread local variables into a struct to ensure that they're in
+ // the same cache line for performance reasons. These are the most touched
+ // variables in GWP-ASan.
+ struct alignas(8) ThreadLocalPackedVariables {
+ constexpr ThreadLocalPackedVariables() {}
+ // Thread-local decrementing counter that indicates that a given allocation
+ // should be sampled when it reaches zero.
+ uint32_t NextSampleCounter = 0;
+ // Guard against recursivity. Unwinders often contain complex behaviour that
+ // may not be safe for the allocator (i.e. the unwinder calls dlopen(),
+ // which calls malloc()). When recursive behaviour is detected, we will
+ // automatically fall back to the supporting allocator to supply the
+ // allocation.
+ bool RecursiveGuard = false;
+ };
+ static TLS_INITIAL_EXEC ThreadLocalPackedVariables ThreadLocals;
};
} // namespace gwp_asan