I have a minimal GStreamer program:
#include <gst/gst.h>
int main() {
gst_init(NULL, NULL);
gst_deinit();
}
I build it with gcc test.c $(pkg-config --cflags --libs gstreamer-1.0) -fsanitize=address
(gcc is version 12.1.0), run it and get the following output from the address sanitizer:
==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).
I'm new to GStreamer and GLib. Is this normal for GStreamer programs? And if it is, what would be an elegant way to ignore this leak when running unit tests with sanitizers?
The issue is that there are some versions of g_intern_string, for example, the one in libglib-2.0.so.0.6400.6, that have a bug where, when they grow the buckets array for a hash map, they fail to free the old array.
The leak is on the last instruction shown in this disassembly of 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
It just happens that libgstreamer uses that function enough that the buckets array requires growth, triggering the leak.
A shallow look at the glib source, for example on github, suggests that this code has changed. The best way to actually avoid the leak would be to try to use a newer version of libglib.
The specific value that is leaked in your test is actually the initial buckets array, which is allocated when libglib is initialized:
#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
So to suppress such errors in a sanitizer, as opposed to avoiding the leak, you'd either have to tell it to ignore any allocations with g_malloc as part of the stack (which may leave you subject to missing some more significant leaks) or to tell it to ignore at least that specific allocation made when glib is initialized and possibly any stacks involving g_intern_string+140, depending on whether your program eventually causes g_intern_string to be called sufficiently many times that the buckets array grows more than once.