|  | // 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/heap_profiler_allocation_context_tracker.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <iterator> | 
|  |  | 
|  | #include "base/atomicops.h" | 
|  | #include "base/debug/debugging_buildflags.h" | 
|  | #include "base/debug/leak_annotations.h" | 
|  | #include "base/debug/stack_trace.h" | 
|  | #include "base/no_destructor.h" | 
|  | #include "base/threading/platform_thread.h" | 
|  | #include "base/threading/thread_local_storage.h" | 
|  | #include "base/trace_event/heap_profiler_allocation_context.h" | 
|  | #include "build/build_config.h" | 
|  |  | 
|  | #if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) | 
|  | #include "base/trace_event/cfi_backtrace_android.h" | 
|  | #endif | 
|  |  | 
|  | #if defined(OS_LINUX) || defined(OS_ANDROID) | 
|  | #include <sys/prctl.h> | 
|  | #endif | 
|  |  | 
|  | namespace base { | 
|  | namespace trace_event { | 
|  |  | 
|  | subtle::Atomic32 AllocationContextTracker::capture_mode_ = | 
|  | static_cast<int32_t>(AllocationContextTracker::CaptureMode::DISABLED); | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const size_t kMaxStackDepth = 128u; | 
|  | const size_t kMaxTaskDepth = 16u; | 
|  | AllocationContextTracker* const kInitializingSentinel = | 
|  | reinterpret_cast<AllocationContextTracker*>(-1); | 
|  |  | 
|  | // This function is added to the TLS slot to clean up the instance when the | 
|  | // thread exits. | 
|  | void DestructAllocationContextTracker(void* alloc_ctx_tracker) { | 
|  | delete static_cast<AllocationContextTracker*>(alloc_ctx_tracker); | 
|  | } | 
|  |  | 
|  | ThreadLocalStorage::Slot& AllocationContextTrackerTLS() { | 
|  | static NoDestructor<ThreadLocalStorage::Slot> tls_alloc_ctx_tracker( | 
|  | &DestructAllocationContextTracker); | 
|  | return *tls_alloc_ctx_tracker; | 
|  | } | 
|  |  | 
|  | // Cannot call ThreadIdNameManager::GetName because it holds a lock and causes | 
|  | // deadlock when lock is already held by ThreadIdNameManager before the current | 
|  | // allocation. Gets the thread name from kernel if available or returns a string | 
|  | // with id. This function intentionally leaks the allocated strings since they | 
|  | // are used to tag allocations even after the thread dies. | 
|  | const char* GetAndLeakThreadName() { | 
|  | char name[16]; | 
|  | #if defined(OS_LINUX) || defined(OS_ANDROID) | 
|  | // If the thread name is not set, try to get it from prctl. Thread name might | 
|  | // not be set in cases where the thread started before heap profiling was | 
|  | // enabled. | 
|  | int err = prctl(PR_GET_NAME, name); | 
|  | if (!err) { | 
|  | return strdup(name); | 
|  | } | 
|  | #endif  // defined(OS_LINUX) || defined(OS_ANDROID) | 
|  |  | 
|  | // Use tid if we don't have a thread name. | 
|  | snprintf(name, sizeof(name), "%lu", | 
|  | static_cast<unsigned long>(PlatformThread::CurrentId())); | 
|  | return strdup(name); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // static | 
|  | AllocationContextTracker* | 
|  | AllocationContextTracker::GetInstanceForCurrentThread() { | 
|  | AllocationContextTracker* tracker = static_cast<AllocationContextTracker*>( | 
|  | AllocationContextTrackerTLS().Get()); | 
|  | if (tracker == kInitializingSentinel) | 
|  | return nullptr;  // Re-entrancy case. | 
|  |  | 
|  | if (!tracker) { | 
|  | AllocationContextTrackerTLS().Set(kInitializingSentinel); | 
|  | tracker = new AllocationContextTracker(); | 
|  | AllocationContextTrackerTLS().Set(tracker); | 
|  | } | 
|  |  | 
|  | return tracker; | 
|  | } | 
|  |  | 
|  | AllocationContextTracker::AllocationContextTracker() | 
|  | : thread_name_(nullptr), ignore_scope_depth_(0) { | 
|  | tracked_stack_.reserve(kMaxStackDepth); | 
|  | task_contexts_.reserve(kMaxTaskDepth); | 
|  | } | 
|  | AllocationContextTracker::~AllocationContextTracker() = default; | 
|  |  | 
|  | // static | 
|  | void AllocationContextTracker::SetCurrentThreadName(const char* name) { | 
|  | if (name && capture_mode() != CaptureMode::DISABLED) { | 
|  | GetInstanceForCurrentThread()->thread_name_ = name; | 
|  | } | 
|  | } | 
|  |  | 
|  | // static | 
|  | void AllocationContextTracker::SetCaptureMode(CaptureMode mode) { | 
|  | // Release ordering ensures that when a thread observes |capture_mode_| to | 
|  | // be true through an acquire load, the TLS slot has been initialized. | 
|  | subtle::Release_Store(&capture_mode_, static_cast<int32_t>(mode)); | 
|  | } | 
|  |  | 
|  | void AllocationContextTracker::PushPseudoStackFrame( | 
|  | AllocationContextTracker::PseudoStackFrame stack_frame) { | 
|  | // Impose a limit on the height to verify that every push is popped, because | 
|  | // in practice the pseudo stack never grows higher than ~20 frames. | 
|  | if (tracked_stack_.size() < kMaxStackDepth) { | 
|  | tracked_stack_.push_back( | 
|  | StackFrame::FromTraceEventName(stack_frame.trace_event_name)); | 
|  | } else { | 
|  | NOTREACHED(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void AllocationContextTracker::PopPseudoStackFrame( | 
|  | AllocationContextTracker::PseudoStackFrame stack_frame) { | 
|  | // Guard for stack underflow. If tracing was started with a TRACE_EVENT in | 
|  | // scope, the frame was never pushed, so it is possible that pop is called | 
|  | // on an empty stack. | 
|  | if (tracked_stack_.empty()) | 
|  | return; | 
|  |  | 
|  | tracked_stack_.pop_back(); | 
|  | } | 
|  |  | 
|  | void AllocationContextTracker::PushNativeStackFrame(const void* pc) { | 
|  | if (tracked_stack_.size() < kMaxStackDepth) | 
|  | tracked_stack_.push_back(StackFrame::FromProgramCounter(pc)); | 
|  | else | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | void AllocationContextTracker::PopNativeStackFrame(const void* pc) { | 
|  | if (tracked_stack_.empty()) | 
|  | return; | 
|  |  | 
|  | DCHECK_EQ(pc, tracked_stack_.back().value); | 
|  | tracked_stack_.pop_back(); | 
|  | } | 
|  |  | 
|  | void AllocationContextTracker::PushCurrentTaskContext(const char* context) { | 
|  | DCHECK(context); | 
|  | if (task_contexts_.size() < kMaxTaskDepth) | 
|  | task_contexts_.push_back(context); | 
|  | else | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | void AllocationContextTracker::PopCurrentTaskContext(const char* context) { | 
|  | // Guard for stack underflow. If tracing was started with a TRACE_EVENT in | 
|  | // scope, the context was never pushed, so it is possible that pop is called | 
|  | // on an empty stack. | 
|  | if (task_contexts_.empty()) | 
|  | return; | 
|  |  | 
|  | DCHECK_EQ(context, task_contexts_.back()) | 
|  | << "Encountered an unmatched context end"; | 
|  | task_contexts_.pop_back(); | 
|  | } | 
|  |  | 
|  | bool AllocationContextTracker::GetContextSnapshot(AllocationContext* ctx) { | 
|  | if (ignore_scope_depth_) | 
|  | return false; | 
|  |  | 
|  | CaptureMode mode = static_cast<CaptureMode>( | 
|  | subtle::NoBarrier_Load(&capture_mode_)); | 
|  |  | 
|  | auto* backtrace = std::begin(ctx->backtrace.frames); | 
|  | auto* backtrace_end = std::end(ctx->backtrace.frames); | 
|  |  | 
|  | if (!thread_name_) { | 
|  | // Ignore the string allocation made by GetAndLeakThreadName to avoid | 
|  | // reentrancy. | 
|  | ignore_scope_depth_++; | 
|  | thread_name_ = GetAndLeakThreadName(); | 
|  | ANNOTATE_LEAKING_OBJECT_PTR(thread_name_); | 
|  | DCHECK(thread_name_); | 
|  | ignore_scope_depth_--; | 
|  | } | 
|  |  | 
|  | // Add the thread name as the first entry in pseudo stack. | 
|  | if (thread_name_) { | 
|  | *backtrace++ = StackFrame::FromThreadName(thread_name_); | 
|  | } | 
|  |  | 
|  | switch (mode) { | 
|  | case CaptureMode::DISABLED: | 
|  | { | 
|  | break; | 
|  | } | 
|  | case CaptureMode::PSEUDO_STACK: | 
|  | case CaptureMode::MIXED_STACK: | 
|  | { | 
|  | for (const StackFrame& stack_frame : tracked_stack_) { | 
|  | if (backtrace == backtrace_end) | 
|  | break; | 
|  | *backtrace++ = stack_frame; | 
|  | } | 
|  | break; | 
|  | } | 
|  | case CaptureMode::NATIVE_STACK: | 
|  | { | 
|  | // Backtrace contract requires us to return bottom frames, i.e. | 
|  | // from main() and up. Stack unwinding produces top frames, i.e. | 
|  | // from this point and up until main(). We intentionally request | 
|  | // kMaxFrameCount + 1 frames, so that we know if there are more frames | 
|  | // than our backtrace capacity. | 
|  | #if !defined(OS_NACL)  // We don't build base/debug/stack_trace.cc for NaCl. | 
|  | #if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) | 
|  | const void* frames[Backtrace::kMaxFrameCount + 1]; | 
|  | static_assert(arraysize(frames) >= Backtrace::kMaxFrameCount, | 
|  | "not requesting enough frames to fill Backtrace"); | 
|  | size_t frame_count = | 
|  | CFIBacktraceAndroid::GetInitializedInstance()->Unwind( | 
|  | frames, arraysize(frames)); | 
|  | #elif BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS) | 
|  | const void* frames[Backtrace::kMaxFrameCount + 1]; | 
|  | static_assert(arraysize(frames) >= Backtrace::kMaxFrameCount, | 
|  | "not requesting enough frames to fill Backtrace"); | 
|  | size_t frame_count = debug::TraceStackFramePointers( | 
|  | frames, arraysize(frames), | 
|  | 1 /* exclude this function from the trace */); | 
|  | #else | 
|  | // Fall-back to capturing the stack with base::debug::StackTrace, | 
|  | // which is likely slower, but more reliable. | 
|  | base::debug::StackTrace stack_trace(Backtrace::kMaxFrameCount + 1); | 
|  | size_t frame_count = 0u; | 
|  | const void* const* frames = stack_trace.Addresses(&frame_count); | 
|  | #endif | 
|  |  | 
|  | // If there are too many frames, keep the ones furthest from main(). | 
|  | size_t backtrace_capacity = backtrace_end - backtrace; | 
|  | int32_t starting_frame_index = frame_count; | 
|  | if (frame_count > backtrace_capacity) { | 
|  | starting_frame_index = backtrace_capacity - 1; | 
|  | *backtrace++ = StackFrame::FromTraceEventName("<truncated>"); | 
|  | } | 
|  | for (int32_t i = starting_frame_index - 1; i >= 0; --i) { | 
|  | const void* frame = frames[i]; | 
|  | *backtrace++ = StackFrame::FromProgramCounter(frame); | 
|  | } | 
|  | #endif  // !defined(OS_NACL) | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | ctx->backtrace.frame_count = backtrace - std::begin(ctx->backtrace.frames); | 
|  |  | 
|  | // TODO(ssid): Fix crbug.com/594803 to add file name as 3rd dimension | 
|  | // (component name) in the heap profiler and not piggy back on the type name. | 
|  | if (!task_contexts_.empty()) { | 
|  | ctx->type_name = task_contexts_.back(); | 
|  | } else { | 
|  | ctx->type_name = nullptr; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // namespace trace_event | 
|  | }  // namespace base |