xref: /aosp_15_r20/external/perfetto/src/profiling/memory/client_api.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "perfetto/heap_profile.h"
18 #include "src/profiling/memory/heap_profile_internal.h"
19 
20 #include <malloc.h>
21 #include <stddef.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include <unistd.h>
27 
28 #include <atomic>
29 #include <cinttypes>
30 #include <memory>
31 #include <type_traits>
32 
33 #include "perfetto/base/build_config.h"
34 #include "perfetto/base/logging.h"
35 #include "perfetto/ext/base/string_utils.h"
36 #include "perfetto/ext/base/unix_socket.h"
37 #include "perfetto/ext/base/utils.h"
38 
39 #include "src/profiling/memory/client.h"
40 #include "src/profiling/memory/client_api_factory.h"
41 #include "src/profiling/memory/scoped_spinlock.h"
42 #include "src/profiling/memory/unhooked_allocator.h"
43 #include "src/profiling/memory/wire_protocol.h"
44 
45 struct AHeapInfo {
46   // Fields set by user.
47   char heap_name[HEAPPROFD_HEAP_NAME_SZ];
48   void (*enabled_callback)(void*, const AHeapProfileEnableCallbackInfo*);
49   void (*disabled_callback)(void*, const AHeapProfileDisableCallbackInfo*);
50   void* enabled_callback_data;
51   void* disabled_callback_data;
52 
53   // Internal fields.
54   perfetto::profiling::Sampler sampler;
55   std::atomic<bool> ready;
56   std::atomic<bool> enabled;
57   std::atomic<uint64_t> adaptive_sampling_shmem_threshold;
58   std::atomic<uint64_t> adaptive_sampling_max_sampling_interval_bytes;
59 };
60 
61 struct AHeapProfileEnableCallbackInfo {
62   uint64_t sampling_interval;
63 };
64 
65 struct AHeapProfileDisableCallbackInfo {};
66 
67 namespace {
68 
69 using perfetto::profiling::ScopedSpinlock;
70 using perfetto::profiling::UnhookedAllocator;
71 
72 #if defined(__GLIBC__)
getprogname()73 const char* getprogname() {
74   return program_invocation_short_name;
75 }
76 #elif !defined(__BIONIC__)
getprogname()77 const char* getprogname() {
78   return "";
79 }
80 #endif
81 
82 // Holds the active profiling client. Is empty at the start, or after we've
83 // started shutting down a profiling session. Hook invocations take shared_ptr
84 // copies (ensuring that the client stays alive until no longer needed), and do
85 // nothing if this primary pointer is empty.
86 //
87 // This shared_ptr itself is protected by g_client_lock. Note that shared_ptr
88 // handles are not thread-safe by themselves:
89 // https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic
90 //
91 // To avoid on-destruction re-entrancy issues, this shared_ptr needs to be
92 // constructed with an allocator that uses the unhooked malloc & free functions.
93 // See UnhookedAllocator.
94 //
95 // We initialize this storage the first time GetClientLocked is called. We
96 // cannot use a static initializer because that leads to ordering problems
97 // of the ELF's constructors.
98 
99 alignas(std::shared_ptr<perfetto::profiling::Client>) char g_client_arr[sizeof(
100     std::shared_ptr<perfetto::profiling::Client>)];
101 
102 bool g_client_init;
103 
GetClientLocked()104 std::shared_ptr<perfetto::profiling::Client>* GetClientLocked() {
105   if (!g_client_init) {
106     new (g_client_arr) std::shared_ptr<perfetto::profiling::Client>;
107     g_client_init = true;
108   }
109   return reinterpret_cast<std::shared_ptr<perfetto::profiling::Client>*>(
110       &g_client_arr);
111 }
112 
113 constexpr auto kMinHeapId = 1;
114 constexpr auto kMaxNumHeaps = 256;
115 
116 AHeapInfo g_heaps[kMaxNumHeaps] = {};
117 
GetHeap(uint32_t id)118 AHeapInfo& GetHeap(uint32_t id) {
119   return g_heaps[id];
120 }
121 
122 // Protects g_client, and serves as an external lock for sampling decisions (see
123 // perfetto::profiling::Sampler).
124 //
125 // We rely on this atomic's destuction being a nop, as it is possible for the
126 // hooks to attempt to acquire the spinlock after its destructor should have run
127 // (technically a use-after-destruct scenario).
128 static_assert(
129     std::is_trivially_destructible<perfetto::profiling::Spinlock>::value,
130     "lock must be trivially destructible.");
131 perfetto::profiling::Spinlock g_client_lock{};
132 
133 std::atomic<uint32_t> g_next_heap_id{kMinHeapId};
134 
135 // This can get called while holding the spinlock (in normal operation), or
136 // without holding the spinlock (from OnSpinlockTimeout).
DisableAllHeaps()137 void DisableAllHeaps() {
138   bool disabled[kMaxNumHeaps] = {};
139   uint32_t max_heap = g_next_heap_id.load();
140   // This has to be done in two passes, in case the disabled_callback for one
141   // enabled heap uses another. In that case, the callbacks for the other heap
142   // would time out trying to acquire the spinlock, which we hold here.
143   for (uint32_t i = kMinHeapId; i < max_heap; ++i) {
144     AHeapInfo& info = GetHeap(i);
145     if (!info.ready.load(std::memory_order_acquire))
146       continue;
147     disabled[i] = info.enabled.exchange(false, std::memory_order_acq_rel);
148   }
149   for (uint32_t i = kMinHeapId; i < max_heap; ++i) {
150     if (!disabled[i]) {
151       continue;
152     }
153     AHeapInfo& info = GetHeap(i);
154     if (info.disabled_callback) {
155       AHeapProfileDisableCallbackInfo disable_info;
156       info.disabled_callback(info.disabled_callback_data, &disable_info);
157     }
158   }
159 }
160 
161 #pragma GCC diagnostic push
162 #if PERFETTO_DCHECK_IS_ON()
163 #pragma GCC diagnostic ignored "-Wmissing-noreturn"
164 #endif
165 
OnSpinlockTimeout()166 void OnSpinlockTimeout() {
167   // Give up on profiling the process but leave it running.
168   // The process enters into a poisoned state and will reject all
169   // subsequent profiling requests.  The current session is kept
170   // running but no samples are reported to it.
171   PERFETTO_DFATAL_OR_ELOG(
172       "Timed out on the spinlock - something is horribly wrong. "
173       "Leaking heapprofd client.");
174   DisableAllHeaps();
175   perfetto::profiling::PoisonSpinlock(&g_client_lock);
176 }
177 #pragma GCC diagnostic pop
178 
179 // Note: g_client can be reset by AHeapProfile_initSession without calling this
180 // function.
ShutdownLazy(const std::shared_ptr<perfetto::profiling::Client> & client)181 void ShutdownLazy(const std::shared_ptr<perfetto::profiling::Client>& client) {
182   ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try);
183   if (PERFETTO_UNLIKELY(!s.locked())) {
184     OnSpinlockTimeout();
185     return;
186   }
187 
188   // other invocation already initiated shutdown
189   if (*GetClientLocked() != client)
190     return;
191 
192   DisableAllHeaps();
193   // Clear primary shared pointer, such that later hook invocations become nops.
194   GetClientLocked()->reset();
195 }
196 
MaybeToggleHeap(uint32_t heap_id,perfetto::profiling::Client * client)197 uint64_t MaybeToggleHeap(uint32_t heap_id,
198                          perfetto::profiling::Client* client) {
199   AHeapInfo& heap = GetHeap(heap_id);
200   if (!heap.ready.load(std::memory_order_acquire))
201     return 0;
202   auto interval =
203       GetHeapSamplingInterval(client->client_config(), heap.heap_name);
204   // The callbacks must be called while NOT LOCKED. Because they run
205   // arbitrary code, it would be very easy to build a deadlock.
206   if (interval) {
207     AHeapProfileEnableCallbackInfo session_info{interval};
208     if (!heap.enabled.load(std::memory_order_acquire) &&
209         heap.enabled_callback) {
210       heap.enabled_callback(heap.enabled_callback_data, &session_info);
211     }
212     heap.adaptive_sampling_shmem_threshold.store(
213         client->client_config().adaptive_sampling_shmem_threshold,
214         std::memory_order_relaxed);
215     heap.adaptive_sampling_max_sampling_interval_bytes.store(
216         client->client_config().adaptive_sampling_max_sampling_interval_bytes,
217         std::memory_order_relaxed);
218     heap.enabled.store(true, std::memory_order_release);
219     client->RecordHeapInfo(heap_id, &heap.heap_name[0], interval);
220   } else if (heap.enabled.load(std::memory_order_acquire)) {
221     heap.enabled.store(false, std::memory_order_release);
222     if (heap.disabled_callback) {
223       AHeapProfileDisableCallbackInfo info;
224       heap.disabled_callback(heap.disabled_callback_data, &info);
225     }
226   }
227   return interval;
228 }
229 
230 // We're a library loaded into a potentially-multithreaded process, which might
231 // not be explicitly aware of this possiblity. Deadling with forks/clones is
232 // extremely complicated in such situations, but we attempt to handle certain
233 // cases.
234 //
235 // There are two classes of forking processes to consider:
236 //  * well-behaved processes that fork only when their threads (if any) are at a
237 //    safe point, and therefore not in the middle of our hooks/client.
238 //  * processes that fork with other threads in an arbitrary state. Though
239 //    technically buggy, such processes exist in practice.
240 //
241 // This atfork handler follows a crude lowest-common-denominator approach, where
242 // to handle the latter class of processes, we systematically leak any |Client|
243 // state (present only when actively profiling at the time of fork) in the
244 // postfork-child path.
245 //
246 // The alternative with acquiring all relevant locks in the prefork handler, and
247 // releasing the state postfork handlers, poses a separate class of edge cases,
248 // and is not deemed to be better as a result.
249 //
250 // Notes:
251 // * this atfork handler fires only for the |fork| libc entrypoint, *not*
252 //   |clone|. See client.cc's |IsPostFork| for some best-effort detection
253 //   mechanisms for clone/vfork.
254 // * it should be possible to start a new profiling session in this child
255 //   process, modulo the bionic's heapprofd-loading state machine being in the
256 //   right state.
257 // * we cannot avoid leaks in all cases anyway (e.g. during shutdown sequence,
258 //   when only individual straggler threads hold onto the Client).
AtForkChild()259 void AtForkChild() {
260   PERFETTO_LOG("heapprofd_client: handling atfork.");
261 
262   // A thread (that has now disappeared across the fork) could have been holding
263   // the spinlock. We're now the only thread post-fork, so we can reset the
264   // spinlock, though the state it protects (the |g_client| shared_ptr) might
265   // not be in a consistent state.
266   g_client_lock.locked.store(false);
267   g_client_lock.poisoned.store(false);
268 
269   // We must not call the disabled callbacks here, because they might require
270   // locks that are being held at the fork point.
271   for (uint32_t i = kMinHeapId; i < g_next_heap_id.load(); ++i) {
272     AHeapInfo& info = GetHeap(i);
273     info.enabled.store(false);
274   }
275   // Leak the existing shared_ptr contents, including the profiling |Client| if
276   // profiling was active at the time of the fork.
277   // Note: this code assumes that the creation of the empty shared_ptr does not
278   // allocate, which should be the case for all implementations as the
279   // constructor has to be noexcept.
280   new (g_client_arr) std::shared_ptr<perfetto::profiling::Client>();
281 }
282 
283 }  // namespace
284 
285 __attribute__((visibility("default"))) uint64_t
AHeapProfileEnableCallbackInfo_getSamplingInterval(const AHeapProfileEnableCallbackInfo * session_info)286 AHeapProfileEnableCallbackInfo_getSamplingInterval(
287     const AHeapProfileEnableCallbackInfo* session_info) {
288   return session_info->sampling_interval;
289 }
290 
AHeapInfo_create(const char * heap_name)291 __attribute__((visibility("default"))) AHeapInfo* AHeapInfo_create(
292     const char* heap_name) {
293   size_t len = strlen(heap_name);
294   if (len >= sizeof(AHeapInfo::heap_name)) {
295     return nullptr;
296   }
297 
298   uint32_t next_id = g_next_heap_id.fetch_add(1);
299   if (next_id >= kMaxNumHeaps) {
300     return nullptr;
301   }
302 
303   if (next_id == kMinHeapId)
304     perfetto::profiling::StartHeapprofdIfStatic();
305 
306   AHeapInfo& info = GetHeap(next_id);
307   perfetto::base::StringCopy(info.heap_name, heap_name, sizeof(info.heap_name));
308   return &info;
309 }
310 
AHeapInfo_setEnabledCallback(AHeapInfo * info,void (* callback)(void *,const AHeapProfileEnableCallbackInfo *),void * data)311 __attribute__((visibility("default"))) AHeapInfo* AHeapInfo_setEnabledCallback(
312     AHeapInfo* info,
313     void (*callback)(void*, const AHeapProfileEnableCallbackInfo*),
314     void* data) {
315   if (info == nullptr)
316     return nullptr;
317   if (info->ready.load(std::memory_order_relaxed)) {
318     PERFETTO_ELOG(
319         "AHeapInfo_setEnabledCallback called after heap was registered. "
320         "This is always a bug.");
321     return nullptr;
322   }
323   info->enabled_callback = callback;
324   info->enabled_callback_data = data;
325   return info;
326 }
327 
AHeapInfo_setDisabledCallback(AHeapInfo * info,void (* callback)(void *,const AHeapProfileDisableCallbackInfo *),void * data)328 __attribute__((visibility("default"))) AHeapInfo* AHeapInfo_setDisabledCallback(
329     AHeapInfo* info,
330     void (*callback)(void*, const AHeapProfileDisableCallbackInfo*),
331     void* data) {
332   if (info == nullptr)
333     return nullptr;
334   if (info->ready.load(std::memory_order_relaxed)) {
335     PERFETTO_ELOG(
336         "AHeapInfo_setDisabledCallback called after heap was registered. "
337         "This is always a bug.");
338     return nullptr;
339   }
340   info->disabled_callback = callback;
341   info->disabled_callback_data = data;
342   return info;
343 }
344 
AHeapProfile_registerHeap(AHeapInfo * info)345 __attribute__((visibility("default"))) uint32_t AHeapProfile_registerHeap(
346     AHeapInfo* info) {
347   if (info == nullptr)
348     return 0;
349   info->ready.store(true, std::memory_order_release);
350   auto heap_id = static_cast<uint32_t>(info - &g_heaps[0]);
351   std::shared_ptr<perfetto::profiling::Client> client;
352   {
353     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try);
354     if (PERFETTO_UNLIKELY(!s.locked())) {
355       OnSpinlockTimeout();
356       return 0;
357     }
358 
359     client = *GetClientLocked();
360   }
361 
362   // Enable the heap immediately if there's a matching ongoing session.
363   if (client) {
364     uint64_t interval = MaybeToggleHeap(heap_id, client.get());
365     if (interval) {
366       ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try);
367       if (PERFETTO_UNLIKELY(!s.locked())) {
368         OnSpinlockTimeout();
369         return 0;
370       }
371       info->sampler.SetSamplingInterval(interval);
372     }
373   }
374   return heap_id;
375 }
376 
377 __attribute__((visibility("default"))) bool
AHeapProfile_reportAllocation(uint32_t heap_id,uint64_t id,uint64_t size)378 AHeapProfile_reportAllocation(uint32_t heap_id, uint64_t id, uint64_t size) {
379   AHeapInfo& heap = GetHeap(heap_id);
380   if (!heap.enabled.load(std::memory_order_acquire)) {
381     return false;
382   }
383   size_t sampled_alloc_sz = 0;
384   std::shared_ptr<perfetto::profiling::Client> client;
385   {
386     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try);
387     if (PERFETTO_UNLIKELY(!s.locked())) {
388       OnSpinlockTimeout();
389       return false;
390     }
391 
392     auto* g_client_ptr = GetClientLocked();
393     if (!*g_client_ptr)  // no active client (most likely shutting down)
394       return false;
395     auto& client_ptr = *g_client_ptr;
396 
397     if (s.blocked_us()) {
398       client_ptr->AddClientSpinlockBlockedUs(s.blocked_us());
399     }
400 
401     sampled_alloc_sz = heap.sampler.SampleSize(static_cast<size_t>(size));
402     if (sampled_alloc_sz == 0)  // not sampling
403       return false;
404     if (client_ptr->write_avail() <
405         client_ptr->adaptive_sampling_shmem_threshold()) {
406       bool should_increment = true;
407       if (client_ptr->adaptive_sampling_max_sampling_interval_bytes() != 0) {
408         should_increment =
409             heap.sampler.sampling_interval() <
410             client_ptr->adaptive_sampling_max_sampling_interval_bytes();
411       }
412       if (should_increment) {
413         uint64_t new_interval = 2 * heap.sampler.sampling_interval();
414         heap.sampler.SetSamplingInterval(2 * heap.sampler.sampling_interval());
415         client_ptr->RecordHeapInfo(heap_id, "", new_interval);
416       }
417     }
418 
419     client = client_ptr;  // owning copy
420   }                       // unlock
421 
422   if (!client->RecordMalloc(heap_id, sampled_alloc_sz, size, id)) {
423     ShutdownLazy(client);
424     return false;
425   }
426   return true;
427 }
428 
429 __attribute__((visibility("default"))) bool
AHeapProfile_reportSample(uint32_t heap_id,uint64_t id,uint64_t size)430 AHeapProfile_reportSample(uint32_t heap_id, uint64_t id, uint64_t size) {
431   const AHeapInfo& heap = GetHeap(heap_id);
432   if (!heap.enabled.load(std::memory_order_acquire)) {
433     return false;
434   }
435   std::shared_ptr<perfetto::profiling::Client> client;
436   {
437     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try);
438     if (PERFETTO_UNLIKELY(!s.locked())) {
439       OnSpinlockTimeout();
440       return false;
441     }
442 
443     auto* g_client_ptr = GetClientLocked();
444     if (!*g_client_ptr)  // no active client (most likely shutting down)
445       return false;
446 
447     if (s.blocked_us()) {
448       (*g_client_ptr)->AddClientSpinlockBlockedUs(s.blocked_us());
449     }
450 
451     client = *g_client_ptr;  // owning copy
452   }                          // unlock
453 
454   if (!client->RecordMalloc(heap_id, size, size, id)) {
455     ShutdownLazy(client);
456     return false;
457   }
458   return true;
459 }
460 
AHeapProfile_reportFree(uint32_t heap_id,uint64_t id)461 __attribute__((visibility("default"))) void AHeapProfile_reportFree(
462     uint32_t heap_id,
463     uint64_t id) {
464   const AHeapInfo& heap = GetHeap(heap_id);
465   if (!heap.enabled.load(std::memory_order_acquire)) {
466     return;
467   }
468   std::shared_ptr<perfetto::profiling::Client> client;
469   {
470     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try);
471     if (PERFETTO_UNLIKELY(!s.locked())) {
472       OnSpinlockTimeout();
473       return;
474     }
475 
476     client = *GetClientLocked();  // owning copy (or empty)
477     if (!client)
478       return;
479 
480     if (s.blocked_us()) {
481       client->AddClientSpinlockBlockedUs(s.blocked_us());
482     }
483   }
484 
485   if (!client->RecordFree(heap_id, id))
486     ShutdownLazy(client);
487 }
488 
AHeapProfile_initSession(void * (* malloc_fn)(size_t),void (* free_fn)(void *))489 __attribute__((visibility("default"))) bool AHeapProfile_initSession(
490     void* (*malloc_fn)(size_t),
491     void (*free_fn)(void*)) {
492   static bool first_init = true;
493   // Install an atfork handler to deal with *some* cases of the host forking.
494   // The handler will be unpatched automatically if we're dlclosed.
495   if (first_init && pthread_atfork(/*prepare=*/nullptr, /*parent=*/nullptr,
496                                    &AtForkChild) != 0) {
497     PERFETTO_PLOG("%s: pthread_atfork failed, not installing hooks.",
498                   getprogname());
499     return false;
500   }
501   first_init = false;
502 
503   // TODO(fmayer): Check other destructions of client and make a decision
504   // whether we want to ban heap objects in the client or not.
505   std::shared_ptr<perfetto::profiling::Client> old_client;
506   {
507     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try);
508     if (PERFETTO_UNLIKELY(!s.locked())) {
509       OnSpinlockTimeout();
510       return false;
511     }
512 
513     auto* g_client_ptr = GetClientLocked();
514     if (*g_client_ptr && (*g_client_ptr)->IsConnected()) {
515       PERFETTO_LOG("%s: Rejecting concurrent profiling initialization.",
516                    getprogname());
517       return true;  // success as we're in a valid state
518     }
519     old_client = *g_client_ptr;
520     g_client_ptr->reset();
521   }
522 
523   old_client.reset();
524 
525   // The dispatch table never changes, so let the custom allocator retain the
526   // function pointers directly.
527   UnhookedAllocator<perfetto::profiling::Client> unhooked_allocator(malloc_fn,
528                                                                     free_fn);
529 
530   // These factory functions use heap objects, so we need to run them without
531   // the spinlock held.
532   std::shared_ptr<perfetto::profiling::Client> client =
533       perfetto::profiling::ConstructClient(unhooked_allocator);
534 
535   if (!client) {
536     PERFETTO_LOG("%s: heapprofd_client not initialized, not installing hooks.",
537                  getprogname());
538     return false;
539   }
540 
541   uint32_t max_heap = g_next_heap_id.load();
542   bool heaps_enabled[kMaxNumHeaps] = {};
543 
544   PERFETTO_LOG("%s: heapprofd_client initialized.", getprogname());
545   {
546     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try);
547     if (PERFETTO_UNLIKELY(!s.locked())) {
548       OnSpinlockTimeout();
549       return false;
550     }
551 
552     // This needs to happen under the lock for mutual exclusion regarding the
553     // random engine.
554     for (uint32_t i = kMinHeapId; i < max_heap; ++i) {
555       AHeapInfo& heap = GetHeap(i);
556       if (!heap.ready.load(std::memory_order_acquire)) {
557         continue;
558       }
559       const uint64_t interval =
560           GetHeapSamplingInterval(client->client_config(), heap.heap_name);
561       if (interval) {
562         heaps_enabled[i] = true;
563         heap.sampler.SetSamplingInterval(interval);
564       }
565     }
566 
567     // This cannot have been set in the meantime. There are never two concurrent
568     // calls to this function, as Bionic uses atomics to guard against that.
569     PERFETTO_DCHECK(*GetClientLocked() == nullptr);
570     *GetClientLocked() = client;
571   }
572 
573   // We want to run MaybeToggleHeap last to make sure we never enable a heap
574   // but subsequently return `false` from this function, which indicates to the
575   // caller that we did not enable anything.
576   //
577   // For startup profiles, `false` is used by Bionic to signal it can unload
578   // the library again.
579   for (uint32_t i = kMinHeapId; i < max_heap; ++i) {
580     if (!heaps_enabled[i]) {
581       continue;
582     }
583     auto interval = MaybeToggleHeap(i, client.get());
584     PERFETTO_DCHECK(interval > 0);
585   }
586 
587   return true;
588 }
589