1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76

base / allocator / dispatcher / reentry_guard.h [blame]

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_ALLOCATOR_DISPATCHER_REENTRY_GUARD_H_
#define BASE_ALLOCATOR_DISPATCHER_REENTRY_GUARD_H_

#include "base/base_export.h"
#include "base/check.h"
#include "base/compiler_specific.h"
#include "build/build_config.h"

#if BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_ANDROID)
#include <pthread.h>
#endif

namespace base::allocator::dispatcher {

#if BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_ANDROID)

// The macOS implementation of libmalloc sometimes calls malloc recursively,
// delegating allocations between zones. That causes our hooks being called
// twice. The scoped guard allows us to detect that.
//
// Besides that the implementations of thread_local on macOS and Android
// seem to allocate memory lazily on the first access to thread_local variables
// (and on Android at least thread_local is implemented on top of pthread so is
// strictly worse for performance). Make use of pthread TLS instead of C++
// thread_local there.
struct BASE_EXPORT ReentryGuard {
  ALWAYS_INLINE ReentryGuard() : allowed_(!pthread_getspecific(entered_key_)) {
    pthread_setspecific(entered_key_, reinterpret_cast<void*>(true));
  }

  ALWAYS_INLINE ~ReentryGuard() {
    if (allowed_) [[likely]] {
      pthread_setspecific(entered_key_, nullptr);
    }
  }

  explicit operator bool() const noexcept { return allowed_; }

  // This function must be called before installing any allocator hooks because
  // some TLS implementations may allocate (eg. glibc will require a malloc call
  // to allocate storage for a higher slot number (>= PTHREAD_KEY_2NDLEVEL_SIZE
  // == 32). This touches the thread-local storage so that any malloc happens
  // before installing the hooks.
  static void InitTLSSlot();

  // InitTLSSlot() is called before crash keys are available. At some point
  // after SetCrashKeyImplementation() is called, this function should be
  // called to record `entered_key_` to a crash key for debugging. This may
  // allocate so it must not be called from inside an allocator hook.
  static void RecordTLSSlotToCrashKey();

 private:
  static pthread_key_t entered_key_;
  const bool allowed_;
};

#else

// Use [[maybe_unused]] as this lightweight stand-in for the more heavyweight
// ReentryGuard above will otherwise trigger the "unused code" warnings.
struct [[maybe_unused]] BASE_EXPORT ReentryGuard {
  constexpr explicit operator bool() const noexcept { return true; }

  static void InitTLSSlot();
  static void RecordTLSSlotToCrashKey();
};

#endif

}  // namespace base::allocator::dispatcher

#endif  // BASE_ALLOCATOR_DISPATCHER_REENTRY_GUARD_H_