如何从 ASAN 的紧密循环中排除分配?

Dan*_*Dan 5 c++ address-sanitizer

在上一个问题中,发现使用最新版本的 GNU libstdc++ 从以空格分隔的人类可读文件( mirror )读取一系列数字会导致大量分配,并随文件大小线性缩放。

鉴于上面链接的文件和这个测试程序:

#include <fstream>

int main(int, char**) {
    std::ifstream ww15mgh("ww15mgh.grd");
    double value;
    while (ww15mgh >> value);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Valgrind--tool=memcheck报告:

==523661==   total heap usage: 1,038,970 allocs, 1,038,970 frees, 59,302,487 
Run Code Online (Sandbox Code Playgroud)

因为这百万分配中的每一个都在operator>>返回之前立即释放,所以没有泄漏并且程序在发布版本中的实际内存占用很小(81KB)。但是,编译-fsanitize=address将大量分配变成了一个真正的问题。

以下是上述程序的总内存占用,运行和不运行 ASAN:

$ g++ stackoverflow.cpp -o _build/stackoverflow
$ /usr/bin/time -v _build/stackoverflow |& grep 'm r'
    Maximum resident set size (kbytes): 3512
Run Code Online (Sandbox Code Playgroud)
$ g++ stackoverflow.cpp -o _build/stackoverflow_asan -fsanitize=address
$ /usr/bin/time -v _build/stackoverflow_asan |& grep 'm r'
    Maximum resident set size (kbytes): 125196
Run Code Online (Sandbox Code Playgroud)

125MB 似乎不是一个大问题,但在一个更大的单元测试程序运行在嵌入式 ARM 板上并多次调用此函数的上下文中,它运行我的 ARM CI 环境内存不足。

此特定情况的解决方法

#include <fstream>
#include <string>
#include <cstdio>

int main(int, char**) {
    std::ifstream ww15mgh("ww15mgh.grd");
    double value;
#if defined(__SANITIZE_ADDRESS__) && (defined(__GLIBCXX__) || defined(__GLIBCPP__))
    std::string text;
    while (ww15mgh >> text)
        value = std::strtod(text.data(), nullptr);
#else
    while (ww15mgh >> value);
#endif
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

使用这个预处理器门会产生更易于管理的总内存占用:

$ g++ stackoverflow_workaround.cpp -o _build/stackoverflow_workaround_asan -fsanitize=address
$ /usr/bin/time -v _build/stackoverflow_workaround_asan |& grep 'm r'
    Maximum resident set size (kbytes): 6396
Run Code Online (Sandbox Code Playgroud)

这是因为 libstdc++operator>>(ifstream&, string&)和 glibcstrtod都没有多余的分配,这可以通过欺骗解决方法在 valgrind 下运行来看到:

$ g++ stackoverflow_workaround.cpp -D__SANITIZE_ADDRESS__
$ valgrind --tool=memcheck --leak-check=yes ./a.out |& grep 'total heap'
==2483624==   total heap usage: 3 allocs, 3 frees, 81,368 bytes allocated
Run Code Online (Sandbox Code Playgroud)

gitlab 上提供了此示例代码CI 管道结果

在这一点上,我的 CI 不再耗尽内存和崩溃,所以我的同事可以继续他们的生活。然而,我觉得在消毒剂中隐藏东西是在#ifdef __SANITIZE_ADDRESS__作弊。

问题

有没有办法让原始程序在 ASAN 下运行,但在operator>>调用期间跳过 ASAN 的分配器填充?在一般情况下,对于调用分配内存的第三方函数的紧密循环,我如何避免使用-fsanitize=address?

Nat*_*dge 4

正如您所说,AddressSanitizer 将延迟已释放内存的重用,以帮助捕获释放后使用错误。此功能称为“隔离”,其使用的内存量可在运行时配置,请参阅https://github.com/google/sanitizers/wiki/AddressSanitizerFlags。例如,如果您在运行程序之前将环境变量设置ASAN_OPTIONSquarantine_size_mb=4,则它应将使用的内存量限制为 4 MB。

这不是特定于有问题的调用,因此它不能完全解决您所要求的问题,但我认为它将解决您的根本问题“如何在内存不足的机器上使用 AddressSanitizer”。