blob: da28dee77cfee40016b7aeacc9cbf9727515ba06 [file] [log] [blame]
// 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