Update PDFium PartitionAlloc hooking
Roll in [1-3] from upstream, which update PartitionAlloc's hooking
support to allow "override" hooks (GWP-ASan hooks that override
alloc/free) as well as "observer" hooks (pre-existing profiler hooks).
[1] https://crrev.com/c/1585325
[2] https://crrev.com/c/1588946
[3] https://crrev.com/c/1583179
Bug: 956824
Change-Id: Ibe5aa244293d64a39d5a9b82a7299cad32bc022e
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/55132
Reviewed-by: Chris Palmer <palmer@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
diff --git a/third_party/base/allocator/partition_allocator/partition_alloc.cc b/third_party/base/allocator/partition_allocator/partition_alloc.cc
index df5d3f1..e0bae85 100644
--- a/third_party/base/allocator/partition_allocator/partition_alloc.cc
+++ b/third_party/base/allocator/partition_allocator/partition_alloc.cc
@@ -66,9 +66,108 @@
static bool g_initialized = false;
void (*internal::PartitionRootBase::gOomHandlingFunction)() = nullptr;
-PartitionAllocHooks::AllocationHook* PartitionAllocHooks::allocation_hook_ =
- nullptr;
-PartitionAllocHooks::FreeHook* PartitionAllocHooks::free_hook_ = nullptr;
+std::atomic<bool> PartitionAllocHooks::hooks_enabled_(false);
+subtle::SpinLock PartitionAllocHooks::set_hooks_lock_;
+std::atomic<PartitionAllocHooks::AllocationObserverHook*>
+ PartitionAllocHooks::allocation_observer_hook_(nullptr);
+std::atomic<PartitionAllocHooks::FreeObserverHook*>
+ PartitionAllocHooks::free_observer_hook_(nullptr);
+std::atomic<PartitionAllocHooks::AllocationOverrideHook*>
+ PartitionAllocHooks::allocation_override_hook_(nullptr);
+std::atomic<PartitionAllocHooks::FreeOverrideHook*>
+ PartitionAllocHooks::free_override_hook_(nullptr);
+std::atomic<PartitionAllocHooks::ReallocOverrideHook*>
+ PartitionAllocHooks::realloc_override_hook_(nullptr);
+
+void PartitionAllocHooks::SetObserverHooks(AllocationObserverHook* alloc_hook,
+ FreeObserverHook* free_hook) {
+ subtle::SpinLock::Guard guard(set_hooks_lock_);
+
+ // Chained hooks are not supported. Registering a non-null hook when a
+ // non-null hook is already registered indicates somebody is trying to
+ // overwrite a hook.
+ CHECK((!allocation_observer_hook_ && !free_observer_hook_) ||
+ (!alloc_hook && !free_hook));
+ allocation_observer_hook_ = alloc_hook;
+ free_observer_hook_ = free_hook;
+
+ hooks_enabled_ = allocation_observer_hook_ || allocation_override_hook_;
+}
+
+void PartitionAllocHooks::SetOverrideHooks(AllocationOverrideHook* alloc_hook,
+ FreeOverrideHook* free_hook,
+ ReallocOverrideHook realloc_hook) {
+ subtle::SpinLock::Guard guard(set_hooks_lock_);
+
+ CHECK((!allocation_override_hook_ && !free_override_hook_ &&
+ !realloc_override_hook_) ||
+ (!alloc_hook && !free_hook && !realloc_hook));
+ allocation_override_hook_ = alloc_hook;
+ free_override_hook_ = free_hook;
+ realloc_override_hook_ = realloc_hook;
+
+ hooks_enabled_ = allocation_observer_hook_ || allocation_override_hook_;
+}
+
+void PartitionAllocHooks::AllocationObserverHookIfEnabled(
+ void* address,
+ size_t size,
+ const char* type_name) {
+ if (AllocationObserverHook* hook =
+ allocation_observer_hook_.load(std::memory_order_relaxed)) {
+ hook(address, size, type_name);
+ }
+}
+
+bool PartitionAllocHooks::AllocationOverrideHookIfEnabled(
+ void** out,
+ int flags,
+ size_t size,
+ const char* type_name) {
+ if (AllocationOverrideHook* hook =
+ allocation_override_hook_.load(std::memory_order_relaxed)) {
+ return hook(out, flags, size, type_name);
+ }
+ return false;
+}
+
+void PartitionAllocHooks::FreeObserverHookIfEnabled(void* address) {
+ if (FreeObserverHook* hook =
+ free_observer_hook_.load(std::memory_order_relaxed)) {
+ hook(address);
+ }
+}
+
+bool PartitionAllocHooks::FreeOverrideHookIfEnabled(void* address) {
+ if (FreeOverrideHook* hook =
+ free_override_hook_.load(std::memory_order_relaxed)) {
+ return hook(address);
+ }
+ return false;
+}
+
+void PartitionAllocHooks::ReallocObserverHookIfEnabled(void* old_address,
+ void* new_address,
+ size_t size,
+ const char* type_name) {
+ // Report a reallocation as a free followed by an allocation.
+ AllocationObserverHook* allocation_hook =
+ allocation_observer_hook_.load(std::memory_order_relaxed);
+ FreeObserverHook* free_hook =
+ free_observer_hook_.load(std::memory_order_relaxed);
+ if (allocation_hook && free_hook) {
+ free_hook(old_address);
+ allocation_hook(new_address, size, type_name);
+ }
+}
+bool PartitionAllocHooks::ReallocOverrideHookIfEnabled(size_t* out,
+ void* address) {
+ if (ReallocOverrideHook* hook =
+ realloc_override_hook_.load(std::memory_order_relaxed)) {
+ return hook(out, address);
+ }
+ return false;
+}
static void PartitionAllocBaseInit(internal::PartitionRootBase* root) {
DCHECK(!root->initialized);
@@ -282,41 +381,53 @@
internal::PartitionExcessiveAllocationSize();
}
- internal::PartitionPage* page = internal::PartitionPage::FromPointer(
- internal::PartitionCookieFreePointerAdjust(ptr));
- // TODO(palmer): See if we can afford to make this a CHECK.
- DCHECK(root->IsValidPage(page));
+ const bool hooks_enabled = PartitionAllocHooks::AreHooksEnabled();
+ bool overridden = false;
+ size_t actual_old_size;
+ if (UNLIKELY(hooks_enabled)) {
+ overridden = PartitionAllocHooks::ReallocOverrideHookIfEnabled(
+ &actual_old_size, ptr);
+ }
+ if (LIKELY(!overridden)) {
+ internal::PartitionPage* page = internal::PartitionPage::FromPointer(
+ internal::PartitionCookieFreePointerAdjust(ptr));
+ // TODO(palmer): See if we can afford to make this a CHECK.
+ DCHECK(root->IsValidPage(page));
- if (UNLIKELY(page->bucket->is_direct_mapped())) {
- // We may be able to perform the realloc in place by changing the
- // accessibility of memory pages and, if reducing the size, decommitting
- // them.
- if (PartitionReallocDirectMappedInPlace(root, page, new_size)) {
- PartitionAllocHooks::ReallocHookIfEnabled(ptr, ptr, new_size, type_name);
+ if (UNLIKELY(page->bucket->is_direct_mapped())) {
+ // We may be able to perform the realloc in place by changing the
+ // accessibility of memory pages and, if reducing the size, decommitting
+ // them.
+ if (PartitionReallocDirectMappedInPlace(root, page, new_size)) {
+ if (UNLIKELY(hooks_enabled)) {
+ PartitionAllocHooks::ReallocObserverHookIfEnabled(ptr, ptr, new_size,
+ type_name);
+ }
+ return ptr;
+ }
+ }
+
+ const size_t actual_new_size = root->ActualSize(new_size);
+ actual_old_size = PartitionAllocGetSize(ptr);
+
+ // TODO: note that tcmalloc will "ignore" a downsizing realloc() unless the
+ // new size is a significant percentage smaller. We could do the same if we
+ // determine it is a win.
+ if (actual_new_size == actual_old_size) {
+ // Trying to allocate a block of size |new_size| would give us a block of
+ // the same size as the one we've already got, so re-use the allocation
+ // after updating statistics (and cookies, if present).
+ page->set_raw_size(internal::PartitionCookieSizeAdjustAdd(new_size));
+#if DCHECK_IS_ON()
+ // Write a new trailing cookie when it is possible to keep track of
+ // |new_size| via the raw size pointer.
+ if (page->get_raw_size_ptr())
+ internal::PartitionCookieWriteValue(static_cast<char*>(ptr) + new_size);
+#endif
return ptr;
}
}
- size_t actual_new_size = root->ActualSize(new_size);
- size_t actual_old_size = PartitionAllocGetSize(ptr);
-
- // TODO: note that tcmalloc will "ignore" a downsizing realloc() unless the
- // new size is a significant percentage smaller. We could do the same if we
- // determine it is a win.
- if (actual_new_size == actual_old_size) {
- // Trying to allocate a block of size |new_size| would give us a block of
- // the same size as the one we've already got, so re-use the allocation
- // after updating statistics (and cookies, if present).
- page->set_raw_size(internal::PartitionCookieSizeAdjustAdd(new_size));
-#if DCHECK_IS_ON()
- // Write a new trailing cookie when it is possible to keep track of
- // |new_size| via the raw size pointer.
- if (page->get_raw_size_ptr())
- internal::PartitionCookieWriteValue(static_cast<char*>(ptr) + new_size);
-#endif
- return ptr;
- }
-
// This realloc cannot be resized in-place. Sadness.
void* ret = PartitionAllocGenericFlags(root, flags, new_size, type_name);
if (!ret) {
diff --git a/third_party/base/allocator/partition_allocator/partition_alloc.h b/third_party/base/allocator/partition_allocator/partition_alloc.h
index c3491a4..88f37bc 100644
--- a/third_party/base/allocator/partition_allocator/partition_alloc.h
+++ b/third_party/base/allocator/partition_allocator/partition_alloc.h
@@ -212,56 +212,74 @@
BASE_EXPORT void PartitionAllocGlobalInit(void (*oom_handling_function)());
+// PartitionAlloc supports setting hooks to observe allocations/frees as they
+// occur as well as 'override' hooks that allow overriding those operations.
class BASE_EXPORT PartitionAllocHooks {
public:
- typedef void AllocationHook(void* address, size_t, const char* type_name);
- typedef void FreeHook(void* address);
-
- // To unhook, call Set*Hook with nullptr.
- static void SetAllocationHook(AllocationHook* hook) {
- // Chained allocation hooks are not supported. Registering a non-null
- // hook when a non-null hook is already registered indicates somebody is
- // trying to overwrite a hook.
- CHECK(!hook || !allocation_hook_);
- allocation_hook_ = hook;
- }
- static void SetFreeHook(FreeHook* hook) {
- CHECK(!hook || !free_hook_);
- free_hook_ = hook;
- }
-
- static void AllocationHookIfEnabled(void* address,
+ // Log allocation and free events.
+ typedef void AllocationObserverHook(void* address,
size_t size,
- const char* type_name) {
- AllocationHook* hook = allocation_hook_;
- if (UNLIKELY(hook != nullptr))
- hook(address, size, type_name);
+ const char* type_name);
+ typedef void FreeObserverHook(void* address);
+
+ // If it returns true, the allocation has been overridden with the pointer in
+ // *out.
+ typedef bool AllocationOverrideHook(void** out,
+ int flags,
+ size_t size,
+ const char* type_name);
+ // If it returns true, then the allocation was overridden and has been freed.
+ typedef bool FreeOverrideHook(void* address);
+ // If it returns true, the underlying allocation is overridden and *out holds
+ // the size of the underlying allocation.
+ typedef bool ReallocOverrideHook(size_t* out, void* address);
+
+ // To unhook, call Set*Hooks with nullptrs.
+ static void SetObserverHooks(AllocationObserverHook* alloc_hook,
+ FreeObserverHook* free_hook);
+ static void SetOverrideHooks(AllocationOverrideHook* alloc_hook,
+ FreeOverrideHook* free_hook,
+ ReallocOverrideHook realloc_hook);
+
+ // Helper method to check whether hooks are enabled. This is an optimization
+ // so that if a function needs to call observer and override hooks in two
+ // different places this value can be cached and only loaded once.
+ static bool AreHooksEnabled() {
+ return hooks_enabled_.load(std::memory_order_relaxed);
}
- static void FreeHookIfEnabled(void* address) {
- FreeHook* hook = free_hook_;
- if (UNLIKELY(hook != nullptr))
- hook(address);
- }
+ static void AllocationObserverHookIfEnabled(void* address,
+ size_t size,
+ const char* type_name);
+ static bool AllocationOverrideHookIfEnabled(void** out,
+ int flags,
+ size_t size,
+ const char* type_name);
- static void ReallocHookIfEnabled(void* old_address,
- void* new_address,
- size_t size,
- const char* type_name) {
- // Report a reallocation as a free followed by an allocation.
- AllocationHook* allocation_hook = allocation_hook_;
- FreeHook* free_hook = free_hook_;
- if (UNLIKELY(allocation_hook && free_hook)) {
- free_hook(old_address);
- allocation_hook(new_address, size, type_name);
- }
- }
+ static void FreeObserverHookIfEnabled(void* address);
+ static bool FreeOverrideHookIfEnabled(void* address);
+
+ static void ReallocObserverHookIfEnabled(void* old_address,
+ void* new_address,
+ size_t size,
+ const char* type_name);
+ static bool ReallocOverrideHookIfEnabled(size_t* out, void* address);
private:
- // Pointers to hook functions that PartitionAlloc will call on allocation and
- // free if the pointers are non-null.
- static AllocationHook* allocation_hook_;
- static FreeHook* free_hook_;
+ // Single bool that is used to indicate whether observer or allocation hooks
+ // are set to reduce the numbers of loads required to check whether hooking is
+ // enabled.
+ static std::atomic<bool> hooks_enabled_;
+
+ // Lock used to synchronize Set*Hooks calls.
+ static subtle::SpinLock set_hooks_lock_;
+
+ static std::atomic<AllocationObserverHook*> allocation_observer_hook_;
+ static std::atomic<FreeObserverHook*> free_observer_hook_;
+
+ static std::atomic<AllocationOverrideHook*> allocation_override_hook_;
+ static std::atomic<FreeOverrideHook*> free_override_hook_;
+ static std::atomic<ReallocOverrideHook*> realloc_override_hook_;
};
ALWAYS_INLINE void* PartitionRoot::Alloc(size_t size, const char* type_name) {
@@ -280,6 +298,16 @@
CHECK(result);
return result;
#else
+ void* result;
+ const bool hooks_enabled = PartitionAllocHooks::AreHooksEnabled();
+ if (UNLIKELY(hooks_enabled)) {
+ if (PartitionAllocHooks::AllocationOverrideHookIfEnabled(&result, flags,
+ size, type_name)) {
+ PartitionAllocHooks::AllocationObserverHookIfEnabled(result, size,
+ type_name);
+ return result;
+ }
+ }
size_t requested_size = size;
size = internal::PartitionCookieSizeAdjustAdd(size);
DCHECK(this->initialized);
@@ -287,9 +315,11 @@
DCHECK(index < this->num_buckets);
DCHECK(size == index << kBucketShift);
internal::PartitionBucket* bucket = &this->buckets()[index];
- void* result = AllocFromBucket(bucket, flags, size);
- PartitionAllocHooks::AllocationHookIfEnabled(result, requested_size,
- type_name);
+ result = AllocFromBucket(bucket, flags, size);
+ if (UNLIKELY(hooks_enabled)) {
+ PartitionAllocHooks::AllocationObserverHookIfEnabled(result, requested_size,
+ type_name);
+ }
return result;
#endif // defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
}
@@ -318,10 +348,14 @@
#if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
free(ptr);
#else
- void* original_ptr = ptr;
// TODO(palmer): Check ptr alignment before continuing. Shall we do the check
// inside PartitionCookieFreePointerAdjust?
- PartitionAllocHooks::FreeHookIfEnabled(original_ptr);
+ if (PartitionAllocHooks::AreHooksEnabled()) {
+ PartitionAllocHooks::FreeObserverHookIfEnabled(ptr);
+ if (PartitionAllocHooks::FreeOverrideHookIfEnabled(ptr))
+ return;
+ }
+
ptr = internal::PartitionCookieFreePointerAdjust(ptr);
internal::PartitionPage* page = internal::PartitionPage::FromPointer(ptr);
// TODO(palmer): See if we can afford to make this a CHECK.
@@ -365,17 +399,29 @@
return result;
#else
DCHECK(root->initialized);
+ void* result;
+ const bool hooks_enabled = PartitionAllocHooks::AreHooksEnabled();
+ if (UNLIKELY(hooks_enabled)) {
+ if (PartitionAllocHooks::AllocationOverrideHookIfEnabled(&result, flags,
+ size, type_name)) {
+ PartitionAllocHooks::AllocationObserverHookIfEnabled(result, size,
+ type_name);
+ return result;
+ }
+ }
size_t requested_size = size;
size = internal::PartitionCookieSizeAdjustAdd(size);
internal::PartitionBucket* bucket = PartitionGenericSizeToBucket(root, size);
- void* ret = nullptr;
{
subtle::SpinLock::Guard guard(root->lock);
- ret = root->AllocFromBucket(bucket, flags, size);
+ result = root->AllocFromBucket(bucket, flags, size);
}
- PartitionAllocHooks::AllocationHookIfEnabled(ret, requested_size, type_name);
+ if (UNLIKELY(hooks_enabled)) {
+ PartitionAllocHooks::AllocationObserverHookIfEnabled(result, requested_size,
+ type_name);
+ }
- return ret;
+ return result;
#endif
}
@@ -399,7 +445,12 @@
if (UNLIKELY(!ptr))
return;
- PartitionAllocHooks::FreeHookIfEnabled(ptr);
+ if (PartitionAllocHooks::AreHooksEnabled()) {
+ PartitionAllocHooks::FreeObserverHookIfEnabled(ptr);
+ if (PartitionAllocHooks::FreeOverrideHookIfEnabled(ptr))
+ return;
+ }
+
ptr = internal::PartitionCookieFreePointerAdjust(ptr);
internal::PartitionPage* page = internal::PartitionPage::FromPointer(ptr);
// TODO(palmer): See if we can afford to make this a CHECK.