LLVM 项目博客

LLVM 项目新闻和来自战壕的细节

使用 Address Sanitizer 测试 libc++

[本文以略微扩展的形式 转载自 Marshall 的博客]
我一直断断续续地运行 libc++ 测试。这是一个相当广泛的测试套件,但我想知道是否有测试套件没有发现的错误。在即将发布的 clang 3.3 中,有一个名为 Address Sanitizer 的新功能,它在您的可执行文件中插入一些运行时检查,以查看是否存在任何对内存的“越界”读写操作。
我一直想,如果能说 libc++ 是“ASan 清洁”的(也就是说,在使用 Address Sanitizer 运行时通过了所有测试套件),那该多好。
所以我决定这么做。

[ 所有这些工作都是在 Mac OS X 10.8.2/3 上完成的 ]

如何运行测试

有一个用于运行测试的脚本。它被称为 testit
    $ cd $LLVM/libcxx/test ; ./testit

其中 $LLVM/libcxx 是 libc++ 的签出位置。这大约需要 30 分钟才能运行。在我的系统上,没有 Address Sanitizer,libc++ 在 4348 个测试中失败了 12 个。

使用 Address Sanitizer 运行测试

    $ cd $LLVM/libcxx/test ; CC=/path/to/tot/clang++ OPTIONS= "-std=c++11 -stdlib=libc++ -fsanitize=address" ./testit
注意:默认选项是“-std=c++11 -stdlib=libc++”,如果您没有指定任何内容,就会得到这些选项。 这大约需要 92 分钟;是之前时间的 3 倍多。使用 Address Sanitizer,libc++ 失败了 54 个测试(同样,在 4348 个测试中)。

哪些测试失败了?

  • 在 11 个测试中,Address Sanitizer 检测到堆块外部的一个字节写入。所有这些都与 iostreams 相关。我创建了一个 ASan 也触发的小型测试程序,并将其发送给 Howard Hinnant(他编写了大部分 libc++ 代码),他发现了一个他错误地分配了零字节缓冲区的地方。一个错误,多个失败。他在版本 177452 中修复了这个问题。
  • std::random 的 2 个测试失败了。事实证明这是测试代码中的一个越界错误,而不是 libc++ 中的错误。我在版本 177355177464 中修复了这些问题。
  • Address Sanitizer 检测到 4 个情况下内存分配失败。这是预期的,因为有些测试正在测试 libc++ 的内存分配系统。但是,似乎 ASan 在内存分配失败时不会调用用户提供的 new_handler(并且可能也不会抛出 std::bad_alloc)。我已经提交了 PR15544 来跟踪这个问题。
  • 25 个情况下程序由于缺少符号而无法加载。这最常见的是 std::__1::__get_sp_mut(void const *),但也有一些其他的。Howard 说这是在 10.8 发布后添加到 libc++ 中的,所以它不在 /usr/lib 中的 dylib 中。如果测试使用从源代码构建的 libc++ 副本运行,它们就会通过。
  • 有 12 个情况在启用 Address Sanitizer 之前就失败了。
在 Howard 和我修复了随机测试和 iostreams 代码中的错误后,我使用最近构建的 libc++.dylib 重新运行了测试。
    $ cd $LLVM/libcxx/test ; DYLD_LIBRARY_PATH=$LLVM/libcxx/lib CC=/path/to/tot/clang++ OPTIONS= "-std=c++11 -stdlib=libc++ -fsanitize=address" ./testit
这导致了 16 个失败。
  • 与内存分配失败相关的 4 个失败。
  • 我们开始时的 12 个失败。

结论

我很高兴看到 libc++ 代码中只有这么少的错误。它是在 Mac OS X 上(以及随着 llvm 的普及,其他系统上的)应用程序的基本构建块。现在它比我们开始做这个练习时更好。然而,我们确实在测试套件中发现了一些错误,在 libc++ 中发现了一个堆破坏错误。我们还发现了 Address Sanitizer 的一个限制,开发人员正在努力解决这个问题。