|  | /* | 
|  | * 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; | 
|  | } |