| 1 | //===-- tsan_mman.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 | //===----------------------------------------------------------------------===// |
| 12 | #include "tsan_mman.h" |
| 13 | |
| 14 | #include "sanitizer_common/sanitizer_allocator_checks.h" |
| 15 | #include "sanitizer_common/sanitizer_allocator_interface.h" |
| 16 | #include "sanitizer_common/sanitizer_allocator_report.h" |
| 17 | #include "sanitizer_common/sanitizer_common.h" |
| 18 | #include "sanitizer_common/sanitizer_errno.h" |
| 19 | #include "sanitizer_common/sanitizer_placement_new.h" |
| 20 | #include "sanitizer_common/sanitizer_stackdepot.h" |
| 21 | #include "tsan_flags.h" |
| 22 | #include "tsan_interface.h" |
| 23 | #include "tsan_report.h" |
| 24 | #include "tsan_rtl.h" |
| 25 | |
| 26 | namespace __tsan { |
| 27 | |
| 28 | struct MapUnmapCallback { |
| 29 | void OnMap(uptr p, uptr size) const { } |
| 30 | void OnMapSecondary(uptr p, uptr size, uptr user_begin, |
| 31 | uptr user_size) const {}; |
| 32 | void OnUnmap(uptr p, uptr size) const { |
| 33 | // We are about to unmap a chunk of user memory. |
| 34 | // Mark the corresponding shadow memory as not needed. |
| 35 | DontNeedShadowFor(addr: p, size); |
| 36 | // Mark the corresponding meta shadow memory as not needed. |
| 37 | // Note the block does not contain any meta info at this point |
| 38 | // (this happens after free). |
| 39 | const uptr kMetaRatio = kMetaShadowCell / kMetaShadowSize; |
| 40 | const uptr kPageSize = GetPageSizeCached() * kMetaRatio; |
| 41 | // Block came from LargeMmapAllocator, so must be large. |
| 42 | // We rely on this in the calculations below. |
| 43 | CHECK_GE(size, 2 * kPageSize); |
| 44 | uptr diff = RoundUp(p, align: kPageSize) - p; |
| 45 | if (diff != 0) { |
| 46 | p += diff; |
| 47 | size -= diff; |
| 48 | } |
| 49 | diff = p + size - RoundDown(p: p + size, align: kPageSize); |
| 50 | if (diff != 0) |
| 51 | size -= diff; |
| 52 | uptr p_meta = (uptr)MemToMeta(x: p); |
| 53 | ReleaseMemoryPagesToOS(beg: p_meta, end: p_meta + size / kMetaRatio); |
| 54 | } |
| 55 | }; |
| 56 | |
| 57 | alignas(64) static char allocator_placeholder[sizeof(Allocator)]; |
| 58 | Allocator *allocator() { |
| 59 | return reinterpret_cast<Allocator*>(&allocator_placeholder); |
| 60 | } |
| 61 | |
| 62 | struct GlobalProc { |
| 63 | Mutex mtx; |
| 64 | Processor *proc; |
| 65 | // This mutex represents the internal allocator combined for |
| 66 | // the purposes of deadlock detection. The internal allocator |
| 67 | // uses multiple mutexes, moreover they are locked only occasionally |
| 68 | // and they are spin mutexes which don't support deadlock detection. |
| 69 | // So we use this fake mutex to serve as a substitute for these mutexes. |
| 70 | CheckedMutex internal_alloc_mtx; |
| 71 | |
| 72 | GlobalProc() |
| 73 | : mtx(MutexTypeGlobalProc), |
| 74 | proc(ProcCreate()), |
| 75 | internal_alloc_mtx(MutexTypeInternalAlloc) {} |
| 76 | }; |
| 77 | |
| 78 | alignas(64) static char global_proc_placeholder[sizeof(GlobalProc)]; |
| 79 | GlobalProc *global_proc() { |
| 80 | return reinterpret_cast<GlobalProc*>(&global_proc_placeholder); |
| 81 | } |
| 82 | |
| 83 | static void InternalAllocAccess() { |
| 84 | global_proc()->internal_alloc_mtx.Lock(); |
| 85 | global_proc()->internal_alloc_mtx.Unlock(); |
| 86 | } |
| 87 | |
| 88 | ScopedGlobalProcessor::ScopedGlobalProcessor() { |
| 89 | GlobalProc *gp = global_proc(); |
| 90 | ThreadState *thr = cur_thread(); |
| 91 | if (thr->proc()) |
| 92 | return; |
| 93 | // If we don't have a proc, use the global one. |
| 94 | // There are currently only two known case where this path is triggered: |
| 95 | // __interceptor_free |
| 96 | // __nptl_deallocate_tsd |
| 97 | // start_thread |
| 98 | // clone |
| 99 | // and: |
| 100 | // ResetRange |
| 101 | // __interceptor_munmap |
| 102 | // __deallocate_stack |
| 103 | // start_thread |
| 104 | // clone |
| 105 | // Ideally, we destroy thread state (and unwire proc) when a thread actually |
| 106 | // exits (i.e. when we join/wait it). Then we would not need the global proc |
| 107 | gp->mtx.Lock(); |
| 108 | ProcWire(proc: gp->proc, thr); |
| 109 | } |
| 110 | |
| 111 | ScopedGlobalProcessor::~ScopedGlobalProcessor() { |
| 112 | GlobalProc *gp = global_proc(); |
| 113 | ThreadState *thr = cur_thread(); |
| 114 | if (thr->proc() != gp->proc) |
| 115 | return; |
| 116 | ProcUnwire(proc: gp->proc, thr); |
| 117 | gp->mtx.Unlock(); |
| 118 | } |
| 119 | |
| 120 | void AllocatorLockBeforeFork() SANITIZER_NO_THREAD_SAFETY_ANALYSIS { |
| 121 | global_proc()->internal_alloc_mtx.Lock(); |
| 122 | InternalAllocatorLock(); |
| 123 | #if !SANITIZER_APPLE |
| 124 | // OS X allocates from hooks, see 6a3958247a. |
| 125 | allocator()->ForceLock(); |
| 126 | StackDepotLockBeforeFork(); |
| 127 | #endif |
| 128 | } |
| 129 | |
| 130 | void AllocatorUnlockAfterFork(bool child) SANITIZER_NO_THREAD_SAFETY_ANALYSIS { |
| 131 | #if !SANITIZER_APPLE |
| 132 | StackDepotUnlockAfterFork(fork_child: child); |
| 133 | allocator()->ForceUnlock(); |
| 134 | #endif |
| 135 | InternalAllocatorUnlock(); |
| 136 | global_proc()->internal_alloc_mtx.Unlock(); |
| 137 | } |
| 138 | |
| 139 | void GlobalProcessorLock() SANITIZER_NO_THREAD_SAFETY_ANALYSIS { |
| 140 | global_proc()->mtx.Lock(); |
| 141 | } |
| 142 | |
| 143 | void GlobalProcessorUnlock() SANITIZER_NO_THREAD_SAFETY_ANALYSIS { |
| 144 | global_proc()->mtx.Unlock(); |
| 145 | } |
| 146 | |
| 147 | static constexpr uptr kMaxAllowedMallocSize = 1ull << 40; |
| 148 | static uptr max_user_defined_malloc_size; |
| 149 | |
| 150 | void InitializeAllocator() { |
| 151 | SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null); |
| 152 | allocator()->Init(release_to_os_interval_ms: common_flags()->allocator_release_to_os_interval_ms); |
| 153 | max_user_defined_malloc_size = common_flags()->max_allocation_size_mb |
| 154 | ? common_flags()->max_allocation_size_mb |
| 155 | << 20 |
| 156 | : kMaxAllowedMallocSize; |
| 157 | } |
| 158 | |
| 159 | void InitializeAllocatorLate() { |
| 160 | new(global_proc()) GlobalProc(); |
| 161 | } |
| 162 | |
| 163 | void AllocatorProcStart(Processor *proc) { |
| 164 | allocator()->InitCache(cache: &proc->alloc_cache); |
| 165 | internal_allocator()->InitCache(cache: &proc->internal_alloc_cache); |
| 166 | } |
| 167 | |
| 168 | void AllocatorProcFinish(Processor *proc) { |
| 169 | allocator()->DestroyCache(cache: &proc->alloc_cache); |
| 170 | internal_allocator()->DestroyCache(cache: &proc->internal_alloc_cache); |
| 171 | } |
| 172 | |
| 173 | void AllocatorPrintStats() { |
| 174 | allocator()->PrintStats(); |
| 175 | } |
| 176 | |
| 177 | static void SignalUnsafeCall(ThreadState *thr, uptr pc) { |
| 178 | if (atomic_load_relaxed(a: &thr->in_signal_handler) == 0 || |
| 179 | !ShouldReport(thr, typ: ReportTypeSignalUnsafe)) |
| 180 | return; |
| 181 | VarSizeStackTrace stack; |
| 182 | ObtainCurrentStack(thr, toppc: pc, stack: &stack); |
| 183 | if (IsFiredSuppression(ctx, type: ReportTypeSignalUnsafe, trace: stack)) |
| 184 | return; |
| 185 | ThreadRegistryLock l(&ctx->thread_registry); |
| 186 | ScopedReport rep(ReportTypeSignalUnsafe); |
| 187 | rep.AddStack(stack, suppressable: true); |
| 188 | OutputReport(thr, srep: rep); |
| 189 | } |
| 190 | |
| 191 | |
| 192 | void *user_alloc_internal(ThreadState *thr, uptr pc, uptr sz, uptr align, |
| 193 | bool signal) { |
| 194 | if (sz >= kMaxAllowedMallocSize || align >= kMaxAllowedMallocSize || |
| 195 | sz > max_user_defined_malloc_size) { |
| 196 | if (AllocatorMayReturnNull()) |
| 197 | return nullptr; |
| 198 | uptr malloc_limit = |
| 199 | Min(a: kMaxAllowedMallocSize, b: max_user_defined_malloc_size); |
| 200 | GET_STACK_TRACE_FATAL(thr, pc); |
| 201 | ReportAllocationSizeTooBig(user_size: sz, max_size: malloc_limit, stack: &stack); |
| 202 | } |
| 203 | if (UNLIKELY(IsRssLimitExceeded())) { |
| 204 | if (AllocatorMayReturnNull()) |
| 205 | return nullptr; |
| 206 | GET_STACK_TRACE_FATAL(thr, pc); |
| 207 | ReportRssLimitExceeded(stack: &stack); |
| 208 | } |
| 209 | void *p = allocator()->Allocate(cache: &thr->proc()->alloc_cache, size: sz, alignment: align); |
| 210 | if (UNLIKELY(!p)) { |
| 211 | SetAllocatorOutOfMemory(); |
| 212 | if (AllocatorMayReturnNull()) |
| 213 | return nullptr; |
| 214 | GET_STACK_TRACE_FATAL(thr, pc); |
| 215 | ReportOutOfMemory(requested_size: sz, stack: &stack); |
| 216 | } |
| 217 | if (ctx && ctx->initialized) |
| 218 | OnUserAlloc(thr, pc, p: (uptr)p, sz, write: true); |
| 219 | if (signal) |
| 220 | SignalUnsafeCall(thr, pc); |
| 221 | return p; |
| 222 | } |
| 223 | |
| 224 | void user_free(ThreadState *thr, uptr pc, void *p, bool signal) { |
| 225 | ScopedGlobalProcessor sgp; |
| 226 | if (ctx && ctx->initialized) |
| 227 | OnUserFree(thr, pc, p: (uptr)p, write: true); |
| 228 | allocator()->Deallocate(cache: &thr->proc()->alloc_cache, p); |
| 229 | if (signal) |
| 230 | SignalUnsafeCall(thr, pc); |
| 231 | } |
| 232 | |
| 233 | void *user_alloc(ThreadState *thr, uptr pc, uptr sz) { |
| 234 | return SetErrnoOnNull(user_alloc_internal(thr, pc, sz, align: kDefaultAlignment)); |
| 235 | } |
| 236 | |
| 237 | void *user_calloc(ThreadState *thr, uptr pc, uptr size, uptr n) { |
| 238 | if (UNLIKELY(CheckForCallocOverflow(size, n))) { |
| 239 | if (AllocatorMayReturnNull()) |
| 240 | return SetErrnoOnNull(nullptr); |
| 241 | GET_STACK_TRACE_FATAL(thr, pc); |
| 242 | ReportCallocOverflow(count: n, size, stack: &stack); |
| 243 | } |
| 244 | void *p = user_alloc_internal(thr, pc, sz: n * size); |
| 245 | if (p) |
| 246 | internal_memset(s: p, c: 0, n: n * size); |
| 247 | return SetErrnoOnNull(p); |
| 248 | } |
| 249 | |
| 250 | void *user_reallocarray(ThreadState *thr, uptr pc, void *p, uptr size, uptr n) { |
| 251 | if (UNLIKELY(CheckForCallocOverflow(size, n))) { |
| 252 | if (AllocatorMayReturnNull()) |
| 253 | return SetErrnoOnNull(nullptr); |
| 254 | GET_STACK_TRACE_FATAL(thr, pc); |
| 255 | ReportReallocArrayOverflow(count: n, size, stack: &stack); |
| 256 | } |
| 257 | return user_realloc(thr, pc, p, sz: size * n); |
| 258 | } |
| 259 | |
| 260 | void OnUserAlloc(ThreadState *thr, uptr pc, uptr p, uptr sz, bool write) { |
| 261 | DPrintf("#%d: alloc(%zu) = 0x%zx\n" , thr->tid, sz, p); |
| 262 | // Note: this can run before thread initialization/after finalization. |
| 263 | // As a result this is not necessarily synchronized with DoReset, |
| 264 | // which iterates over and resets all sync objects, |
| 265 | // but it is fine to create new MBlocks in this context. |
| 266 | ctx->metamap.AllocBlock(thr, pc, p, sz); |
| 267 | // If this runs before thread initialization/after finalization |
| 268 | // and we don't have trace initialized, we can't imitate writes. |
| 269 | // In such case just reset the shadow range, it is fine since |
| 270 | // it affects only a small fraction of special objects. |
| 271 | if (write && thr->ignore_reads_and_writes == 0 && |
| 272 | atomic_load_relaxed(a: &thr->trace_pos)) |
| 273 | MemoryRangeImitateWrite(thr, pc, addr: (uptr)p, size: sz); |
| 274 | else |
| 275 | MemoryResetRange(thr, pc, addr: (uptr)p, size: sz); |
| 276 | } |
| 277 | |
| 278 | void OnUserFree(ThreadState *thr, uptr pc, uptr p, bool write) { |
| 279 | CHECK_NE(p, (void*)0); |
| 280 | if (!thr->slot) { |
| 281 | // Very early/late in thread lifetime, or during fork. |
| 282 | UNUSED uptr sz = ctx->metamap.FreeBlock(proc: thr->proc(), p, reset: false); |
| 283 | DPrintf("#%d: free(0x%zx, %zu) (no slot)\n" , thr->tid, p, sz); |
| 284 | return; |
| 285 | } |
| 286 | SlotLocker locker(thr); |
| 287 | uptr sz = ctx->metamap.FreeBlock(proc: thr->proc(), p, reset: true); |
| 288 | DPrintf("#%d: free(0x%zx, %zu)\n" , thr->tid, p, sz); |
| 289 | if (write && thr->ignore_reads_and_writes == 0) |
| 290 | MemoryRangeFreed(thr, pc, addr: (uptr)p, size: sz); |
| 291 | } |
| 292 | |
| 293 | void *user_realloc(ThreadState *thr, uptr pc, void *p, uptr sz) { |
| 294 | // FIXME: Handle "shrinking" more efficiently, |
| 295 | // it seems that some software actually does this. |
| 296 | if (!p) |
| 297 | return SetErrnoOnNull(user_alloc_internal(thr, pc, sz)); |
| 298 | if (!sz) { |
| 299 | user_free(thr, pc, p); |
| 300 | return nullptr; |
| 301 | } |
| 302 | void *new_p = user_alloc_internal(thr, pc, sz); |
| 303 | if (new_p) { |
| 304 | uptr old_sz = user_alloc_usable_size(p); |
| 305 | internal_memcpy(dest: new_p, src: p, n: min(a: old_sz, b: sz)); |
| 306 | user_free(thr, pc, p); |
| 307 | } |
| 308 | return SetErrnoOnNull(new_p); |
| 309 | } |
| 310 | |
| 311 | void *user_memalign(ThreadState *thr, uptr pc, uptr align, uptr sz) { |
| 312 | if (UNLIKELY(!IsPowerOfTwo(align))) { |
| 313 | errno = errno_EINVAL; |
| 314 | if (AllocatorMayReturnNull()) |
| 315 | return nullptr; |
| 316 | GET_STACK_TRACE_FATAL(thr, pc); |
| 317 | ReportInvalidAllocationAlignment(alignment: align, stack: &stack); |
| 318 | } |
| 319 | return SetErrnoOnNull(user_alloc_internal(thr, pc, sz, align)); |
| 320 | } |
| 321 | |
| 322 | int user_posix_memalign(ThreadState *thr, uptr pc, void **memptr, uptr align, |
| 323 | uptr sz) { |
| 324 | if (UNLIKELY(!CheckPosixMemalignAlignment(align))) { |
| 325 | if (AllocatorMayReturnNull()) |
| 326 | return errno_EINVAL; |
| 327 | GET_STACK_TRACE_FATAL(thr, pc); |
| 328 | ReportInvalidPosixMemalignAlignment(alignment: align, stack: &stack); |
| 329 | } |
| 330 | void *ptr = user_alloc_internal(thr, pc, sz, align); |
| 331 | if (UNLIKELY(!ptr)) |
| 332 | // OOM error is already taken care of by user_alloc_internal. |
| 333 | return errno_ENOMEM; |
| 334 | CHECK(IsAligned((uptr)ptr, align)); |
| 335 | *memptr = ptr; |
| 336 | return 0; |
| 337 | } |
| 338 | |
| 339 | void *user_aligned_alloc(ThreadState *thr, uptr pc, uptr align, uptr sz) { |
| 340 | if (UNLIKELY(!CheckAlignedAllocAlignmentAndSize(align, sz))) { |
| 341 | errno = errno_EINVAL; |
| 342 | if (AllocatorMayReturnNull()) |
| 343 | return nullptr; |
| 344 | GET_STACK_TRACE_FATAL(thr, pc); |
| 345 | ReportInvalidAlignedAllocAlignment(size: sz, alignment: align, stack: &stack); |
| 346 | } |
| 347 | return SetErrnoOnNull(user_alloc_internal(thr, pc, sz, align)); |
| 348 | } |
| 349 | |
| 350 | void *user_valloc(ThreadState *thr, uptr pc, uptr sz) { |
| 351 | return SetErrnoOnNull(user_alloc_internal(thr, pc, sz, align: GetPageSizeCached())); |
| 352 | } |
| 353 | |
| 354 | void *user_pvalloc(ThreadState *thr, uptr pc, uptr sz) { |
| 355 | uptr PageSize = GetPageSizeCached(); |
| 356 | if (UNLIKELY(CheckForPvallocOverflow(sz, PageSize))) { |
| 357 | errno = errno_ENOMEM; |
| 358 | if (AllocatorMayReturnNull()) |
| 359 | return nullptr; |
| 360 | GET_STACK_TRACE_FATAL(thr, pc); |
| 361 | ReportPvallocOverflow(size: sz, stack: &stack); |
| 362 | } |
| 363 | // pvalloc(0) should allocate one page. |
| 364 | sz = sz ? RoundUpTo(size: sz, boundary: PageSize) : PageSize; |
| 365 | return SetErrnoOnNull(user_alloc_internal(thr, pc, sz, align: PageSize)); |
| 366 | } |
| 367 | |
| 368 | static const void *user_alloc_begin(const void *p) { |
| 369 | if (p == nullptr || !IsAppMem(mem: (uptr)p)) |
| 370 | return nullptr; |
| 371 | void *beg = allocator()->GetBlockBegin(p); |
| 372 | if (!beg) |
| 373 | return nullptr; |
| 374 | |
| 375 | MBlock *b = ctx->metamap.GetBlock(p: (uptr)beg); |
| 376 | if (!b) |
| 377 | return nullptr; // Not a valid pointer. |
| 378 | |
| 379 | return (const void *)beg; |
| 380 | } |
| 381 | |
| 382 | uptr user_alloc_usable_size(const void *p) { |
| 383 | if (p == 0 || !IsAppMem(mem: (uptr)p)) |
| 384 | return 0; |
| 385 | MBlock *b = ctx->metamap.GetBlock(p: (uptr)p); |
| 386 | if (!b) |
| 387 | return 0; // Not a valid pointer. |
| 388 | if (b->siz == 0) |
| 389 | return 1; // Zero-sized allocations are actually 1 byte. |
| 390 | return b->siz; |
| 391 | } |
| 392 | |
| 393 | uptr user_alloc_usable_size_fast(const void *p) { |
| 394 | MBlock *b = ctx->metamap.GetBlock(p: (uptr)p); |
| 395 | // Static objects may have malloc'd before tsan completes |
| 396 | // initialization, and may believe returned ptrs to be valid. |
| 397 | if (!b) |
| 398 | return 0; // Not a valid pointer. |
| 399 | if (b->siz == 0) |
| 400 | return 1; // Zero-sized allocations are actually 1 byte. |
| 401 | return b->siz; |
| 402 | } |
| 403 | |
| 404 | void invoke_malloc_hook(void *ptr, uptr size) { |
| 405 | ThreadState *thr = cur_thread(); |
| 406 | if (ctx == 0 || !ctx->initialized || thr->ignore_interceptors) |
| 407 | return; |
| 408 | RunMallocHooks(ptr, size); |
| 409 | } |
| 410 | |
| 411 | void invoke_free_hook(void *ptr) { |
| 412 | ThreadState *thr = cur_thread(); |
| 413 | if (ctx == 0 || !ctx->initialized || thr->ignore_interceptors) |
| 414 | return; |
| 415 | RunFreeHooks(ptr); |
| 416 | } |
| 417 | |
| 418 | void *Alloc(uptr sz) { |
| 419 | ThreadState *thr = cur_thread(); |
| 420 | if (thr->nomalloc) { |
| 421 | thr->nomalloc = 0; // CHECK calls internal_malloc(). |
| 422 | CHECK(0); |
| 423 | } |
| 424 | InternalAllocAccess(); |
| 425 | return InternalAlloc(size: sz, cache: &thr->proc()->internal_alloc_cache); |
| 426 | } |
| 427 | |
| 428 | void FreeImpl(void *p) { |
| 429 | ThreadState *thr = cur_thread(); |
| 430 | if (thr->nomalloc) { |
| 431 | thr->nomalloc = 0; // CHECK calls internal_malloc(). |
| 432 | CHECK(0); |
| 433 | } |
| 434 | InternalAllocAccess(); |
| 435 | InternalFree(p, cache: &thr->proc()->internal_alloc_cache); |
| 436 | } |
| 437 | |
| 438 | } // namespace __tsan |
| 439 | |
| 440 | using namespace __tsan; |
| 441 | |
| 442 | extern "C" { |
| 443 | uptr __sanitizer_get_current_allocated_bytes() { |
| 444 | uptr stats[AllocatorStatCount]; |
| 445 | allocator()->GetStats(s: stats); |
| 446 | return stats[AllocatorStatAllocated]; |
| 447 | } |
| 448 | |
| 449 | uptr __sanitizer_get_heap_size() { |
| 450 | uptr stats[AllocatorStatCount]; |
| 451 | allocator()->GetStats(s: stats); |
| 452 | return stats[AllocatorStatMapped]; |
| 453 | } |
| 454 | |
| 455 | uptr __sanitizer_get_free_bytes() { |
| 456 | return 1; |
| 457 | } |
| 458 | |
| 459 | uptr __sanitizer_get_unmapped_bytes() { |
| 460 | return 1; |
| 461 | } |
| 462 | |
| 463 | uptr __sanitizer_get_estimated_allocated_size(uptr size) { |
| 464 | return size; |
| 465 | } |
| 466 | |
| 467 | int __sanitizer_get_ownership(const void *p) { |
| 468 | return allocator()->GetBlockBegin(p) != 0; |
| 469 | } |
| 470 | |
| 471 | const void *__sanitizer_get_allocated_begin(const void *p) { |
| 472 | return user_alloc_begin(p); |
| 473 | } |
| 474 | |
| 475 | uptr __sanitizer_get_allocated_size(const void *p) { |
| 476 | return user_alloc_usable_size(p); |
| 477 | } |
| 478 | |
| 479 | uptr __sanitizer_get_allocated_size_fast(const void *p) { |
| 480 | DCHECK_EQ(p, __sanitizer_get_allocated_begin(p)); |
| 481 | uptr ret = user_alloc_usable_size_fast(p); |
| 482 | DCHECK_EQ(ret, __sanitizer_get_allocated_size(p)); |
| 483 | return ret; |
| 484 | } |
| 485 | |
| 486 | void __sanitizer_purge_allocator() { |
| 487 | allocator()->ForceReleaseToOS(); |
| 488 | } |
| 489 | |
| 490 | void __tsan_on_thread_idle() { |
| 491 | ThreadState *thr = cur_thread(); |
| 492 | allocator()->SwallowCache(cache: &thr->proc()->alloc_cache); |
| 493 | internal_allocator()->SwallowCache(cache: &thr->proc()->internal_alloc_cache); |
| 494 | ctx->metamap.OnProcIdle(proc: thr->proc()); |
| 495 | } |
| 496 | } // extern "C" |
| 497 | |