|  | // Copyright (c) 2022 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/allocator/dispatcher/dispatcher.h" | 
|  |  | 
|  | #include "base/allocator/allocator_shim.h" | 
|  | #include "base/allocator/buildflags.h" | 
|  | #include "base/allocator/dispatcher/reentry_guard.h" | 
|  | #include "base/allocator/partition_allocator/partition_alloc.h" | 
|  | #include "base/sampling_heap_profiler/poisson_allocation_sampler.h" | 
|  |  | 
|  | #if BUILDFLAG(USE_ALLOCATOR_SHIM) | 
|  | namespace base::allocator::dispatcher::allocator_shim_details { | 
|  | namespace { | 
|  |  | 
|  | using allocator::AllocatorDispatch; | 
|  |  | 
|  | void* AllocFn(const AllocatorDispatch* self, size_t size, void* context) { | 
|  | ReentryGuard guard; | 
|  | void* address = self->next->alloc_function(self->next, size, context); | 
|  | if (LIKELY(guard)) { | 
|  | PoissonAllocationSampler::RecordAlloc( | 
|  | address, size, PoissonAllocationSampler::kMalloc, nullptr); | 
|  | } | 
|  | return address; | 
|  | } | 
|  |  | 
|  | void* AllocUncheckedFn(const AllocatorDispatch* self, | 
|  | size_t size, | 
|  | void* context) { | 
|  | ReentryGuard guard; | 
|  | void* address = | 
|  | self->next->alloc_unchecked_function(self->next, size, context); | 
|  | if (LIKELY(guard)) { | 
|  | PoissonAllocationSampler::RecordAlloc( | 
|  | address, size, PoissonAllocationSampler::kMalloc, nullptr); | 
|  | } | 
|  | return address; | 
|  | } | 
|  |  | 
|  | void* AllocZeroInitializedFn(const AllocatorDispatch* self, | 
|  | size_t n, | 
|  | size_t size, | 
|  | void* context) { | 
|  | ReentryGuard guard; | 
|  | void* address = | 
|  | self->next->alloc_zero_initialized_function(self->next, n, size, context); | 
|  | if (LIKELY(guard)) { | 
|  | PoissonAllocationSampler::RecordAlloc( | 
|  | address, n * size, PoissonAllocationSampler::kMalloc, nullptr); | 
|  | } | 
|  | return address; | 
|  | } | 
|  |  | 
|  | void* AllocAlignedFn(const AllocatorDispatch* self, | 
|  | size_t alignment, | 
|  | size_t size, | 
|  | void* context) { | 
|  | ReentryGuard guard; | 
|  | void* address = | 
|  | self->next->alloc_aligned_function(self->next, alignment, size, context); | 
|  | if (LIKELY(guard)) { | 
|  | PoissonAllocationSampler::RecordAlloc( | 
|  | address, size, PoissonAllocationSampler::kMalloc, nullptr); | 
|  | } | 
|  | return address; | 
|  | } | 
|  |  | 
|  | void* ReallocFn(const AllocatorDispatch* self, | 
|  | void* address, | 
|  | size_t size, | 
|  | void* context) { | 
|  | ReentryGuard guard; | 
|  | // Note: size == 0 actually performs free. | 
|  | PoissonAllocationSampler::RecordFree(address); | 
|  | address = self->next->realloc_function(self->next, address, size, context); | 
|  | if (LIKELY(guard)) { | 
|  | PoissonAllocationSampler::RecordAlloc( | 
|  | address, size, PoissonAllocationSampler::kMalloc, nullptr); | 
|  | } | 
|  | return address; | 
|  | } | 
|  |  | 
|  | void FreeFn(const AllocatorDispatch* self, void* address, void* context) { | 
|  | // Note: The RecordFree should be called before free_function | 
|  | // (here and in other places). | 
|  | // That is because we need to remove the recorded allocation sample before | 
|  | // free_function, as once the latter is executed the address becomes available | 
|  | // and can be allocated by another thread. That would be racy otherwise. | 
|  | PoissonAllocationSampler::RecordFree(address); | 
|  | self->next->free_function(self->next, address, context); | 
|  | } | 
|  |  | 
|  | size_t GetSizeEstimateFn(const AllocatorDispatch* self, | 
|  | void* address, | 
|  | void* context) { | 
|  | return self->next->get_size_estimate_function(self->next, address, context); | 
|  | } | 
|  |  | 
|  | unsigned BatchMallocFn(const AllocatorDispatch* self, | 
|  | size_t size, | 
|  | void** results, | 
|  | unsigned num_requested, | 
|  | void* context) { | 
|  | ReentryGuard guard; | 
|  | unsigned num_allocated = self->next->batch_malloc_function( | 
|  | self->next, size, results, num_requested, context); | 
|  | if (LIKELY(guard)) { | 
|  | for (unsigned i = 0; i < num_allocated; ++i) { | 
|  | PoissonAllocationSampler::RecordAlloc( | 
|  | results[i], size, PoissonAllocationSampler::kMalloc, nullptr); | 
|  | } | 
|  | } | 
|  | return num_allocated; | 
|  | } | 
|  |  | 
|  | void BatchFreeFn(const AllocatorDispatch* self, | 
|  | void** to_be_freed, | 
|  | unsigned num_to_be_freed, | 
|  | void* context) { | 
|  | for (unsigned i = 0; i < num_to_be_freed; ++i) | 
|  | PoissonAllocationSampler::RecordFree(to_be_freed[i]); | 
|  | self->next->batch_free_function(self->next, to_be_freed, num_to_be_freed, | 
|  | context); | 
|  | } | 
|  |  | 
|  | void FreeDefiniteSizeFn(const AllocatorDispatch* self, | 
|  | void* address, | 
|  | size_t size, | 
|  | void* context) { | 
|  | PoissonAllocationSampler::RecordFree(address); | 
|  | self->next->free_definite_size_function(self->next, address, size, context); | 
|  | } | 
|  |  | 
|  | static void* AlignedMallocFn(const AllocatorDispatch* self, | 
|  | size_t size, | 
|  | size_t alignment, | 
|  | void* context) { | 
|  | ReentryGuard guard; | 
|  | void* address = | 
|  | self->next->aligned_malloc_function(self->next, size, alignment, context); | 
|  | if (LIKELY(guard)) { | 
|  | PoissonAllocationSampler::RecordAlloc( | 
|  | address, size, PoissonAllocationSampler::kMalloc, nullptr); | 
|  | } | 
|  | return address; | 
|  | } | 
|  |  | 
|  | static void* AlignedReallocFn(const AllocatorDispatch* self, | 
|  | void* address, | 
|  | size_t size, | 
|  | size_t alignment, | 
|  | void* context) { | 
|  | ReentryGuard guard; | 
|  | // Note: size == 0 actually performs free. | 
|  | PoissonAllocationSampler::RecordFree(address); | 
|  | address = self->next->aligned_realloc_function(self->next, address, size, | 
|  | alignment, context); | 
|  | if (LIKELY(guard)) { | 
|  | PoissonAllocationSampler::RecordAlloc( | 
|  | address, size, PoissonAllocationSampler::kMalloc, nullptr); | 
|  | } | 
|  | return address; | 
|  | } | 
|  |  | 
|  | static void AlignedFreeFn(const AllocatorDispatch* self, | 
|  | void* address, | 
|  | void* context) { | 
|  | PoissonAllocationSampler::RecordFree(address); | 
|  | self->next->aligned_free_function(self->next, address, context); | 
|  | } | 
|  |  | 
|  | AllocatorDispatch g_allocator_dispatch = {&AllocFn, | 
|  | &AllocUncheckedFn, | 
|  | &AllocZeroInitializedFn, | 
|  | &AllocAlignedFn, | 
|  | &ReallocFn, | 
|  | &FreeFn, | 
|  | &GetSizeEstimateFn, | 
|  | &BatchMallocFn, | 
|  | &BatchFreeFn, | 
|  | &FreeDefiniteSizeFn, | 
|  | &AlignedMallocFn, | 
|  | &AlignedReallocFn, | 
|  | &AlignedFreeFn, | 
|  | nullptr}; | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace base::allocator::dispatcher::allocator_shim_details | 
|  | #endif  // BUILDFLAG(USE_ALLOCATOR_SHIM) | 
|  |  | 
|  | #if BUILDFLAG(USE_PARTITION_ALLOC) && !BUILDFLAG(IS_NACL) | 
|  | namespace base::allocator::dispatcher::partition_allocator_details { | 
|  | namespace { | 
|  |  | 
|  | void PartitionAllocHook(void* address, size_t size, const char* type) { | 
|  | PoissonAllocationSampler::RecordAlloc( | 
|  | address, size, PoissonAllocationSampler::kPartitionAlloc, type); | 
|  | } | 
|  |  | 
|  | void PartitionFreeHook(void* address) { | 
|  | PoissonAllocationSampler::RecordFree(address); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace base::allocator::dispatcher::partition_allocator_details | 
|  | #endif  // BUILDFLAG(USE_PARTITION_ALLOC) && !BUILDFLAG(IS_NACL) | 
|  |  | 
|  | namespace base::allocator::dispatcher { | 
|  |  | 
|  | void InstallStandardAllocatorHooks() { | 
|  | #if BUILDFLAG(USE_ALLOCATOR_SHIM) | 
|  | allocator::InsertAllocatorDispatch( | 
|  | &allocator_shim_details::g_allocator_dispatch); | 
|  | #else | 
|  | // If the allocator shim isn't available, then we don't install any hooks. | 
|  | // There's no point in printing an error message, since this can regularly | 
|  | // happen for tests. | 
|  | #endif  // BUILDFLAG(USE_ALLOCATOR_SHIM) | 
|  |  | 
|  | #if BUILDFLAG(USE_PARTITION_ALLOC) && !BUILDFLAG(IS_NACL) | 
|  | partition_alloc::PartitionAllocHooks::SetObserverHooks( | 
|  | &partition_allocator_details::PartitionAllocHook, | 
|  | &partition_allocator_details::PartitionFreeHook); | 
|  | #endif  // BUILDFLAG(USE_PARTITION_ALLOC) && !BUILDFLAG(IS_NACL) | 
|  | } | 
|  |  | 
|  | void RemoveStandardAllocatorHooksForTesting() { | 
|  | #if BUILDFLAG(USE_ALLOCATOR_SHIM) | 
|  | allocator::RemoveAllocatorDispatchForTesting( | 
|  | &allocator_shim_details::g_allocator_dispatch);  // IN-TEST | 
|  | #endif | 
|  | #if BUILDFLAG(USE_PARTITION_ALLOC) && !BUILDFLAG(IS_NACL) | 
|  | partition_alloc::PartitionAllocHooks::SetObserverHooks(nullptr, nullptr); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | }  // namespace base::allocator::dispatcher |