| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/memory_system/memory_system.h" |
| |
| #include "base/allocator/dispatcher/dispatcher.h" |
| #include "base/allocator/dispatcher/initializer.h" |
| #include "base/debug/crash_logging.h" |
| #include "base/debug/debugging_buildflags.h" |
| #include "build/build_config.h" |
| #include "components/gwp_asan/buildflags/buildflags.h" |
| #include "components/memory_system/buildflags.h" |
| #include "components/memory_system/memory_system_features.h" |
| #include "components/memory_system/parameters.h" |
| #include "partition_alloc/buildflags.h" |
| |
| #if BUILDFLAG(ENABLE_GWP_ASAN) |
| #include "components/gwp_asan/client/gwp_asan.h" // nogncheck |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "components/crash/core/app/crashpad.h" // nogncheck |
| #endif |
| #endif |
| |
| #if BUILDFLAG(IS_IOS) && PA_BUILDFLAG(USE_ALLOCATOR_SHIM) |
| #include "base/ios/ios_util.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "partition_alloc/shim/allocator_interception_apple.h" |
| #include "partition_alloc/shim/allocator_shim.h" |
| #endif |
| |
| // HeapProfilerController's dependencies are not compiled on iOS unless |
| // AllocatorShim is enabled. |
| #if !BUILDFLAG(IS_IOS) || PA_BUILDFLAG(USE_ALLOCATOR_SHIM) |
| #define HEAP_PROFILING_SUPPORTED 1 |
| #else |
| #define HEAP_PROFILING_SUPPORTED 0 |
| #endif |
| |
| #if HEAP_PROFILING_SUPPORTED |
| #include "components/heap_profiling/in_process/heap_profiler_controller.h" // nogncheck |
| #include "components/services/heap_profiling/public/cpp/profiling_client.h" // nogncheck |
| #endif |
| |
| #if HEAP_PROFILING_SUPPORTED |
| // If profiling is not supported, the PoissonAllocationSampler is removed from |
| // base, which causes linker errors. Since we need it only for the dispatcher, |
| // we include it only if both, dispatcher and heap-profiling, are enabled. |
| #include "base/sampling_heap_profiler/poisson_allocation_sampler.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER) |
| #include "base/cpu.h" |
| #include "base/debug/allocation_trace.h" |
| #include "components/allocation_recorder/crash_client/client.h" |
| #if BUILDFLAG(ENABLE_ALLOCATION_TRACE_RECORDER_FULL_REPORTING) |
| #include "components/memory_system/allocation_trace_recorder_statistics_reporter.h" |
| #endif // BUILDFLAG(ENABLE_ALLOCATION_TRACE_RECORDER_FULL_REPORTING) |
| #endif // BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER) |
| |
| namespace memory_system { |
| namespace { |
| |
| #if BUILDFLAG(IS_IOS) && PA_BUILDFLAG(USE_ALLOCATOR_SHIM) |
| // Do not install allocator shim on iOS 13.4 due to high crash volume on this |
| // particular version of OS. TODO(crbug.com/40707342): Remove this workaround |
| // when/if the bug gets fixed. |
| bool ShouldInstallAllocatorShim() { |
| return !base::ios::IsRunningOnOrLater(13, 4, 0) || |
| base::ios::IsRunningOnOrLater(13, 5, 0); |
| } |
| #endif |
| |
| } // namespace |
| |
| struct MemorySystem::Impl { |
| Impl(); |
| ~Impl(); |
| |
| void Initialize( |
| const std::optional<GwpAsanParameters>& gwp_asan_parameters, |
| const std::optional<ProfilingClientParameters>& |
| profiling_client_parameters, |
| const std::optional<DispatcherParameters>& dispatcher_parameters); |
| |
| private: |
| // Initialization functions for the various subsystems. |
| |
| // Structure of information passed from one step to another. |
| struct InitializationData { |
| #if HEAP_PROFILING_SUPPORTED |
| bool has_profiling_client_started = false; |
| #endif |
| }; |
| |
| // Initialize GWP-ASan with the given set of parameters. |
| // |
| // Initialization will be performed if support for GWP-ASan is compiled in. On |
| // ChromeOs Crashpad must be enabled in addition. |
| void InitializeGwpASan(const GwpAsanParameters& gwp_asan_parameters, |
| InitializationData& initialization_data); |
| |
| // Initialize HeapProfiler with the given set of parameters. |
| void InitializeHeapProfiler( |
| const ProfilingClientParameters& profiling_client_parameters, |
| InitializationData& initialization_data); |
| |
| void InitializeDispatcher(const DispatcherParameters& dispatcher_parameters, |
| InitializationData& initialization_data); |
| |
| // Has the allocator shim been initialized successfully? |
| bool IsAllocatorShimInitialized(); |
| |
| #if HEAP_PROFILING_SUPPORTED |
| // Check if the the dispatcher should include the PoissonAllocationSampler as |
| // observer. |
| bool DispatcherIncludesPoissonAllocationSampler( |
| const DispatcherParameters& dispatcher_parameters, |
| const InitializationData& initialization_data); |
| |
| #if BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER) |
| // Check if the the dispatcher should include the AllocationTraceRecorder as |
| // an observer. |
| bool DispatcherIncludesAllocationTraceRecorder( |
| const DispatcherParameters& dispatcher_parameters); |
| #endif |
| |
| std::unique_ptr<heap_profiling::HeapProfilerController> |
| heap_profiler_controller_; |
| #endif |
| |
| #if BUILDFLAG(IS_IOS) && PA_BUILDFLAG(USE_ALLOCATOR_SHIM) |
| const bool should_install_allocator_shim_ = ShouldInstallAllocatorShim(); |
| #endif |
| |
| #if BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER) |
| struct { |
| // We must not delete the recorder upon shutdown. Firstly, we do not have a |
| // possibility to remove an allocation hook reliably. So, once installed, |
| // the recorder may constantly be used by the allocation hooks. Secondly, |
| // the reporting may continue using the recorder event after destruction |
| // (see AllocationTraceRecorderStatisticsReporter for details). Therefore, |
| // we extend its lifetime as much as possible by making it an unmanaged |
| // pointer and not deleting in the course of the destruction of the memory |
| // system. |
| raw_ptr<base::debug::tracer::AllocationTraceRecorder> recorder; |
| #if BUILDFLAG(ENABLE_ALLOCATION_TRACE_RECORDER_FULL_REPORTING) |
| internal::AllocationTraceRecorderStatisticsReporter reporting; |
| #endif |
| } allocation_recording_; |
| #endif |
| }; |
| |
| MemorySystem::Impl::Impl() { |
| #if BUILDFLAG(IS_IOS) && PA_BUILDFLAG(USE_ALLOCATOR_SHIM) |
| if (should_install_allocator_shim_) { |
| allocator_shim::InitializeAllocatorShim(); |
| } |
| #endif |
| |
| #if HEAP_PROFILING_SUPPORTED |
| // The TLS slot used by the memlog allocator shim needs to be initialized |
| // early to ensure that it gets assigned a low slot number. If it gets |
| // initialized too late, the glibc TLS system will require a malloc call in |
| // order to allocate storage for a higher slot number. Since malloc is hooked, |
| // this causes re-entrancy into the allocator shim, while the TLS object is |
| // partially-initialized, which the TLS object is supposed to protect again. |
| heap_profiling::InitTLSSlot(); |
| #endif |
| } |
| |
| MemorySystem::Impl::~Impl() { |
| #if BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER) |
| #if BUILDFLAG(ENABLE_ALLOCATION_TRACE_RECORDER_FULL_REPORTING) |
| allocation_recording_.reporting = {}; |
| #endif |
| |
| if (allocation_recording_.recorder) { |
| allocation_recorder::crash_client::UnregisterRecorderWithCrashpad(); |
| } |
| |
| // Do not delete the recorder that |allocation_recording_.recorder| points to |
| // to prevent the allocations hooks and the reporting from operating on |
| // potentially invalid data. See the declaration of |
| // |allocation_recording_.recorder| for details. |
| #endif // BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER) |
| } |
| |
| void MemorySystem::Impl::Initialize( |
| const std::optional<GwpAsanParameters>& gwp_asan_parameters, |
| const std::optional<ProfilingClientParameters>& profiling_client_parameters, |
| const std::optional<DispatcherParameters>& dispatcher_parameters) { |
| if (!IsAllocatorShimInitialized()) { |
| return; |
| } |
| |
| InitializationData initialization_data; |
| |
| if (gwp_asan_parameters) { |
| InitializeGwpASan(*gwp_asan_parameters, initialization_data); |
| } |
| |
| if (profiling_client_parameters) { |
| InitializeHeapProfiler(*profiling_client_parameters, initialization_data); |
| } |
| |
| if (dispatcher_parameters) { |
| InitializeDispatcher(*dispatcher_parameters, initialization_data); |
| } |
| } |
| |
| bool MemorySystem::Impl::IsAllocatorShimInitialized() { |
| #if BUILDFLAG(IS_IOS) && PA_BUILDFLAG(USE_ALLOCATOR_SHIM) |
| if (!should_install_allocator_shim_) { |
| return false; |
| } |
| |
| const bool malloc_intercepted = allocator_shim::AreMallocZonesIntercepted(); |
| base::UmaHistogramBoolean("IOS.Allocator.ShimInstalled", malloc_intercepted); |
| |
| return malloc_intercepted; |
| #else |
| return true; |
| #endif |
| } |
| |
| void MemorySystem::Impl::InitializeGwpASan( |
| const GwpAsanParameters& gwp_asan_parameters, |
| InitializationData& initialization_data) { |
| #if BUILDFLAG(ENABLE_GWP_ASAN) |
| // LUD has the highest priority and the Extreme LUD has the lowest priority. |
| // An allocator shim later installed has priority over the already-installed |
| // shims. |
| gwp_asan::MaybeEnableExtremeLightweightDetector( |
| gwp_asan_parameters.boost_sampling, |
| gwp_asan_parameters.process_type.c_str()); |
| |
| #if BUILDFLAG(ENABLE_GWP_ASAN_MALLOC) |
| gwp_asan::EnableForMalloc(gwp_asan_parameters.boost_sampling, |
| gwp_asan_parameters.process_type); |
| #endif |
| |
| #if BUILDFLAG(ENABLE_GWP_ASAN_PARTITIONALLOC) |
| gwp_asan::EnableForPartitionAlloc(gwp_asan_parameters.boost_sampling, |
| gwp_asan_parameters.process_type); |
| #endif |
| |
| gwp_asan::MaybeEnableLightweightDetector( |
| gwp_asan_parameters.boost_sampling, |
| gwp_asan_parameters.process_type.c_str()); |
| #endif // BUILDFLAG(ENABLE_GWP_ASAN) |
| } |
| |
| void MemorySystem::Impl::InitializeHeapProfiler( |
| const ProfilingClientParameters& profiling_client_parameters, |
| InitializationData& initialization_data) { |
| #if HEAP_PROFILING_SUPPORTED |
| heap_profiler_controller_ = |
| std::make_unique<heap_profiling::HeapProfilerController>( |
| profiling_client_parameters.channel, |
| profiling_client_parameters.process_type); |
| |
| initialization_data.has_profiling_client_started = |
| heap_profiler_controller_->StartIfEnabled(); |
| #endif |
| } |
| |
| #if HEAP_PROFILING_SUPPORTED |
| bool MemorySystem::Impl::DispatcherIncludesPoissonAllocationSampler( |
| const DispatcherParameters& dispatcher_parameters, |
| const InitializationData& initialization_data) { |
| switch (dispatcher_parameters.poisson_allocation_sampler_inclusion) { |
| case DispatcherParameters::PoissonAllocationSamplerInclusion::kEnforce: |
| return true; |
| case DispatcherParameters::PoissonAllocationSamplerInclusion::kDynamic: |
| return initialization_data.has_profiling_client_started; |
| case DispatcherParameters::PoissonAllocationSamplerInclusion::kIgnore: |
| return false; |
| } |
| } |
| #endif |
| |
| #if BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER) |
| bool MemorySystem::Impl::DispatcherIncludesAllocationTraceRecorder( |
| const DispatcherParameters& dispatcher_parameters) { |
| #if BUILDFLAG(FORCE_ALLOCATION_TRACE_RECORDER) |
| return true; |
| #else |
| switch (dispatcher_parameters.allocation_trace_recorder_inclusion) { |
| case DispatcherParameters::AllocationTraceRecorderInclusion::kDynamic: |
| return base::CPU::GetInstanceNoAllocation().has_mte() && |
| base::FeatureList::IsEnabled(features::kAllocationTraceRecorder); |
| case DispatcherParameters::AllocationTraceRecorderInclusion::kIgnore: |
| return false; |
| } |
| #endif |
| } |
| #endif |
| |
| void MemorySystem::Impl::InitializeDispatcher( |
| const DispatcherParameters& dispatcher_parameters, |
| InitializationData& initialization_data) { |
| #if HEAP_PROFILING_SUPPORTED |
| // Include the PoissonAllocationSampler as an optional observer always, even |
| // if the inclusion parameter is |kEnforce|. If we distinguish between |
| // mandatory and optional, the nesting becomes a real mess once we add yet |
| // another observer. Adding the value this way should be fine. |
| const bool include_poisson_allocation_sampler = |
| DispatcherIncludesPoissonAllocationSampler(dispatcher_parameters, |
| initialization_data); |
| |
| auto* const poisson_allocation_sampler = |
| include_poisson_allocation_sampler ? base::PoissonAllocationSampler::Get() |
| : nullptr; |
| #endif |
| |
| #if BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER) |
| const bool include_allocation_recorder = |
| DispatcherIncludesAllocationTraceRecorder(dispatcher_parameters); |
| |
| static auto* const crash_key = base::debug::AllocateCrashKeyString( |
| "allocation_trace_recorder", base::debug::CrashKeySize::Size32); |
| base::debug::SetCrashKeyString( |
| crash_key, include_allocation_recorder ? "enabled" : "disabled"); |
| |
| if (include_allocation_recorder) { |
| allocation_recording_.recorder = |
| new base::debug::tracer::AllocationTraceRecorder(); |
| |
| allocation_recorder::crash_client::RegisterRecorderWithCrashpad( |
| *allocation_recording_.recorder); |
| |
| #if BUILDFLAG(ENABLE_ALLOCATION_TRACE_RECORDER_FULL_REPORTING) |
| allocation_recording_.reporting = { |
| *allocation_recording_.recorder, dispatcher_parameters.process_type, |
| base::Seconds(15), logging::LOGGING_ERROR}; |
| #endif |
| } |
| #endif // BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER) |
| |
| base::allocator::dispatcher::CreateInitializer() |
| #if HEAP_PROFILING_SUPPORTED |
| .AddOptionalObservers(poisson_allocation_sampler) |
| #endif |
| #if BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER) |
| .AddOptionalObservers(allocation_recording_.recorder.get()) |
| #endif |
| .DoInitialize(base::allocator::dispatcher::Dispatcher::GetInstance()); |
| } |
| |
| MemorySystem::MemorySystem() : impl_(std::make_unique<Impl>()) {} |
| |
| MemorySystem::~MemorySystem() = default; |
| |
| void MemorySystem::Initialize( |
| const std::optional<GwpAsanParameters>& gwp_asan_parameters, |
| const std::optional<ProfilingClientParameters>& profiling_client_parameters, |
| const std::optional<DispatcherParameters>& dispatcher_parameters) { |
| impl_->Initialize(gwp_asan_parameters, profiling_client_parameters, |
| dispatcher_parameters); |
| } |
| |
| } // namespace memory_system |