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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
|
//===-- tsan_platform_mac.cpp ---------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file is a part of ThreadSanitizer (TSan), a race detector.
//
// Mac-specific code.
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_platform.h"
#if SANITIZER_APPLE
#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_libc.h"
#include "sanitizer_common/sanitizer_posix.h"
#include "sanitizer_common/sanitizer_procmaps.h"
#include "sanitizer_common/sanitizer_ptrauth.h"
#include "sanitizer_common/sanitizer_stackdepot.h"
#include "tsan_platform.h"
#include "tsan_rtl.h"
#include "tsan_flags.h"
#include <limits.h>
#include <mach/mach.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <sched.h>
namespace __tsan {
#if !SANITIZER_GO
static char main_thread_state[sizeof(ThreadState)] ALIGNED(
SANITIZER_CACHE_LINE_SIZE);
static ThreadState *dead_thread_state;
static pthread_key_t thread_state_key;
// We rely on the following documented, but Darwin-specific behavior to keep the
// reference to the ThreadState object alive in TLS:
// pthread_key_create man page:
// If, after all the destructors have been called for all non-NULL values with
// associated destructors, there are still some non-NULL values with
// associated destructors, then the process is repeated. If, after at least
// [PTHREAD_DESTRUCTOR_ITERATIONS] iterations of destructor calls for
// outstanding non-NULL values, there are still some non-NULL values with
// associated destructors, the implementation stops calling destructors.
static_assert(PTHREAD_DESTRUCTOR_ITERATIONS == 4, "Small number of iterations");
static void ThreadStateDestructor(void *thr) {
int res = pthread_setspecific(thread_state_key, thr);
CHECK_EQ(res, 0);
}
static void InitializeThreadStateStorage() {
int res;
CHECK_EQ(thread_state_key, 0);
res = pthread_key_create(&thread_state_key, ThreadStateDestructor);
CHECK_EQ(res, 0);
res = pthread_setspecific(thread_state_key, main_thread_state);
CHECK_EQ(res, 0);
auto dts = (ThreadState *)MmapOrDie(sizeof(ThreadState), "ThreadState");
dts->fast_state.SetIgnoreBit();
dts->ignore_interceptors = 1;
dts->is_dead = true;
const_cast<Tid &>(dts->tid) = kInvalidTid;
res = internal_mprotect(dts, sizeof(ThreadState), PROT_READ); // immutable
CHECK_EQ(res, 0);
dead_thread_state = dts;
}
ThreadState *cur_thread() {
// Some interceptors get called before libpthread has been initialized and in
// these cases we must avoid calling any pthread APIs.
if (UNLIKELY(!thread_state_key)) {
return (ThreadState *)main_thread_state;
}
// We only reach this line after InitializeThreadStateStorage() ran, i.e,
// after TSan (and therefore libpthread) have been initialized.
ThreadState *thr = (ThreadState *)pthread_getspecific(thread_state_key);
if (UNLIKELY(!thr)) {
thr = (ThreadState *)MmapOrDie(sizeof(ThreadState), "ThreadState");
int res = pthread_setspecific(thread_state_key, thr);
CHECK_EQ(res, 0);
}
return thr;
}
void set_cur_thread(ThreadState *thr) {
int res = pthread_setspecific(thread_state_key, thr);
CHECK_EQ(res, 0);
}
void cur_thread_finalize() {
ThreadState *thr = (ThreadState *)pthread_getspecific(thread_state_key);
CHECK(thr);
if (thr == (ThreadState *)main_thread_state) {
// Calling dispatch_main() or xpc_main() actually invokes pthread_exit to
// exit the main thread. Let's keep the main thread's ThreadState.
return;
}
// Intercepted functions can still get called after cur_thread_finalize()
// (called from DestroyThreadState()), so put a fake thread state for "dead"
// threads. An alternative solution would be to release the ThreadState
// object from THREAD_DESTROY (which is delivered later and on the parent
// thread) instead of THREAD_TERMINATE.
int res = pthread_setspecific(thread_state_key, dead_thread_state);
CHECK_EQ(res, 0);
UnmapOrDie(thr, sizeof(ThreadState));
}
#endif
void FlushShadowMemory() {
}
static void RegionMemUsage(uptr start, uptr end, uptr *res, uptr *dirty) {
vm_address_t address = start;
vm_address_t end_address = end;
uptr resident_pages = 0;
uptr dirty_pages = 0;
while (address < end_address) {
vm_size_t vm_region_size;
mach_msg_type_number_t count = VM_REGION_EXTENDED_INFO_COUNT;
vm_region_extended_info_data_t vm_region_info;
mach_port_t object_name;
kern_return_t ret = vm_region_64(
mach_task_self(), &address, &vm_region_size, VM_REGION_EXTENDED_INFO,
(vm_region_info_t)&vm_region_info, &count, &object_name);
if (ret != KERN_SUCCESS) break;
resident_pages += vm_region_info.pages_resident;
dirty_pages += vm_region_info.pages_dirtied;
address += vm_region_size;
}
*res = resident_pages * GetPageSizeCached();
*dirty = dirty_pages * GetPageSizeCached();
}
void WriteMemoryProfile(char *buf, uptr buf_size, u64 uptime_ns) {
uptr shadow_res, shadow_dirty;
uptr meta_res, meta_dirty;
uptr trace_res, trace_dirty;
RegionMemUsage(ShadowBeg(), ShadowEnd(), &shadow_res, &shadow_dirty);
RegionMemUsage(MetaShadowBeg(), MetaShadowEnd(), &meta_res, &meta_dirty);
RegionMemUsage(TraceMemBeg(), TraceMemEnd(), &trace_res, &trace_dirty);
#if !SANITIZER_GO
uptr low_res, low_dirty;
uptr high_res, high_dirty;
uptr heap_res, heap_dirty;
RegionMemUsage(LoAppMemBeg(), LoAppMemEnd(), &low_res, &low_dirty);
RegionMemUsage(HiAppMemBeg(), HiAppMemEnd(), &high_res, &high_dirty);
RegionMemUsage(HeapMemBeg(), HeapMemEnd(), &heap_res, &heap_dirty);
#else // !SANITIZER_GO
uptr app_res, app_dirty;
RegionMemUsage(LoAppMemBeg(), LoAppMemEnd(), &app_res, &app_dirty);
#endif
StackDepotStats stacks = StackDepotGetStats();
uptr nthread, nlive;
ctx->thread_registry.GetNumberOfThreads(&nthread, &nlive);
internal_snprintf(
buf, buf_size,
"shadow (0x%016zx-0x%016zx): resident %zd kB, dirty %zd kB\n"
"meta (0x%016zx-0x%016zx): resident %zd kB, dirty %zd kB\n"
"traces (0x%016zx-0x%016zx): resident %zd kB, dirty %zd kB\n"
# if !SANITIZER_GO
"low app (0x%016zx-0x%016zx): resident %zd kB, dirty %zd kB\n"
"high app (0x%016zx-0x%016zx): resident %zd kB, dirty %zd kB\n"
"heap (0x%016zx-0x%016zx): resident %zd kB, dirty %zd kB\n"
# else // !SANITIZER_GO
"app (0x%016zx-0x%016zx): resident %zd kB, dirty %zd kB\n"
# endif
"stacks: %zd unique IDs, %zd kB allocated\n"
"threads: %zd total, %zd live\n"
"------------------------------\n",
ShadowBeg(), ShadowEnd(), shadow_res / 1024, shadow_dirty / 1024,
MetaShadowBeg(), MetaShadowEnd(), meta_res / 1024, meta_dirty / 1024,
TraceMemBeg(), TraceMemEnd(), trace_res / 1024, trace_dirty / 1024,
# if !SANITIZER_GO
LoAppMemBeg(), LoAppMemEnd(), low_res / 1024, low_dirty / 1024,
HiAppMemBeg(), HiAppMemEnd(), high_res / 1024, high_dirty / 1024,
HeapMemBeg(), HeapMemEnd(), heap_res / 1024, heap_dirty / 1024,
# else // !SANITIZER_GO
LoAppMemBeg(), LoAppMemEnd(), app_res / 1024, app_dirty / 1024,
# endif
stacks.n_uniq_ids, stacks.allocated / 1024, nthread, nlive);
}
# if !SANITIZER_GO
void InitializeShadowMemoryPlatform() { }
// On OS X, GCD worker threads are created without a call to pthread_create. We
// need to properly register these threads with ThreadCreate and ThreadStart.
// These threads don't have a parent thread, as they are created "spuriously".
// We're using a libpthread API that notifies us about a newly created thread.
// The `thread == pthread_self()` check indicates this is actually a worker
// thread. If it's just a regular thread, this hook is called on the parent
// thread.
typedef void (*pthread_introspection_hook_t)(unsigned int event,
pthread_t thread, void *addr,
size_t size);
extern "C" pthread_introspection_hook_t pthread_introspection_hook_install(
pthread_introspection_hook_t hook);
static const uptr PTHREAD_INTROSPECTION_THREAD_CREATE = 1;
static const uptr PTHREAD_INTROSPECTION_THREAD_TERMINATE = 3;
static pthread_introspection_hook_t prev_pthread_introspection_hook;
static void my_pthread_introspection_hook(unsigned int event, pthread_t thread,
void *addr, size_t size) {
if (event == PTHREAD_INTROSPECTION_THREAD_CREATE) {
if (thread == pthread_self()) {
// The current thread is a newly created GCD worker thread.
ThreadState *thr = cur_thread();
Processor *proc = ProcCreate();
ProcWire(proc, thr);
ThreadState *parent_thread_state = nullptr; // No parent.
Tid tid = ThreadCreate(parent_thread_state, 0, (uptr)thread, true);
CHECK_NE(tid, kMainTid);
ThreadStart(thr, tid, GetTid(), ThreadType::Worker);
}
} else if (event == PTHREAD_INTROSPECTION_THREAD_TERMINATE) {
CHECK_EQ(thread, pthread_self());
ThreadState *thr = cur_thread();
if (thr->tctx) {
DestroyThreadState();
}
}
if (prev_pthread_introspection_hook != nullptr)
prev_pthread_introspection_hook(event, thread, addr, size);
}
#endif
void InitializePlatformEarly() {
# if !SANITIZER_GO && SANITIZER_IOS
uptr max_vm = GetMaxUserVirtualAddress() + 1;
if (max_vm != HiAppMemEnd()) {
Printf("ThreadSanitizer: unsupported vm address limit %p, expected %p.\n",
(void *)max_vm, (void *)HiAppMemEnd());
Die();
}
#endif
}
static uptr longjmp_xor_key = 0;
void InitializePlatform() {
DisableCoreDumperIfNecessary();
#if !SANITIZER_GO
CheckAndProtect();
InitializeThreadStateStorage();
prev_pthread_introspection_hook =
pthread_introspection_hook_install(&my_pthread_introspection_hook);
#endif
if (GetMacosAlignedVersion() >= MacosVersion(10, 14)) {
// Libsystem currently uses a process-global key; this might change.
const unsigned kTLSLongjmpXorKeySlot = 0x7;
longjmp_xor_key = (uptr)pthread_getspecific(kTLSLongjmpXorKeySlot);
}
}
#ifdef __aarch64__
# define LONG_JMP_SP_ENV_SLOT \
((GetMacosAlignedVersion() >= MacosVersion(10, 14)) ? 12 : 13)
#else
# define LONG_JMP_SP_ENV_SLOT 2
#endif
uptr ExtractLongJmpSp(uptr *env) {
uptr mangled_sp = env[LONG_JMP_SP_ENV_SLOT];
uptr sp = mangled_sp ^ longjmp_xor_key;
sp = (uptr)ptrauth_auth_data((void *)sp, ptrauth_key_asdb,
ptrauth_string_discriminator("sp"));
return sp;
}
#if !SANITIZER_GO
extern "C" void __tsan_tls_initialization() {}
void ImitateTlsWrite(ThreadState *thr, uptr tls_addr, uptr tls_size) {
const uptr pc = StackTrace::GetNextInstructionPc(
reinterpret_cast<uptr>(__tsan_tls_initialization));
// Unlike Linux, we only store a pointer to the ThreadState object in TLS;
// just mark the entire range as written to.
MemoryRangeImitateWrite(thr, pc, tls_addr, tls_size);
}
#endif
#if !SANITIZER_GO
// Note: this function runs with async signals enabled,
// so it must not touch any tsan state.
int call_pthread_cancel_with_cleanup(int (*fn)(void *arg),
void (*cleanup)(void *arg), void *arg) {
// pthread_cleanup_push/pop are hardcore macros mess.
// We can't intercept nor call them w/o including pthread.h.
int res;
pthread_cleanup_push(cleanup, arg);
res = fn(arg);
pthread_cleanup_pop(0);
return res;
}
#endif
} // namespace __tsan
#endif // SANITIZER_APPLE
|