blob: bf4c63a3d3ad92f6021c8d739ba79a5be550c8a3 [file] [log] [blame]
Christopher Ferrise4cdbc42019-02-08 17:30:58 -08001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in
12 * the documentation and/or other materials provided with the
13 * distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
22 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#if defined(LIBC_STATIC)
30#error This file should not be compiled for static targets.
31#endif
32
33#include <dlfcn.h>
34#include <fcntl.h>
Christopher Ferris1fc5ccf2019-02-15 18:06:15 -080035#include <signal.h>
Christopher Ferrise4cdbc42019-02-08 17:30:58 -080036#include <stdio.h>
37#include <stdlib.h>
38#include <unistd.h>
39
Christopher Ferris2b0638e2019-09-11 19:05:29 -070040#include <platform/bionic/malloc.h>
Christopher Ferrise4cdbc42019-02-08 17:30:58 -080041#include <private/bionic_config.h>
Christopher Ferrise4cdbc42019-02-08 17:30:58 -080042#include <private/bionic_malloc_dispatch.h>
43#include <sys/system_properties.h>
44
45#include "malloc_common.h"
46#include "malloc_common_dynamic.h"
47#include "malloc_heapprofd.h"
Mitch Phillips3083cc92020-02-11 15:23:47 -080048#include "malloc_limit.h"
Christopher Ferrise4cdbc42019-02-08 17:30:58 -080049
50static constexpr char kHeapprofdSharedLib[] = "heapprofd_client.so";
51static constexpr char kHeapprofdPrefix[] = "heapprofd";
52static constexpr char kHeapprofdPropertyEnable[] = "heapprofd.enable";
Christopher Ferrise4cdbc42019-02-08 17:30:58 -080053
54// The logic for triggering heapprofd (at runtime) is as follows:
Ryan Savitski175c8862020-01-02 19:54:57 +000055// 1. A reserved profiling signal is received by the process, its si_value
56// discriminating between different handlers. For the case of heapprofd,
57// HandleHeapprofdSignal is called.
Christopher Ferrise4cdbc42019-02-08 17:30:58 -080058// 2. If the initialization is not already in flight
59// (gHeapprofdInitInProgress is false), the malloc hook is set to
60// point at InitHeapprofdHook, and gHeapprofdInitInProgress is set to
61// true.
62// 3. The next malloc call enters InitHeapprofdHook, which removes the malloc
63// hook, and spawns a detached pthread to run the InitHeapprofd task.
Ryan Savitski175c8862020-01-02 19:54:57 +000064// (gHeapprofdInitHookInstalled atomic is used to perform this once.)
Christopher Ferrise4cdbc42019-02-08 17:30:58 -080065// 4. InitHeapprofd, on a dedicated pthread, loads the heapprofd client library,
66// installs the full set of heapprofd hooks, and invokes the client's
67// initializer. The dedicated pthread then terminates.
68// 5. gHeapprofdInitInProgress and gHeapprofdInitHookInstalled are
69// reset to false such that heapprofd can be reinitialized. Reinitialization
70// means that a new profiling session is started, and any still active is
71// torn down.
72//
73// The incremental hooking and a dedicated task thread are used since we cannot
74// do heavy work within a signal handler, or when blocking a malloc invocation.
75
76// The handle returned by dlopen when previously loading the heapprofd
77// hooks. nullptr if shared library has not been already been loaded.
78static _Atomic (void*) gHeapprofdHandle = nullptr;
79
80static _Atomic bool gHeapprofdInitInProgress = false;
81static _Atomic bool gHeapprofdInitHookInstalled = false;
82
Ryan Savitski175c8862020-01-02 19:54:57 +000083// Set to true if the process has enabled malloc_debug or malloc_hooks, which
84// are incompatible (and take precedence over) heapprofd.
85static _Atomic bool gHeapprofdIncompatibleHooks = false;
Christopher Ferrise4cdbc42019-02-08 17:30:58 -080086
87extern "C" void* MallocInitHeapprofdHook(size_t);
88
89static constexpr MallocDispatch __heapprofd_init_dispatch
90 __attribute__((unused)) = {
91 Malloc(calloc),
92 Malloc(free),
93 Malloc(mallinfo),
Ryan Savitski175c8862020-01-02 19:54:57 +000094 MallocInitHeapprofdHook, // malloc replacement
Christopher Ferrise4cdbc42019-02-08 17:30:58 -080095 Malloc(malloc_usable_size),
96 Malloc(memalign),
97 Malloc(posix_memalign),
98#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
99 Malloc(pvalloc),
100#endif
101 Malloc(realloc),
102#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
103 Malloc(valloc),
104#endif
Christopher Ferris6f517cd2019-11-08 11:28:38 -0800105 Malloc(malloc_iterate),
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800106 Malloc(malloc_disable),
107 Malloc(malloc_enable),
108 Malloc(mallopt),
109 Malloc(aligned_alloc),
Christopher Ferris6c619a02019-03-01 17:59:51 -0800110 Malloc(malloc_info),
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800111 };
112
Florian Mayerf6d221e2019-05-03 16:24:52 +0100113constexpr char kHeapprofdProgramPropertyPrefix[] = "heapprofd.enable.";
114constexpr size_t kHeapprofdProgramPropertyPrefixSize = sizeof(kHeapprofdProgramPropertyPrefix) - 1;
115constexpr size_t kMaxCmdlineSize = 512;
116
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800117static bool GetHeapprofdProgramProperty(char* data, size_t size) {
Florian Mayerf6d221e2019-05-03 16:24:52 +0100118 if (size < kHeapprofdProgramPropertyPrefixSize) {
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800119 error_log("%s: Overflow constructing heapprofd property", getprogname());
120 return false;
121 }
Florian Mayerf6d221e2019-05-03 16:24:52 +0100122 memcpy(data, kHeapprofdProgramPropertyPrefix, kHeapprofdProgramPropertyPrefixSize);
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800123
124 int fd = open("/proc/self/cmdline", O_RDONLY | O_CLOEXEC);
125 if (fd == -1) {
126 error_log("%s: Failed to open /proc/self/cmdline", getprogname());
127 return false;
128 }
Florian Mayerf6d221e2019-05-03 16:24:52 +0100129 char cmdline[kMaxCmdlineSize];
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800130 ssize_t rd = read(fd, cmdline, sizeof(cmdline) - 1);
131 close(fd);
132 if (rd == -1) {
133 error_log("%s: Failed to read /proc/self/cmdline", getprogname());
134 return false;
135 }
136 cmdline[rd] = '\0';
137 char* first_arg = static_cast<char*>(memchr(cmdline, '\0', rd));
Florian Mayerf6d221e2019-05-03 16:24:52 +0100138 if (first_arg == nullptr) {
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800139 error_log("%s: Overflow reading cmdline", getprogname());
140 return false;
141 }
142 // For consistency with what we do with Java app cmdlines, trim everything
143 // after the @ sign of the first arg.
144 char* first_at = static_cast<char*>(memchr(cmdline, '@', rd));
145 if (first_at != nullptr && first_at < first_arg) {
146 *first_at = '\0';
147 first_arg = first_at;
148 }
149
150 char* start = static_cast<char*>(memrchr(cmdline, '/', first_arg - cmdline));
151 if (start == first_arg) {
152 // The first argument ended in a slash.
153 error_log("%s: cmdline ends in /", getprogname());
154 return false;
155 } else if (start == nullptr) {
156 start = cmdline;
157 } else {
158 // Skip the /.
159 start++;
160 }
161
162 size_t name_size = static_cast<size_t>(first_arg - start);
Florian Mayerf6d221e2019-05-03 16:24:52 +0100163 if (name_size >= size - kHeapprofdProgramPropertyPrefixSize) {
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800164 error_log("%s: overflow constructing heapprofd property.", getprogname());
165 return false;
166 }
167 // + 1 to also copy the trailing null byte.
Florian Mayerf6d221e2019-05-03 16:24:52 +0100168 memcpy(data + kHeapprofdProgramPropertyPrefixSize, start, name_size + 1);
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800169 return true;
170}
171
Ryan Savitski175c8862020-01-02 19:54:57 +0000172// Runtime triggering entry-point. Two possible call sites:
173// * when receiving a profiling signal with a si_value indicating heapprofd.
174// * when a Zygote child is marking itself as profileable, and there's a
175// matching profiling request for this process (in which case heapprofd client
176// is loaded synchronously).
177// In both cases, the caller is responsible for verifying that the process is
178// considered profileable.
179void HandleHeapprofdSignal() {
180 if (atomic_load_explicit(&gHeapprofdIncompatibleHooks, memory_order_acquire)) {
181 error_log("%s: not enabling heapprofd, malloc_debug/malloc_hooks are enabled.", getprogname());
182 return;
183 }
184
185 // Checking this variable is only necessary when this could conflict with
186 // the change to enable the allocation limit. All other places will
187 // not ever have a conflict modifying the globals.
188 if (!atomic_exchange(&gGlobalsMutating, true)) {
189 if (!atomic_exchange(&gHeapprofdInitInProgress, true)) {
190 __libc_globals.mutate([](libc_globals* globals) {
191 atomic_store(&globals->default_dispatch_table, &__heapprofd_init_dispatch);
192 auto dispatch_table = GetDispatchTable();
Mitch Phillips3083cc92020-02-11 15:23:47 -0800193 if (!MallocLimitInstalled() || dispatch_table == &globals->malloc_dispatch_table) {
Ryan Savitski175c8862020-01-02 19:54:57 +0000194 atomic_store(&globals->current_dispatch_table, &__heapprofd_init_dispatch);
195 }
196 });
197 }
198 atomic_store(&gGlobalsMutating, false);
199 }
200 // Otherwise, we're racing against malloc_limit's enable logic (at most once
201 // per process, and a niche feature). This is highly unlikely, so simply give
202 // up if it does happen.
203}
204
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800205bool HeapprofdShouldLoad() {
206 // First check for heapprofd.enable. If it is set to "all", enable
207 // heapprofd for all processes. Otherwise, check heapprofd.enable.${prog},
208 // if it is set and not 0, enable heap profiling for this process.
209 char property_value[PROP_VALUE_MAX];
210 if (__system_property_get(kHeapprofdPropertyEnable, property_value) == 0) {
211 return false;
212 }
213 if (strcmp(property_value, "all") == 0) {
214 return true;
215 }
216
Florian Mayerf6d221e2019-05-03 16:24:52 +0100217 char program_property[kHeapprofdProgramPropertyPrefixSize + kMaxCmdlineSize];
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800218 if (!GetHeapprofdProgramProperty(program_property,
219 sizeof(program_property))) {
220 return false;
221 }
222 if (__system_property_get(program_property, property_value) == 0) {
223 return false;
224 }
Christopher Ferris503c17b2019-02-22 12:47:23 -0800225 return property_value[0] != '\0';
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800226}
227
Ryan Savitski175c8862020-01-02 19:54:57 +0000228void HeapprofdRememberHookConflict() {
229 atomic_store_explicit(&gHeapprofdIncompatibleHooks, true, memory_order_release);
Christopher Ferris28228562019-02-14 10:23:58 -0800230}
231
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800232static void CommonInstallHooks(libc_globals* globals) {
233 void* impl_handle = atomic_load(&gHeapprofdHandle);
234 bool reusing_handle = impl_handle != nullptr;
235 if (!reusing_handle) {
236 impl_handle = LoadSharedLibrary(kHeapprofdSharedLib, kHeapprofdPrefix, &globals->malloc_dispatch_table);
237 if (impl_handle == nullptr) {
238 return;
239 }
240 } else if (!InitSharedLibrary(impl_handle, kHeapprofdSharedLib, kHeapprofdPrefix, &globals->malloc_dispatch_table)) {
241 return;
242 }
243
244 if (FinishInstallHooks(globals, nullptr, kHeapprofdPrefix)) {
245 atomic_store(&gHeapprofdHandle, impl_handle);
246 } else if (!reusing_handle) {
247 dlclose(impl_handle);
248 }
249
250 atomic_store(&gHeapprofdInitInProgress, false);
251}
252
253void HeapprofdInstallHooksAtInit(libc_globals* globals) {
254 if (atomic_exchange(&gHeapprofdInitInProgress, true)) {
255 return;
256 }
257 CommonInstallHooks(globals);
258}
259
260static void* InitHeapprofd(void*) {
Christopher Ferris1fc5ccf2019-02-15 18:06:15 -0800261 pthread_mutex_lock(&gGlobalsMutateLock);
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800262 __libc_globals.mutate([](libc_globals* globals) {
263 CommonInstallHooks(globals);
264 });
Christopher Ferris1fc5ccf2019-02-15 18:06:15 -0800265 pthread_mutex_unlock(&gGlobalsMutateLock);
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800266
267 // Allow to install hook again to re-initialize heap profiling after the
268 // current session finished.
269 atomic_store(&gHeapprofdInitHookInstalled, false);
270 return nullptr;
271}
272
273extern "C" void* MallocInitHeapprofdHook(size_t bytes) {
274 if (!atomic_exchange(&gHeapprofdInitHookInstalled, true)) {
Christopher Ferris1fc5ccf2019-02-15 18:06:15 -0800275 pthread_mutex_lock(&gGlobalsMutateLock);
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800276 __libc_globals.mutate([](libc_globals* globals) {
Christopher Ferris1fc5ccf2019-02-15 18:06:15 -0800277 auto old_dispatch = GetDefaultDispatchTable();
278 atomic_store(&globals->default_dispatch_table, nullptr);
279 if (GetDispatchTable() == old_dispatch) {
280 atomic_store(&globals->current_dispatch_table, nullptr);
281 }
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800282 });
Christopher Ferris1fc5ccf2019-02-15 18:06:15 -0800283 pthread_mutex_unlock(&gGlobalsMutateLock);
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800284
285 pthread_t thread_id;
286 if (pthread_create(&thread_id, nullptr, InitHeapprofd, nullptr) != 0) {
287 error_log("%s: heapprofd: failed to pthread_create.", getprogname());
288 } else if (pthread_detach(thread_id) != 0) {
289 error_log("%s: heapprofd: failed to pthread_detach", getprogname());
290 }
291 if (pthread_setname_np(thread_id, "heapprofdinit") != 0) {
292 error_log("%s: heapprod: failed to pthread_setname_np", getprogname());
293 }
294 }
295 return Malloc(malloc)(bytes);
296}
297
Ryan Savitski175c8862020-01-02 19:54:57 +0000298bool HeapprofdInitZygoteChildProfiling() {
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800299 // Conditionally start "from startup" profiling.
300 if (HeapprofdShouldLoad()) {
Ryan Savitski175c8862020-01-02 19:54:57 +0000301 // Directly call the signal handler codepath (properly protects against
302 // concurrent invocations).
303 HandleHeapprofdSignal();
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800304 }
305 return true;
306}
307
308static bool DispatchReset() {
309 if (!atomic_exchange(&gHeapprofdInitInProgress, true)) {
Christopher Ferris1fc5ccf2019-02-15 18:06:15 -0800310 pthread_mutex_lock(&gGlobalsMutateLock);
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800311 __libc_globals.mutate([](libc_globals* globals) {
Christopher Ferris1fc5ccf2019-02-15 18:06:15 -0800312 auto old_dispatch = GetDefaultDispatchTable();
313 atomic_store(&globals->default_dispatch_table, nullptr);
314 if (GetDispatchTable() == old_dispatch) {
315 atomic_store(&globals->current_dispatch_table, nullptr);
316 }
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800317 });
Christopher Ferris1fc5ccf2019-02-15 18:06:15 -0800318 pthread_mutex_unlock(&gGlobalsMutateLock);
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800319 atomic_store(&gHeapprofdInitInProgress, false);
320 return true;
321 }
322 errno = EAGAIN;
323 return false;
324}
325
326bool HeapprofdMallopt(int opcode, void* arg, size_t arg_size) {
Christopher Ferrise4cdbc42019-02-08 17:30:58 -0800327 if (opcode == M_RESET_HOOKS) {
328 if (arg != nullptr || arg_size != 0) {
329 errno = EINVAL;
330 return false;
331 }
332 return DispatchReset();
333 }
334 errno = ENOTSUP;
335 return false;
336}