1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifndef PARTITION_ALLOC_PARTITION_TLS_H_
6 #define PARTITION_ALLOC_PARTITION_TLS_H_
7
8 #include "build/build_config.h"
9 #include "partition_alloc/partition_alloc_base/compiler_specific.h"
10 #include "partition_alloc/partition_alloc_base/component_export.h"
11 #include "partition_alloc/partition_alloc_base/immediate_crash.h"
12 #include "partition_alloc/partition_alloc_check.h"
13
14 #if BUILDFLAG(IS_POSIX)
15 #include <pthread.h>
16 #endif
17
18 #if BUILDFLAG(IS_WIN)
19 #include "partition_alloc/partition_alloc_base/win/windows_types.h"
20 #endif
21
22 // Barebones TLS implementation for use in PartitionAlloc. This doesn't use the
23 // general chromium TLS handling to avoid dependencies, but more importantly
24 // because it allocates memory.
25 namespace partition_alloc::internal {
26
27 #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
28 using PartitionTlsKey = pthread_key_t;
29
30 // Only on x86_64, the implementation is not stable on ARM64. For instance, in
31 // macOS 11, the TPIDRRO_EL0 registers holds the CPU index in the low bits,
32 // which is not the case in macOS 12. See libsyscall/os/tsd.h in XNU
33 // (_os_tsd_get_direct() is used by pthread_getspecific() internally).
34 #if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_X86_64)
35 namespace {
36
FastTlsGet(PartitionTlsKey index)37 PA_ALWAYS_INLINE void* FastTlsGet(PartitionTlsKey index) {
38 // On macOS, pthread_getspecific() is in libSystem, so a call to it has to go
39 // through PLT. However, and contrary to some other platforms, *all* TLS keys
40 // are in a static array in the thread structure. So they are *always* at a
41 // fixed offset from the segment register holding the thread structure
42 // address.
43 //
44 // We could use _pthread_getspecific_direct(), but it is not
45 // exported. However, on all macOS versions we support, the TLS array is at
46 // %gs. This is used in V8 to back up InternalGetExistingThreadLocal(), and
47 // can also be seen by looking at pthread_getspecific() disassembly:
48 //
49 // libsystem_pthread.dylib`pthread_getspecific:
50 // libsystem_pthread.dylib[0x7ff800316099] <+0>: movq %gs:(,%rdi,8), %rax
51 // libsystem_pthread.dylib[0x7ff8003160a2] <+9>: retq
52 //
53 // This function is essentially inlining the content of pthread_getspecific()
54 // here.
55 intptr_t result;
56 static_assert(sizeof index <= sizeof(intptr_t));
57 asm("movq %%gs:(,%1,8), %0;"
58 : "=r"(result)
59 : "r"(static_cast<intptr_t>(index)));
60
61 return reinterpret_cast<void*>(result);
62 }
63
64 } // namespace
65 #endif // BUILDFLAG(IS_MAC) && defined(ARCH_CPU_X86_64)
66
PartitionTlsCreate(PartitionTlsKey * key,void (* destructor)(void *))67 PA_ALWAYS_INLINE bool PartitionTlsCreate(PartitionTlsKey* key,
68 void (*destructor)(void*)) {
69 return !pthread_key_create(key, destructor);
70 }
71
PartitionTlsGet(PartitionTlsKey key)72 PA_ALWAYS_INLINE void* PartitionTlsGet(PartitionTlsKey key) {
73 #if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_X86_64)
74 PA_DCHECK(pthread_getspecific(key) == FastTlsGet(key));
75 return FastTlsGet(key);
76 #else
77 return pthread_getspecific(key);
78 #endif
79 }
80
PartitionTlsSet(PartitionTlsKey key,void * value)81 PA_ALWAYS_INLINE void PartitionTlsSet(PartitionTlsKey key, void* value) {
82 int ret = pthread_setspecific(key, value);
83 PA_DCHECK(!ret);
84 }
85
86 #elif BUILDFLAG(IS_WIN)
87 // Note: supports only a single TLS key on Windows. Not a hard constraint, may
88 // be lifted.
89 using PartitionTlsKey = unsigned long;
90
91 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
92 bool PartitionTlsCreate(PartitionTlsKey* key, void (*destructor)(void*));
93
94 PA_ALWAYS_INLINE void* PartitionTlsGet(PartitionTlsKey key) {
95 // Accessing TLS resets the last error, which then makes |GetLastError()|
96 // return something misleading. While this means that properly using
97 // |GetLastError()| is difficult, there is currently code in Chromium which
98 // expects malloc() to *not* reset it. Meaning that we either have to fix this
99 // code, or pay the cost of saving/restoring it.
100 //
101 // Source:
102 // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-tlsgetvalue
103 // "Functions that return indications of failure call SetLastError() when they
104 // fail. They generally do not call SetLastError() when they succeed. The
105 // TlsGetValue() function is an exception to this general rule. The
106 // TlsGetValue() function calls SetLastError() to clear a thread's last error
107 // when it succeeds."
108 DWORD saved_error = GetLastError();
109 void* ret = TlsGetValue(key);
110 // Only non-zero errors need to be restored.
111 if (PA_UNLIKELY(saved_error)) {
112 SetLastError(saved_error);
113 }
114 return ret;
115 }
116
117 PA_ALWAYS_INLINE void PartitionTlsSet(PartitionTlsKey key, void* value) {
118 BOOL ret = TlsSetValue(key, value);
119 PA_DCHECK(ret);
120 }
121
122 // Registers a callback for DLL_PROCESS_DETACH events.
123 void PartitionTlsSetOnDllProcessDetach(void (*callback)());
124
125 #else
126 // Not supported.
127 using PartitionTlsKey = int;
128
129 PA_ALWAYS_INLINE bool PartitionTlsCreate(PartitionTlsKey* key,
130 void (*destructor)(void*)) {
131 // NOTIMPLEMENTED() may allocate, crash instead.
132 PA_IMMEDIATE_CRASH();
133 }
134
135 PA_ALWAYS_INLINE void* PartitionTlsGet(PartitionTlsKey key) {
136 PA_IMMEDIATE_CRASH();
137 }
138
139 PA_ALWAYS_INLINE void PartitionTlsSet(PartitionTlsKey key, void* value) {
140 PA_IMMEDIATE_CRASH();
141 }
142
143 #endif // BUILDFLAG(IS_WIN)
144
145 } // namespace partition_alloc::internal
146
147 #endif // PARTITION_ALLOC_PARTITION_TLS_H_
148