Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

使用 Sanitizer

什么是 Sanitizer?

Sanitizer 是一种静态检测工具,能够帮助找出传统调试器通常无法捕捉的问题。非常适合在持续集成中与 单元测试 结合使用。

在 Windows、macOS、Linux 平台使用 Clang(LLVM)、GCC、Visual Studio 编译器时能够使用 Sanitizer。部分平台可能也可以使用其独有的 Sanitizer。多种不同的编译器都提供相同的某种 Sanitizer 时,请记住它们的输出和行为有可能略有不同。

在 Godot 中使用 Sanitizer

Sanitizer 要求重新编译二进制文件。这意味着你无法使用官方 Godot 二进制文件来运行 Sanitizer。

当在 compiling 时启用了任意一种检测器,最终生成的二进制文件(程序)名称后面都会自动加上 .san 后缀,以此来和那些没有开启检测器的普通版本区分开。

由于需要执行大量额外的运行时检查,开启消毒剂会对程序性能产生影响,同时内存占用也会增加。虽然理论上可以在同一个构建版本中启用某些特定的多种检测器组合,但请务必注意同时使用多个检测器带来的性能代价,因为这样生成的程序可能会变得极其缓慢。

有些选项可以通过环境变量直接传递给检测器,而无需重新编译二进制程序。

地址 Sanitizer(ASAN)

  • Clang 和 GCC 中可用。

  • 支持的平台:Linux、macOS、Windows(Visual Studio)、Web

  • Clang ASAN 文档

地址检测器(Address Sanitizer)通常是使用频率最高的检测器(Sanitizer)。它可以诊断诸如缓冲区溢出和越界访问等问题。如果引擎崩溃并提示 free(): invalid pointer 这样的信息,这通常就是缓冲区溢出导致的。(需要注意的是,这条提示信息是由 C 运行时库打印的,而不是 Godot 引擎本身。)

在某些特定情况下(比如检测未初始化的内存读取),地址消毒剂(Address Sanitizer)就力不从心了。这种情况下,应该改用 内存 Sanitizer(MSAN)

此外,你还可以在 运行 Godot 之前(注意:不是编译 Godot 的时候),通过指定环境变量 ASAN_OPTIONS=detect_stack_use_after_return=1 ,来检测‘释放后使用(栈内存返回后使用)’的情况。不过,开启这个功能会增加地址检测器(ASAN)的运行时代价,所以请只在确实需要排查这类问题时才启用它。

如果想在 Godot 构建中启用地址检测器(ASAN),只需在编译时传入 use_asan=yes 这个 SCons 选项即可。不过需要注意的是,开启 ASAN 通常会让生成的二进制程序运行速度变慢 2 倍左右。

警告

由于一项 设计决策,地址消毒剂、内存消毒剂和线程消毒剂这三者是互斥的。这意味着,在同一个编译好的二进制程序中,你只能选择启用其中一种消毒剂。

泄漏 Sanitizer(LSAN)

内存泄漏检测器(leak sanitizer)能够检测出‘内存泄漏’。所谓内存泄漏,就是指程序在运行过程中,那些已经不再使用的内存从来没有被释放过。如果程序运行时间足够长,这种情况极有可能导致内存耗尽(out-of-memory)。由于 Godot 可能会在 专用服务器 上连续运行数月甚至数年而不重启,因此,一旦发现内存泄漏,及时修复就显得尤为重要。

要在 Godot 引擎构建中启用内存泄漏检测器(LSAN),只需在编译时传入 use_lsan=yes 这个 SCons 选项即可。

内存 Sanitizer(MSAN)

内存清理器是对 地址 Sanitizer(ASAN) (地址清理器)的一个有力补充。与地址清理器不同的是,内存清理器能够精准检测出‘未初始化的内存读取’问题。

如果想在 Godot 构建中启用内存清理器(MSAN),只需在编译时传入 use_msan=yes 这个 SCons 选项即可。不过需要注意的是,开启 MSAN 通常会让生成的二进制程序运行速度变慢 3 倍左右。

警告

由于一项 设计决策,地址消毒剂、内存消毒剂和线程消毒剂这三者是互斥的。这意味着,在同一个编译好的二进制程序中,你只能选择启用其中一种消毒剂。

线程 Sanitizer(TSAN)

线程检测器(thread sanitizer)主要用于追踪与多线程相关的‘竞态条件(race condition)’。竞态条件是指多个线程试图在同一时间修改同一份数据的情况。由于操作系统对线程的调度顺序是任意的,这就会导致程序出现一些偶尔才会发生、且很难排查的错误行为。想要避免竞态条件,你需要加上‘锁(lock)’,以确保在任意时刻,只有一个线程能够访问共享数据。

如果想在 Godot 构建中启用线程清理器(TSAN),只需在编译时传入 use_tsan=yes 这个 SCons 选项即可。不过需要注意的是,开启 TSAN 通常会让生成的二进制程序运行速度变慢 10 倍左右,同时内存占用量也会飙升到原本的 8 倍左右。

警告

由于一项 设计决策,地址消毒剂、内存消毒剂和线程消毒剂这三者是互斥的。这意味着,在同一个编译好的二进制程序中,你只能选择启用其中一种消毒剂。

备注

在 Linux 系统上,如果你遇到了以下错误:

FATAL: ThreadSanitizer: unexpected memory mapping

你可能需要临时降低系统中的地址空间布局随机化(ASLR)熵值,可以通过以下命令来实现:

sudo sysctl vm.mmap_rnd_bits=28

或者,最好直接使用以下命令将其完全禁用:

sudo sysctl kernel.randomize_va_space=0

一旦你使用完线程清理器(thread sanitizer),请立刻通过以下命令来调高 ASLR 的熵值:

sudo sysctl vm.mmap_rnd_bits=32

或者使用以下命令来重新开启 ASLR:

sudo sysctl kernel.randomize_va_space=2

重启电脑,也会让 ASLR 的状态自动恢复成默认值。

务必尽快把修改还原,因为降低 ASLR 的熵值,或者完全禁用 ASLR,都可能会带来安全风险。

未定义行为 Sanitizer(UBSAN)

未定义行为检测器(undefined behavior sanitizer)用于追踪程序表现出随机且不可预测行为的情况。这通常是由那些编译器虽然能够接受,但在逻辑上并 不正确 的 C/C++ 代码引起的。此外,使用不同的优化选项进行编译,也可能会改变未定义行为所呈现出的结果。

要在 Godot 构建中启用“未定义行为(UBSAN)”,在编译时传入 SCons 选项 use_ubsan=yes 即可。开启 UBSAN 只会带来很小的性能开销。

平台相关 Sanitizer

Web

compiling for the Web 时,还有 2 个额外的 SCons 消毒剂(sanitizer)选项可供使用:

  • use_assertions=yes 这个选项会开启 Emscripten 的运行时断言(runtime assertions),它能够帮助捕获各种潜在的问题。

  • 加上 use_safe_heap=yes 这个选项,就能开启 Emscripten's SAFE_HEAP sanitizer。它提供的功能和 ASAN(地址消毒器)很类似,但它更专注于排查 WebAssembly 特有的那些内存问题。由于 SAFE_HEAP 并不能保证与 ASAN 和 UBSAN 在同一个程序文件里完美兼容,所以你可能需要单独为它编译一个版本。