线程安全检查器

简介

线程安全检查器是一个检测数据竞争的工具。它包含一个编译器插桩模块和一个运行时库。线程安全检查器通常会导致约 **5x-15x** 的性能下降。线程安全检查器通常会导致约 **5x-10x** 的内存开销。

如何构建

使用 CMake 构建 LLVM/Clang。

支持的平台

线程安全检查器在以下操作系统上受支持

  • Android aarch64, x86_64

  • Darwin arm64, x86_64

  • FreeBSD

  • Linux aarch64, x86_64, powerpc64, powerpc64le

  • NetBSD

对其他 64 位架构的支持是可能的,欢迎贡献。对 32 位平台的支持存在问题,目前没有计划。

用法

只需使用 -fsanitize=thread 编译和链接您的程序。为了获得合理的性能,请添加 -O1 或更高。使用 -g 在警告消息中获取文件名和行号。

示例

% cat projects/compiler-rt/lib/tsan/lit_tests/tiny_race.c
#include <pthread.h>
int Global;
void *Thread1(void *x) {
  Global = 42;
  return x;
}
int main() {
  pthread_t t;
  pthread_create(&t, NULL, Thread1, NULL);
  Global = 43;
  pthread_join(t, NULL);
  return Global;
}

$ clang -fsanitize=thread -g -O1 tiny_race.c

如果检测到错误,程序将向 stderr 打印错误消息。目前,线程安全检查器使用外部 addr2line 进程来符号化其输出(将在将来修复)。

% ./a.out
WARNING: ThreadSanitizer: data race (pid=19219)
  Write of size 4 at 0x7fcf47b21bc0 by thread T1:
    #0 Thread1 tiny_race.c:4 (exe+0x00000000a360)

  Previous write of size 4 at 0x7fcf47b21bc0 by main thread:
    #0 main tiny_race.c:10 (exe+0x00000000a3b4)

  Thread T1 (running) created at:
    #0 pthread_create tsan_interceptors.cc:705 (exe+0x00000000c790)
    #1 main tiny_race.c:9 (exe+0x00000000a3a4)

__has_feature(thread_sanitizer)

在某些情况下,可能需要根据是否启用了线程安全检查器来执行不同的代码。 __has_feature 可用于此目的。

#if defined(__has_feature)
#  if __has_feature(thread_sanitizer)
// code that builds only under ThreadSanitizer
#  endif
#endif

__attribute__((no_sanitize("thread")))

某些代码不应受线程安全检查器的插桩。可以使用函数属性 no_sanitize("thread") 来禁用对特定函数中简单(非原子)的加载/存储的插桩。线程安全检查器仍然会对这些函数进行插桩,以避免误报并提供有意义的堆栈跟踪。该属性可能不受其他编译器的支持,因此建议将其与 __has_feature(thread_sanitizer) 一起使用。

__attribute__((disable_sanitizer_instrumentation))

disable_sanitizer_instrumentation 属性可以应用于函数,以防止所有类型的插桩。因此,它可能会导致误报和错误的堆栈跟踪。因此,应谨慎使用,并且仅在绝对必要时使用;例如,对于某些无法容忍任何插桩及其产生的副作用的代码。该属性会覆盖 no_sanitize("thread")

忽略列表

线程安全检查器在 Sanitizer 特殊情况列表 中支持 srcfun 实体类型,这些类型可用于抑制在指定源文件或函数中的数据竞争报告。与使用 no_sanitize("thread") 属性标记的函数不同,被忽略的函数根本不会被插桩。由于通过原子操作错过了同步以及报告中错过了堆栈帧,这会导致误报。

局限性

  • 线程安全检查器比原生运行使用更多的实际内存。在默认设置下,内存开销为 5x 加上每个线程 1Mb。还提供 3x(不太准确的分析)和 9x(更准确的分析)开销设置。

  • 线程安全检查器会映射(但不保留)大量虚拟地址空间。这意味着像 ulimit 这样的工具可能不会像预期的那样工作。

  • 不支持 Libc/libstdc++ 静态链接。

  • 不支持非位置无关的可执行文件。因此,如果在没有 -fPIC 的情况下编译,则 fsanitize=thread 标志会导致 Clang 就像提供了 -fPIE 标志一样,如果链接可执行文件,则就像提供了 -pie 标志一样。

安全注意事项

线程安全检查器是一个错误检测工具,其运行时不打算与生产可执行文件链接。虽然它可能对测试有用,但线程安全检查器的运行时并非针对安全敏感的约束而开发,并且可能会损害结果可执行文件的安全性。

当前状态

线程安全检查器处于测试阶段。已知它适用于使用 pthreads 的大型 C++ 程序,但我们不承诺任何事情(目前)。C++11 线程在 llvm libc++ 中受支持。测试套件已集成到 CMake 构建中,可以使用 make check-tsan 命令运行。

我们正在积极努力改进该工具 - 请持续关注。任何帮助,特别是以最小化的独立测试的形式,都非常受欢迎。

更多信息

https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual