1// Copyright 2015 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "base/trace_event/heap_profiler_allocation_context_tracker.h" 6 7#include <algorithm> 8#include <iterator> 9 10#include "base/atomicops.h" 11#include "base/debug/leak_annotations.h" 12#include "base/threading/platform_thread.h" 13#include "base/threading/thread_local_storage.h" 14#include "base/trace_event/heap_profiler_allocation_context.h" 15 16#if defined(OS_LINUX) || defined(OS_ANDROID) 17#include <sys/prctl.h> 18#endif 19 20namespace base { 21namespace trace_event { 22 23subtle::Atomic32 AllocationContextTracker::capture_mode_ = 24 static_cast<int32_t>(AllocationContextTracker::CaptureMode::DISABLED); 25 26namespace { 27 28const size_t kMaxStackDepth = 128u; 29const size_t kMaxTaskDepth = 16u; 30AllocationContextTracker* const kInitializingSentinel = 31 reinterpret_cast<AllocationContextTracker*>(-1); 32 33ThreadLocalStorage::StaticSlot g_tls_alloc_ctx_tracker = TLS_INITIALIZER; 34 35// This function is added to the TLS slot to clean up the instance when the 36// thread exits. 37void DestructAllocationContextTracker(void* alloc_ctx_tracker) { 38 delete static_cast<AllocationContextTracker*>(alloc_ctx_tracker); 39} 40 41// Cannot call ThreadIdNameManager::GetName because it holds a lock and causes 42// deadlock when lock is already held by ThreadIdNameManager before the current 43// allocation. Gets the thread name from kernel if available or returns a string 44// with id. This function intenionally leaks the allocated strings since they 45// are used to tag allocations even after the thread dies. 46const char* GetAndLeakThreadName() { 47 char name[16]; 48#if defined(OS_LINUX) || defined(OS_ANDROID) 49 // If the thread name is not set, try to get it from prctl. Thread name might 50 // not be set in cases where the thread started before heap profiling was 51 // enabled. 52 int err = prctl(PR_GET_NAME, name); 53 if (!err) { 54 return strdup(name); 55 } 56#endif // defined(OS_LINUX) || defined(OS_ANDROID) 57 58 // Use tid if we don't have a thread name. 59 snprintf(name, sizeof(name), "%lu", 60 static_cast<unsigned long>(PlatformThread::CurrentId())); 61 return strdup(name); 62} 63 64} // namespace 65 66// static 67AllocationContextTracker* 68AllocationContextTracker::GetInstanceForCurrentThread() { 69 AllocationContextTracker* tracker = 70 static_cast<AllocationContextTracker*>(g_tls_alloc_ctx_tracker.Get()); 71 if (tracker == kInitializingSentinel) 72 return nullptr; // Re-entrancy case. 73 74 if (!tracker) { 75 g_tls_alloc_ctx_tracker.Set(kInitializingSentinel); 76 tracker = new AllocationContextTracker(); 77 g_tls_alloc_ctx_tracker.Set(tracker); 78 } 79 80 return tracker; 81} 82 83AllocationContextTracker::AllocationContextTracker() 84 : thread_name_(nullptr), ignore_scope_depth_(0) { 85 pseudo_stack_.reserve(kMaxStackDepth); 86 task_contexts_.reserve(kMaxTaskDepth); 87} 88AllocationContextTracker::~AllocationContextTracker() {} 89 90// static 91void AllocationContextTracker::SetCurrentThreadName(const char* name) { 92 if (name && capture_mode() != CaptureMode::DISABLED) { 93 GetInstanceForCurrentThread()->thread_name_ = name; 94 } 95} 96 97// static 98void AllocationContextTracker::SetCaptureMode(CaptureMode mode) { 99 // When enabling capturing, also initialize the TLS slot. This does not create 100 // a TLS instance yet. 101 if (mode != CaptureMode::DISABLED && !g_tls_alloc_ctx_tracker.initialized()) 102 g_tls_alloc_ctx_tracker.Initialize(DestructAllocationContextTracker); 103 104 // Release ordering ensures that when a thread observes |capture_mode_| to 105 // be true through an acquire load, the TLS slot has been initialized. 106 subtle::Release_Store(&capture_mode_, static_cast<int32_t>(mode)); 107} 108 109void AllocationContextTracker::PushPseudoStackFrame( 110 AllocationContextTracker::PseudoStackFrame stack_frame) { 111 // Impose a limit on the height to verify that every push is popped, because 112 // in practice the pseudo stack never grows higher than ~20 frames. 113 if (pseudo_stack_.size() < kMaxStackDepth) 114 pseudo_stack_.push_back(stack_frame); 115 else 116 NOTREACHED(); 117} 118 119void AllocationContextTracker::PopPseudoStackFrame( 120 AllocationContextTracker::PseudoStackFrame stack_frame) { 121 // Guard for stack underflow. If tracing was started with a TRACE_EVENT in 122 // scope, the frame was never pushed, so it is possible that pop is called 123 // on an empty stack. 124 if (pseudo_stack_.empty()) 125 return; 126 127 // Assert that pushes and pops are nested correctly. This DCHECK can be 128 // hit if some TRACE_EVENT macro is unbalanced (a TRACE_EVENT_END* call 129 // without a corresponding TRACE_EVENT_BEGIN). 130 DCHECK(stack_frame == pseudo_stack_.back()) 131 << "Encountered an unmatched TRACE_EVENT_END: " 132 << stack_frame.trace_event_name 133 << " vs event in stack: " << pseudo_stack_.back().trace_event_name; 134 135 pseudo_stack_.pop_back(); 136} 137 138void AllocationContextTracker::PushCurrentTaskContext(const char* context) { 139 DCHECK(context); 140 if (task_contexts_.size() < kMaxTaskDepth) 141 task_contexts_.push_back(context); 142 else 143 NOTREACHED(); 144} 145 146void AllocationContextTracker::PopCurrentTaskContext(const char* context) { 147 // Guard for stack underflow. If tracing was started with a TRACE_EVENT in 148 // scope, the context was never pushed, so it is possible that pop is called 149 // on an empty stack. 150 if (task_contexts_.empty()) 151 return; 152 153 DCHECK_EQ(context, task_contexts_.back()) 154 << "Encountered an unmatched context end"; 155 task_contexts_.pop_back(); 156} 157 158// static 159bool AllocationContextTracker::GetContextSnapshot(AllocationContext* ctx) { 160 if (ignore_scope_depth_) 161 return false; 162 163 CaptureMode mode = static_cast<CaptureMode>( 164 subtle::NoBarrier_Load(&capture_mode_)); 165 166 auto* backtrace = std::begin(ctx->backtrace.frames); 167 auto* backtrace_end = std::end(ctx->backtrace.frames); 168 169 if (!thread_name_) { 170 // Ignore the string allocation made by GetAndLeakThreadName to avoid 171 // reentrancy. 172 ignore_scope_depth_++; 173 thread_name_ = GetAndLeakThreadName(); 174 ANNOTATE_LEAKING_OBJECT_PTR(thread_name_); 175 DCHECK(thread_name_); 176 ignore_scope_depth_--; 177 } 178 179 // Add the thread name as the first entry in pseudo stack. 180 if (thread_name_) { 181 *backtrace++ = StackFrame::FromThreadName(thread_name_); 182 } 183 184 switch (mode) { 185 case CaptureMode::DISABLED: 186 { 187 break; 188 } 189 case CaptureMode::PSEUDO_STACK: 190 { 191 for (const PseudoStackFrame& stack_frame : pseudo_stack_) { 192 if (backtrace == backtrace_end) { 193 break; 194 } 195 *backtrace++ = 196 StackFrame::FromTraceEventName(stack_frame.trace_event_name); 197 } 198 break; 199 } 200 case CaptureMode::NATIVE_STACK: 201 { 202 // Backtrace contract requires us to return bottom frames, i.e. 203 // from main() and up. Stack unwinding produces top frames, i.e. 204 // from this point and up until main(). We request many frames to 205 // make sure we reach main(), and then copy bottom portion of them. 206 const void* frames[128]; 207 static_assert(arraysize(frames) >= Backtrace::kMaxFrameCount, 208 "not requesting enough frames to fill Backtrace"); 209#if HAVE_TRACE_STACK_FRAME_POINTERS && !defined(OS_NACL) 210 size_t frame_count = debug::TraceStackFramePointers( 211 frames, 212 arraysize(frames), 213 1 /* exclude this function from the trace */ ); 214#else 215 size_t frame_count = 0; 216 NOTREACHED(); 217#endif 218 219 // Copy frames backwards 220 size_t backtrace_capacity = backtrace_end - backtrace; 221 int32_t top_frame_index = (backtrace_capacity >= frame_count) 222 ? 0 223 : frame_count - backtrace_capacity; 224 for (int32_t i = frame_count - 1; i >= top_frame_index; --i) { 225 const void* frame = frames[i]; 226 *backtrace++ = StackFrame::FromProgramCounter(frame); 227 } 228 break; 229 } 230 } 231 232 ctx->backtrace.frame_count = backtrace - std::begin(ctx->backtrace.frames); 233 234 // TODO(ssid): Fix crbug.com/594803 to add file name as 3rd dimension 235 // (component name) in the heap profiler and not piggy back on the type name. 236 if (!task_contexts_.empty()) { 237 ctx->type_name = task_contexts_.back(); 238 } else if (!pseudo_stack_.empty()) { 239 // If task context was unavailable, then the category names are taken from 240 // trace events. 241 ctx->type_name = pseudo_stack_.back().trace_event_category; 242 } else { 243 ctx->type_name = nullptr; 244 } 245 246 return true; 247} 248 249} // namespace trace_event 250} // namespace base 251