diff --git a/compiler-rt/lib/tsan/CMakeLists.txt b/compiler-rt/lib/tsan/CMakeLists.txt --- a/compiler-rt/lib/tsan/CMakeLists.txt +++ b/compiler-rt/lib/tsan/CMakeLists.txt @@ -63,6 +63,7 @@ if(APPLE) list(APPEND TSAN_SOURCES rtl/tsan_interceptors_mac.cpp + rtl/tsan_interceptors_mach_vm.cpp rtl/tsan_platform_mac.cpp rtl/tsan_platform_posix.cpp ) diff --git a/compiler-rt/lib/tsan/rtl/tsan_interceptors.cpp b/compiler-rt/lib/tsan/rtl/tsan_interceptors.cpp --- a/compiler-rt/lib/tsan/rtl/tsan_interceptors.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors.cpp @@ -745,6 +745,8 @@ return REAL(strdup)(str); } +// Zero out addr if it points into shadow memory and was provided as a hint +// only, i.e., MAP_FIXED is not set. static bool fix_mmap_addr(void **addr, long_t sz, int flags) { if (*addr) { if (!IsAppMem((uptr)*addr) || !IsAppMem((uptr)*addr + sz - 1)) { diff --git a/compiler-rt/lib/tsan/rtl/tsan_interceptors_mach_vm.cpp b/compiler-rt/lib/tsan/rtl/tsan_interceptors_mach_vm.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors_mach_vm.cpp @@ -0,0 +1,67 @@ +//===-- tsan_interceptors_mach_vm.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. +// +// Interceptors for mach_vm_* user space memory routines on Darwin. +//===----------------------------------------------------------------------===// + +#include "interception/interception.h" +#include "tsan_interceptors.h" +#include "tsan_platform.h" + +#include + +namespace __tsan { + +static bool intersects_with_shadow(mach_vm_address_t *address, + mach_vm_size_t size, int flags) { + // VM_FLAGS_FIXED is 0x0, so we have to test for VM_FLAGS_ANYWHERE. + if (flags & VM_FLAGS_ANYWHERE) return false; + uptr ptr = *address; + return !IsAppMem(ptr) || !IsAppMem(ptr + size - 1); +} + +// Also see mmap_interceptor in tsan_interceptors.cpp. +TSAN_INTERCEPTOR(kern_return_t, mach_vm_allocate, vm_map_t target, + mach_vm_address_t *address, mach_vm_size_t size, int flags) { + SCOPED_TSAN_INTERCEPTOR(mach_vm_allocate, target, address, size, flags); + if (target != mach_task_self()) { + return REAL(mach_vm_allocate)(target, address, size, flags); + } + if (intersects_with_shadow(address, size, flags)) { + return KERN_NO_SPACE; + } + kern_return_t res = REAL(mach_vm_allocate)(target, address, size, flags); + if (res == KERN_SUCCESS) { + if (thr->ignore_reads_and_writes == 0) + MemoryRangeImitateWrite(thr, pc, *address, size); + else + MemoryResetRange(thr, pc, *address, size); + } + return res; +} + +// Also see TSAN_INTERCEPTOR(int, munmap, ...) in tsan_interceptors.cpp. +TSAN_INTERCEPTOR(kern_return_t, mach_vm_deallocate, vm_map_t target, + mach_vm_address_t address, mach_vm_size_t size) { + SCOPED_TSAN_INTERCEPTOR(mach_vm_deallocate, target, address, size); + if (target != mach_task_self()) { + return REAL(mach_vm_deallocate)(target, address, size); + } + if (size != 0) { + // If size == 0, mach_vm_deallocate will return KERN_SUCCESS and does not + // deallocate any memory. + DontNeedShadowFor(address, size); + ScopedGlobalProcessor sgp; + ctx->metamap.ResetRange(thr->proc(), address, size); + } + return REAL(mach_vm_deallocate)(target, address, size); +} + +} // namespace __tsan diff --git a/compiler-rt/test/tsan/Darwin/mach_vm_allocate.c b/compiler-rt/test/tsan/Darwin/mach_vm_allocate.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tsan/Darwin/mach_vm_allocate.c @@ -0,0 +1,73 @@ +// Test that mach_vm_[de]allocate resets shadow memory status. +// +// RUN: %clang_tsan %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s --implicit-check-not='ThreadSanitizer' + +#include +#include +#include +#include +#include + +#include "../test.h" + +void AnnotateIgnoreReadsBegin(const char *f, int l); +void AnnotateIgnoreReadsEnd(const char *f, int l); +void AnnotateIgnoreWritesBegin(const char *f, int l); +void AnnotateIgnoreWritesEnd(const char *f, int l); + +static int *global_ptr; +const mach_vm_size_t alloc_size = sizeof(int); + +static int *alloc() { + mach_vm_address_t addr; + kern_return_t res = + mach_vm_allocate(mach_task_self(), &addr, alloc_size, VM_FLAGS_ANYWHERE); + assert(res == KERN_SUCCESS); + return (int *)addr; +} + +static void alloc_fixed(int *ptr) { + mach_vm_address_t addr = (mach_vm_address_t)ptr; + kern_return_t res = + mach_vm_allocate(mach_task_self(), &addr, alloc_size, VM_FLAGS_FIXED); + assert(res == KERN_SUCCESS); +} + +static void dealloc(int *ptr) { + kern_return_t res = + mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)ptr, alloc_size); + assert(res == KERN_SUCCESS); +} + +static void *Thread(void *arg) { + *global_ptr = 7; // Assignment 1 + + // We want to test that TSan does not report a race between the two + // assignments to global_ptr when memory is re-allocated here. The calls to + // the API itself are racy though, so ignore them. + AnnotateIgnoreWritesBegin(__FILE__, __LINE__); + dealloc(global_ptr); + alloc_fixed(global_ptr); + AnnotateIgnoreWritesEnd(__FILE__, __LINE__); + + barrier_wait(&barrier); + return NULL;; +} + +int main(int argc, const char *argv[]) { + barrier_init(&barrier, 2); + global_ptr = alloc(); + pthread_t t; + pthread_create(&t, NULL, Thread, NULL); + + barrier_wait(&barrier); + *global_ptr = 8; // Assignment 2 + + pthread_join(t, NULL); + dealloc(global_ptr); + printf("Done.\n"); + return 0; +} + +// CHECK: Done.