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