Skip to content

XCloseDisplay() calls wild pointer after GLX->EGL transition #5484

@cgutman

Description

@cgutman

This is a pretty weird one. I was seeing some strange behavior on the Raspberry Pi 4 in X11 where I would call SDL_Quit() then XCloseDisplay() would end up calling an invalid pointer and crashing. It appears that XCloseDisplay() is trying to invoke a callback in a GLX library that had been unloaded (when loading the EGL libraries).

Program received signal SIGSEGV, Segmentation fault.
0xb5f3da08 in ?? ()
(gdb) bt
#0  0xb5f3da08 in ?? ()
#1  0xb6700664 in XCloseDisplay () from /lib/arm-linux-gnueabihf/libX11.so.6
#2  0xb6efc1e0 in X11_DeleteDevice () from /usr/local/lib/arm-linux-gnueabihf/libSDL2-2.0.so.0
#3  0xb6ec97f0 in SDL_VideoQuit_REAL () from /usr/local/lib/arm-linux-gnueabihf/libSDL2-2.0.so.0
#4  0xb6d817b4 in SDL_QuitSubSystem_REAL () from /usr/local/lib/arm-linux-gnueabihf/libSDL2-2.0.so.0
#5  0xb6d81a1c in SDL_Quit_REAL () from /usr/local/lib/arm-linux-gnueabihf/libSDL2-2.0.so.0
#6  0xb6da9a14 in SDL_Quit () from /usr/local/lib/arm-linux-gnueabihf/libSDL2-2.0.so.0
#7  0x00010a44 in main ()

This was impacting Moonlight, but I was able to write a simpler reproducer and bisected it to f37e4a9.

I have no idea why this change seems to have triggered the issue, but perhaps the OpenGL test itself was causing the libraries to stay loaded or something.

I did some debugging and found that my code is hitting the path that unloads GLX and loads EGL:

if (((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) ||
SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_FORCE_EGL, SDL_FALSE)) &&
X11_GL_UseEGL(_this) ) {
#if SDL_VIDEO_OPENGL_EGL
X11_GL_UnloadLibrary(_this);
/* Better avoid conflicts! */
if (_this->gl_config.dll_handle != NULL ) {
GL_UnloadObject(_this->gl_config.dll_handle);
_this->gl_config.dll_handle = NULL;
}
_this->GL_LoadLibrary = X11_GLES_LoadLibrary;
_this->GL_GetProcAddress = X11_GLES_GetProcAddress;
_this->GL_UnloadLibrary = X11_GLES_UnloadLibrary;
_this->GL_CreateContext = X11_GLES_CreateContext;
_this->GL_MakeCurrent = X11_GLES_MakeCurrent;
_this->GL_SetSwapInterval = X11_GLES_SetSwapInterval;
_this->GL_GetSwapInterval = X11_GLES_GetSwapInterval;
_this->GL_SwapWindow = X11_GLES_SwapWindow;
_this->GL_DeleteContext = X11_GLES_DeleteContext;
return X11_GLES_LoadLibrary(_this, NULL);
#else
return SDL_SetError("SDL not configured with EGL support");
#endif

Interestingly, the normal GL unload function (X11_GL_UnloadLibrary()) explicitly does not unload the GL library due to "X11 shutdown hooks" (sounds very similar to what's going here):

/* Don't actually unload the library, since it may have registered
* X11 shutdown hooks, per the notes at:
* http://dri.sourceforge.net/doc/DRIuserguide.html
*/
#if 0
GL_UnloadObject(_this->gl_config.dll_handle);
_this->gl_config.dll_handle = NULL;
#endif

I wonder if we should not be unloading the GL library in the GLX->EGL path either. The link mentioned in that comment seems to strongly indicate that we shouldn't:

Do not close the library with dlclose() until after XCloseDisplay() has been called. When libGL.so initializes itself it registers several callbacks functions with Xlib. When XCloseDisplay() is called those callback functions are called. If libGL.so has already been unloaded with dlclose() this will cause a segmentation fault.

Reproducer:

#include <SDL.h>

int main(int argc, char* argv[])
{
    SDL_Init(SDL_INIT_VIDEO);

    SDL_Window* wnd = SDL_CreateWindow("Test",
                                       SDL_WINDOWPOS_UNDEFINED,
                                       SDL_WINDOWPOS_UNDEFINED,
                                       100,
                                       100,
                                       SDL_WINDOW_HIDDEN | SDL_WINDOW_ALLOW_HIGHDPI);
    if (!wnd) {
        SDL_Log("SDL_CreateWindow() failed: %s", SDL_GetError());
        return -1;
    }

    SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1");
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);

    int renderIndex;
    int maxRenderers = SDL_GetNumRenderDrivers();
    SDL_assert(maxRenderers >= 0);

    SDL_RendererInfo renderInfo;
    for (renderIndex = 0; renderIndex < maxRenderers; ++renderIndex) {
        if (SDL_GetRenderDriverInfo(renderIndex, &renderInfo))
            continue;
        if (!strcmp(renderInfo.name, "opengles2")) {
            SDL_assert(renderInfo.flags & SDL_RENDERER_ACCELERATED);
            break;
        }
    }
    if (renderIndex == maxRenderers) {
        SDL_Log("Could not find a suitable SDL_Renderer");
        return -1;
    }

    // This will call SDL_RecreateWindow() to load EGL
    SDL_Renderer* rnd = SDL_CreateRenderer(wnd, renderIndex, SDL_RENDERER_ACCELERATED);
    if (!rnd) {
        SDL_Log("SDL_CreateRenderer() failed: %s", SDL_GetError());
        return -1;
    }

    SDL_Quit();
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions