cgccopenglvalgrindegl

EGL memory leak


I've been told EGL is a good choice to render graphics in wayland window. I'm not yet convinced and I still don't know if (except vulkan) it is the only way to have some openGL magic in a wayland windowed client.

I've tryed and I'm at very first lines decieved facing memory leaks.

Here is the makefile :

WAYLAND_FLAGS = `pkg-config wayland-client wayland-egl --cflags --libs`
GL_FLAGS = `pkg-config egl glesv2 --cflags --libs`
CGLM_FLAGS = `pkg-config cglm --cflags --libs` -lm
WAYLAND_PROTOCOLS_DIR = `pkg-config wayland-protocols --variable=pkgdatadir`
WAYLAND_SCANNER = `pkg-config --variable=wayland_scanner wayland-scanner`
CFLAGS = -std=c11 -Wall -Werror -g

XDG_SHELL_PROTOCOL = $(WAYLAND_PROTOCOLS_DIR)/stable/xdg-shell/xdg-shell.xml

XDG_SHELL_FILES=xdg-shell-client-protocol.h xdg-shell-protocol.c

all: hello-wayland

hello-wayland: main.c $(XDG_SHELL_FILES)
    $(CC) $(CFLAGS) -o hello-wayland $(WAYLAND_FLAGS) $(GL_FLAGS) $(CGLM_FLAGS) *.c

xdg-shell-client-protocol.h:
    $(WAYLAND_SCANNER) client-header $(XDG_SHELL_PROTOCOL) xdg-shell-client-protocol.h

xdg-shell-protocol.c:
    $(WAYLAND_SCANNER) code $(XDG_SHELL_PROTOCOL) xdg-shell-protocol.c

.PHONY: clean
clean:
    $(RM) hello-wayland $(XDG_SHELL_FILES)

Here is code before EGL :

#include <cglm/cglm.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <wayland-egl.h>
#include "xdg-shell-client-protocol.h"

int main(int argc, char *argv[]) {
    struct wl_display *display = wl_display_connect(0);
    printf("Display : %p\n", display);

    if(display) wl_display_disconnect(display);
    printf("Display disconnected\n");

    return 0;
}

No memory leak :

==10344== Memcheck, a memory error detector
==10344== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==10344== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==10344== Command: ./hello-wayland
==10344== 
Display : 0x4c31890
Display disconnected
==10344== 
==10344== HEAP SUMMARY:
==10344==     in use at exit: 0 bytes in 0 blocks
==10344==   total heap usage: 61 allocs, 61 frees, 20,039 bytes allocated
==10344== 
==10344== All heap blocks were freed -- no leaks are possible
==10344== 
==10344== For lists of detected and suppressed errors, rerun with: -s
==10344== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

And when I add some EGL :

int main(int argc, char *argv[]) {
    struct wl_display *display = wl_display_connect(0);
    EGLDisplay egl_display;
    printf("Display : %p\n", display);

    egl_display = eglGetDisplay((EGLNativeDisplayType) display);
    printf("EGL display : %p\n", egl_display);

    if(egl_display != EGL_NO_DISPLAY) eglTerminate(egl_display);
    printf("EGL disconnected\n");

    if(display) wl_display_disconnect(display);
    printf("Display disconnected\n");

    return 0;
}

I got this :

