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