tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }