LogAlloc.cpp (8067B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include <cstdlib> 8 #include <fcntl.h> 9 10 #ifdef _WIN32 11 # include <windows.h> 12 # include <io.h> 13 # include <process.h> 14 #else 15 # include <unistd.h> 16 # include <pthread.h> 17 #endif 18 19 #include "replace_malloc.h" 20 #include "FdPrintf.h" 21 #include "Mutex.h" 22 23 static malloc_table_t sFuncs; 24 static platform_handle_t sFd = 0; 25 static bool sStdoutOrStderr = false; 26 27 static Mutex sMutex MOZ_UNANNOTATED; 28 29 #ifndef _WIN32 30 static void prefork() MOZ_NO_THREAD_SAFETY_ANALYSIS { sMutex.Lock(); } 31 static void postfork_parent() MOZ_NO_THREAD_SAFETY_ANALYSIS { sMutex.Unlock(); } 32 static void postfork_child() { sMutex.Init(); } 33 #endif 34 35 static size_t GetPid() { return size_t(getpid()); } 36 37 static size_t GetTid() { 38 #if defined(_WIN32) 39 return size_t(GetCurrentThreadId()); 40 #else 41 return size_t(pthread_self()); 42 #endif 43 } 44 45 #ifdef ANDROID 46 /* Android doesn't have pthread_atfork defined in pthread.h */ 47 extern "C" MOZ_EXPORT int pthread_atfork(void (*)(void), void (*)(void), 48 void (*)(void)); 49 #endif 50 51 class LogAllocBridge : public ReplaceMallocBridge { 52 virtual void InitDebugFd(mozilla::DebugFdRegistry& aRegistry) override { 53 if (!sStdoutOrStderr) { 54 aRegistry.RegisterHandle(sFd); 55 } 56 } 57 }; 58 59 /* Do a simple, text-form, log of all calls to replace-malloc functions. 60 * Use locking to guarantee that an allocation that did happen is logged 61 * before any other allocation/free happens. 62 */ 63 64 static void* replace_malloc(size_t aSize) { 65 void* ptr = sFuncs.malloc(aSize); 66 MutexAutoLock lock(sMutex); 67 FdPrintf(sFd, "%zu %zu malloc(%zu)=%p\n", GetPid(), GetTid(), aSize, ptr); 68 return ptr; 69 } 70 71 static int replace_posix_memalign(void** aPtr, size_t aAlignment, 72 size_t aSize) { 73 int ret = sFuncs.posix_memalign(aPtr, aAlignment, aSize); 74 MutexAutoLock lock(sMutex); 75 FdPrintf(sFd, "%zu %zu posix_memalign(%zu,%zu)=%p\n", GetPid(), GetTid(), 76 aAlignment, aSize, (ret == 0) ? *aPtr : nullptr); 77 return ret; 78 } 79 80 static void* replace_aligned_alloc(size_t aAlignment, size_t aSize) { 81 void* ptr = sFuncs.aligned_alloc(aAlignment, aSize); 82 MutexAutoLock lock(sMutex); 83 FdPrintf(sFd, "%zu %zu aligned_alloc(%zu,%zu)=%p\n", GetPid(), GetTid(), 84 aAlignment, aSize, ptr); 85 return ptr; 86 } 87 88 static void* replace_calloc(size_t aNum, size_t aSize) { 89 void* ptr = sFuncs.calloc(aNum, aSize); 90 MutexAutoLock lock(sMutex); 91 FdPrintf(sFd, "%zu %zu calloc(%zu,%zu)=%p\n", GetPid(), GetTid(), aNum, aSize, 92 ptr); 93 return ptr; 94 } 95 96 static void* replace_realloc(void* aPtr, size_t aSize) { 97 void* new_ptr = sFuncs.realloc(aPtr, aSize); 98 MutexAutoLock lock(sMutex); 99 FdPrintf(sFd, "%zu %zu realloc(%p,%zu)=%p\n", GetPid(), GetTid(), aPtr, aSize, 100 new_ptr); 101 return new_ptr; 102 } 103 104 static void replace_free(void* aPtr) { 105 { 106 MutexAutoLock lock(sMutex); 107 FdPrintf(sFd, "%zu %zu free(%p)\n", GetPid(), GetTid(), aPtr); 108 } 109 sFuncs.free(aPtr); 110 } 111 112 static void* replace_memalign(size_t aAlignment, size_t aSize) { 113 void* ptr = sFuncs.memalign(aAlignment, aSize); 114 MutexAutoLock lock(sMutex); 115 FdPrintf(sFd, "%zu %zu memalign(%zu,%zu)=%p\n", GetPid(), GetTid(), 116 aAlignment, aSize, ptr); 117 return ptr; 118 } 119 120 static void* replace_valloc(size_t aSize) { 121 void* ptr = sFuncs.valloc(aSize); 122 MutexAutoLock lock(sMutex); 123 FdPrintf(sFd, "%zu %zu valloc(%zu)=%p\n", GetPid(), GetTid(), aSize, ptr); 124 return ptr; 125 } 126 127 static void replace_jemalloc_stats(jemalloc_stats_t* aStats, 128 jemalloc_bin_stats_t* aBinStats) { 129 sFuncs.jemalloc_stats_internal(aStats, aBinStats); 130 MutexAutoLock lock(sMutex); 131 FdPrintf(sFd, "%zu %zu jemalloc_stats()\n", GetPid(), GetTid()); 132 } 133 134 void replace_init(malloc_table_t* aTable, ReplaceMallocBridge** aBridge) { 135 /* Initialize output file descriptor from the MALLOC_LOG environment 136 * variable. Numbers up to 9999 are considered as a preopened file 137 * descriptor number. Other values are considered as a file name. */ 138 #ifdef _WIN32 139 wchar_t* log = _wgetenv(L"MALLOC_LOG"); 140 #else 141 char* log = getenv("MALLOC_LOG"); 142 #endif 143 if (log && *log) { 144 int fd = 0; 145 const auto* fd_num = log; 146 while (*fd_num) { 147 /* Reject non digits. */ 148 if (*fd_num < '0' || *fd_num > '9') { 149 fd = -1; 150 break; 151 } 152 fd = fd * 10 + (*fd_num - '0'); 153 /* Reject values >= 10000. */ 154 if (fd >= 10000) { 155 fd = -1; 156 break; 157 } 158 fd_num++; 159 } 160 if (fd == 1 || fd == 2) { 161 sStdoutOrStderr = true; 162 } 163 #ifdef _WIN32 164 // See comment in FdPrintf.h as to why CreateFile is used. 165 HANDLE handle; 166 if (fd > 0) { 167 handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); 168 } else { 169 handle = 170 CreateFileW(log, FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, 171 nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); 172 } 173 if (handle != INVALID_HANDLE_VALUE) { 174 sFd = handle; 175 } 176 #else 177 if (fd == -1) { 178 fd = open(log, O_WRONLY | O_CREAT | O_APPEND, 0644); 179 } 180 if (fd > 0) { 181 sFd = fd; 182 } 183 #endif 184 } 185 186 // Don't initialize if we weren't passed a valid MALLOC_LOG. 187 if (sFd == 0) { 188 return; 189 } 190 191 sMutex.Init(); 192 static LogAllocBridge bridge; 193 sFuncs = *aTable; 194 #define MALLOC_FUNCS MALLOC_FUNCS_MALLOC_BASE 195 #define MALLOC_DECL(name, ...) aTable->name = replace_##name; 196 #include "malloc_decls.h" 197 aTable->jemalloc_stats_internal = replace_jemalloc_stats; 198 if (!getenv("MALLOC_LOG_MINIMAL")) { 199 aTable->posix_memalign = replace_posix_memalign; 200 aTable->aligned_alloc = replace_aligned_alloc; 201 aTable->valloc = replace_valloc; 202 } 203 *aBridge = &bridge; 204 205 #ifndef _WIN32 206 /* When another thread has acquired a lock before forking, the child 207 * process will inherit the lock state but the thread, being nonexistent 208 * in the child process, will never release it, leading to a dead-lock 209 * whenever the child process gets the lock. We thus need to ensure no 210 * other thread is holding the lock before forking, by acquiring it 211 * ourselves, and releasing it after forking in the parent process and 212 * resetting it to its initial state in the child process. The latter is 213 * important because some implementations (notably macOS) prevent a lock from 214 * being unlocked by a different thread than the one which locked it in the 215 * first place. 216 * Windows doesn't have this problem since there is no fork(). 217 * The real allocator, however, might be doing the same thing (jemalloc 218 * does). But pthread_atfork `prepare` handlers (first argument) are 219 * processed in reverse order they were established. But replace_init 220 * runs before the real allocator has had any chance to initialize and 221 * call pthread_atfork itself. This leads to its prefork running before 222 * ours. This leads to a race condition that can lead to a deadlock like 223 * the following: 224 * - thread A forks. 225 * - libc calls real allocator's prefork, so thread A holds the real 226 * allocator lock. 227 * - thread B calls malloc, which calls our replace_malloc. 228 * - consequently, thread B holds our lock. 229 * - thread B then proceeds to call the real allocator's malloc, and 230 * waits for the real allocator's lock, which thread A holds. 231 * - libc calls our prefork, so thread A waits for our lock, which 232 * thread B holds. 233 * To avoid this race condition, the real allocator's prefork must be 234 * called after ours, which means it needs to be registered before ours. 235 * So trick the real allocator into initializing itself without more side 236 * effects by calling malloc with a size it can't possibly allocate. */ 237 sFuncs.malloc(-1); 238 pthread_atfork(prefork, postfork_parent, postfork_child); 239 #endif 240 }