| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 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> | 
| Christopher Ferris | 1fc5ccf | 2019-02-15 18:06:15 -0800 | [diff] [blame] | 35 | #include <signal.h> | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 36 | #include <stdio.h> | 
|  | 37 | #include <stdlib.h> | 
|  | 38 | #include <unistd.h> | 
|  | 39 |  | 
| Christopher Ferris | 2b0638e | 2019-09-11 19:05:29 -0700 | [diff] [blame] | 40 | #include <platform/bionic/malloc.h> | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 41 | #include <private/bionic_config.h> | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 42 | #include <private/bionic_malloc_dispatch.h> | 
|  | 43 | #include <sys/system_properties.h> | 
|  | 44 |  | 
| Mitch Phillips | c03856c | 2020-02-13 16:41:14 -0800 | [diff] [blame] | 45 | #include "gwp_asan_wrappers.h" | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 46 | #include "malloc_common.h" | 
|  | 47 | #include "malloc_common_dynamic.h" | 
|  | 48 | #include "malloc_heapprofd.h" | 
| Mitch Phillips | 3083cc9 | 2020-02-11 15:23:47 -0800 | [diff] [blame] | 49 | #include "malloc_limit.h" | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 50 |  | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 51 | // Installing heapprofd hooks is a multi step process, as outlined below. | 
|  | 52 | // | 
|  | 53 | // The incremental hooking and a dedicated task thread are used since we cannot | 
|  | 54 | // do heavy work within a signal handler, or when blocking a malloc invocation. | 
|  | 55 | // | 
|  | 56 | // +--->+-------------+------------------+ | 
|  | 57 | // | +->+kInitialState+----------------+ |  malloc functions are not intercepted in any way. | 
|  | 58 | // | |  +-------+-----+                | | | 
| Florian Mayer | c618960 | 2020-06-26 14:27:58 +0200 | [diff] [blame] | 59 | // | |          | HandleHeapprofd      | | | 
|  | 60 | // | |          v Signal()             | | | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 61 | // | |  +-------+----------------+     | |  currently installing the ephemeral hooks. | 
|  | 62 | // | |  |kInstallingEphemeralHook|<--+ | | | 
|  | 63 | // | |  +-------+----------------+   | | | | 
|  | 64 | // | |          |                    | | | | 
|  | 65 | // | |          v                    | | | | 
|  | 66 | // | |  +-------+---------------+    | | |  ephemeral hooks are installed. on the first call to | 
|  | 67 | // | |  |kEphemeralHookInstalled|    | | |  malloc these hooks spawn a thread that installs the | 
| Florian Mayer | c618960 | 2020-06-26 14:27:58 +0200 | [diff] [blame] | 68 | // | |  +-------+---------------+    A B C  heapprofd hooks. | 
|  | 69 | // | |          | MallocInit         | | | | 
|  | 70 | // | |          v HeapprofdHook ()   | | | | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 71 | // | |  +-------+--------------+     | | |  first call to malloc happened. the hooks are reset to | 
|  | 72 | // | +--|kRemovingEphemeralHook|     | | |  kInitialState. | 
|  | 73 | // |    +----------------------+     | | | | 
|  | 74 | // |                                 | | | | 
|  | 75 | // |                                 | | | | 
|  | 76 | // |    +---------------+            | | |  currently installing the heapprofd hook | 
|  | 77 | // |    |kInstallingHook|<-----------|-+ | | 
|  | 78 | // |    +-------+-------+            |   | | 
|  | 79 | // |            |                    |   | | 
|  | 80 | // |            v                    |   | | 
|  | 81 | // |    +-------+------+             |   |  heapprofd hooks are installed. these forward calls to | 
|  | 82 | // |    |kHookInstalled|-------------+   |  malloc / free / etc. to heapprofd_client.so. | 
|  | 83 | // |    +-------+------+                 | | 
| Florian Mayer | c618960 | 2020-06-26 14:27:58 +0200 | [diff] [blame] | 84 | // |            | DispatchReset()        | | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 85 | // |            v                        | | 
|  | 86 | // |    +-------+---------+              |  currently resetting the hooks to default. | 
|  | 87 | // |----+kUninstallingHook|              | | 
|  | 88 | //      +-----------------+              | | 
|  | 89 | //                                       | | 
|  | 90 | //                                       | | 
|  | 91 | //      +------------------+             |  malloc debug / malloc hooks are active. these take | 
|  | 92 | //      |kIncompatibleHooks+<------------+  precendence over heapprofd, so heapprofd will not get | 
|  | 93 | //      +------------------+                enabled. this is a terminal state. | 
|  | 94 | // | 
| Florian Mayer | c618960 | 2020-06-26 14:27:58 +0200 | [diff] [blame] | 95 | // | 
|  | 96 | // A) HandleHeapprofdSignal() | 
|  | 97 | // B) HeapprofdInstallHooksAtInit() / InitHeapprofd() | 
|  | 98 | // C) HeapprofdRememberHookConflict() | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 99 | enum MallocHeapprofdState : uint8_t { | 
|  | 100 | kInitialState, | 
|  | 101 | kInstallingEphemeralHook, | 
|  | 102 | kEphemeralHookInstalled, | 
|  | 103 | kRemovingEphemeralHook, | 
|  | 104 | kInstallingHook, | 
|  | 105 | kHookInstalled, | 
|  | 106 | kUninstallingHook, | 
|  | 107 | kIncompatibleHooks | 
|  | 108 | }; | 
|  | 109 |  | 
|  | 110 | enum ModifyGlobalsMode { | 
|  | 111 | kWithLock,   // all calls to MaybeModifyGlobals with kWithLock will serialise. they can fail | 
|  | 112 | // due to a concurrent call with kWithoutLock. | 
|  | 113 | kWithoutLock // calls to MaybeModifyGlobals with kWithoutLock do not serialise. they can fail | 
|  | 114 | // due to concurrent calls with kWithoutLock or kWithLock. | 
|  | 115 | }; | 
|  | 116 |  | 
|  | 117 | // Provide mutual exclusion so no two threads try to modify the globals at the same time. | 
|  | 118 | template <typename Fn> | 
|  | 119 | bool MaybeModifyGlobals(ModifyGlobalsMode mode, Fn f) { | 
|  | 120 | bool success = false; | 
|  | 121 | if (mode == kWithLock) { | 
|  | 122 | pthread_mutex_lock(&gGlobalsMutateLock); | 
|  | 123 | } | 
|  | 124 | // As we have grabbed the mutex, the following condition should always hold, except | 
|  | 125 | // if we are currently running HandleHeapprofdSignal. | 
|  | 126 | if (!atomic_exchange(&gGlobalsMutating, true)) { | 
|  | 127 | f(); | 
|  | 128 | success = true; | 
|  | 129 | atomic_store(&gGlobalsMutating, false); | 
|  | 130 | } else { | 
|  | 131 | error_log("%s: heapprofd client: concurrent modification.", getprogname()); | 
|  | 132 | } | 
|  | 133 | if (mode == kWithLock) { | 
|  | 134 | pthread_mutex_unlock(&gGlobalsMutateLock); | 
|  | 135 | } | 
|  | 136 | return success; | 
|  | 137 | } | 
|  | 138 |  | 
|  | 139 | extern "C" void* MallocInitHeapprofdHook(size_t); | 
|  | 140 |  | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 141 | static constexpr char kHeapprofdSharedLib[] = "heapprofd_client.so"; | 
|  | 142 | static constexpr char kHeapprofdPrefix[] = "heapprofd"; | 
|  | 143 | static constexpr char kHeapprofdPropertyEnable[] = "heapprofd.enable"; | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 144 |  | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 145 | constexpr char kHeapprofdProgramPropertyPrefix[] = "heapprofd.enable."; | 
|  | 146 | constexpr size_t kHeapprofdProgramPropertyPrefixSize = sizeof(kHeapprofdProgramPropertyPrefix) - 1; | 
|  | 147 | constexpr size_t kMaxCmdlineSize = 512; | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 148 |  | 
|  | 149 | // The handle returned by dlopen when previously loading the heapprofd | 
|  | 150 | // hooks. nullptr if shared library has not been already been loaded. | 
|  | 151 | static _Atomic (void*) gHeapprofdHandle = nullptr; | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 152 | static _Atomic MallocHeapprofdState gHeapprofdState = kInitialState; | 
| Florian Mayer | f6d221e | 2019-05-03 16:24:52 +0100 | [diff] [blame] | 153 |  | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 154 | static bool GetHeapprofdProgramProperty(char* data, size_t size) { | 
| Florian Mayer | f6d221e | 2019-05-03 16:24:52 +0100 | [diff] [blame] | 155 | if (size < kHeapprofdProgramPropertyPrefixSize) { | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 156 | error_log("%s: Overflow constructing heapprofd property", getprogname()); | 
|  | 157 | return false; | 
|  | 158 | } | 
| Florian Mayer | f6d221e | 2019-05-03 16:24:52 +0100 | [diff] [blame] | 159 | memcpy(data, kHeapprofdProgramPropertyPrefix, kHeapprofdProgramPropertyPrefixSize); | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 160 |  | 
|  | 161 | int fd = open("/proc/self/cmdline", O_RDONLY | O_CLOEXEC); | 
|  | 162 | if (fd == -1) { | 
|  | 163 | error_log("%s: Failed to open /proc/self/cmdline", getprogname()); | 
|  | 164 | return false; | 
|  | 165 | } | 
| Florian Mayer | f6d221e | 2019-05-03 16:24:52 +0100 | [diff] [blame] | 166 | char cmdline[kMaxCmdlineSize]; | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 167 | ssize_t rd = read(fd, cmdline, sizeof(cmdline) - 1); | 
|  | 168 | close(fd); | 
|  | 169 | if (rd == -1) { | 
|  | 170 | error_log("%s: Failed to read /proc/self/cmdline", getprogname()); | 
|  | 171 | return false; | 
|  | 172 | } | 
|  | 173 | cmdline[rd] = '\0'; | 
|  | 174 | char* first_arg = static_cast<char*>(memchr(cmdline, '\0', rd)); | 
| Florian Mayer | f6d221e | 2019-05-03 16:24:52 +0100 | [diff] [blame] | 175 | if (first_arg == nullptr) { | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 176 | error_log("%s: Overflow reading cmdline", getprogname()); | 
|  | 177 | return false; | 
|  | 178 | } | 
|  | 179 | // For consistency with what we do with Java app cmdlines, trim everything | 
|  | 180 | // after the @ sign of the first arg. | 
|  | 181 | char* first_at = static_cast<char*>(memchr(cmdline, '@', rd)); | 
|  | 182 | if (first_at != nullptr && first_at < first_arg) { | 
|  | 183 | *first_at = '\0'; | 
|  | 184 | first_arg = first_at; | 
|  | 185 | } | 
|  | 186 |  | 
|  | 187 | char* start = static_cast<char*>(memrchr(cmdline, '/', first_arg - cmdline)); | 
|  | 188 | if (start == first_arg) { | 
|  | 189 | // The first argument ended in a slash. | 
|  | 190 | error_log("%s: cmdline ends in /", getprogname()); | 
|  | 191 | return false; | 
|  | 192 | } else if (start == nullptr) { | 
|  | 193 | start = cmdline; | 
|  | 194 | } else { | 
|  | 195 | // Skip the /. | 
|  | 196 | start++; | 
|  | 197 | } | 
|  | 198 |  | 
|  | 199 | size_t name_size = static_cast<size_t>(first_arg - start); | 
| Florian Mayer | f6d221e | 2019-05-03 16:24:52 +0100 | [diff] [blame] | 200 | if (name_size >= size - kHeapprofdProgramPropertyPrefixSize) { | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 201 | error_log("%s: overflow constructing heapprofd property.", getprogname()); | 
|  | 202 | return false; | 
|  | 203 | } | 
|  | 204 | // + 1 to also copy the trailing null byte. | 
| Florian Mayer | f6d221e | 2019-05-03 16:24:52 +0100 | [diff] [blame] | 205 | memcpy(data + kHeapprofdProgramPropertyPrefixSize, start, name_size + 1); | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 206 | return true; | 
|  | 207 | } | 
|  | 208 |  | 
| Ryan Savitski | 175c886 | 2020-01-02 19:54:57 +0000 | [diff] [blame] | 209 | // Runtime triggering entry-point. Two possible call sites: | 
|  | 210 | // * when receiving a profiling signal with a si_value indicating heapprofd. | 
|  | 211 | // * when a Zygote child is marking itself as profileable, and there's a | 
|  | 212 | //   matching profiling request for this process (in which case heapprofd client | 
|  | 213 | //   is loaded synchronously). | 
|  | 214 | // In both cases, the caller is responsible for verifying that the process is | 
|  | 215 | // considered profileable. | 
| Mitch Phillips | c03856c | 2020-02-13 16:41:14 -0800 | [diff] [blame] | 216 |  | 
|  | 217 | // Previously installed default dispatch table, if it exists. This is used to | 
|  | 218 | // load heapprofd properly when GWP-ASan was already installed. If GWP-ASan was | 
|  | 219 | // already installed, heapprofd will take over the dispatch table, but will use | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 220 | // GWP-ASan as the backing dispatch. Writes to this variable is atomically | 
|  | 221 | // protected by MaybeModifyGlobals. | 
|  | 222 | // Reads are not protected, so this is atomic. We cannot fail the call in | 
|  | 223 | // MallocInitHeapprofdHook. | 
|  | 224 | static _Atomic (const MallocDispatch*) gPreviousDefaultDispatchTable = nullptr; | 
| Mitch Phillips | c03856c | 2020-02-13 16:41:14 -0800 | [diff] [blame] | 225 | static MallocDispatch gEphemeralDispatch; | 
|  | 226 |  | 
| Ryan Savitski | 175c886 | 2020-01-02 19:54:57 +0000 | [diff] [blame] | 227 | void HandleHeapprofdSignal() { | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 228 | if (atomic_load(&gHeapprofdState) == kIncompatibleHooks) { | 
| Ryan Savitski | 175c886 | 2020-01-02 19:54:57 +0000 | [diff] [blame] | 229 | error_log("%s: not enabling heapprofd, malloc_debug/malloc_hooks are enabled.", getprogname()); | 
|  | 230 | return; | 
|  | 231 | } | 
|  | 232 |  | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 233 | // We cannot grab the mutex here, as this is used in a signal handler. | 
|  | 234 | MaybeModifyGlobals(kWithoutLock, [] { | 
|  | 235 | MallocHeapprofdState expected = kInitialState; | 
|  | 236 | // If hooks are already installed, we still want to install ephemeral hooks to retrigger | 
|  | 237 | // heapprofd client initialization. | 
|  | 238 | MallocHeapprofdState expected2 = kHookInstalled; | 
|  | 239 | if (atomic_compare_exchange_strong(&gHeapprofdState, &expected, | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 240 | kInstallingEphemeralHook)) { | 
| Mitch Phillips | c03856c | 2020-02-13 16:41:14 -0800 | [diff] [blame] | 241 | const MallocDispatch* default_dispatch = GetDefaultDispatchTable(); | 
| Mitch Phillips | 5f91bf4 | 2020-02-26 11:28:11 -0800 | [diff] [blame] | 242 |  | 
|  | 243 | // Below, we initialize heapprofd lazily by redirecting libc's malloc() to | 
|  | 244 | // call MallocInitHeapprofdHook, which spawns off a thread and initializes | 
|  | 245 | // heapprofd. During the short period between now and when heapprofd is | 
|  | 246 | // initialized, allocations may need to be serviced. There are three | 
|  | 247 | // possible configurations: | 
|  | 248 |  | 
| Florian Mayer | 3a0ced8 | 2021-07-20 15:43:37 +0100 | [diff] [blame] | 249 | if (DispatchIsGwpAsan(default_dispatch)) { | 
|  | 250 | //  1. GWP-ASan was installed. We should use GWP-ASan for everything but | 
| Mitch Phillips | 5f91bf4 | 2020-02-26 11:28:11 -0800 | [diff] [blame] | 251 | //  malloc() in the interim period before heapprofd is properly | 
|  | 252 | //  installed. After heapprofd is finished installing, we will use | 
|  | 253 | //  GWP-ASan as heapprofd's backing allocator to allow heapprofd and | 
|  | 254 | //  GWP-ASan to coexist. | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 255 | atomic_store(&gPreviousDefaultDispatchTable, default_dispatch); | 
| Mitch Phillips | 5f91bf4 | 2020-02-26 11:28:11 -0800 | [diff] [blame] | 256 | gEphemeralDispatch = *default_dispatch; | 
|  | 257 | } else { | 
| Florian Mayer | 3a0ced8 | 2021-07-20 15:43:37 +0100 | [diff] [blame] | 258 | // Either, | 
|  | 259 | // 2. No malloc hooking has been done (heapprofd, GWP-ASan, etc.). In | 
|  | 260 | // this case, everything but malloc() should come from the system | 
|  | 261 | // allocator. | 
|  | 262 | // | 
|  | 263 | // or, | 
|  | 264 | // | 
| Mitch Phillips | 5f91bf4 | 2020-02-26 11:28:11 -0800 | [diff] [blame] | 265 | // 3. It may be possible at this point in time that heapprofd is | 
| Florian Mayer | 3a0ced8 | 2021-07-20 15:43:37 +0100 | [diff] [blame] | 266 | // *already* the default dispatch, and when it was initialized there | 
|  | 267 | // was no default dispatch installed. As such we don't want to use | 
| Mitch Phillips | 5f91bf4 | 2020-02-26 11:28:11 -0800 | [diff] [blame] | 268 | // heapprofd as the backing store for itself (otherwise infinite | 
|  | 269 | // recursion occurs). We will use the system allocator functions. Note: | 
|  | 270 | // We've checked that no other malloc interceptors are being used by | 
|  | 271 | // validating `gHeapprofdIncompatibleHooks` above, so we don't need to | 
|  | 272 | // worry about that case here. | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 273 | atomic_store(&gPreviousDefaultDispatchTable, nullptr); | 
| Mitch Phillips | 5f91bf4 | 2020-02-26 11:28:11 -0800 | [diff] [blame] | 274 | gEphemeralDispatch = *NativeAllocatorDispatch(); | 
| Mitch Phillips | c03856c | 2020-02-13 16:41:14 -0800 | [diff] [blame] | 275 | } | 
| Daniele Di Proietto | b6d3c78 | 2021-10-06 15:19:30 +0100 | [diff] [blame] | 276 | } else if (expected == kEphemeralHookInstalled) { | 
|  | 277 | // Nothing to do here. The ephemeral hook was installed, but | 
|  | 278 | // MallocInitHeapprofdHook() was never called. Since the ephemeral hook | 
|  | 279 | // is already there, no need to reinstall it. | 
|  | 280 | return; | 
| Florian Mayer | 3a0ced8 | 2021-07-20 15:43:37 +0100 | [diff] [blame] | 281 | } else if (atomic_compare_exchange_strong(&gHeapprofdState, &expected2, | 
|  | 282 | kInstallingEphemeralHook)) { | 
|  | 283 | // if we still have hook installed, we can reuse the previous | 
|  | 284 | // decision. THIS IS REQUIRED FOR CORRECTNESS, because otherwise the | 
|  | 285 | // following can happen | 
|  | 286 | // 1. Assume DispatchIsGwpAsan(default_dispatch) | 
|  | 287 | // 2. This function is ran, sets gPreviousDefaultDispatchTable to | 
|  | 288 | //    GWP ASan. | 
|  | 289 | // 3. The sessions ends, DispatchReset FAILS due to a race. Now | 
|  | 290 | //    heapprofd hooks are default dispatch. | 
|  | 291 | // 4. We re-enter this function later. If we did NOT look at the | 
|  | 292 | //    previously recorded gPreviousDefaultDispatchTable, we would | 
|  | 293 | //    incorrectly reach case 3. below. | 
|  | 294 | // 5. The session ends, DispatchReset now resets the hooks to the | 
|  | 295 | //    system allocator. This is incorrect. | 
|  | 296 | const MallocDispatch* prev_dispatch = | 
|  | 297 | atomic_load(&gPreviousDefaultDispatchTable); | 
|  | 298 | gEphemeralDispatch = prev_dispatch ? *prev_dispatch : *NativeAllocatorDispatch(); | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 299 | } else { | 
|  | 300 | error_log("%s: heapprofd: failed to transition kInitialState -> kInstallingEphemeralHook. " | 
|  | 301 | "current state (possible race): %d", getprogname(), expected2); | 
| Florian Mayer | 3a0ced8 | 2021-07-20 15:43:37 +0100 | [diff] [blame] | 302 | return; | 
| Ryan Savitski | 175c886 | 2020-01-02 19:54:57 +0000 | [diff] [blame] | 303 | } | 
| Florian Mayer | 3a0ced8 | 2021-07-20 15:43:37 +0100 | [diff] [blame] | 304 | // Now, replace the malloc function so that the next call to malloc() will | 
|  | 305 | // initialize heapprofd. | 
|  | 306 | gEphemeralDispatch.malloc = MallocInitHeapprofdHook; | 
|  | 307 |  | 
|  | 308 | // And finally, install these new malloc-family interceptors. | 
|  | 309 | __libc_globals.mutate([](libc_globals* globals) { | 
|  | 310 | atomic_store(&globals->default_dispatch_table, &gEphemeralDispatch); | 
|  | 311 | if (!MallocLimitInstalled()) { | 
|  | 312 | atomic_store(&globals->current_dispatch_table, &gEphemeralDispatch); | 
|  | 313 | } | 
|  | 314 | }); | 
|  | 315 | atomic_store(&gHeapprofdState, kEphemeralHookInstalled); | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 316 | }); | 
| Ryan Savitski | 175c886 | 2020-01-02 19:54:57 +0000 | [diff] [blame] | 317 | // Otherwise, we're racing against malloc_limit's enable logic (at most once | 
|  | 318 | // per process, and a niche feature). This is highly unlikely, so simply give | 
|  | 319 | // up if it does happen. | 
|  | 320 | } | 
|  | 321 |  | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 322 | bool HeapprofdShouldLoad() { | 
|  | 323 | // First check for heapprofd.enable. If it is set to "all", enable | 
|  | 324 | // heapprofd for all processes. Otherwise, check heapprofd.enable.${prog}, | 
|  | 325 | // if it is set and not 0, enable heap profiling for this process. | 
|  | 326 | char property_value[PROP_VALUE_MAX]; | 
|  | 327 | if (__system_property_get(kHeapprofdPropertyEnable, property_value) == 0) { | 
|  | 328 | return false; | 
|  | 329 | } | 
|  | 330 | if (strcmp(property_value, "all") == 0) { | 
|  | 331 | return true; | 
|  | 332 | } | 
|  | 333 |  | 
| Florian Mayer | f6d221e | 2019-05-03 16:24:52 +0100 | [diff] [blame] | 334 | char program_property[kHeapprofdProgramPropertyPrefixSize + kMaxCmdlineSize]; | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 335 | if (!GetHeapprofdProgramProperty(program_property, | 
|  | 336 | sizeof(program_property))) { | 
|  | 337 | return false; | 
|  | 338 | } | 
|  | 339 | if (__system_property_get(program_property, property_value) == 0) { | 
|  | 340 | return false; | 
|  | 341 | } | 
| Christopher Ferris | 503c17b | 2019-02-22 12:47:23 -0800 | [diff] [blame] | 342 | return property_value[0] != '\0'; | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 343 | } | 
|  | 344 |  | 
| Ryan Savitski | 175c886 | 2020-01-02 19:54:57 +0000 | [diff] [blame] | 345 | void HeapprofdRememberHookConflict() { | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 346 | atomic_store(&gHeapprofdState, kIncompatibleHooks); | 
| Christopher Ferris | 2822856 | 2019-02-14 10:23:58 -0800 | [diff] [blame] | 347 | } | 
|  | 348 |  | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 349 | static void CommonInstallHooks(libc_globals* globals) { | 
|  | 350 | void* impl_handle = atomic_load(&gHeapprofdHandle); | 
| Florian Mayer | 85c7838 | 2021-06-02 14:43:29 +0100 | [diff] [blame] | 351 | if (impl_handle == nullptr) { | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 352 | impl_handle = LoadSharedLibrary(kHeapprofdSharedLib, kHeapprofdPrefix, &globals->malloc_dispatch_table); | 
|  | 353 | if (impl_handle == nullptr) { | 
|  | 354 | return; | 
|  | 355 | } | 
| Florian Mayer | 85c7838 | 2021-06-02 14:43:29 +0100 | [diff] [blame] | 356 | atomic_store(&gHeapprofdHandle, impl_handle); | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 357 | } else if (!InitSharedLibrary(impl_handle, kHeapprofdSharedLib, kHeapprofdPrefix, &globals->malloc_dispatch_table)) { | 
|  | 358 | return; | 
|  | 359 | } | 
|  | 360 |  | 
| Florian Mayer | 3a0ced8 | 2021-07-20 15:43:37 +0100 | [diff] [blame] | 361 | FinishInstallHooks(globals, nullptr, kHeapprofdPrefix); | 
|  | 362 | } | 
|  | 363 |  | 
|  | 364 | void HeapprofdInstallHooksAtInit(libc_globals *globals) { | 
| Mitch Phillips | c03856c | 2020-02-13 16:41:14 -0800 | [diff] [blame] | 365 | // Before we set the new default_dispatch_table in FinishInstallHooks, save | 
|  | 366 | // the previous dispatch table. If DispatchReset() gets called later, we want | 
|  | 367 | // to be able to restore the dispatch. We're still under | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 368 | // MaybeModifyGlobals locks at this point. | 
|  | 369 | atomic_store(&gPreviousDefaultDispatchTable, GetDefaultDispatchTable()); | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 370 | MaybeModifyGlobals(kWithoutLock, [globals] { | 
|  | 371 | MallocHeapprofdState expected = kInitialState; | 
|  | 372 | if (atomic_compare_exchange_strong(&gHeapprofdState, &expected, kInstallingHook)) { | 
|  | 373 | CommonInstallHooks(globals); | 
|  | 374 | atomic_store(&gHeapprofdState, kHookInstalled); | 
|  | 375 | } else { | 
|  | 376 | error_log("%s: heapprofd: failed to transition kInitialState -> kInstallingHook. " | 
|  | 377 | "current state (possible race): %d", getprogname(), expected); | 
|  | 378 | } | 
|  | 379 | }); | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 380 | } | 
|  | 381 |  | 
|  | 382 | static void* InitHeapprofd(void*) { | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 383 | MaybeModifyGlobals(kWithLock, [] { | 
|  | 384 | MallocHeapprofdState expected = kInitialState; | 
|  | 385 | if (atomic_compare_exchange_strong(&gHeapprofdState, &expected, kInstallingHook)) { | 
|  | 386 | __libc_globals.mutate([](libc_globals* globals) { | 
|  | 387 | CommonInstallHooks(globals); | 
|  | 388 | }); | 
|  | 389 | atomic_store(&gHeapprofdState, kHookInstalled); | 
|  | 390 | } else { | 
|  | 391 | error_log("%s: heapprofd: failed to transition kInitialState -> kInstallingHook. " | 
|  | 392 | "current state (possible race): %d", getprogname(), expected); | 
|  | 393 | } | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 394 | }); | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 395 | return nullptr; | 
|  | 396 | } | 
|  | 397 |  | 
|  | 398 | extern "C" void* MallocInitHeapprofdHook(size_t bytes) { | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 399 | MaybeModifyGlobals(kWithLock, [] { | 
|  | 400 | MallocHeapprofdState expected = kEphemeralHookInstalled; | 
|  | 401 | if (atomic_compare_exchange_strong(&gHeapprofdState, &expected, kRemovingEphemeralHook)) { | 
|  | 402 | __libc_globals.mutate([](libc_globals* globals) { | 
|  | 403 | const MallocDispatch* previous_dispatch = atomic_load(&gPreviousDefaultDispatchTable); | 
|  | 404 | atomic_store(&globals->default_dispatch_table, previous_dispatch); | 
|  | 405 | if (!MallocLimitInstalled()) { | 
|  | 406 | atomic_store(&globals->current_dispatch_table, previous_dispatch); | 
|  | 407 | } | 
|  | 408 | }); | 
|  | 409 | atomic_store(&gHeapprofdState, kInitialState); | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 410 |  | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 411 | pthread_t thread_id; | 
|  | 412 | if (pthread_create(&thread_id, nullptr, InitHeapprofd, nullptr) != 0) { | 
|  | 413 | error_log("%s: heapprofd: failed to pthread_create.", getprogname()); | 
|  | 414 | } else if (pthread_setname_np(thread_id, "heapprofdinit") != 0) { | 
|  | 415 | error_log("%s: heapprod: failed to pthread_setname_np", getprogname()); | 
|  | 416 | } else if (pthread_detach(thread_id) != 0) { | 
|  | 417 | error_log("%s: heapprofd: failed to pthread_detach", getprogname()); | 
|  | 418 | } | 
|  | 419 | } else { | 
|  | 420 | warning_log("%s: heapprofd: could not transition kEphemeralHookInstalled -> " | 
|  | 421 | "kRemovingEphemeralHook. current state (possible race): %d. this can be benign " | 
|  | 422 | "if two threads try this transition at the same time", getprogname(), | 
|  | 423 | expected); | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 424 | } | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 425 | }); | 
| Mitch Phillips | 449c26a | 2020-02-28 07:37:19 -0800 | [diff] [blame] | 426 | // If we had a previous dispatch table, use that to service the allocation, | 
|  | 427 | // otherwise fall back to the native allocator. | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 428 | // This could be modified by a concurrent HandleHeapprofdSignal, but that is | 
|  | 429 | // benign as we will dispatch to the ephemeral handler, which will then dispatch | 
|  | 430 | // to the underlying one. | 
|  | 431 | const MallocDispatch* previous_dispatch = atomic_load(&gPreviousDefaultDispatchTable); | 
|  | 432 | if (previous_dispatch) { | 
|  | 433 | return previous_dispatch->malloc(bytes); | 
| Mitch Phillips | 449c26a | 2020-02-28 07:37:19 -0800 | [diff] [blame] | 434 | } | 
|  | 435 | return NativeAllocatorDispatch()->malloc(bytes); | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 436 | } | 
|  | 437 |  | 
| Ryan Savitski | 175c886 | 2020-01-02 19:54:57 +0000 | [diff] [blame] | 438 | bool HeapprofdInitZygoteChildProfiling() { | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 439 | // Conditionally start "from startup" profiling. | 
|  | 440 | if (HeapprofdShouldLoad()) { | 
| Ryan Savitski | 175c886 | 2020-01-02 19:54:57 +0000 | [diff] [blame] | 441 | // Directly call the signal handler codepath (properly protects against | 
|  | 442 | // concurrent invocations). | 
|  | 443 | HandleHeapprofdSignal(); | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 444 | } | 
|  | 445 | return true; | 
|  | 446 | } | 
|  | 447 |  | 
|  | 448 | static bool DispatchReset() { | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 449 | if (atomic_load(&gHeapprofdState) == kInitialState) { | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 450 | return true; | 
|  | 451 | } | 
| Florian Mayer | fdd5eb1 | 2020-03-02 18:25:46 -0800 | [diff] [blame] | 452 |  | 
|  | 453 | bool success = false; | 
|  | 454 | MaybeModifyGlobals(kWithLock, [&success] { | 
|  | 455 | MallocHeapprofdState expected = kHookInstalled; | 
|  | 456 |  | 
|  | 457 | if(atomic_compare_exchange_strong(&gHeapprofdState, &expected, kUninstallingHook)){ | 
|  | 458 | __libc_globals.mutate([](libc_globals* globals) { | 
|  | 459 | const MallocDispatch* previous_dispatch = atomic_load(&gPreviousDefaultDispatchTable); | 
|  | 460 | atomic_store(&globals->default_dispatch_table, previous_dispatch); | 
|  | 461 | if (!MallocLimitInstalled()) { | 
|  | 462 | atomic_store(&globals->current_dispatch_table, previous_dispatch); | 
|  | 463 | } | 
|  | 464 | }); | 
|  | 465 | atomic_store(&gHeapprofdState, kInitialState); | 
|  | 466 | success = true; | 
|  | 467 | } else { | 
|  | 468 | error_log("%s: heapprofd: failed to transition kHookInstalled -> kUninstallingHook. " | 
|  | 469 | "current state (possible race): %d", getprogname(), | 
|  | 470 | expected); | 
|  | 471 | } | 
|  | 472 | }); | 
|  | 473 | if (!success) { | 
|  | 474 | errno = EAGAIN; | 
|  | 475 | } | 
|  | 476 | return success; | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 477 | } | 
|  | 478 |  | 
|  | 479 | bool HeapprofdMallopt(int opcode, void* arg, size_t arg_size) { | 
| Christopher Ferris | e4cdbc4 | 2019-02-08 17:30:58 -0800 | [diff] [blame] | 480 | if (opcode == M_RESET_HOOKS) { | 
|  | 481 | if (arg != nullptr || arg_size != 0) { | 
|  | 482 | errno = EINVAL; | 
|  | 483 | return false; | 
|  | 484 | } | 
|  | 485 | return DispatchReset(); | 
|  | 486 | } | 
|  | 487 | errno = ENOTSUP; | 
|  | 488 | return false; | 
|  | 489 | } |