Home | History | Annotate | Download | only in bionic
      1 /*
      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>
     35 #include <signal.h>
     36 #include <stdio.h>
     37 #include <stdlib.h>
     38 #include <unistd.h>
     39 
     40 #include <private/bionic_config.h>
     41 #include <private/bionic_malloc.h>
     42 #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"
     48 
     49 static constexpr char kHeapprofdSharedLib[] = "heapprofd_client.so";
     50 static constexpr char kHeapprofdPrefix[] = "heapprofd";
     51 static constexpr char kHeapprofdPropertyEnable[] = "heapprofd.enable";
     52 static constexpr int kHeapprofdSignal = __SIGRTMIN + 4;
     53 
     54 // The logic for triggering heapprofd (at runtime) is as follows:
     55 // 1. HEAPPROFD_SIGNAL is received by the process, entering the
     56 //    MaybeInstallInitHeapprofdHook signal handler.
     57 // 2. If the initialization is not already in flight
     58 //    (gHeapprofdInitInProgress is false), the malloc hook is set to
     59 //    point at InitHeapprofdHook, and gHeapprofdInitInProgress is set to
     60 //    true.
     61 // 3. The next malloc call enters InitHeapprofdHook, which removes the malloc
     62 //    hook, and spawns a detached pthread to run the InitHeapprofd task.
     63 //    (gHeapprofdInitHook_installed atomic is used to perform this once.)
     64 // 4. InitHeapprofd, on a dedicated pthread, loads the heapprofd client library,
     65 //    installs the full set of heapprofd hooks, and invokes the client's
     66 //    initializer. The dedicated pthread then terminates.
     67 // 5. gHeapprofdInitInProgress and gHeapprofdInitHookInstalled are
     68 //    reset to false such that heapprofd can be reinitialized. Reinitialization
     69 //    means that a new profiling session is started, and any still active is
     70 //    torn down.
     71 //
     72 // The incremental hooking and a dedicated task thread are used since we cannot
     73 // do heavy work within a signal handler, or when blocking a malloc invocation.
     74 
     75 // The handle returned by dlopen when previously loading the heapprofd
     76 // hooks. nullptr if shared library has not been already been loaded.
     77 static _Atomic (void*) gHeapprofdHandle = nullptr;
     78 
     79 static _Atomic bool gHeapprofdInitInProgress = false;
     80 static _Atomic bool gHeapprofdInitHookInstalled = false;
     81 
     82 // In a Zygote child process, this is set to true if profiling of this process
     83 // is allowed. Note that this is set at a later time than the global
     84 // gZygoteChild. The latter is set during the fork (while still in
     85 // zygote's SELinux domain). While this bit is set after the child is
     86 // specialized (and has transferred SELinux domains if applicable).
     87 static _Atomic bool gZygoteChildProfileable = false;
     88 
     89 extern "C" void* MallocInitHeapprofdHook(size_t);
     90 
     91 static constexpr MallocDispatch __heapprofd_init_dispatch
     92   __attribute__((unused)) = {
     93     Malloc(calloc),
     94     Malloc(free),
     95     Malloc(mallinfo),
     96     MallocInitHeapprofdHook,
     97     Malloc(malloc_usable_size),
     98     Malloc(memalign),
     99     Malloc(posix_memalign),
    100 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
    101     Malloc(pvalloc),
    102 #endif
    103     Malloc(realloc),
    104 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
    105     Malloc(valloc),
    106 #endif
    107     Malloc(iterate),
    108     Malloc(malloc_disable),
    109     Malloc(malloc_enable),
    110     Malloc(mallopt),
    111     Malloc(aligned_alloc),
    112     Malloc(malloc_info),
    113   };
    114 
    115 static void MaybeInstallInitHeapprofdHook(int) {
    116   // Zygote child processes must be marked profileable.
    117   if (gZygoteChild &&
    118       !atomic_load_explicit(&gZygoteChildProfileable, memory_order_acquire)) {
    119     return;
    120   }
    121 
    122   // Checking this variable is only necessary when this could conflict with
    123   // the change to enable the allocation limit. All other places will
    124   // not ever have a conflict modifying the globals.
    125   if (!atomic_exchange(&gGlobalsMutating, true)) {
    126     if (!atomic_exchange(&gHeapprofdInitInProgress, true)) {
    127       __libc_globals.mutate([](libc_globals* globals) {
    128         atomic_store(&globals->default_dispatch_table, &__heapprofd_init_dispatch);
    129         auto dispatch_table = GetDispatchTable();
    130         if (dispatch_table == nullptr || dispatch_table == &globals->malloc_dispatch_table) {
    131           atomic_store(&globals->current_dispatch_table, &__heapprofd_init_dispatch);
    132         }
    133       });
    134     }
    135     atomic_store(&gGlobalsMutating, false);
    136   } else {
    137     // The only way you can get to this point is if the signal has been
    138     // blocked by a call to HeapprofdMaskSignal. The raise below will
    139     // do nothing until a call to HeapprofdUnmaskSignal, which will cause
    140     // the signal to be resent. Using this avoids the need for a busy loop
    141     // waiting for gGlobalsMutating to change back to false.
    142     raise(kHeapprofdSignal);
    143   }
    144 }
    145 
    146 constexpr char kHeapprofdProgramPropertyPrefix[] = "heapprofd.enable.";
    147 constexpr size_t kHeapprofdProgramPropertyPrefixSize = sizeof(kHeapprofdProgramPropertyPrefix) - 1;
    148 constexpr size_t kMaxCmdlineSize = 512;
    149 
    150 static bool GetHeapprofdProgramProperty(char* data, size_t size) {
    151   if (size < kHeapprofdProgramPropertyPrefixSize) {
    152     error_log("%s: Overflow constructing heapprofd property", getprogname());
    153     return false;
    154   }
    155   memcpy(data, kHeapprofdProgramPropertyPrefix, kHeapprofdProgramPropertyPrefixSize);
    156 
    157   int fd = open("/proc/self/cmdline", O_RDONLY | O_CLOEXEC);
    158   if (fd == -1) {
    159     error_log("%s: Failed to open /proc/self/cmdline", getprogname());
    160     return false;
    161   }
    162   char cmdline[kMaxCmdlineSize];
    163   ssize_t rd = read(fd, cmdline, sizeof(cmdline) - 1);
    164   close(fd);
    165   if (rd == -1) {
    166     error_log("%s: Failed to read /proc/self/cmdline", getprogname());
    167     return false;
    168   }
    169   cmdline[rd] = '\0';
    170   char* first_arg = static_cast<char*>(memchr(cmdline, '\0', rd));
    171   if (first_arg == nullptr) {
    172     error_log("%s: Overflow reading cmdline", getprogname());
    173     return false;
    174   }
    175   // For consistency with what we do with Java app cmdlines, trim everything
    176   // after the @ sign of the first arg.
    177   char* first_at = static_cast<char*>(memchr(cmdline, '@', rd));
    178   if (first_at != nullptr && first_at < first_arg) {
    179     *first_at = '\0';
    180     first_arg = first_at;
    181   }
    182 
    183   char* start = static_cast<char*>(memrchr(cmdline, '/', first_arg - cmdline));
    184   if (start == first_arg) {
    185     // The first argument ended in a slash.
    186     error_log("%s: cmdline ends in /", getprogname());
    187     return false;
    188   } else if (start == nullptr) {
    189     start = cmdline;
    190   } else {
    191     // Skip the /.
    192     start++;
    193   }
    194 
    195   size_t name_size = static_cast<size_t>(first_arg - start);
    196   if (name_size >= size - kHeapprofdProgramPropertyPrefixSize) {
    197     error_log("%s: overflow constructing heapprofd property.", getprogname());
    198     return false;
    199   }
    200   // + 1 to also copy the trailing null byte.
    201   memcpy(data + kHeapprofdProgramPropertyPrefixSize, start, name_size + 1);
    202   return true;
    203 }
    204 
    205 bool 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 
    217   char program_property[kHeapprofdProgramPropertyPrefixSize + kMaxCmdlineSize];
    218   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   }
    225   return property_value[0] != '\0';
    226 }
    227 
    228 void HeapprofdInstallSignalHandler() {
    229   struct sigaction action = {};
    230   action.sa_handler = MaybeInstallInitHeapprofdHook;
    231   sigaction(kHeapprofdSignal, &action, nullptr);
    232 }
    233 
    234 extern "C" int __rt_sigprocmask(int, const sigset64_t*, sigset64_t*, size_t);
    235 
    236 void HeapprofdMaskSignal() {
    237   sigset64_t mask_set;
    238   // Need to use this function instead because sigprocmask64 filters
    239   // out this signal.
    240   __rt_sigprocmask(SIG_SETMASK, nullptr, &mask_set, sizeof(mask_set));
    241   sigaddset64(&mask_set, kHeapprofdSignal);
    242   __rt_sigprocmask(SIG_SETMASK, &mask_set, nullptr, sizeof(mask_set));
    243 }
    244 
    245 void HeapprofdUnmaskSignal() {
    246   sigset64_t mask_set;
    247   __rt_sigprocmask(SIG_SETMASK, nullptr, &mask_set, sizeof(mask_set));
    248   sigdelset64(&mask_set, kHeapprofdSignal);
    249   __rt_sigprocmask(SIG_SETMASK, &mask_set, nullptr, sizeof(mask_set));
    250 }
    251 
    252 static void DisplayError(int) {
    253   error_log("Cannot install heapprofd while malloc debug/malloc hooks are enabled.");
    254 }
    255 
    256 void HeapprofdInstallErrorSignalHandler() {
    257   struct sigaction action = {};
    258   action.sa_handler = DisplayError;
    259   sigaction(kHeapprofdSignal, &action, nullptr);
    260 }
    261 
    262 static void CommonInstallHooks(libc_globals* globals) {
    263   void* impl_handle = atomic_load(&gHeapprofdHandle);
    264   bool reusing_handle = impl_handle != nullptr;
    265   if (!reusing_handle) {
    266     impl_handle = LoadSharedLibrary(kHeapprofdSharedLib, kHeapprofdPrefix, &globals->malloc_dispatch_table);
    267     if (impl_handle == nullptr) {
    268       return;
    269     }
    270   } else if (!InitSharedLibrary(impl_handle, kHeapprofdSharedLib, kHeapprofdPrefix, &globals->malloc_dispatch_table)) {
    271     return;
    272   }
    273 
    274   if (FinishInstallHooks(globals, nullptr, kHeapprofdPrefix)) {
    275     atomic_store(&gHeapprofdHandle, impl_handle);
    276   } else if (!reusing_handle) {
    277     dlclose(impl_handle);
    278   }
    279 
    280   atomic_store(&gHeapprofdInitInProgress, false);
    281 }
    282 
    283 void HeapprofdInstallHooksAtInit(libc_globals* globals) {
    284   if (atomic_exchange(&gHeapprofdInitInProgress, true)) {
    285     return;
    286   }
    287   CommonInstallHooks(globals);
    288 }
    289 
    290 static void* InitHeapprofd(void*) {
    291   pthread_mutex_lock(&gGlobalsMutateLock);
    292   __libc_globals.mutate([](libc_globals* globals) {
    293     CommonInstallHooks(globals);
    294   });
    295   pthread_mutex_unlock(&gGlobalsMutateLock);
    296 
    297   // Allow to install hook again to re-initialize heap profiling after the
    298   // current session finished.
    299   atomic_store(&gHeapprofdInitHookInstalled, false);
    300   return nullptr;
    301 }
    302 
    303 extern "C" void* MallocInitHeapprofdHook(size_t bytes) {
    304   if (!atomic_exchange(&gHeapprofdInitHookInstalled, true)) {
    305     pthread_mutex_lock(&gGlobalsMutateLock);
    306     __libc_globals.mutate([](libc_globals* globals) {
    307       auto old_dispatch = GetDefaultDispatchTable();
    308       atomic_store(&globals->default_dispatch_table, nullptr);
    309       if (GetDispatchTable() == old_dispatch) {
    310         atomic_store(&globals->current_dispatch_table, nullptr);
    311       }
    312     });
    313     pthread_mutex_unlock(&gGlobalsMutateLock);
    314 
    315     pthread_t thread_id;
    316     if (pthread_create(&thread_id, nullptr, InitHeapprofd, nullptr) != 0) {
    317       error_log("%s: heapprofd: failed to pthread_create.", getprogname());
    318     } else if (pthread_detach(thread_id) != 0) {
    319       error_log("%s: heapprofd: failed to pthread_detach", getprogname());
    320     }
    321     if (pthread_setname_np(thread_id, "heapprofdinit") != 0) {
    322       error_log("%s: heapprod: failed to pthread_setname_np", getprogname());
    323     }
    324   }
    325   return Malloc(malloc)(bytes);
    326 }
    327 
    328 // Marks this process as a profileable zygote child.
    329 static bool HandleInitZygoteChildProfiling() {
    330   atomic_store_explicit(&gZygoteChildProfileable, true, memory_order_release);
    331 
    332   // Conditionally start "from startup" profiling.
    333   if (HeapprofdShouldLoad()) {
    334     // Directly call the signal handler (will correctly guard against
    335     // concurrent signal delivery).
    336     MaybeInstallInitHeapprofdHook(kHeapprofdSignal);
    337   }
    338   return true;
    339 }
    340 
    341 static bool DispatchReset() {
    342   if (!atomic_exchange(&gHeapprofdInitInProgress, true)) {
    343     pthread_mutex_lock(&gGlobalsMutateLock);
    344     __libc_globals.mutate([](libc_globals* globals) {
    345       auto old_dispatch = GetDefaultDispatchTable();
    346       atomic_store(&globals->default_dispatch_table, nullptr);
    347       if (GetDispatchTable() == old_dispatch) {
    348         atomic_store(&globals->current_dispatch_table, nullptr);
    349       }
    350     });
    351     pthread_mutex_unlock(&gGlobalsMutateLock);
    352     atomic_store(&gHeapprofdInitInProgress, false);
    353     return true;
    354   }
    355   errno = EAGAIN;
    356   return false;
    357 }
    358 
    359 bool HeapprofdMallopt(int opcode, void* arg, size_t arg_size) {
    360   if (opcode == M_INIT_ZYGOTE_CHILD_PROFILING) {
    361     if (arg != nullptr || arg_size != 0) {
    362       errno = EINVAL;
    363       return false;
    364     }
    365     return HandleInitZygoteChildProfiling();
    366   }
    367   if (opcode == M_RESET_HOOKS) {
    368     if (arg != nullptr || arg_size != 0) {
    369       errno = EINVAL;
    370       return false;
    371     }
    372     return DispatchReset();
    373   }
    374   errno = ENOTSUP;
    375   return false;
    376 }
    377