==10430== Memcheck, a memory error detector
==10430== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==10430== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==10430== Command: ./hello-wayland
==10430== 
Display : 0x4c31890
EGL display : 0x4c508c0
EGL disconnected
Display disconnected
==10430== 
==10430== HEAP SUMMARY:
==10430==     in use at exit: 28,526 bytes in 68 blocks
==10430==   total heap usage: 176 allocs, 108 frees, 125,398 bytes allocated
==10430== 
==10430== 24 bytes in 1 blocks are still reachable in loss record 1 of 8
==10430==    at 0x484282F: malloc (vg_replace_malloc.c:446)
==10430==    by 0x402587F: malloc (rtld-malloc.h:56)
==10430==    by 0x402587F: strdup (strdup.c:42)
==10430==    by 0x4014B48: _dl_load_cache_lookup (dl-cache.c:515)
==10430==    by 0x4008F1F: _dl_map_object (dl-load.c:2116)
==10430==    by 0x400C9CB: dl_open_worker_begin (dl-open.c:578)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C10F: dl_open_worker (dl-open.c:803)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C563: _dl_open (dl-open.c:905)
==10430==    by 0x4A49E23: dlopen_doit (dlopen.c:56)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x4001678: _dl_catch_error (dl-catch.c:256)
==10430== 
==10430== 24 bytes in 1 blocks are still reachable in loss record 2 of 8
==10430==    at 0x484282F: malloc (vg_replace_malloc.c:446)
==10430==    by 0x400BE90: UnknownInlinedFun (rtld-malloc.h:56)
==10430==    by 0x400BE90: _dl_new_object (dl-object.c:199)
==10430==    by 0x4007299: _dl_map_object_from_fd (dl-load.c:1053)
==10430==    by 0x4008CB7: _dl_map_object (dl-load.c:2249)
==10430==    by 0x400C9CB: dl_open_worker_begin (dl-open.c:578)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C10F: dl_open_worker (dl-open.c:803)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C563: _dl_open (dl-open.c:905)
==10430==    by 0x4A49E23: dlopen_doit (dlopen.c:56)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x4001678: _dl_catch_error (dl-catch.c:256)
==10430== 
==10430== 367 bytes in 16 blocks are still reachable in loss record 3 of 8
==10430==    at 0x484282F: malloc (vg_replace_malloc.c:446)
==10430==    by 0x402587F: malloc (rtld-malloc.h:56)
==10430==    by 0x402587F: strdup (strdup.c:42)
==10430==    by 0x4014B48: _dl_load_cache_lookup (dl-cache.c:515)
==10430==    by 0x4008F1F: _dl_map_object (dl-load.c:2116)
==10430==    by 0x400287C: openaux (dl-deps.c:64)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x4002CDF: _dl_map_object_deps (dl-deps.c:232)
==10430==    by 0x400CA34: dl_open_worker_begin (dl-open.c:638)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C10F: dl_open_worker (dl-open.c:803)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C563: _dl_open (dl-open.c:905)
==10430== 
==10430== 367 bytes in 16 blocks are still reachable in loss record 4 of 8
==10430==    at 0x484282F: malloc (vg_replace_malloc.c:446)
==10430==    by 0x400BE90: UnknownInlinedFun (rtld-malloc.h:56)
==10430==    by 0x400BE90: _dl_new_object (dl-object.c:199)
==10430==    by 0x4007299: _dl_map_object_from_fd (dl-load.c:1053)
==10430==    by 0x4008CB7: _dl_map_object (dl-load.c:2249)
==10430==    by 0x400287C: openaux (dl-deps.c:64)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x4002CDF: _dl_map_object_deps (dl-deps.c:232)
==10430==    by 0x400CA34: dl_open_worker_begin (dl-open.c:638)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C10F: dl_open_worker (dl-open.c:803)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C563: _dl_open (dl-open.c:905)
==10430== 
==10430== 1,257 bytes in 1 blocks are still reachable in loss record 5 of 8
==10430==    at 0x484A0FC: calloc (vg_replace_malloc.c:1675)
==10430==    by 0x400BBAD: UnknownInlinedFun (rtld-malloc.h:44)
==10430==    by 0x400BBAD: _dl_new_object (dl-object.c:92)
==10430==    by 0x4007299: _dl_map_object_from_fd (dl-load.c:1053)
==10430==    by 0x4008CB7: _dl_map_object (dl-load.c:2249)
==10430==    by 0x400C9CB: dl_open_worker_begin (dl-open.c:578)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C10F: dl_open_worker (dl-open.c:803)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C563: _dl_open (dl-open.c:905)
==10430==    by 0x4A49E23: dlopen_doit (dlopen.c:56)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x4001678: _dl_catch_error (dl-catch.c:256)
==10430== 
==10430== 2,672 bytes in 1 blocks are still reachable in loss record 6 of 8
==10430==    at 0x484A0FC: calloc (vg_replace_malloc.c:1675)
==10430==    by 0x504635E: _eglFindDisplay (egldisplay.c:270)
==10430==    by 0x5046A2F: _eglGetWaylandDisplay (egldisplay.c:632)
==10430==    by 0x488EF6C: GetPlatformDisplayCommon (libegl.c:324)
==10430==    by 0x4011B4: main (main.c:17)
==10430== 
==10430== 3,720 bytes in 16 blocks are still reachable in loss record 7 of 8
==10430==    at 0x484A0FC: calloc (vg_replace_malloc.c:1675)
==10430==    by 0x4013D77: UnknownInlinedFun (rtld-malloc.h:44)
==10430==    by 0x4013D77: _dl_check_map_versions (dl-version.c:280)
==10430==    by 0x400CA7A: dl_open_worker_begin (dl-open.c:646)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C10F: dl_open_worker (dl-open.c:803)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C563: _dl_open (dl-open.c:905)
==10430==    by 0x4A49E23: dlopen_doit (dlopen.c:56)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x4001678: _dl_catch_error (dl-catch.c:256)
==10430==    by 0x4A49912: _dlerror_run (dlerror.c:138)
==10430==    by 0x4A49EDE: dlopen_implementation (dlopen.c:71)
==10430==    by 0x4A49EDE: dlopen@@GLIBC_2.34 (dlopen.c:81)
==10430== 
==10430== 20,095 bytes in 16 blocks are still reachable in loss record 8 of 8
==10430==    at 0x484A0FC: calloc (vg_replace_malloc.c:1675)
==10430==    by 0x400BBAD: UnknownInlinedFun (rtld-malloc.h:44)
==10430==    by 0x400BBAD: _dl_new_object (dl-object.c:92)
==10430==    by 0x4007299: _dl_map_object_from_fd (dl-load.c:1053)
==10430==    by 0x4008CB7: _dl_map_object (dl-load.c:2249)
==10430==    by 0x400287C: openaux (dl-deps.c:64)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x4002CDF: _dl_map_object_deps (dl-deps.c:232)
==10430==    by 0x400CA34: dl_open_worker_begin (dl-open.c:638)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C10F: dl_open_worker (dl-open.c:803)
==10430==    by 0x4001522: _dl_catch_exception (dl-catch.c:237)
==10430==    by 0x400C563: _dl_open (dl-open.c:905)
==10430== 
==10430== LEAK SUMMARY:
==10430==    definitely lost: 0 bytes in 0 blocks
==10430==    indirectly lost: 0 bytes in 0 blocks
==10430==      possibly lost: 0 bytes in 0 blocks
==10430==    still reachable: 28,526 bytes in 68 blocks
==10430==         suppressed: 0 bytes in 0 blocks
==10430== 
==10430== For lists of detected and suppressed errors, rerun with: -s
==10430== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Did I do something wrong? Valgrind buggy? EGL Leaking? This is directly a call in my code :

==10430== 2,672 bytes in 1 blocks are still reachable in loss record 6 of 8
==10430==    at 0x484A0FC: calloc (vg_replace_malloc.c:1675)
==10430==    by 0x504635E: _eglFindDisplay (egldisplay.c:270)
==10430==    by 0x5046A2F: _eglGetWaylandDisplay (egldisplay.c:632)
==10430==    by 0x488EF6C: GetPlatformDisplayCommon (libegl.c:324)
==10430==    by 0x4011B4: main (main.c:17)

Is not eglTerminate the direct counterpart of eglGetDisplay? I didn't found some doc saying something else. Did I wrongly searched?

Thanks.


Solution

  • Did I do something wrong?

    Probably not.

    Valgrind buggy?

    Valgrind has a great number of issues (and some grey areas) but this isn't one of them. I've read hundreds if not thousands of cases of delusional confirmation bias where people jump to the conclusion that there must be a memcheck false positive.

    Leak checking is one of the easiest things that memcheck does. As long as Valgrind manages to correctly redirect malloc and family then it is fairly hard for it to get it wrong. The categorization of leaks is a bit more tricky - searching everywhere for pointers to look for potential leaks is a bit fiddly.

    EGL Leaking?

    Valgrind showing still reachable memory leak with getaddrinfo

    Indirectly, it's the dynamic linker that is leaking. The problem is really in GNU libc.

    As in the link above, I recommend that you use the suppression mechanism for the still-reachable memory allocated by the dynamic linker.

    That leaves the calloc from _eglFindDisplay (ewwww retch undefined behaviour, a global symbol with a leading underscore). You'll need to use the EGL source to work out if there is a way to free that memory or whether the EGL developers just couldn't be bothered to free it. If you don't want to go to those lengths you can again use a suppression.

    EDIT: To suppress errors you can ask Valgrind to generate suppressions by using --gen-suppresions=all. Then copy and paste the suppression to a file and use that in subsequent runs (with --suppressions=[filename] which can appear more than once on the command line). You can also put the --suppressions option in .valgrindrcin your home directory).

    This is untested, but I think that the suppression should probably look like

    {
       GNU-LIBC-DLOPEN-REACHABLE
       Memcheck:Leak
       match-leak-kinds: reachable
       fun:*alloc
       ...
       fun:_dl_open
    }
    

    Some explanations