clang之ThreadSanitizer

2,177 阅读4分钟

Clang 12 documentation

Clang 12 documentation包含了一系列工具,如 AddressSanitizerThreadSanitizerLeakSanitizerLibTooling等。

  1. clang之AddressSanitizer
  2. clang之MemorySanitizer
  3. clang之LeakSanitizer
  4. clang之UndefinedBehaviorSanitizer
  5. clang之Hardware-assisted-AddressSanitizer
  6. clang之SafeStack
  7. clang之ShadowCallStack
  8. clang之ThreadSanitizer
  9. clang之Thread-Safety-Analysis
  10. clang之DataFlowSanitizer

这部分是对clang文档 Clang 12 documentation ThreadSanitizer 的翻译。仅供参考。

介绍

ThreadSanitizer 是一个检测数据竞争的工具。它包含一个编译器插桩模块和一个运行时库。ThreadSanitizer 通常会带来大约5x-15x的速度影响,以及大约5x-10x的内存消耗。

ThreadSanitizer 能够检测出如下问题:

  1. Use of uninitialized mutexes。(使用了未初始化的互斥锁)
  2. Thread leaks (missing phread_johin)。(线程泄漏,没有使用 phread_johin
  3. Unsafe calls in signal handlers (ex:malloc)。(信号处理回调中执行了不安全的调用,如 malloc
  4. Unlock from wrong thread。(从错误的线程中加锁)
  5. Data race。(数据竞争)

如何构建

使用 CMake 来构建 LLVM/Clang

支持的平台

ThreadSanitizer 支持如下操作系统:

  • 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

如果检测到了一个bug,程序就会输出一个错误信息到 stderr 中。目前,ThreadSanitizer 使用外部的 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)

在一些场景中,可能需要根据是否开启 ThreadSanitizer 来执行不同的代码。这时就需要使用到 __has_feature 了。

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

attribute((no_sanitize("thread")))

有些代码不需要使用 ThreadSanitizer 来插桩。可以使用函数特性 no_sanitize("thread") 在特定函数中来禁用该功能(英文原文:disable instrumentation of plain (non-atomic) loads/stores)。ThreadSanitizer 依然会对这类函数进行插桩,以避免误报并提供更有意义的栈帧。而其他的编译器可能不支持该特性,所以建议将其与 __has_feature(thread_sanitizer) 一起使用。

黑名单

ThreadSanitizer supports src and fun entity types in Sanitizer special case list, that can be used to suppress data race reports in the specified source files or functions. 与使用 no_sanitize("thread") 特性标记的函数不同,黑名单函数根本就不会被插桩。这可能会导致误报,因为没有使用atomic操作来保证同步,并且报告中缺少栈帧。

限制

ThreadSanitizer 会消耗更多的内存。在默认设置下,每个线程的内存消耗是每1MB内存为5x。而3x(更不精准的分析)和9x(更精准的分析)的内存消耗都是可以设置的。

ThreadSanitizer 会映射(而不会保留)大量的虚拟内存空间。这意味着类似 ulimit 这样的工具可能不会如预期那样工作正常。不支持 Libc/libstdc++ 的静态链接。

不支持非地址独立的可执行文件。然而,fsanitize=thread 标记会导致Clang表现得如开启了 -fPIE 标记一样(即便并未使用 -fPIE 来编译),且在链接可执行文件时表现得如开启了 -pie 一样。

当前状态

ThreadSanitizer 当前处于beta阶段。它在使用 pthreads 的大型C++程序中时可行的,但是却不保证完全没问题。llvm libc++ 已经支持了 C++11 的多线程。测试套件已经集成到了CMake中,可以使用 make check-tsan 命令来运行。

我们正致力于增强这个工具,敬请期待。欢迎提供一切帮助,尤其是最小的、独立执行的测试样例。

更多信息