Skip to content

Commit 5f0f4e3

Browse files
committed
[GWP-ASan] Mutex implementation [2].
Summary: See D60593 for further information. This patch pulls out the mutex implementation and the required definitions file. We implement our own mutex for GWP-ASan currently, because: 1. We must be compatible with the sum of the most restrictive elements of the supporting allocator's build system. Current targets for GWP-ASan include Scudo (on Linux and Fuchsia), and bionic (on Android). 2. Scudo specifies `-nostdlib++ -nonodefaultlibs`, meaning we can't use `std::mutex` or `mtx_t`. 3. We can't use `sanitizer_common`'s mutex, as the supporting allocators cannot afford the extra maintenance (Android, Fuchsia) and code size (Fuchsia) overheads that this would incur. In future, we would like to implement a shared base mutex for GWP-ASan, Scudo and sanitizer_common. This will likely happen when both GWP-ASan and Scudo standalone are not in the development phase, at which point they will have stable requirements. Reviewers: vlad.tsyrklevich, morehouse, jfb Reviewed By: morehouse Subscribers: dexonsmith, srhines, cfe-commits, kubamracek, mgorny, cryptoad, jfb, #sanitizers, llvm-commits, vitalybuka, eugenis Tags: #sanitizers, #llvm, #clang Differential Revision: https://reviews.llvm.org/D61923 llvm-svn: 362138
1 parent 04a38b9 commit 5f0f4e3

File tree

12 files changed

+340
-2
lines changed

12 files changed

+340
-2
lines changed

clang/runtime/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ if(LLVM_BUILD_EXTERNAL_COMPILER_RT AND EXISTS ${COMPILER_RT_SRC_ROOT}/)
132132
# Add top-level targets for various compiler-rt test suites.
133133
set(COMPILER_RT_TEST_SUITES check-fuzzer check-asan check-hwasan check-asan-dynamic check-dfsan
134134
check-lsan check-msan check-sanitizer check-tsan check-ubsan check-ubsan-minimal
135-
check-profile check-cfi check-cfi-and-supported check-safestack)
135+
check-profile check-cfi check-cfi-and-supported check-safestack check-gwp_asan)
136136
foreach(test_suite ${COMPILER_RT_TEST_SUITES})
137137
get_ext_project_build_command(run_test_suite ${test_suite})
138138
add_custom_target(${test_suite}

compiler-rt/lib/gwp_asan/CMakeLists.txt

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@ add_compiler_rt_component(gwp_asan)
33
include_directories(..)
44

55
set(GWP_ASAN_SOURCES
6+
platform_specific/mutex_posix.cpp
67
random.cpp
78
)
89

910
set(GWP_ASAN_HEADERS
11+
mutex.h
1012
random.h
1113
)
1214

1315
# Ensure that GWP-ASan meets the delegated requirements of some supporting
1416
# allocators. Some supporting allocators (e.g. scudo standalone) cannot use any
1517
# parts of the C++ standard library.
16-
set(GWP_ASAN_CFLAGS -fno-rtti -fno-exceptions -nostdinc++)
18+
set(GWP_ASAN_CFLAGS -fno-rtti -fno-exceptions -nostdinc++ -pthread)
1719

1820
# Remove -stdlib= which is unused when passing -nostdinc++.
1921
string(REGEX REPLACE "-stdlib=[a-zA-Z+]*" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
@@ -37,3 +39,7 @@ if (COMPILER_RT_HAS_GWP_ASAN)
3739
ADDITIONAL_HEADERS ${GWP_ASAN_HEADERS}
3840
CFLAGS ${GWP_ASAN_CFLAGS})
3941
endif()
42+
43+
if(COMPILER_RT_INCLUDE_TESTS)
44+
add_subdirectory(tests)
45+
endif()

compiler-rt/lib/gwp_asan/mutex.h

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//===-- mutex.h -------------------------------------------------*- C++ -*-===//
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+
#ifndef GWP_ASAN_MUTEX_H_
10+
#define GWP_ASAN_MUTEX_H_
11+
12+
#ifdef __unix__
13+
#include <pthread.h>
14+
#else
15+
#error "GWP-ASan is not supported on this platform."
16+
#endif
17+
18+
namespace gwp_asan {
19+
class Mutex {
20+
public:
21+
constexpr Mutex() = default;
22+
~Mutex() = default;
23+
Mutex(const Mutex &) = delete;
24+
Mutex &operator=(const Mutex &) = delete;
25+
// Lock the mutex.
26+
void lock();
27+
// Nonblocking trylock of the mutex. Returns true if the lock was acquired.
28+
bool tryLock();
29+
// Unlock the mutex.
30+
void unlock();
31+
32+
private:
33+
#ifdef __unix__
34+
pthread_mutex_t Mu = PTHREAD_MUTEX_INITIALIZER;
35+
#endif // defined(__unix__)
36+
};
37+
38+
class ScopedLock {
39+
public:
40+
explicit ScopedLock(Mutex &Mx) : Mu(Mx) { Mu.lock(); }
41+
~ScopedLock() { Mu.unlock(); }
42+
ScopedLock(const ScopedLock &) = delete;
43+
ScopedLock &operator=(const ScopedLock &) = delete;
44+
45+
private:
46+
Mutex &Mu;
47+
};
48+
} // namespace gwp_asan
49+
50+
#endif // GWP_ASAN_MUTEX_H_
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//===-- mutex_posix.cpp -----------------------------------------*- C++ -*-===//
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+
#include "mutex.h"
10+
11+
#include <assert.h>
12+
#include <pthread.h>
13+
14+
namespace gwp_asan {
15+
void Mutex::lock() {
16+
int Status = pthread_mutex_lock(&Mu);
17+
assert(Status == 0);
18+
// Remove warning for non-debug builds.
19+
(void)Status;
20+
}
21+
22+
bool Mutex::tryLock() { return pthread_mutex_trylock(&Mu) == 0; }
23+
24+
void Mutex::unlock() {
25+
int Status = pthread_mutex_unlock(&Mu);
26+
assert(Status == 0);
27+
// Remove warning for non-debug builds.
28+
(void)Status;
29+
}
30+
} // namespace gwp_asan
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
include(CompilerRTCompile)
2+
3+
set(GWP_ASAN_UNITTEST_CFLAGS
4+
${COMPILER_RT_UNITTEST_CFLAGS}
5+
${COMPILER_RT_GTEST_CFLAGS}
6+
-I${COMPILER_RT_SOURCE_DIR}/lib/
7+
-O2)
8+
9+
file(GLOB GWP_ASAN_HEADERS ../*.h)
10+
file(GLOB GWP_ASAN_UNITTESTS *.cpp)
11+
set(GWP_ASAN_UNIT_TEST_HEADERS
12+
${GWP_ASAN_HEADERS})
13+
14+
add_custom_target(GwpAsanUnitTests)
15+
set_target_properties(GwpAsanUnitTests PROPERTIES FOLDER "Compiler-RT Tests")
16+
17+
set(GWP_ASAN_UNITTEST_LINK_FLAGS ${COMPILER_RT_UNITTEST_LINK_FLAGS})
18+
list(APPEND GWP_ASAN_UNITTEST_LINK_FLAGS --driver-mode=g++)
19+
if(NOT WIN32)
20+
list(APPEND GWP_ASAN_UNITTEST_LINK_FLAGS -lpthread)
21+
endif()
22+
23+
if(COMPILER_RT_DEFAULT_TARGET_ARCH IN_LIST GWP_ASAN_SUPPORTED_ARCH)
24+
# GWP-ASan unit tests are only run on the host machine.
25+
set(arch ${COMPILER_RT_DEFAULT_TARGET_ARCH})
26+
27+
set(GWP_ASAN_TEST_RUNTIME RTGwpAsanTest.${arch})
28+
29+
set(GWP_ASAN_TEST_RUNTIME_OBJECTS
30+
$<TARGET_OBJECTS:RTGwpAsan.${arch}>)
31+
32+
add_library(${GWP_ASAN_TEST_RUNTIME} STATIC
33+
${GWP_ASAN_TEST_RUNTIME_OBJECTS})
34+
35+
set_target_properties(${GWP_ASAN_TEST_RUNTIME} PROPERTIES
36+
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
37+
FOLDER "Compiler-RT Runtime tests")
38+
39+
set(GwpAsanTestObjects)
40+
generate_compiler_rt_tests(GwpAsanTestObjects
41+
GwpAsanUnitTests "GwpAsan-${arch}-Test" ${arch}
42+
SOURCES ${GWP_ASAN_UNITTESTS} ${COMPILER_RT_GTEST_SOURCE}
43+
RUNTIME ${GWP_ASAN_TEST_RUNTIME}
44+
DEPS gtest ${GWP_ASAN_UNIT_TEST_HEADERS}
45+
CFLAGS ${GWP_ASAN_UNITTEST_CFLAGS}
46+
LINK_FLAGS ${GWP_ASAN_UNITTEST_LINK_FLAGS})
47+
set_target_properties(GwpAsanUnitTests PROPERTIES
48+
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
49+
endif()
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//===-- driver.cpp ----------------------------------------------*- C++ -*-===//
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+
#include "gtest/gtest.h"
10+
11+
int main(int argc, char **argv) {
12+
testing::InitGoogleTest(&argc, argv);
13+
return RUN_ALL_TESTS();
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//===-- mutex_test.cpp ------------------------------------------*- C++ -*-===//
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+
#include "gwp_asan/mutex.h"
10+
#include "gtest/gtest.h"
11+
12+
#include <atomic>
13+
#include <mutex>
14+
#include <thread>
15+
#include <vector>
16+
17+
using gwp_asan::Mutex;
18+
using gwp_asan::ScopedLock;
19+
20+
TEST(GwpAsanMutexTest, LockUnlockTest) {
21+
Mutex Mu;
22+
23+
ASSERT_TRUE(Mu.tryLock());
24+
ASSERT_FALSE(Mu.tryLock());
25+
Mu.unlock();
26+
27+
Mu.lock();
28+
Mu.unlock();
29+
30+
// Ensure that the mutex actually unlocked.
31+
ASSERT_TRUE(Mu.tryLock());
32+
Mu.unlock();
33+
}
34+
35+
TEST(GwpAsanMutexTest, ScopedLockUnlockTest) {
36+
Mutex Mu;
37+
{ ScopedLock L(Mu); }
38+
// Locking will fail here if the scoped lock failed to unlock.
39+
EXPECT_TRUE(Mu.tryLock());
40+
Mu.unlock();
41+
42+
{
43+
ScopedLock L(Mu);
44+
EXPECT_FALSE(Mu.tryLock()); // Check that the c'tor did lock.
45+
46+
// Manually unlock and check that this succeeds.
47+
Mu.unlock();
48+
EXPECT_TRUE(Mu.tryLock()); // Manually lock.
49+
}
50+
EXPECT_TRUE(Mu.tryLock()); // Assert that the scoped destructor did unlock.
51+
Mu.unlock();
52+
}
53+
54+
static void synchronousIncrementTask(std::atomic<bool> *StartingGun, Mutex *Mu,
55+
unsigned *Counter,
56+
unsigned NumIterations) {
57+
while (!StartingGun) {
58+
// Wait for starting gun.
59+
}
60+
for (unsigned i = 0; i < NumIterations; ++i) {
61+
ScopedLock L(*Mu);
62+
(*Counter)++;
63+
}
64+
}
65+
66+
static void runSynchronisedTest(unsigned NumThreads, unsigned CounterMax) {
67+
std::vector<std::thread> Threads;
68+
69+
ASSERT_TRUE(CounterMax % NumThreads == 0);
70+
71+
std::atomic<bool> StartingGun{false};
72+
Mutex Mu;
73+
unsigned Counter = 0;
74+
75+
for (unsigned i = 0; i < NumThreads; ++i)
76+
Threads.emplace_back(synchronousIncrementTask, &StartingGun, &Mu, &Counter,
77+
CounterMax / NumThreads);
78+
79+
StartingGun = true;
80+
for (auto &T : Threads)
81+
T.join();
82+
83+
EXPECT_EQ(CounterMax, Counter);
84+
}
85+
86+
TEST(GwpAsanMutexTest, SynchronisedCounterTest) {
87+
runSynchronisedTest(4, 100000);
88+
runSynchronisedTest(1000, 1000000);
89+
}
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
set(GWP_ASAN_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
2+
set(GWP_ASAN_LIT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
3+
4+
set(GWP_ASAN_TESTSUITES)
5+
6+
set(GWP_ASAN_UNITTEST_DEPS)
7+
set(GWP_ASAN_TEST_DEPS
8+
${SANITIZER_COMMON_LIT_TEST_DEPS}
9+
gwp_asan)
10+
11+
if (COMPILER_RT_INCLUDE_TESTS)
12+
list(APPEND GWP_ASAN_TEST_DEPS GwpAsanUnitTests)
13+
configure_lit_site_cfg(
14+
${CMAKE_CURRENT_SOURCE_DIR}/unit/lit.site.cfg.in
15+
${CMAKE_CURRENT_BINARY_DIR}/unit/lit.site.cfg)
16+
add_lit_testsuite(check-gwp_asan-unit "Running GWP-ASan unit tests"
17+
${CMAKE_CURRENT_BINARY_DIR}/unit
18+
DEPENDS ${GWP_ASAN_TEST_DEPS})
19+
set_target_properties(check-gwp_asan-unit PROPERTIES FOLDER
20+
"Compiler-RT Tests")
21+
list(APPEND GWP_ASAN_TEST_DEPS check-gwp_asan-unit)
22+
endif()
23+
24+
configure_lit_site_cfg(
25+
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in
26+
${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg
27+
)
28+
29+
foreach(arch ${GWP_ASAN_SUPPORTED_ARCH})
30+
set(GWP_ASAN_TEST_TARGET_ARCH ${arch})
31+
string(TOLOWER "-${arch}" GWP_ASAN_TEST_CONFIG_SUFFIX)
32+
get_test_cc_for_arch(${arch} GWP_ASAN_TEST_TARGET_CC GWP_ASAN_TEST_TARGET_CFLAGS)
33+
string(TOUPPER ${arch} ARCH_UPPER_CASE)
34+
set(CONFIG_NAME ${ARCH_UPPER_CASE}${OS_NAME}Config)
35+
36+
configure_lit_site_cfg(
37+
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in
38+
${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg)
39+
list(APPEND GWP_ASAN_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
40+
endforeach()
41+
42+
add_lit_testsuite(check-gwp_asan "Running the GWP-ASan tests"
43+
${GWP_ASAN_TESTSUITES}
44+
DEPENDS ${GWP_ASAN_TEST_DEPS})
45+
set_target_properties(check-gwp_asan PROPERTIES FOLDER "Compiler-RT Misc")
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Exists to simply stop warnings about lit not discovering any tests here.
2+
// RUN: %clang %s
3+
4+
int main() { return 0; }

compiler-rt/test/gwp_asan/lit.cfg

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# -*- Python -*-
2+
3+
import os
4+
5+
# Setup config name.
6+
config.name = 'GWP-ASan' + config.name_suffix
7+
8+
# Setup source root.
9+
config.test_source_root = os.path.dirname(__file__)
10+
11+
# Test suffixes.
12+
config.suffixes = ['.c', '.cc', '.cpp', '.test']
13+
14+
# C & CXX flags.
15+
c_flags = ([config.target_cflags])
16+
17+
# Android doesn't want -lrt.
18+
if not config.android:
19+
c_flags += ["-lrt"]
20+
21+
cxx_flags = (c_flags + config.cxx_mode_flags + ["-std=c++11"])
22+
23+
def build_invocation(compile_flags):
24+
return " " + " ".join([config.clang] + compile_flags) + " "
25+
26+
# Add substitutions.
27+
config.substitutions.append(("%clang ", build_invocation(c_flags)))
28+
29+
# GWP-ASan tests are currently supported on Linux only.
30+
if config.host_os not in ['Linux']:
31+
config.unsupported = True
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@LIT_SITE_CFG_IN_HEADER@
2+
3+
config.name_suffix = "@GWP_ASAN_TEST_CONFIG_SUFFIX@"
4+
config.target_arch = "@GWP_ASAN_TEST_TARGET_ARCH@"
5+
config.target_cflags = "@GWP_ASAN_TEST_TARGET_CFLAGS@"
6+
7+
# Load common config for all compiler-rt lit tests.
8+
lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured")
9+
10+
# Load tool-specific config that would do the real work.
11+
lit_config.load_config(config, "@GWP_ASAN_LIT_SOURCE_DIR@/lit.cfg")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@LIT_SITE_CFG_IN_HEADER@
2+
3+
config.name = "GwpAsan-Unittest"
4+
# Load common config for all compiler-rt unit tests.
5+
lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/unittests/lit.common.unit.configured")
6+
7+
config.test_exec_root = os.path.join("@COMPILER_RT_BINARY_DIR@",
8+
"lib", "gwp_asan", "tests")
9+
config.test_source_root = config.test_exec_root

0 commit comments

Comments
 (0)