• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9 
10 #include "base/sampling_heap_profiler/sampling_heap_profiler.h"
11 
12 #include <algorithm>
13 #include <cmath>
14 #include <utility>
15 
16 #include "base/allocator/dispatcher/tls.h"
17 #include "base/compiler_specific.h"
18 #include "base/containers/to_vector.h"
19 #include "base/debug/stack_trace.h"
20 #include "base/functional/bind.h"
21 #include "base/logging.h"
22 #include "base/metrics/histogram_functions.h"
23 #include "base/no_destructor.h"
24 #include "base/notreached.h"
25 #include "base/sampling_heap_profiler/lock_free_address_hash_set.h"
26 #include "base/sampling_heap_profiler/poisson_allocation_sampler.h"
27 #include "base/threading/thread_local_storage.h"
28 #include "base/trace_event/heap_profiler_allocation_context_tracker.h"  // no-presubmit-check
29 #include "build/build_config.h"
30 #include "partition_alloc/shim/allocator_shim.h"
31 
32 #if PA_BUILDFLAG(USE_PARTITION_ALLOC)
33 #include "partition_alloc/partition_alloc.h"  // nogncheck
34 #endif
35 
36 #if BUILDFLAG(IS_APPLE)
37 #include <pthread.h>
38 #endif
39 
40 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
41 #include <sys/prctl.h>
42 #endif
43 
44 namespace base {
45 
46 constexpr uint32_t kMaxStackEntries = 256;
47 
48 namespace {
49 
50 struct ThreadLocalData {
51   const char* thread_name = nullptr;
52 };
53 
GetThreadLocalData()54 ThreadLocalData* GetThreadLocalData() {
55 #if USE_LOCAL_TLS_EMULATION()
56   static base::NoDestructor<
57       base::allocator::dispatcher::ThreadLocalStorage<ThreadLocalData>>
58       thread_local_data("sampling_heap_profiler");
59   return thread_local_data->GetThreadLocalData();
60 #else
61   static thread_local ThreadLocalData thread_local_data;
62   return &thread_local_data;
63 #endif
64 }
65 
66 using StackUnwinder = SamplingHeapProfiler::StackUnwinder;
67 using base::allocator::dispatcher::AllocationSubsystem;
68 
69 // If a thread name has been set from ThreadIdNameManager, use that. Otherwise,
70 // gets the thread name from kernel if available or returns a string with id.
71 // This function intentionally leaks the allocated strings since they are used
72 // to tag allocations even after the thread dies.
GetAndLeakThreadName()73 const char* GetAndLeakThreadName() {
74   const char* thread_name =
75       base::ThreadIdNameManager::GetInstance()->GetNameForCurrentThread();
76   if (thread_name && *thread_name != '\0')
77     return thread_name;
78 
79   // prctl requires 16 bytes, snprintf requires 19, pthread_getname_np requires
80   // 64 on macOS, see PlatformThread::SetName in platform_thread_apple.mm.
81   constexpr size_t kBufferLen = 64;
82   char name[kBufferLen];
83 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
84   // If the thread name is not set, try to get it from prctl. Thread name might
85   // not be set in cases where the thread started before heap profiling was
86   // enabled.
87   int err = prctl(PR_GET_NAME, name);
88   if (!err)
89     return strdup(name);
90 #elif BUILDFLAG(IS_APPLE)
91   int err = pthread_getname_np(pthread_self(), name, kBufferLen);
92   if (err == 0 && *name != '\0')
93     return strdup(name);
94 #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) ||
95         // BUILDFLAG(IS_ANDROID)
96 
97   // Use tid if we don't have a thread name.
98   snprintf(name, sizeof(name), "Thread %lu",
99            static_cast<unsigned long>(base::PlatformThread::CurrentId()));
100   return strdup(name);
101 }
102 
UpdateAndGetThreadName(const char * name)103 const char* UpdateAndGetThreadName(const char* name) {
104   ThreadLocalData* const thread_local_data = GetThreadLocalData();
105   if (name)
106     thread_local_data->thread_name = name;
107   if (!thread_local_data->thread_name) {
108     thread_local_data->thread_name = GetAndLeakThreadName();
109   }
110   return thread_local_data->thread_name;
111 }
112 
113 // Checks whether unwinding from this function works.
CheckForDefaultUnwindTables()114 [[maybe_unused]] StackUnwinder CheckForDefaultUnwindTables() {
115   const void* stack[kMaxStackEntries];
116   size_t frame_count = base::debug::CollectStackTrace(stack);
117   // First frame is the current function and can be found without unwind tables.
118   return frame_count > 1 ? StackUnwinder::kDefault
119                          : StackUnwinder::kUnavailable;
120 }
121 
ChooseStackUnwinder()122 StackUnwinder ChooseStackUnwinder() {
123 #if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
124   // Use frame pointers if available, since they can be faster than the default.
125   return StackUnwinder::kFramePointers;
126 #elif BUILDFLAG(IS_ANDROID)
127   // Default unwind tables aren't always present on Android.
128   return CheckForDefaultUnwindTables();
129 #else
130   return StackUnwinder::kDefault;
131 #endif
132 }
133 
134 }  // namespace
135 
Sample(size_t size,size_t total,uint32_t ordinal)136 SamplingHeapProfiler::Sample::Sample(size_t size,
137                                      size_t total,
138                                      uint32_t ordinal)
139     : size(size), total(total), ordinal(ordinal) {}
140 
141 SamplingHeapProfiler::Sample::Sample(const Sample&) = default;
142 SamplingHeapProfiler::Sample::~Sample() = default;
143 
144 SamplingHeapProfiler::SamplingHeapProfiler() = default;
~SamplingHeapProfiler()145 SamplingHeapProfiler::~SamplingHeapProfiler() {
146   if (record_thread_names_.load(std::memory_order_acquire)) {
147     base::ThreadIdNameManager::GetInstance()->RemoveObserver(this);
148   }
149 }
150 
Start()151 uint32_t SamplingHeapProfiler::Start() {
152   const auto unwinder = ChooseStackUnwinder();
153 #if BUILDFLAG(IS_ANDROID)
154   // Record which unwinder is in use on Android, since it's hard to keep track
155   // of which methods are available at runtime.
156   base::UmaHistogramEnumeration("HeapProfiling.AndroidStackUnwinder", unwinder);
157 #endif
158   if (unwinder == StackUnwinder::kUnavailable) {
159     LOG(WARNING) << "Sampling heap profiler: Stack unwinding is not available.";
160     return 0;
161   }
162   unwinder_.store(unwinder, std::memory_order_release);
163 
164   AutoLock lock(start_stop_mutex_);
165   if (!running_sessions_++)
166     PoissonAllocationSampler::Get()->AddSamplesObserver(this);
167   return last_sample_ordinal_.load(std::memory_order_acquire);
168 }
169 
Stop()170 void SamplingHeapProfiler::Stop() {
171   AutoLock lock(start_stop_mutex_);
172   DCHECK_GT(running_sessions_, 0);
173   if (!--running_sessions_)
174     PoissonAllocationSampler::Get()->RemoveSamplesObserver(this);
175 }
176 
SetSamplingInterval(size_t sampling_interval_bytes)177 void SamplingHeapProfiler::SetSamplingInterval(size_t sampling_interval_bytes) {
178   PoissonAllocationSampler::Get()->SetSamplingInterval(sampling_interval_bytes);
179 }
180 
EnableRecordThreadNames()181 void SamplingHeapProfiler::EnableRecordThreadNames() {
182   bool was_enabled = record_thread_names_.exchange(/*desired=*/true,
183                                                    std::memory_order_acq_rel);
184   if (!was_enabled) {
185     base::ThreadIdNameManager::GetInstance()->AddObserver(this);
186   }
187 }
188 
189 // static
CachedThreadName()190 const char* SamplingHeapProfiler::CachedThreadName() {
191   return UpdateAndGetThreadName(nullptr);
192 }
193 
CaptureStackTrace(span<const void * > frames)194 span<const void*> SamplingHeapProfiler::CaptureStackTrace(
195     span<const void*> frames) {
196   size_t skip_frames = 0;
197   size_t frame_count = 0;
198   switch (unwinder_.load(std::memory_order_acquire)) {
199 #if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
200     case StackUnwinder::kFramePointers:
201       frame_count = base::debug::TraceStackFramePointers(frames, skip_frames);
202       return frames.first(frame_count);
203 #endif
204     case StackUnwinder::kDefault:
205       // Fall-back to capturing the stack with base::debug::CollectStackTrace,
206       // which is likely slower, but more reliable.
207       frame_count = base::debug::CollectStackTrace(frames);
208       // Skip top frames as they correspond to the profiler itself.
209       skip_frames = std::min(frame_count, size_t{3});
210       return frames.first(frame_count).subspan(skip_frames);
211     default:
212       // Profiler should not be started if ChooseStackUnwinder() returns
213       // anything else.
214       NOTREACHED();
215   }
216 }
217 
SampleAdded(void * address,size_t size,size_t total,AllocationSubsystem type,const char * context)218 void SamplingHeapProfiler::SampleAdded(void* address,
219                                        size_t size,
220                                        size_t total,
221                                        AllocationSubsystem type,
222                                        const char* context) {
223   // CaptureStack and allocation context tracking may use TLS.
224   // Bail out if it has been destroyed.
225   if (base::ThreadLocalStorage::HasBeenDestroyed()) [[unlikely]] {
226     return;
227   }
228   DCHECK(PoissonAllocationSampler::ScopedMuteThreadSamples::IsMuted());
229   uint32_t previous_last =
230       last_sample_ordinal_.fetch_add(1, std::memory_order_acq_rel);
231   Sample sample(size, total, previous_last + 1);
232   sample.allocator = type;
233   CaptureNativeStack(context, &sample);
234   AutoLock lock(mutex_);
235   if (PoissonAllocationSampler::AreHookedSamplesMuted() &&
236       type != AllocationSubsystem::kManualForTesting) [[unlikely]] {
237     // Throw away any non-test samples that were being collected before
238     // ScopedMuteHookedSamplesForTesting was enabled. This is done inside the
239     // lock to catch any samples that were being collected while
240     // MuteHookedSamplesForTesting is running.
241     return;
242   }
243   RecordString(sample.context);
244 
245   // If a sample is already present with the same address, then that means that
246   // the sampling heap profiler failed to observe the destruction -- possibly
247   // because the sampling heap profiler was temporarily disabled. We should
248   // override the old entry.
249   samples_.insert_or_assign(address, std::move(sample));
250 }
251 
CaptureNativeStack(const char * context,Sample * sample)252 void SamplingHeapProfiler::CaptureNativeStack(const char* context,
253                                               Sample* sample) {
254   const void* stack[kMaxStackEntries];
255   span<const void*> frames = CaptureStackTrace(
256       // One frame is reserved for the thread name.
257       base::span(stack).first(kMaxStackEntries - 1));
258   sample->stack = ToVector(frames);
259 
260   if (record_thread_names_.load(std::memory_order_acquire)) {
261     sample->thread_name = CachedThreadName();
262   }
263 
264   if (!context) {
265     const auto* tracker =
266         trace_event::AllocationContextTracker::GetInstanceForCurrentThread();
267     if (tracker)
268       context = tracker->TaskContext();
269   }
270   sample->context = context;
271 }
272 
RecordString(const char * string)273 const char* SamplingHeapProfiler::RecordString(const char* string) {
274   return string ? *strings_.insert(string).first : nullptr;
275 }
276 
SampleRemoved(void * address)277 void SamplingHeapProfiler::SampleRemoved(void* address) {
278   DCHECK(base::PoissonAllocationSampler::ScopedMuteThreadSamples::IsMuted());
279   base::AutoLock lock(mutex_);
280   samples_.erase(address);
281 }
282 
GetSamples(uint32_t profile_id)283 std::vector<SamplingHeapProfiler::Sample> SamplingHeapProfiler::GetSamples(
284     uint32_t profile_id) {
285   // Make sure the sampler does not invoke |SampleAdded| or |SampleRemoved|
286   // on this thread. Otherwise it could have end up with a deadlock.
287   // See crbug.com/882495
288   PoissonAllocationSampler::ScopedMuteThreadSamples no_samples_scope;
289   AutoLock lock(mutex_);
290   std::vector<Sample> samples;
291   samples.reserve(samples_.size());
292   for (auto& it : samples_) {
293     Sample& sample = it.second;
294     if (sample.ordinal > profile_id)
295       samples.push_back(sample);
296   }
297   return samples;
298 }
299 
GetStrings()300 std::vector<const char*> SamplingHeapProfiler::GetStrings() {
301   PoissonAllocationSampler::ScopedMuteThreadSamples no_samples_scope;
302   AutoLock lock(mutex_);
303   return std::vector<const char*>(strings_.begin(), strings_.end());
304 }
305 
306 // static
Init()307 void SamplingHeapProfiler::Init() {
308   GetThreadLocalData();
309   PoissonAllocationSampler::Init();
310 }
311 
312 // static
Get()313 SamplingHeapProfiler* SamplingHeapProfiler::Get() {
314   static NoDestructor<SamplingHeapProfiler> instance;
315   return instance.get();
316 }
317 
OnThreadNameChanged(const char * name)318 void SamplingHeapProfiler::OnThreadNameChanged(const char* name) {
319   UpdateAndGetThreadName(name);
320 }
321 
322 PoissonAllocationSampler::ScopedMuteHookedSamplesForTesting
MuteHookedSamplesForTesting()323 SamplingHeapProfiler::MuteHookedSamplesForTesting() {
324   // Only one ScopedMuteHookedSamplesForTesting can exist at a time.
325   CHECK(!PoissonAllocationSampler::AreHookedSamplesMuted());
326   PoissonAllocationSampler::ScopedMuteHookedSamplesForTesting
327       mute_hooked_samples;
328 
329   base::AutoLock lock(mutex_);
330   samples_.clear();
331   // Since hooked samples are muted, any samples that are waiting to take the
332   // lock in SampleAdded will be discarded. Tests can now call
333   // PoissonAllocationSampler::RecordAlloc with allocator type kManualForTesting
334   // to add samples cleanly.
335   return mute_hooked_samples;
336 }
337 
338 }  // namespace base
339