GStreamer最小程序泄漏内存

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 程序来说正常吗?如果是的话,在使用消毒剂运行单元测试时,有什么优雅的方法可以忽略这种泄漏?

Tim*_*ddy 5

问题是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 被调用足够多次,以致存储桶数组增长不止一次。