-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
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:
SDL/src/video/x11/SDL_x11opengl.c
Lines 245 to 267 in 57118fb
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):
SDL/src/video/x11/SDL_x11opengl.c
Lines 285 to 292 in 57118fb
/* 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();
}