diff options
-rw-r--r-- | libc/Android.bp | 1 | ||||
-rw-r--r-- | libc/bionic/android_profiling_dynamic.cpp | 79 | ||||
-rw-r--r-- | libc/bionic/libc_init_dynamic.cpp | 3 | ||||
-rw-r--r-- | libc/bionic/malloc_common.h | 2 | ||||
-rw-r--r-- | libc/bionic/malloc_common_dynamic.cpp | 45 | ||||
-rw-r--r-- | libc/bionic/malloc_heapprofd.cpp | 136 | ||||
-rw-r--r-- | libc/bionic/malloc_heapprofd.h | 9 | ||||
-rw-r--r-- | libc/bionic/malloc_limit.cpp | 4 | ||||
-rw-r--r-- | libc/platform/bionic/malloc.h | 6 | ||||
-rw-r--r-- | libc/platform/bionic/reserved_signals.h | 3 | ||||
-rw-r--r-- | libc/private/bionic_globals.h | 1 | ||||
-rw-r--r-- | tests/malloc_test.cpp | 9 |
12 files changed, 188 insertions, 110 deletions
diff --git a/libc/Android.bp b/libc/Android.bp index 8284174bb..626afd972 100644 --- a/libc/Android.bp +++ b/libc/Android.bp @@ -1613,6 +1613,7 @@ filegroup { "bionic/icu.cpp", "bionic/malloc_common.cpp", "bionic/malloc_common_dynamic.cpp", + "bionic/android_profiling_dynamic.cpp", "bionic/malloc_heapprofd.cpp", "bionic/malloc_limit.cpp", "bionic/ndk_cruft.cpp", diff --git a/libc/bionic/android_profiling_dynamic.cpp b/libc/bionic/android_profiling_dynamic.cpp new file mode 100644 index 000000000..dd87ed840 --- /dev/null +++ b/libc/bionic/android_profiling_dynamic.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_STATIC) +#error This file should not be compiled for static targets. +#endif + +#include <signal.h> + +#include <async_safe/log.h> +#include <platform/bionic/malloc.h> +#include <platform/bionic/reserved_signals.h> + +#include "malloc_heapprofd.h" + +// This file defines the handler for the reserved signal sent by the Android +// platform's profilers. The accompanying signal value discriminates between +// specific requestors: +// 0: heapprofd heap profiler. +static constexpr int kHeapprofdSignalValue = 0; + +static void HandleProfilingSignal(int, siginfo_t*, void*); + +// Called during dynamic libc preinit. +__LIBC_HIDDEN__ void __libc_init_profiling_handlers() { + struct sigaction action = {}; + action.sa_flags = SA_SIGINFO | SA_RESTART; + action.sa_sigaction = HandleProfilingSignal; + sigaction(BIONIC_SIGNAL_PROFILER, &action, nullptr); +} + +static void HandleProfilingSignal(int /*signal_number*/, siginfo_t* info, void* /*ucontext*/) { + if (info->si_code != SI_QUEUE) + return; + + int signal_value = info->si_value.sival_int; + async_safe_format_log(ANDROID_LOG_WARN, "libc", "%s: received profiling signal with si_value: %d", + getprogname(), signal_value); + + // Proceed only if the process is considered profileable. + bool profileable = false; + android_mallopt(M_GET_PROCESS_PROFILEABLE, &profileable, sizeof(profileable)); + if (!profileable) { + async_safe_write_log(ANDROID_LOG_ERROR, "libc", "profiling signal rejected (not profileable)"); + return; + } + + if (signal_value == kHeapprofdSignalValue) { + HandleHeapprofdSignal(); + } else { + async_safe_format_log(ANDROID_LOG_ERROR, "libc", "unrecognized profiling signal si_value: %d", + signal_value); + } +} diff --git a/libc/bionic/libc_init_dynamic.cpp b/libc/bionic/libc_init_dynamic.cpp index aafefabde..4eaf37a0b 100644 --- a/libc/bionic/libc_init_dynamic.cpp +++ b/libc/bionic/libc_init_dynamic.cpp @@ -99,6 +99,9 @@ static void __libc_preinit_impl() { // Hooks for various libraries to let them know that we're starting up. __libc_globals.mutate(__libc_init_malloc); + // Install reserved signal handlers for assisting the platform's profilers. + __libc_init_profiling_handlers(); + __libc_init_fork_handler(); #if __has_feature(hwaddress_sanitizer) diff --git a/libc/bionic/malloc_common.h b/libc/bionic/malloc_common.h index 4a726db12..79bf98c0d 100644 --- a/libc/bionic/malloc_common.h +++ b/libc/bionic/malloc_common.h @@ -74,8 +74,6 @@ __END_DECLS #endif -extern bool gZygoteChild; - static inline const MallocDispatch* GetDispatchTable() { return atomic_load_explicit(&__libc_globals->current_dispatch_table, memory_order_acquire); } diff --git a/libc/bionic/malloc_common_dynamic.cpp b/libc/bionic/malloc_common_dynamic.cpp index 8068f4e39..2cb1e8a56 100644 --- a/libc/bionic/malloc_common_dynamic.cpp +++ b/libc/bionic/malloc_common_dynamic.cpp @@ -75,9 +75,19 @@ // ============================================================================= pthread_mutex_t gGlobalsMutateLock = PTHREAD_MUTEX_INITIALIZER; -bool gZygoteChild = false; - _Atomic bool gGlobalsMutating = false; + +static bool gZygoteChild = false; + +// In a Zygote child process, this is set to true if profiling of this process +// is allowed. Note that this is set at a later time than gZygoteChild. The +// latter is set during the fork (while still in zygote's SELinux domain). While +// this bit is set after the child is specialized (and has transferred SELinux +// domains if applicable). These two flags are read by the +// BIONIC_SIGNAL_PROFILER handler, which does nothing if the process is not +// profileable. +static _Atomic bool gZygoteChildProfileable = false; + // ============================================================================= static constexpr MallocDispatch __libc_malloc_default_dispatch @@ -391,13 +401,10 @@ static void MallocInitImpl(libc_globals* globals) { if (HeapprofdShouldLoad()) { HeapprofdInstallHooksAtInit(globals); } - - // Install this last to avoid as many race conditions as possible. - HeapprofdInstallSignalHandler(); } else { - // Install a signal handler that prints an error since we don't support - // heapprofd and any other hook to be installed at the same time. - HeapprofdInstallErrorSignalHandler(); + // Record the fact that incompatible hooks are active, to skip any later + // heapprofd signal handler invocations. + HeapprofdRememberHookConflict(); } } @@ -476,6 +483,27 @@ extern "C" bool android_mallopt(int opcode, void* arg, size_t arg_size) { gZygoteChild = true; return true; } + if (opcode == M_INIT_ZYGOTE_CHILD_PROFILING) { + if (arg != nullptr || arg_size != 0) { + errno = EINVAL; + return false; + } + atomic_store_explicit(&gZygoteChildProfileable, true, memory_order_release); + // Also check if heapprofd should start profiling from app startup. + HeapprofdInitZygoteChildProfiling(); + return true; + } + if (opcode == M_GET_PROCESS_PROFILEABLE) { + if (arg == nullptr || arg_size != sizeof(bool)) { + errno = EINVAL; + return false; + } + // Native processes are considered profileable. Zygote children are considered + // profileable only when appropriately tagged. + *reinterpret_cast<bool*>(arg) = + !gZygoteChild || atomic_load_explicit(&gZygoteChildProfileable, memory_order_acquire); + return true; + } if (opcode == M_SET_ALLOCATION_LIMIT_BYTES) { return LimitEnable(arg, arg_size); } @@ -503,6 +531,7 @@ extern "C" bool android_mallopt(int opcode, void* arg, size_t arg_size) { if (opcode == M_SET_HEAP_TAGGING_LEVEL) { return SetHeapTaggingLevel(arg, arg_size); } + // Try heapprofd's mallopt, as it handles options not covered here. return HeapprofdMallopt(opcode, arg, arg_size); } // ============================================================================= diff --git a/libc/bionic/malloc_heapprofd.cpp b/libc/bionic/malloc_heapprofd.cpp index 586022200..3d2dc2b9c 100644 --- a/libc/bionic/malloc_heapprofd.cpp +++ b/libc/bionic/malloc_heapprofd.cpp @@ -49,18 +49,18 @@ static constexpr char kHeapprofdSharedLib[] = "heapprofd_client.so"; static constexpr char kHeapprofdPrefix[] = "heapprofd"; static constexpr char kHeapprofdPropertyEnable[] = "heapprofd.enable"; -static constexpr int kHeapprofdSignal = __SIGRTMIN + 4; // The logic for triggering heapprofd (at runtime) is as follows: -// 1. HEAPPROFD_SIGNAL is received by the process, entering the -// MaybeInstallInitHeapprofdHook signal handler. +// 1. A reserved profiling signal is received by the process, its si_value +// discriminating between different handlers. For the case of heapprofd, +// HandleHeapprofdSignal is called. // 2. If the initialization is not already in flight // (gHeapprofdInitInProgress is false), the malloc hook is set to // point at InitHeapprofdHook, and gHeapprofdInitInProgress is set to // true. // 3. The next malloc call enters InitHeapprofdHook, which removes the malloc // hook, and spawns a detached pthread to run the InitHeapprofd task. -// (gHeapprofdInitHook_installed atomic is used to perform this once.) +// (gHeapprofdInitHookInstalled atomic is used to perform this once.) // 4. InitHeapprofd, on a dedicated pthread, loads the heapprofd client library, // installs the full set of heapprofd hooks, and invokes the client's // initializer. The dedicated pthread then terminates. @@ -79,12 +79,9 @@ static _Atomic (void*) gHeapprofdHandle = nullptr; static _Atomic bool gHeapprofdInitInProgress = false; static _Atomic bool gHeapprofdInitHookInstalled = false; -// In a Zygote child process, this is set to true if profiling of this process -// is allowed. Note that this is set at a later time than the global -// gZygoteChild. The latter is set during the fork (while still in -// zygote's SELinux domain). While this bit is set after the child is -// specialized (and has transferred SELinux domains if applicable). -static _Atomic bool gZygoteChildProfileable = false; +// Set to true if the process has enabled malloc_debug or malloc_hooks, which +// are incompatible (and take precedence over) heapprofd. +static _Atomic bool gHeapprofdIncompatibleHooks = false; extern "C" void* MallocInitHeapprofdHook(size_t); @@ -93,7 +90,7 @@ static constexpr MallocDispatch __heapprofd_init_dispatch Malloc(calloc), Malloc(free), Malloc(mallinfo), - MallocInitHeapprofdHook, + MallocInitHeapprofdHook, // malloc replacement Malloc(malloc_usable_size), Malloc(memalign), Malloc(posix_memalign), @@ -112,38 +109,6 @@ static constexpr MallocDispatch __heapprofd_init_dispatch Malloc(malloc_info), }; -static void MaybeInstallInitHeapprofdHook(int) { - // Zygote child processes must be marked profileable. - if (gZygoteChild && - !atomic_load_explicit(&gZygoteChildProfileable, memory_order_acquire)) { - error_log("%s: not enabling heapprofd, not marked profileable.", getprogname()); - return; - } - - // Checking this variable is only necessary when this could conflict with - // the change to enable the allocation limit. All other places will - // not ever have a conflict modifying the globals. - if (!atomic_exchange(&gGlobalsMutating, true)) { - if (!atomic_exchange(&gHeapprofdInitInProgress, true)) { - __libc_globals.mutate([](libc_globals* globals) { - atomic_store(&globals->default_dispatch_table, &__heapprofd_init_dispatch); - auto dispatch_table = GetDispatchTable(); - if (dispatch_table == nullptr || dispatch_table == &globals->malloc_dispatch_table) { - atomic_store(&globals->current_dispatch_table, &__heapprofd_init_dispatch); - } - }); - } - atomic_store(&gGlobalsMutating, false); - } else { - // The only way you can get to this point is if the signal has been - // blocked by a call to HeapprofdMaskSignal. The raise below will - // do nothing until a call to HeapprofdUnmaskSignal, which will cause - // the signal to be resent. Using this avoids the need for a busy loop - // waiting for gGlobalsMutating to change back to false. - raise(kHeapprofdSignal); - } -} - constexpr char kHeapprofdProgramPropertyPrefix[] = "heapprofd.enable."; constexpr size_t kHeapprofdProgramPropertyPrefixSize = sizeof(kHeapprofdProgramPropertyPrefix) - 1; constexpr size_t kMaxCmdlineSize = 512; @@ -203,6 +168,39 @@ static bool GetHeapprofdProgramProperty(char* data, size_t size) { return true; } +// Runtime triggering entry-point. Two possible call sites: +// * when receiving a profiling signal with a si_value indicating heapprofd. +// * when a Zygote child is marking itself as profileable, and there's a +// matching profiling request for this process (in which case heapprofd client +// is loaded synchronously). +// In both cases, the caller is responsible for verifying that the process is +// considered profileable. +void HandleHeapprofdSignal() { + if (atomic_load_explicit(&gHeapprofdIncompatibleHooks, memory_order_acquire)) { + error_log("%s: not enabling heapprofd, malloc_debug/malloc_hooks are enabled.", getprogname()); + return; + } + + // Checking this variable is only necessary when this could conflict with + // the change to enable the allocation limit. All other places will + // not ever have a conflict modifying the globals. + if (!atomic_exchange(&gGlobalsMutating, true)) { + if (!atomic_exchange(&gHeapprofdInitInProgress, true)) { + __libc_globals.mutate([](libc_globals* globals) { + atomic_store(&globals->default_dispatch_table, &__heapprofd_init_dispatch); + auto dispatch_table = GetDispatchTable(); + if (dispatch_table == nullptr || dispatch_table == &globals->malloc_dispatch_table) { + atomic_store(&globals->current_dispatch_table, &__heapprofd_init_dispatch); + } + }); + } + atomic_store(&gGlobalsMutating, false); + } + // Otherwise, we're racing against malloc_limit's enable logic (at most once + // per process, and a niche feature). This is highly unlikely, so simply give + // up if it does happen. +} + bool HeapprofdShouldLoad() { // First check for heapprofd.enable. If it is set to "all", enable // heapprofd for all processes. Otherwise, check heapprofd.enable.${prog}, @@ -226,38 +224,8 @@ bool HeapprofdShouldLoad() { return property_value[0] != '\0'; } -void HeapprofdInstallSignalHandler() { - struct sigaction action = {}; - action.sa_handler = MaybeInstallInitHeapprofdHook; - sigaction(kHeapprofdSignal, &action, nullptr); -} - -extern "C" int __rt_sigprocmask(int, const sigset64_t*, sigset64_t*, size_t); - -void HeapprofdMaskSignal() { - sigset64_t mask_set; - // Need to use this function instead because sigprocmask64 filters - // out this signal. - __rt_sigprocmask(SIG_SETMASK, nullptr, &mask_set, sizeof(mask_set)); - sigaddset64(&mask_set, kHeapprofdSignal); - __rt_sigprocmask(SIG_SETMASK, &mask_set, nullptr, sizeof(mask_set)); -} - -void HeapprofdUnmaskSignal() { - sigset64_t mask_set; - __rt_sigprocmask(SIG_SETMASK, nullptr, &mask_set, sizeof(mask_set)); - sigdelset64(&mask_set, kHeapprofdSignal); - __rt_sigprocmask(SIG_SETMASK, &mask_set, nullptr, sizeof(mask_set)); -} - -static void DisplayError(int) { - error_log("Cannot install heapprofd while malloc debug/malloc hooks are enabled."); -} - -void HeapprofdInstallErrorSignalHandler() { - struct sigaction action = {}; - action.sa_handler = DisplayError; - sigaction(kHeapprofdSignal, &action, nullptr); +void HeapprofdRememberHookConflict() { + atomic_store_explicit(&gHeapprofdIncompatibleHooks, true, memory_order_release); } static void CommonInstallHooks(libc_globals* globals) { @@ -326,15 +294,12 @@ extern "C" void* MallocInitHeapprofdHook(size_t bytes) { return Malloc(malloc)(bytes); } -// Marks this process as a profileable zygote child. -static bool HandleInitZygoteChildProfiling() { - atomic_store_explicit(&gZygoteChildProfileable, true, memory_order_release); - +bool HeapprofdInitZygoteChildProfiling() { // Conditionally start "from startup" profiling. if (HeapprofdShouldLoad()) { - // Directly call the signal handler (will correctly guard against - // concurrent signal delivery). - MaybeInstallInitHeapprofdHook(kHeapprofdSignal); + // Directly call the signal handler codepath (properly protects against + // concurrent invocations). + HandleHeapprofdSignal(); } return true; } @@ -358,13 +323,6 @@ static bool DispatchReset() { } bool HeapprofdMallopt(int opcode, void* arg, size_t arg_size) { - if (opcode == M_INIT_ZYGOTE_CHILD_PROFILING) { - if (arg != nullptr || arg_size != 0) { - errno = EINVAL; - return false; - } - return HandleInitZygoteChildProfiling(); - } if (opcode == M_RESET_HOOKS) { if (arg != nullptr || arg_size != 0) { errno = EINVAL; diff --git a/libc/bionic/malloc_heapprofd.h b/libc/bionic/malloc_heapprofd.h index 9e846b6f5..d67a78887 100644 --- a/libc/bionic/malloc_heapprofd.h +++ b/libc/bionic/malloc_heapprofd.h @@ -36,12 +36,11 @@ bool HeapprofdShouldLoad(); void HeapprofdInstallHooksAtInit(libc_globals* globals); -void HeapprofdInstallSignalHandler(); +void HeapprofdRememberHookConflict(); -void HeapprofdInstallErrorSignalHandler(); +void HandleHeapprofdSignal(); -void HeapprofdMaskSignal(); - -void HeapprofdUnmaskSignal(); +bool HeapprofdInitZygoteChildProfiling(); bool HeapprofdMallopt(int optcode, void* arg, size_t arg_size); + diff --git a/libc/bionic/malloc_limit.cpp b/libc/bionic/malloc_limit.cpp index d8c0ebe54..ebc33ab7c 100644 --- a/libc/bionic/malloc_limit.cpp +++ b/libc/bionic/malloc_limit.cpp @@ -264,7 +264,6 @@ static bool EnableLimitDispatchTable() { } #else static bool EnableLimitDispatchTable() { - HeapprofdMaskSignal(); pthread_mutex_lock(&gGlobalsMutateLock); // All other code that calls mutate will grab the gGlobalsMutateLock. // However, there is one case where the lock cannot be acquired, in the @@ -272,7 +271,7 @@ static bool EnableLimitDispatchTable() { // threads calling mutate at the same time, use an atomic variable to // verify that only this function or the signal handler are calling mutate. // If this function is called at the same time as the signal handler is - // being called, allow up to five ms for the signal handler to complete + // being called, allow a short period for the signal handler to complete // before failing. bool enabled = false; size_t num_tries = 20; @@ -291,7 +290,6 @@ static bool EnableLimitDispatchTable() { usleep(1000); } pthread_mutex_unlock(&gGlobalsMutateLock); - HeapprofdUnmaskSignal(); if (enabled) { info_log("malloc_limit: Allocation limit enabled, max size %" PRId64 " bytes\n", gAllocLimit); } else { diff --git a/libc/platform/bionic/malloc.h b/libc/platform/bionic/malloc.h index 70fd27348..29e4edc29 100644 --- a/libc/platform/bionic/malloc.h +++ b/libc/platform/bionic/malloc.h @@ -91,6 +91,12 @@ enum { // arg_size = sizeof(HeapTaggingLevel) M_SET_HEAP_TAGGING_LEVEL = 8, #define M_SET_HEAP_TAGGING_LEVEL M_SET_HEAP_TAGGING_LEVEL + // Query whether the current process is considered to be profileable by the + // Android platform. Result is assigned to the arg pointer's destination. + // arg = bool* + // arg_size = sizeof(bool) + M_GET_PROCESS_PROFILEABLE = 9, +#define M_GET_PROCESS_PROFILEABLE M_GET_PROCESS_PROFILEABLE }; enum HeapTaggingLevel { diff --git a/libc/platform/bionic/reserved_signals.h b/libc/platform/bionic/reserved_signals.h index f062b1cad..fcfcaafaf 100644 --- a/libc/platform/bionic/reserved_signals.h +++ b/libc/platform/bionic/reserved_signals.h @@ -39,7 +39,7 @@ // 33 (__SIGRTMIN + 1) libbacktrace // 34 (__SIGRTMIN + 2) libcore // 35 (__SIGRTMIN + 3) debuggerd -// 36 (__SIGRTMIN + 4) heapprofd native dumps +// 36 (__SIGRTMIN + 4) platform profiling (heapprofd) // 37 (__SIGRTMIN + 5) coverage (libprofile-extras) // 38 (__SIGRTMIN + 6) heapprofd ART managed heap dumps // @@ -47,6 +47,7 @@ // in <android/legacy_signal_inlines.h> to match. #define BIONIC_SIGNAL_DEBUGGER __SIGRTMIN + 3 +#define BIONIC_SIGNAL_PROFILER __SIGRTMIN + 4 #define __SIGRT_RESERVED 7 static inline __always_inline sigset64_t filter_reserved_signals(sigset64_t sigset, int how) { diff --git a/libc/private/bionic_globals.h b/libc/private/bionic_globals.h index ef735bab0..7f1b3d938 100644 --- a/libc/private/bionic_globals.h +++ b/libc/private/bionic_globals.h @@ -99,6 +99,7 @@ struct libc_shared_globals { __LIBC_HIDDEN__ libc_shared_globals* __libc_shared_globals(); __LIBC_HIDDEN__ void __libc_init_fdsan(); +__LIBC_HIDDEN__ void __libc_init_profiling_handlers(); __LIBC_HIDDEN__ void __libc_init_malloc(libc_globals* globals); __LIBC_HIDDEN__ void __libc_init_setjmp_cookie(libc_globals* globals); diff --git a/tests/malloc_test.cpp b/tests/malloc_test.cpp index ebbd24793..c7050dc68 100644 --- a/tests/malloc_test.cpp +++ b/tests/malloc_test.cpp @@ -18,14 +18,15 @@ #include <elf.h> #include <limits.h> +#include <malloc.h> #include <pthread.h> +#include <signal.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> -#include <malloc.h> #include <unistd.h> #include <atomic> @@ -962,7 +963,11 @@ static void SetAllocationLimitMultipleThreads() { // Let them go all at once. go = true; - ASSERT_EQ(0, kill(getpid(), __SIGRTMIN + 4)); + // Send hardcoded signal (BIONIC_SIGNAL_PROFILER with value 0) to trigger + // heapprofd handler. + union sigval signal_value; + signal_value.sival_int = 0; + ASSERT_EQ(0, sigqueue(getpid(), __SIGRTMIN + 4, signal_value)); size_t num_successful = 0; for (size_t i = 0; i < kNumThreads; i++) { |