Gar*_*zet 1 gcc memory-leaks glib gstreamer leak-sanitizer
我有一个最小的GStreamer程序:
#include <gst/gst.h>
int main() {
gst_init(NULL, NULL);
gst_deinit();
}
Run Code Online (Sandbox Code Playgroud)
我使用gcc test.c $(pkg-config --cflags --libs gstreamer-1.0) -fsanitize=address
(gcc 版本为 12.1.0)构建它,运行它并从地址清理器获得以下输出:
==87326==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 16384 byte(s) in 1 object(s) allocated from:
#0 0x7f53e28bfa89 in __interceptor_malloc /usr/src/debug/gcc/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x7f53e26c1b19 in g_malloc (/usr/lib/libglib-2.0.so.0+0x5db19)
SUMMARY: AddressSanitizer: 16384 byte(s) leaked in 1 allocation(s).
Run Code Online (Sandbox Code Playgroud)
我是 GStreamer 和 GLib 的新手。这对于 GStreamer 程序来说正常吗?如果是的话,在使用消毒剂运行单元测试时,有什么优雅的方法可以忽略这种泄漏?
问题是g_intern_string的某些版本(例如libglib-2.0.so.0.6400.6中的版本)存在一个错误,当它们为哈希映射增加存储桶数组时,它们无法释放旧的大批。
泄漏位于 g_intern_string 反汇编中显示的最后一条指令上:
0x7ffff7b44a70 <g_intern_string>: endbr64
0x7ffff7b44a74 <g_intern_string+4>: push %r12
0x7ffff7b44a76 <g_intern_string+6>: push %rbp
0x7ffff7b44a77 <g_intern_string+7>: push %rbx
0x7ffff7b44a78 <g_intern_string+8>: test %rdi,%rdi
0x7ffff7b44a7b <g_intern_string+11>:
je 0x7ffff7b44b88 <g_intern_string+280>
0x7ffff7b44a81 <g_intern_string+17>: mov %rdi,%rbp
0x7ffff7b44a84 <g_intern_string+20>:
lea 0xc5225(%rip),%rdi # 0x7ffff7c09cb0
0x7ffff7b44a8b <g_intern_string+27>: callq 0x7ffff7b814d0 <g_mutex_lock>
0x7ffff7b44a90 <g_intern_string+32>:
mov 0xc5211(%rip),%rdi # 0x7ffff7c09ca8
0x7ffff7b44a97 <g_intern_string+39>: mov %rbp,%rsi
0x7ffff7b44a9a <g_intern_string+42>:
callq 0x7ffff7b21710 <g_hash_table_lookup>
0x7ffff7b44a9f <g_intern_string+47>: test %eax,%eax
0x7ffff7b44aa1 <g_intern_string+49>:
je 0x7ffff7b44ad0 <g_intern_string+96>
0x7ffff7b44aa3 <g_intern_string+51>: mov %eax,%eax
0x7ffff7b44aa5 <g_intern_string+53>: lea 0x0(,%rax,8),%rbx
0x7ffff7b44aad <g_intern_string+61>:
mov 0xc51ec(%rip),%rax # 0x7ffff7c09ca0
0x7ffff7b44ab4 <g_intern_string+68>:
lea 0xc51f5(%rip),%rdi # 0x7ffff7c09cb0
0x7ffff7b44abb <g_intern_string+75>: mov (%rax,%rbx,1),%r12
0x7ffff7b44abf <g_intern_string+79>: callq 0x7ffff7b81500 <g_mutex_unlock>
0x7ffff7b44ac4 <g_intern_string+84>: pop %rbx
0x7ffff7b44ac5 <g_intern_string+85>: pop %rbp
0x7ffff7b44ac6 <g_intern_string+86>: mov %r12,%rax
0x7ffff7b44ac9 <g_intern_string+89>: pop %r12
0x7ffff7b44acb <g_intern_string+91>: retq
0x7ffff7b44acc <g_intern_string+92>: nopl 0x0(%rax)
0x7ffff7b44ad0 <g_intern_string+96>: mov %rbp,%rdi
0x7ffff7b44ad3 <g_intern_string+99>: callq 0x7ffff7b446e0
0x7ffff7b44ad8 <g_intern_string+104>:
mov 0xc51ba(%rip),%edi # 0x7ffff7c09c98
0x7ffff7b44ade <g_intern_string+110>: mov %rax,%rbp
0x7ffff7b44ae1 <g_intern_string+113>: mov %edi,%edx
0x7ffff7b44ae3 <g_intern_string+115>: test $0x7ff,%edi
0x7ffff7b44ae9 <g_intern_string+121>:
jne 0x7ffff7b44b2f <g_intern_string+191>
0x7ffff7b44aeb <g_intern_string+123>: add $0x800,%edi
0x7ffff7b44af1 <g_intern_string+129>: mov $0x8,%esi
0x7ffff7b44af6 <g_intern_string+134>: xor %r12d,%r12d
0x7ffff7b44af9 <g_intern_string+137>: movslq %edi,%rdi
0x7ffff7b44afc <g_intern_string+140>:
callq 0x7ffff7b3a020 <g_malloc_n>
0x7ffff7b44b01 <g_intern_string+145>:
movslq 0xc5190(%rip),%rdi # 0x7ffff7c09c98
0x7ffff7b44b08 <g_intern_string+152>: mov %rax,%rbx
0x7ffff7b44b0b <g_intern_string+155>: test %edi,%edi
0x7ffff7b44b0d <g_intern_string+157>:
jne 0x7ffff7b44b68 <g_intern_string+248>
0x7ffff7b44b0f <g_intern_string+159>: mov $0x4000,%edx
0x7ffff7b44b14 <g_intern_string+164>: lea (%rbx,%r12,1),%rdi
0x7ffff7b44b18 <g_intern_string+168>: xor %esi,%esi
0x7ffff7b44b1a <g_intern_string+170>:
callq 0x7ffff7aff2a0 <memset@plt>
0x7ffff7b44b1f <g_intern_string+175>:
mov %rbx,0xc517a(%rip) # 0x7ffff7c09ca0
Run Code Online (Sandbox Code Playgroud)
碰巧 libgstreamer 使用该函数的次数足够多,以至于存储桶数组需要增长,从而触发泄漏。
粗略地查看 glib 源代码(例如 github 上的源代码)表明此代码已更改。真正避免泄漏的最佳方法是尝试使用较新版本的 libglib。
在你的测试中泄漏的具体值实际上是初始的buckets数组,它是在libglib初始化时分配的:
#0 __GI___libc_malloc (bytes=16384) at malloc.c:3023
#1 0x00007ffff7b39e99 in g_malloc () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#2 0x00007ffff7b447d4 in () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#3 0x00007ffff7fe0b9a in call_init
(l=<optimized out>, argc=argc@entry=1, argv=argv@entry=0x7fffffffe0d8, env=env@entry=0x7fffffffe0e8) at dl-init.c:72
#4 0x00007ffff7fe0ca1 in call_init
(env=0x7fffffffe0e8, argv=0x7fffffffe0d8, argc=1, l=<optimized out>)
at dl-init.c:30
#5 _dl_init
(main_map=0x7ffff7ffe190, argc=1, argv=0x7fffffffe0d8, env=0x7fffffffe0e8)
at dl-init.c:119
#6 0x00007ffff7fd013a in _dl_start_user () at /lib64/ld-linux-x86-64.so.2
(gdb) x/2i 0x00007ffff7b447d4
0x7ffff7b447d4: movl $0x1,0xc54ba(%rip) # 0x7ffff7c09c98
0x7ffff7b447de: mov %rax,0xc54bb(%rip) # 0x7ffff7c09ca0
Run Code Online (Sandbox Code Playgroud)
因此,为了抑制清理程序中的此类错误,而不是避免泄漏,您要么必须告诉它忽略任何带有 g_malloc 作为堆栈一部分的分配(这可能会让您错过一些更重要的泄漏),要么告诉它至少忽略初始化 glib 时进行的特定分配,以及可能涉及g_intern_string+140的任何堆栈,具体取决于您的程序最终是否导致 g_intern_string 被调用足够多次,以致存储桶数组增长不止一次。