// Copyright 2015 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/trace_event/memory_dump_manager.h" #include #include #include #include #include #include "base/allocator/buildflags.h" #include "base/base_switches.h" #include "base/command_line.h" #include "base/debug/alias.h" #include "base/debug/stack_trace.h" #include "base/debug/thread_heap_usage_tracker.h" #include "base/memory/ptr_util.h" #include "base/sequenced_task_runner.h" #include "base/strings/string_util.h" #include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/threading/thread.h" #include "base/threading/thread_task_runner_handle.h" #include "base/trace_event/heap_profiler.h" #include "base/trace_event/heap_profiler_allocation_context_tracker.h" #include "base/trace_event/heap_profiler_event_filter.h" #include "base/trace_event/malloc_dump_provider.h" #include "base/trace_event/memory_dump_provider.h" #include "base/trace_event/memory_dump_scheduler.h" #include "base/trace_event/memory_infra_background_whitelist.h" #include "base/trace_event/process_memory_dump.h" #include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event_argument.h" #include "build/build_config.h" #if defined(OS_ANDROID) #include "base/trace_event/java_heap_dump_provider_android.h" #if BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) #include "base/trace_event/cfi_backtrace_android.h" #endif #endif // defined(OS_ANDROID) namespace base { namespace trace_event { namespace { MemoryDumpManager* g_memory_dump_manager_for_testing = nullptr; // Temporary (until scheduler is moved outside of here) // trampoline function to match the |request_dump_function| passed to Initialize // to the callback expected by MemoryDumpScheduler. // TODO(primiano): remove this. void DoGlobalDumpWithoutCallback( MemoryDumpManager::RequestGlobalDumpFunction global_dump_fn, MemoryDumpType dump_type, MemoryDumpLevelOfDetail level_of_detail) { global_dump_fn.Run(dump_type, level_of_detail); } } // namespace // static const char* const MemoryDumpManager::kTraceCategory = TRACE_DISABLED_BY_DEFAULT("memory-infra"); // static const int MemoryDumpManager::kMaxConsecutiveFailuresCount = 3; // static const uint64_t MemoryDumpManager::kInvalidTracingProcessId = 0; // static const char* const MemoryDumpManager::kSystemAllocatorPoolName = #if defined(MALLOC_MEMORY_TRACING_SUPPORTED) MallocDumpProvider::kAllocatedObjects; #else nullptr; #endif // static MemoryDumpManager* MemoryDumpManager::GetInstance() { if (g_memory_dump_manager_for_testing) return g_memory_dump_manager_for_testing; return Singleton>::get(); } // static std::unique_ptr MemoryDumpManager::CreateInstanceForTesting() { DCHECK(!g_memory_dump_manager_for_testing); std::unique_ptr instance(new MemoryDumpManager()); g_memory_dump_manager_for_testing = instance.get(); return instance; } MemoryDumpManager::MemoryDumpManager() : is_coordinator_(false), tracing_process_id_(kInvalidTracingProcessId), dumper_registrations_ignored_for_testing_(false) {} MemoryDumpManager::~MemoryDumpManager() { Thread* dump_thread = nullptr; { AutoLock lock(lock_); if (dump_thread_) { dump_thread = dump_thread_.get(); } } if (dump_thread) { dump_thread->Stop(); } AutoLock lock(lock_); dump_thread_.reset(); g_memory_dump_manager_for_testing = nullptr; } void MemoryDumpManager::Initialize( RequestGlobalDumpFunction request_dump_function, bool is_coordinator) { { AutoLock lock(lock_); DCHECK(!request_dump_function.is_null()); DCHECK(!can_request_global_dumps()); request_dump_function_ = request_dump_function; is_coordinator_ = is_coordinator; } // Enable the core dump providers. #if defined(MALLOC_MEMORY_TRACING_SUPPORTED) RegisterDumpProvider(MallocDumpProvider::GetInstance(), "Malloc", nullptr); #endif #if defined(OS_ANDROID) RegisterDumpProvider(JavaHeapDumpProvider::GetInstance(), "JavaHeap", nullptr); #endif TRACE_EVENT_WARMUP_CATEGORY(kTraceCategory); } void MemoryDumpManager::RegisterDumpProvider( MemoryDumpProvider* mdp, const char* name, scoped_refptr task_runner, MemoryDumpProvider::Options options) { options.dumps_on_single_thread_task_runner = true; RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options); } void MemoryDumpManager::RegisterDumpProvider( MemoryDumpProvider* mdp, const char* name, scoped_refptr task_runner) { // Set |dumps_on_single_thread_task_runner| to true because all providers // without task runner are run on dump thread. MemoryDumpProvider::Options options; options.dumps_on_single_thread_task_runner = true; RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options); } void MemoryDumpManager::RegisterDumpProviderWithSequencedTaskRunner( MemoryDumpProvider* mdp, const char* name, scoped_refptr task_runner, MemoryDumpProvider::Options options) { DCHECK(task_runner); options.dumps_on_single_thread_task_runner = false; RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options); } void MemoryDumpManager::RegisterDumpProviderInternal( MemoryDumpProvider* mdp, const char* name, scoped_refptr task_runner, const MemoryDumpProvider::Options& options) { if (dumper_registrations_ignored_for_testing_) return; // Only a handful of MDPs are required to compute the memory metrics. These // have small enough performance overhead that it is resonable to run them // in the background while the user is doing other things. Those MDPs are // 'whitelisted for background mode'. bool whitelisted_for_background_mode = IsMemoryDumpProviderWhitelisted(name); scoped_refptr mdpinfo = new MemoryDumpProviderInfo(mdp, name, std::move(task_runner), options, whitelisted_for_background_mode); { AutoLock lock(lock_); bool already_registered = !dump_providers_.insert(mdpinfo).second; // This actually happens in some tests which don't have a clean tear-down // path for RenderThreadImpl::Init(). if (already_registered) return; } } void MemoryDumpManager::UnregisterDumpProvider(MemoryDumpProvider* mdp) { UnregisterDumpProviderInternal(mdp, false /* delete_async */); } void MemoryDumpManager::UnregisterAndDeleteDumpProviderSoon( std::unique_ptr mdp) { UnregisterDumpProviderInternal(mdp.release(), true /* delete_async */); } void MemoryDumpManager::UnregisterDumpProviderInternal( MemoryDumpProvider* mdp, bool take_mdp_ownership_and_delete_async) { std::unique_ptr owned_mdp; if (take_mdp_ownership_and_delete_async) owned_mdp.reset(mdp); AutoLock lock(lock_); auto mdp_iter = dump_providers_.begin(); for (; mdp_iter != dump_providers_.end(); ++mdp_iter) { if ((*mdp_iter)->dump_provider == mdp) break; } if (mdp_iter == dump_providers_.end()) return; // Not registered / already unregistered. if (take_mdp_ownership_and_delete_async) { // The MDP will be deleted whenever the MDPInfo struct will, that is either: // - At the end of this function, if no dump is in progress. // - In ContinueAsyncProcessDump() when MDPInfo is removed from // |pending_dump_providers|. DCHECK(!(*mdp_iter)->owned_dump_provider); (*mdp_iter)->owned_dump_provider = std::move(owned_mdp); } else { // If you hit this DCHECK, your dump provider has a bug. // Unregistration of a MemoryDumpProvider is safe only if: // - The MDP has specified a sequenced task runner affinity AND the // unregistration happens on the same task runner. So that the MDP cannot // unregister and be in the middle of a OnMemoryDump() at the same time. // - The MDP has NOT specified a task runner affinity and its ownership is // transferred via UnregisterAndDeleteDumpProviderSoon(). // In all the other cases, it is not possible to guarantee that the // unregistration will not race with OnMemoryDump() calls. DCHECK((*mdp_iter)->task_runner && (*mdp_iter)->task_runner->RunsTasksInCurrentSequence()) << "MemoryDumpProvider \"" << (*mdp_iter)->name << "\" attempted to " << "unregister itself in a racy way. Please file a crbug."; } // The MDPInfo instance can still be referenced by the // |ProcessMemoryDumpAsyncState.pending_dump_providers|. For this reason // the MDPInfo is flagged as disabled. It will cause InvokeOnMemoryDump() // to just skip it, without actually invoking the |mdp|, which might be // destroyed by the caller soon after this method returns. (*mdp_iter)->disabled = true; dump_providers_.erase(mdp_iter); } bool MemoryDumpManager::IsDumpProviderRegisteredForTesting( MemoryDumpProvider* provider) { AutoLock lock(lock_); for (const auto& info : dump_providers_) { if (info->dump_provider == provider) return true; } return false; } scoped_refptr MemoryDumpManager::GetOrCreateBgTaskRunnerLocked() { lock_.AssertAcquired(); if (dump_thread_) return dump_thread_->task_runner(); dump_thread_ = std::make_unique("MemoryInfra"); bool started = dump_thread_->Start(); CHECK(started); return dump_thread_->task_runner(); } void MemoryDumpManager::CreateProcessDump( const MemoryDumpRequestArgs& args, const ProcessMemoryDumpCallback& callback) { char guid_str[20]; sprintf(guid_str, "0x%" PRIx64, args.dump_guid); TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(kTraceCategory, "ProcessMemoryDump", TRACE_ID_LOCAL(args.dump_guid), "dump_guid", TRACE_STR_COPY(guid_str)); // If argument filter is enabled then only background mode dumps should be // allowed. In case the trace config passed for background tracing session // missed the allowed modes argument, it crashes here instead of creating // unexpected dumps. if (TraceLog::GetInstance() ->GetCurrentTraceConfig() .IsArgumentFilterEnabled()) { CHECK_EQ(MemoryDumpLevelOfDetail::BACKGROUND, args.level_of_detail); } std::unique_ptr pmd_async_state; { AutoLock lock(lock_); pmd_async_state.reset(new ProcessMemoryDumpAsyncState( args, dump_providers_, callback, GetOrCreateBgTaskRunnerLocked())); } // Start the process dump. This involves task runner hops as specified by the // MemoryDumpProvider(s) in RegisterDumpProvider()). ContinueAsyncProcessDump(pmd_async_state.release()); } // Invokes OnMemoryDump() on all MDPs that are next in the pending list and run // on the current sequenced task runner. If the next MDP does not run in current // sequenced task runner, then switches to that task runner and continues. All // OnMemoryDump() invocations are linearized. |lock_| is used in these functions // purely to ensure consistency w.r.t. (un)registrations of |dump_providers_|. void MemoryDumpManager::ContinueAsyncProcessDump( ProcessMemoryDumpAsyncState* owned_pmd_async_state) { HEAP_PROFILER_SCOPED_IGNORE; // Initalizes the ThreadLocalEventBuffer to guarantee that the TRACE_EVENTs // in the PostTask below don't end up registering their own dump providers // (for discounting trace memory overhead) while holding the |lock_|. TraceLog::GetInstance()->InitializeThreadLocalEventBufferIfSupported(); // In theory |owned_pmd_async_state| should be a unique_ptr. The only reason // why it isn't is because of the corner case logic of |did_post_task| // above, which needs to take back the ownership of the |pmd_async_state| when // the PostTask() fails. // Unfortunately, PostTask() destroys the unique_ptr arguments upon failure // to prevent accidental leaks. Using a unique_ptr would prevent us to to // skip the hop and move on. Hence the manual naked -> unique ptr juggling. auto pmd_async_state = WrapUnique(owned_pmd_async_state); owned_pmd_async_state = nullptr; while (!pmd_async_state->pending_dump_providers.empty()) { // Read MemoryDumpProviderInfo thread safety considerations in // memory_dump_manager.h when accessing |mdpinfo| fields. MemoryDumpProviderInfo* mdpinfo = pmd_async_state->pending_dump_providers.back().get(); // If we are in background mode, we should invoke only the whitelisted // providers. Ignore other providers and continue. if (pmd_async_state->req_args.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND && !mdpinfo->whitelisted_for_background_mode) { pmd_async_state->pending_dump_providers.pop_back(); continue; } // If the dump provider did not specify a task runner affinity, dump on // |dump_thread_|. scoped_refptr task_runner = mdpinfo->task_runner; if (!task_runner) { DCHECK(mdpinfo->options.dumps_on_single_thread_task_runner); task_runner = pmd_async_state->dump_thread_task_runner; DCHECK(task_runner); } // If |RunsTasksInCurrentSequence()| is true then no PostTask is // required since we are on the right SequencedTaskRunner. if (task_runner->RunsTasksInCurrentSequence()) { InvokeOnMemoryDump(mdpinfo, pmd_async_state->process_memory_dump.get()); pmd_async_state->pending_dump_providers.pop_back(); continue; } bool did_post_task = task_runner->PostTask( FROM_HERE, BindOnce(&MemoryDumpManager::ContinueAsyncProcessDump, Unretained(this), Unretained(pmd_async_state.get()))); if (did_post_task) { // Ownership is tranferred to the posted task. ignore_result(pmd_async_state.release()); return; } // PostTask usually fails only if the process or thread is shut down. So, // the dump provider is disabled here. But, don't disable unbound dump // providers, since the |dump_thread_| is controlled by MDM. if (mdpinfo->task_runner) { // A locked access is required to R/W |disabled| (for the // UnregisterAndDeleteDumpProviderSoon() case). AutoLock lock(lock_); mdpinfo->disabled = true; } // PostTask failed. Ignore the dump provider and continue. pmd_async_state->pending_dump_providers.pop_back(); } FinishAsyncProcessDump(std::move(pmd_async_state)); } // This function is called on the right task runner for current MDP. It is // either the task runner specified by MDP or |dump_thread_task_runner| if the // MDP did not specify task runner. Invokes the dump provider's OnMemoryDump() // (unless disabled). void MemoryDumpManager::InvokeOnMemoryDump(MemoryDumpProviderInfo* mdpinfo, ProcessMemoryDump* pmd) { HEAP_PROFILER_SCOPED_IGNORE; DCHECK(!mdpinfo->task_runner || mdpinfo->task_runner->RunsTasksInCurrentSequence()); TRACE_EVENT1(kTraceCategory, "MemoryDumpManager::InvokeOnMemoryDump", "dump_provider.name", mdpinfo->name); // Do not add any other TRACE_EVENT macro (or function that might have them) // below this point. Under some rare circunstances, they can re-initialize // and invalide the current ThreadLocalEventBuffer MDP, making the // |should_dump| check below susceptible to TOCTTOU bugs // (https://crbug.com/763365). bool is_thread_bound; { // A locked access is required to R/W |disabled| (for the // UnregisterAndDeleteDumpProviderSoon() case). AutoLock lock(lock_); // Unregister the dump provider if it failed too many times consecutively. if (!mdpinfo->disabled && mdpinfo->consecutive_failures >= kMaxConsecutiveFailuresCount) { mdpinfo->disabled = true; DLOG(ERROR) << "Disabling MemoryDumpProvider \"" << mdpinfo->name << "\". Dump failed multiple times consecutively."; } if (mdpinfo->disabled) return; is_thread_bound = mdpinfo->task_runner != nullptr; } // AutoLock lock(lock_); // Invoke the dump provider. // A stack allocated string with dump provider name is useful to debug // crashes while invoking dump after a |dump_provider| is not unregistered // in safe way. char provider_name_for_debugging[16]; strncpy(provider_name_for_debugging, mdpinfo->name, sizeof(provider_name_for_debugging) - 1); provider_name_for_debugging[sizeof(provider_name_for_debugging) - 1] = '\0'; base::debug::Alias(provider_name_for_debugging); ANNOTATE_BENIGN_RACE(&mdpinfo->disabled, "best-effort race detection"); CHECK(!is_thread_bound || !*(static_cast(&mdpinfo->disabled))); bool dump_successful = mdpinfo->dump_provider->OnMemoryDump(pmd->dump_args(), pmd); mdpinfo->consecutive_failures = dump_successful ? 0 : mdpinfo->consecutive_failures + 1; } void MemoryDumpManager::FinishAsyncProcessDump( std::unique_ptr pmd_async_state) { HEAP_PROFILER_SCOPED_IGNORE; DCHECK(pmd_async_state->pending_dump_providers.empty()); const uint64_t dump_guid = pmd_async_state->req_args.dump_guid; if (!pmd_async_state->callback_task_runner->BelongsToCurrentThread()) { scoped_refptr callback_task_runner = pmd_async_state->callback_task_runner; callback_task_runner->PostTask( FROM_HERE, BindOnce(&MemoryDumpManager::FinishAsyncProcessDump, Unretained(this), std::move(pmd_async_state))); return; } TRACE_EVENT0(kTraceCategory, "MemoryDumpManager::FinishAsyncProcessDump"); if (!pmd_async_state->callback.is_null()) { pmd_async_state->callback.Run( true /* success */, dump_guid, std::move(pmd_async_state->process_memory_dump)); pmd_async_state->callback.Reset(); } TRACE_EVENT_NESTABLE_ASYNC_END0(kTraceCategory, "ProcessMemoryDump", TRACE_ID_LOCAL(dump_guid)); } void MemoryDumpManager::SetupForTracing( const TraceConfig::MemoryDumpConfig& memory_dump_config) { AutoLock lock(lock_); // At this point we must have the ability to request global dumps. DCHECK(can_request_global_dumps()); MemoryDumpScheduler::Config periodic_config; for (const auto& trigger : memory_dump_config.triggers) { if (trigger.trigger_type == MemoryDumpType::PERIODIC_INTERVAL) { if (periodic_config.triggers.empty()) { periodic_config.callback = BindRepeating(&DoGlobalDumpWithoutCallback, request_dump_function_, MemoryDumpType::PERIODIC_INTERVAL); } periodic_config.triggers.push_back( {trigger.level_of_detail, trigger.min_time_between_dumps_ms}); } } // Only coordinator process triggers periodic memory dumps. if (is_coordinator_ && !periodic_config.triggers.empty()) { MemoryDumpScheduler::GetInstance()->Start(periodic_config, GetOrCreateBgTaskRunnerLocked()); } } void MemoryDumpManager::TeardownForTracing() { // There might be a memory dump in progress while this happens. Therefore, // ensure that the MDM state which depends on the tracing enabled / disabled // state is always accessed by the dumping methods holding the |lock_|. AutoLock lock(lock_); MemoryDumpScheduler::GetInstance()->Stop(); } MemoryDumpManager::ProcessMemoryDumpAsyncState::ProcessMemoryDumpAsyncState( MemoryDumpRequestArgs req_args, const MemoryDumpProviderInfo::OrderedSet& dump_providers, ProcessMemoryDumpCallback callback, scoped_refptr dump_thread_task_runner) : req_args(req_args), callback(callback), callback_task_runner(ThreadTaskRunnerHandle::Get()), dump_thread_task_runner(std::move(dump_thread_task_runner)) { pending_dump_providers.reserve(dump_providers.size()); pending_dump_providers.assign(dump_providers.rbegin(), dump_providers.rend()); MemoryDumpArgs args = {req_args.level_of_detail, req_args.dump_guid}; process_memory_dump = std::make_unique(args); } MemoryDumpManager::ProcessMemoryDumpAsyncState::~ProcessMemoryDumpAsyncState() = default; } // namespace trace_event } // namespace base