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