2

我发现如果我使用 tcmalloc 堆检查器以 draconian 模式检查下面的代码会导致堆泄漏,但 LSan 没有发现泄漏
(我假设 glibc 中的内部分配在 LSan 中被抑制)

#include <string.h>
#include <netdb.h>

int foo() {
    struct addrinfo hints, *res;
    memset(&hints, 0, sizeof hints);

    getaddrinfo("www.example.com", 0, &hints, &res);

    freeaddrinfo(res);
}

int main() {
    foo();
}

我检查了一下,发现在 glibc 内部getaddrinfo()使用了暂存缓冲区
, 并怀疑这些暂存缓冲区会导致内存泄漏
(即使它无害)

但遗憾的是没有完整的解释
,只说“暂存缓冲区是具有堆栈默认分配的可变大小缓冲区”;;

但究竟是什么暂存缓冲区?

你可以参考glibc/include/scratch_buffer.h 这里

4

2 回答 2

1

来自 google-perftools 的自述文件:

为了捕获所有堆泄漏,tcmalloc 必须最后链接到您的可执行文件中。堆检查器可能会错误地描述链接行上列出的库中的某些内存访问。例如,它可能会将这些库报告为内存泄漏,而实际上它们不是。(有关详细信息,请参阅源代码。)

通常,libc 最后链接。

暂存缓冲区或暂存空间是一个经常用于预分配内存的术语(因为启动时间通常比运行时性能更重要),用于各种东西。我不知道它在 glibc 中的确切用法,但我只是假设他们需要一个缓冲区来进行内部计算。他们不是动态分配,而是使用预先分配的暂存缓冲区。

LSan 支持抑制一些泄漏,但您必须检查自己是否以及哪些抑制在您的构建中处于活动状态。

至于严厉模式:我强烈怀疑暂存缓冲区是在您的main函数之前分配并在它之后释放的。在这种情况下,HeapChecker 会报告它。不要太担心它。

于 2020-04-09T08:08:18.417 回答
1

在内部,所有 NSS 接口(其中getaddrinfo一个)看起来像gethostbyname_r

   int gethostbyname_r(const char *name,
           struct hostent *ret, char *buf, size_t buflen,
           struct hostent **result, int *h_errnop);

调用者通过buf,buflen字节为结果数据提供一个缓冲区。如果事实证明此缓冲区的大小不足,则该函数将失败并出现ERANGE错误。调用者应该增长缓冲区(以某种方式重新分配它)并调用函数,其他参数相同。这会重复,直到缓冲区足够大并且函数成功(或函数由于其他原因而失败)。我们如何最终得到这个奇怪的界面是一个较长的故事,但它是我们今天拥有的界面。看起来不同,但内部支持实现与公共函数getaddrinfo非常相似。gethostbyname_r

因为 retry-with-a-larger-buffer 习惯用法在整个 NSS 代码中很常见,所以struct scratch_buffer引入了。(以前,固定缓冲区大小的组合相当不拘一格allocaalloca带有malloc后备,等等。)struct scratch_buffer组合了一个固定大小的堆栈缓冲区,用于第一个 NSS 调用。如果调用失败ERANGEscratch_buffer_grow则调用 , 切换到堆缓冲区,并在后续调用中分配更大的堆缓冲区。scratch_buffer_free如果有,则释放堆缓冲区。

在您的示例中,tcmalloc报告的泄漏与暂存缓冲区无关。(我们在 中肯定有这样的错误getaddrinfo,特别是在晦涩的错误路径上,但当前代码应该基本没问题。)链接顺序也不是问题,因为显然tcmalloc它是活动的,否则您将不会收到任何泄漏报告。

您看到泄漏tcmalloc(但没有使用其他工具,例如 valgrind)的原因是tcmalloc它没有调用__libc_freeres专门为堆检查器添加的魔术函数。通常,当进程终止时,glibc 不会释放所有内部分配,因为内核无论如何都会释放该内存。大多数子系统以某种方式在__libc_freeres. 在getaddrinfo示例中,我看到以下仍然分配的资源:

  • 解析结果/etc/resolv.conf(系统 DNS 配置)。
  • 解析结果/etc/nsswitch.conf(NSS 配置)。
  • dlopen内部调用产生的各种动态加载器数据结构(用于加载 NSS 服务模块。
  • 用于记录系统 IPv4/IPv6 支持的缓存getaddrinfo

如果您在 valgrind 下运行示例,您可以使用如下命令轻松查看这些分配:

valgrind --leak-check=full --show-reachable=yes --run-libc-freeres=no

关键部分是--run-libc-freeres=no,它指示 valgrind不要调用__libc_freeres,默认情况下它会这样做。如果省略此参数,valgrind 将不会报告任何内存泄漏。

于 2020-04-14T18:10:58.607 回答