| /* | 
 |  * Copyright (C) 2020 The Android Open Source Project | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *      http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | #include "perfetto/heap_profile.h" | 
 | #include "src/profiling/memory/heap_profile_internal.h" | 
 |  | 
 | #include <malloc.h> | 
 | #include <stddef.h> | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <sys/types.h> | 
 | #include <sys/wait.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include <atomic> | 
 | #include <cinttypes> | 
 | #include <memory> | 
 | #include <type_traits> | 
 |  | 
 | #include "perfetto/base/build_config.h" | 
 | #include "perfetto/base/logging.h" | 
 | #include "perfetto/ext/base/string_utils.h" | 
 | #include "perfetto/ext/base/unix_socket.h" | 
 | #include "perfetto/ext/base/utils.h" | 
 |  | 
 | #include "src/profiling/memory/client.h" | 
 | #include "src/profiling/memory/client_api_factory.h" | 
 | #include "src/profiling/memory/scoped_spinlock.h" | 
 | #include "src/profiling/memory/unhooked_allocator.h" | 
 | #include "src/profiling/memory/wire_protocol.h" | 
 |  | 
 | struct AHeapInfo { | 
 |   // Fields set by user. | 
 |   char heap_name[HEAPPROFD_HEAP_NAME_SZ]; | 
 |   void (*enabled_callback)(void*, const AHeapProfileEnableCallbackInfo*); | 
 |   void (*disabled_callback)(void*, const AHeapProfileDisableCallbackInfo*); | 
 |   void* enabled_callback_data; | 
 |   void* disabled_callback_data; | 
 |  | 
 |   // Internal fields. | 
 |   perfetto::profiling::Sampler sampler; | 
 |   std::atomic<bool> ready; | 
 |   std::atomic<bool> enabled; | 
 |   std::atomic<uint64_t> adaptive_sampling_shmem_threshold; | 
 |   std::atomic<uint64_t> adaptive_sampling_max_sampling_interval_bytes; | 
 | }; | 
 |  | 
 | struct AHeapProfileEnableCallbackInfo { | 
 |   uint64_t sampling_interval; | 
 | }; | 
 |  | 
 | struct AHeapProfileDisableCallbackInfo {}; | 
 |  | 
 | namespace { | 
 |  | 
 | using perfetto::profiling::ScopedSpinlock; | 
 | using perfetto::profiling::UnhookedAllocator; | 
 |  | 
 | #if defined(__GLIBC__) | 
 | const char* getprogname() { | 
 |   return program_invocation_short_name; | 
 | } | 
 | #elif !defined(__BIONIC__) | 
 | const char* getprogname() { | 
 |   return ""; | 
 | } | 
 | #endif | 
 |  | 
 | // Holds the active profiling client. Is empty at the start, or after we've | 
 | // started shutting down a profiling session. Hook invocations take shared_ptr | 
 | // copies (ensuring that the client stays alive until no longer needed), and do | 
 | // nothing if this primary pointer is empty. | 
 | // | 
 | // This shared_ptr itself is protected by g_client_lock. Note that shared_ptr | 
 | // handles are not thread-safe by themselves: | 
 | // https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic | 
 | // | 
 | // To avoid on-destruction re-entrancy issues, this shared_ptr needs to be | 
 | // constructed with an allocator that uses the unhooked malloc & free functions. | 
 | // See UnhookedAllocator. | 
 | // | 
 | // We initialize this storage the first time GetClientLocked is called. We | 
 | // cannot use a static initializer because that leads to ordering problems | 
 | // of the ELF's constructors. | 
 |  | 
 | alignas(std::shared_ptr<perfetto::profiling::Client>) char g_client_arr[sizeof( | 
 |     std::shared_ptr<perfetto::profiling::Client>)]; | 
 |  | 
 | bool g_client_init; | 
 |  | 
 | std::shared_ptr<perfetto::profiling::Client>* GetClientLocked() { | 
 |   if (!g_client_init) { | 
 |     new (g_client_arr) std::shared_ptr<perfetto::profiling::Client>; | 
 |     g_client_init = true; | 
 |   } | 
 |   return reinterpret_cast<std::shared_ptr<perfetto::profiling::Client>*>( | 
 |       &g_client_arr); | 
 | } | 
 |  | 
 | constexpr auto kMinHeapId = 1; | 
 | constexpr auto kMaxNumHeaps = 256; | 
 |  | 
 | AHeapInfo g_heaps[kMaxNumHeaps] = {}; | 
 |  | 
 | AHeapInfo& GetHeap(uint32_t id) { | 
 |   return g_heaps[id]; | 
 | } | 
 |  | 
 | // Protects g_client, and serves as an external lock for sampling decisions (see | 
 | // perfetto::profiling::Sampler). | 
 | // | 
 | // We rely on this atomic's destuction being a nop, as it is possible for the | 
 | // hooks to attempt to acquire the spinlock after its destructor should have run | 
 | // (technically a use-after-destruct scenario). | 
 | static_assert( | 
 |     std::is_trivially_destructible<perfetto::profiling::Spinlock>::value, | 
 |     "lock must be trivially destructible."); | 
 | perfetto::profiling::Spinlock g_client_lock{}; | 
 |  | 
 | std::atomic<uint32_t> g_next_heap_id{kMinHeapId}; | 
 |  | 
 | // This can get called while holding the spinlock (in normal operation), or | 
 | // without holding the spinlock (from OnSpinlockTimeout). | 
 | void DisableAllHeaps() { | 
 |   bool disabled[kMaxNumHeaps] = {}; | 
 |   uint32_t max_heap = g_next_heap_id.load(); | 
 |   // This has to be done in two passes, in case the disabled_callback for one | 
 |   // enabled heap uses another. In that case, the callbacks for the other heap | 
 |   // would time out trying to acquire the spinlock, which we hold here. | 
 |   for (uint32_t i = kMinHeapId; i < max_heap; ++i) { | 
 |     AHeapInfo& info = GetHeap(i); | 
 |     if (!info.ready.load(std::memory_order_acquire)) | 
 |       continue; | 
 |     disabled[i] = info.enabled.exchange(false, std::memory_order_acq_rel); | 
 |   } | 
 |   for (uint32_t i = kMinHeapId; i < max_heap; ++i) { | 
 |     if (!disabled[i]) { | 
 |       continue; | 
 |     } | 
 |     AHeapInfo& info = GetHeap(i); | 
 |     if (info.disabled_callback) { | 
 |       AHeapProfileDisableCallbackInfo disable_info; | 
 |       info.disabled_callback(info.disabled_callback_data, &disable_info); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | #pragma GCC diagnostic push | 
 | #if PERFETTO_DCHECK_IS_ON() | 
 | #pragma GCC diagnostic ignored "-Wmissing-noreturn" | 
 | #endif | 
 |  | 
 | void OnSpinlockTimeout() { | 
 |   // Give up on profiling the process but leave it running. | 
 |   // The process enters into a poisoned state and will reject all | 
 |   // subsequent profiling requests.  The current session is kept | 
 |   // running but no samples are reported to it. | 
 |   PERFETTO_DFATAL_OR_ELOG( | 
 |       "Timed out on the spinlock - something is horribly wrong. " | 
 |       "Leaking heapprofd client."); | 
 |   DisableAllHeaps(); | 
 |   perfetto::profiling::PoisonSpinlock(&g_client_lock); | 
 | } | 
 | #pragma GCC diagnostic pop | 
 |  | 
 | // Note: g_client can be reset by AHeapProfile_initSession without calling this | 
 | // function. | 
 | void ShutdownLazy(const std::shared_ptr<perfetto::profiling::Client>& client) { | 
 |   ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try); | 
 |   if (PERFETTO_UNLIKELY(!s.locked())) { | 
 |     OnSpinlockTimeout(); | 
 |     return; | 
 |   } | 
 |  | 
 |   // other invocation already initiated shutdown | 
 |   if (*GetClientLocked() != client) | 
 |     return; | 
 |  | 
 |   DisableAllHeaps(); | 
 |   // Clear primary shared pointer, such that later hook invocations become nops. | 
 |   GetClientLocked()->reset(); | 
 | } | 
 |  | 
 | uint64_t MaybeToggleHeap(uint32_t heap_id, | 
 |                          perfetto::profiling::Client* client) { | 
 |   AHeapInfo& heap = GetHeap(heap_id); | 
 |   if (!heap.ready.load(std::memory_order_acquire)) | 
 |     return 0; | 
 |   auto interval = | 
 |       GetHeapSamplingInterval(client->client_config(), heap.heap_name); | 
 |   // The callbacks must be called while NOT LOCKED. Because they run | 
 |   // arbitrary code, it would be very easy to build a deadlock. | 
 |   if (interval) { | 
 |     AHeapProfileEnableCallbackInfo session_info{interval}; | 
 |     if (!heap.enabled.load(std::memory_order_acquire) && | 
 |         heap.enabled_callback) { | 
 |       heap.enabled_callback(heap.enabled_callback_data, &session_info); | 
 |     } | 
 |     heap.adaptive_sampling_shmem_threshold.store( | 
 |         client->client_config().adaptive_sampling_shmem_threshold, | 
 |         std::memory_order_relaxed); | 
 |     heap.adaptive_sampling_max_sampling_interval_bytes.store( | 
 |         client->client_config().adaptive_sampling_max_sampling_interval_bytes, | 
 |         std::memory_order_relaxed); | 
 |     heap.enabled.store(true, std::memory_order_release); | 
 |     client->RecordHeapInfo(heap_id, &heap.heap_name[0], interval); | 
 |   } else if (heap.enabled.load(std::memory_order_acquire)) { | 
 |     heap.enabled.store(false, std::memory_order_release); | 
 |     if (heap.disabled_callback) { | 
 |       AHeapProfileDisableCallbackInfo info; | 
 |       heap.disabled_callback(heap.disabled_callback_data, &info); | 
 |     } | 
 |   } | 
 |   return interval; | 
 | } | 
 |  | 
 | // We're a library loaded into a potentially-multithreaded process, which might | 
 | // not be explicitly aware of this possiblity. Deadling with forks/clones is | 
 | // extremely complicated in such situations, but we attempt to handle certain | 
 | // cases. | 
 | // | 
 | // There are two classes of forking processes to consider: | 
 | //  * well-behaved processes that fork only when their threads (if any) are at a | 
 | //    safe point, and therefore not in the middle of our hooks/client. | 
 | //  * processes that fork with other threads in an arbitrary state. Though | 
 | //    technically buggy, such processes exist in practice. | 
 | // | 
 | // This atfork handler follows a crude lowest-common-denominator approach, where | 
 | // to handle the latter class of processes, we systematically leak any |Client| | 
 | // state (present only when actively profiling at the time of fork) in the | 
 | // postfork-child path. | 
 | // | 
 | // The alternative with acquiring all relevant locks in the prefork handler, and | 
 | // releasing the state postfork handlers, poses a separate class of edge cases, | 
 | // and is not deemed to be better as a result. | 
 | // | 
 | // Notes: | 
 | // * this atfork handler fires only for the |fork| libc entrypoint, *not* | 
 | //   |clone|. See client.cc's |IsPostFork| for some best-effort detection | 
 | //   mechanisms for clone/vfork. | 
 | // * it should be possible to start a new profiling session in this child | 
 | //   process, modulo the bionic's heapprofd-loading state machine being in the | 
 | //   right state. | 
 | // * we cannot avoid leaks in all cases anyway (e.g. during shutdown sequence, | 
 | //   when only individual straggler threads hold onto the Client). | 
 | void AtForkChild() { | 
 |   PERFETTO_LOG("heapprofd_client: handling atfork."); | 
 |  | 
 |   // A thread (that has now disappeared across the fork) could have been holding | 
 |   // the spinlock. We're now the only thread post-fork, so we can reset the | 
 |   // spinlock, though the state it protects (the |g_client| shared_ptr) might | 
 |   // not be in a consistent state. | 
 |   g_client_lock.locked.store(false); | 
 |   g_client_lock.poisoned.store(false); | 
 |  | 
 |   // We must not call the disabled callbacks here, because they might require | 
 |   // locks that are being held at the fork point. | 
 |   for (uint32_t i = kMinHeapId; i < g_next_heap_id.load(); ++i) { | 
 |     AHeapInfo& info = GetHeap(i); | 
 |     info.enabled.store(false); | 
 |   } | 
 |   // Leak the existing shared_ptr contents, including the profiling |Client| if | 
 |   // profiling was active at the time of the fork. | 
 |   // Note: this code assumes that the creation of the empty shared_ptr does not | 
 |   // allocate, which should be the case for all implementations as the | 
 |   // constructor has to be noexcept. | 
 |   new (g_client_arr) std::shared_ptr<perfetto::profiling::Client>(); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | __attribute__((visibility("default"))) uint64_t | 
 | AHeapProfileEnableCallbackInfo_getSamplingInterval( | 
 |     const AHeapProfileEnableCallbackInfo* session_info) { | 
 |   return session_info->sampling_interval; | 
 | } | 
 |  | 
 | __attribute__((visibility("default"))) AHeapInfo* AHeapInfo_create( | 
 |     const char* heap_name) { | 
 |   size_t len = strlen(heap_name); | 
 |   if (len >= sizeof(AHeapInfo::heap_name)) { | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   uint32_t next_id = g_next_heap_id.fetch_add(1); | 
 |   if (next_id >= kMaxNumHeaps) { | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   if (next_id == kMinHeapId) | 
 |     perfetto::profiling::StartHeapprofdIfStatic(); | 
 |  | 
 |   AHeapInfo& info = GetHeap(next_id); | 
 |   perfetto::base::StringCopy(info.heap_name, heap_name, sizeof(info.heap_name)); | 
 |   return &info; | 
 | } | 
 |  | 
 | __attribute__((visibility("default"))) AHeapInfo* AHeapInfo_setEnabledCallback( | 
 |     AHeapInfo* info, | 
 |     void (*callback)(void*, const AHeapProfileEnableCallbackInfo*), | 
 |     void* data) { | 
 |   if (info == nullptr) | 
 |     return nullptr; | 
 |   if (info->ready.load(std::memory_order_relaxed)) { | 
 |     PERFETTO_ELOG( | 
 |         "AHeapInfo_setEnabledCallback called after heap was registered. " | 
 |         "This is always a bug."); | 
 |     return nullptr; | 
 |   } | 
 |   info->enabled_callback = callback; | 
 |   info->enabled_callback_data = data; | 
 |   return info; | 
 | } | 
 |  | 
 | __attribute__((visibility("default"))) AHeapInfo* AHeapInfo_setDisabledCallback( | 
 |     AHeapInfo* info, | 
 |     void (*callback)(void*, const AHeapProfileDisableCallbackInfo*), | 
 |     void* data) { | 
 |   if (info == nullptr) | 
 |     return nullptr; | 
 |   if (info->ready.load(std::memory_order_relaxed)) { | 
 |     PERFETTO_ELOG( | 
 |         "AHeapInfo_setDisabledCallback called after heap was registered. " | 
 |         "This is always a bug."); | 
 |     return nullptr; | 
 |   } | 
 |   info->disabled_callback = callback; | 
 |   info->disabled_callback_data = data; | 
 |   return info; | 
 | } | 
 |  | 
 | __attribute__((visibility("default"))) uint32_t AHeapProfile_registerHeap( | 
 |     AHeapInfo* info) { | 
 |   if (info == nullptr) | 
 |     return 0; | 
 |   info->ready.store(true, std::memory_order_release); | 
 |   auto heap_id = static_cast<uint32_t>(info - &g_heaps[0]); | 
 |   std::shared_ptr<perfetto::profiling::Client> client; | 
 |   { | 
 |     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try); | 
 |     if (PERFETTO_UNLIKELY(!s.locked())) { | 
 |       OnSpinlockTimeout(); | 
 |       return 0; | 
 |     } | 
 |  | 
 |     client = *GetClientLocked(); | 
 |   } | 
 |  | 
 |   // Enable the heap immediately if there's a matching ongoing session. | 
 |   if (client) { | 
 |     uint64_t interval = MaybeToggleHeap(heap_id, client.get()); | 
 |     if (interval) { | 
 |       ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try); | 
 |       if (PERFETTO_UNLIKELY(!s.locked())) { | 
 |         OnSpinlockTimeout(); | 
 |         return 0; | 
 |       } | 
 |       info->sampler.SetSamplingInterval(interval); | 
 |     } | 
 |   } | 
 |   return heap_id; | 
 | } | 
 |  | 
 | __attribute__((visibility("default"))) bool | 
 | AHeapProfile_reportAllocation(uint32_t heap_id, uint64_t id, uint64_t size) { | 
 |   AHeapInfo& heap = GetHeap(heap_id); | 
 |   if (!heap.enabled.load(std::memory_order_acquire)) { | 
 |     return false; | 
 |   } | 
 |   size_t sampled_alloc_sz = 0; | 
 |   std::shared_ptr<perfetto::profiling::Client> client; | 
 |   { | 
 |     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try); | 
 |     if (PERFETTO_UNLIKELY(!s.locked())) { | 
 |       OnSpinlockTimeout(); | 
 |       return false; | 
 |     } | 
 |  | 
 |     auto* g_client_ptr = GetClientLocked(); | 
 |     if (!*g_client_ptr)  // no active client (most likely shutting down) | 
 |       return false; | 
 |     auto& client_ptr = *g_client_ptr; | 
 |  | 
 |     if (s.blocked_us()) { | 
 |       client_ptr->AddClientSpinlockBlockedUs(s.blocked_us()); | 
 |     } | 
 |  | 
 |     sampled_alloc_sz = heap.sampler.SampleSize(static_cast<size_t>(size)); | 
 |     if (sampled_alloc_sz == 0)  // not sampling | 
 |       return false; | 
 |     if (client_ptr->write_avail() < | 
 |         client_ptr->adaptive_sampling_shmem_threshold()) { | 
 |       bool should_increment = true; | 
 |       if (client_ptr->adaptive_sampling_max_sampling_interval_bytes() != 0) { | 
 |         should_increment = | 
 |             heap.sampler.sampling_interval() < | 
 |             client_ptr->adaptive_sampling_max_sampling_interval_bytes(); | 
 |       } | 
 |       if (should_increment) { | 
 |         uint64_t new_interval = 2 * heap.sampler.sampling_interval(); | 
 |         heap.sampler.SetSamplingInterval(2 * heap.sampler.sampling_interval()); | 
 |         client_ptr->RecordHeapInfo(heap_id, "", new_interval); | 
 |       } | 
 |     } | 
 |  | 
 |     client = client_ptr;  // owning copy | 
 |   }                       // unlock | 
 |  | 
 |   if (!client->RecordMalloc(heap_id, sampled_alloc_sz, size, id)) { | 
 |     ShutdownLazy(client); | 
 |     return false; | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | __attribute__((visibility("default"))) bool | 
 | AHeapProfile_reportSample(uint32_t heap_id, uint64_t id, uint64_t size) { | 
 |   const AHeapInfo& heap = GetHeap(heap_id); | 
 |   if (!heap.enabled.load(std::memory_order_acquire)) { | 
 |     return false; | 
 |   } | 
 |   std::shared_ptr<perfetto::profiling::Client> client; | 
 |   { | 
 |     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try); | 
 |     if (PERFETTO_UNLIKELY(!s.locked())) { | 
 |       OnSpinlockTimeout(); | 
 |       return false; | 
 |     } | 
 |  | 
 |     auto* g_client_ptr = GetClientLocked(); | 
 |     if (!*g_client_ptr)  // no active client (most likely shutting down) | 
 |       return false; | 
 |  | 
 |     if (s.blocked_us()) { | 
 |       (*g_client_ptr)->AddClientSpinlockBlockedUs(s.blocked_us()); | 
 |     } | 
 |  | 
 |     client = *g_client_ptr;  // owning copy | 
 |   }                          // unlock | 
 |  | 
 |   if (!client->RecordMalloc(heap_id, size, size, id)) { | 
 |     ShutdownLazy(client); | 
 |     return false; | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | __attribute__((visibility("default"))) void AHeapProfile_reportFree( | 
 |     uint32_t heap_id, | 
 |     uint64_t id) { | 
 |   const AHeapInfo& heap = GetHeap(heap_id); | 
 |   if (!heap.enabled.load(std::memory_order_acquire)) { | 
 |     return; | 
 |   } | 
 |   std::shared_ptr<perfetto::profiling::Client> client; | 
 |   { | 
 |     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try); | 
 |     if (PERFETTO_UNLIKELY(!s.locked())) { | 
 |       OnSpinlockTimeout(); | 
 |       return; | 
 |     } | 
 |  | 
 |     client = *GetClientLocked();  // owning copy (or empty) | 
 |     if (!client) | 
 |       return; | 
 |  | 
 |     if (s.blocked_us()) { | 
 |       client->AddClientSpinlockBlockedUs(s.blocked_us()); | 
 |     } | 
 |   } | 
 |  | 
 |   if (!client->RecordFree(heap_id, id)) | 
 |     ShutdownLazy(client); | 
 | } | 
 |  | 
 | __attribute__((visibility("default"))) bool AHeapProfile_initSession( | 
 |     void* (*malloc_fn)(size_t), | 
 |     void (*free_fn)(void*)) { | 
 |   static bool first_init = true; | 
 |   // Install an atfork handler to deal with *some* cases of the host forking. | 
 |   // The handler will be unpatched automatically if we're dlclosed. | 
 |   if (first_init && pthread_atfork(/*prepare=*/nullptr, /*parent=*/nullptr, | 
 |                                    &AtForkChild) != 0) { | 
 |     PERFETTO_PLOG("%s: pthread_atfork failed, not installing hooks.", | 
 |                   getprogname()); | 
 |     return false; | 
 |   } | 
 |   first_init = false; | 
 |  | 
 |   // TODO(fmayer): Check other destructions of client and make a decision | 
 |   // whether we want to ban heap objects in the client or not. | 
 |   std::shared_ptr<perfetto::profiling::Client> old_client; | 
 |   { | 
 |     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try); | 
 |     if (PERFETTO_UNLIKELY(!s.locked())) { | 
 |       OnSpinlockTimeout(); | 
 |       return false; | 
 |     } | 
 |  | 
 |     auto* g_client_ptr = GetClientLocked(); | 
 |     if (*g_client_ptr && (*g_client_ptr)->IsConnected()) { | 
 |       PERFETTO_LOG("%s: Rejecting concurrent profiling initialization.", | 
 |                    getprogname()); | 
 |       return true;  // success as we're in a valid state | 
 |     } | 
 |     old_client = *g_client_ptr; | 
 |     g_client_ptr->reset(); | 
 |   } | 
 |  | 
 |   old_client.reset(); | 
 |  | 
 |   // The dispatch table never changes, so let the custom allocator retain the | 
 |   // function pointers directly. | 
 |   UnhookedAllocator<perfetto::profiling::Client> unhooked_allocator(malloc_fn, | 
 |                                                                     free_fn); | 
 |  | 
 |   // These factory functions use heap objects, so we need to run them without | 
 |   // the spinlock held. | 
 |   std::shared_ptr<perfetto::profiling::Client> client = | 
 |       perfetto::profiling::ConstructClient(unhooked_allocator); | 
 |  | 
 |   if (!client) { | 
 |     PERFETTO_LOG("%s: heapprofd_client not initialized, not installing hooks.", | 
 |                  getprogname()); | 
 |     return false; | 
 |   } | 
 |  | 
 |   uint32_t max_heap = g_next_heap_id.load(); | 
 |   bool heaps_enabled[kMaxNumHeaps] = {}; | 
 |  | 
 |   PERFETTO_LOG("%s: heapprofd_client initialized.", getprogname()); | 
 |   { | 
 |     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try); | 
 |     if (PERFETTO_UNLIKELY(!s.locked())) { | 
 |       OnSpinlockTimeout(); | 
 |       return false; | 
 |     } | 
 |  | 
 |     // This needs to happen under the lock for mutual exclusion regarding the | 
 |     // random engine. | 
 |     for (uint32_t i = kMinHeapId; i < max_heap; ++i) { | 
 |       AHeapInfo& heap = GetHeap(i); | 
 |       if (!heap.ready.load(std::memory_order_acquire)) { | 
 |         continue; | 
 |       } | 
 |       const uint64_t interval = | 
 |           GetHeapSamplingInterval(client->client_config(), heap.heap_name); | 
 |       if (interval) { | 
 |         heaps_enabled[i] = true; | 
 |         heap.sampler.SetSamplingInterval(interval); | 
 |       } | 
 |     } | 
 |  | 
 |     // This cannot have been set in the meantime. There are never two concurrent | 
 |     // calls to this function, as Bionic uses atomics to guard against that. | 
 |     PERFETTO_DCHECK(*GetClientLocked() == nullptr); | 
 |     *GetClientLocked() = client; | 
 |   } | 
 |  | 
 |   // We want to run MaybeToggleHeap last to make sure we never enable a heap | 
 |   // but subsequently return `false` from this function, which indicates to the | 
 |   // caller that we did not enable anything. | 
 |   // | 
 |   // For startup profiles, `false` is used by Bionic to signal it can unload | 
 |   // the library again. | 
 |   for (uint32_t i = kMinHeapId; i < max_heap; ++i) { | 
 |     if (!heaps_enabled[i]) { | 
 |       continue; | 
 |     } | 
 |     auto interval = MaybeToggleHeap(i, client.get()); | 
 |     PERFETTO_DCHECK(interval > 0); | 
 |   } | 
 |  | 
 |   return true; | 
 | } |