From fb1ad96c047683f5c76fcefa5933b086f1954f93 Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Fri, 7 Aug 2026 04:13:55 -0400 Subject: [PATCH] [Fix] (MG_Backend): stop DirectGLES twin destructors calling a dead driver at exit The static twin registries destroy their backend objects from __run_exit_handlers, and a twin destructor then jumps through g_GLESFuncs into a driver library that exit() may already have torn down - a latent SIGSEGV that DriverBench has been dumping core with on every exit, and that any relink shuffling static destructor order can hand to the trace-replay binary (a byte-perfect replay then "fails with status Segmentation fault"). A process-teardown flag now short-circuits the program, VAO and texture twin destructors: past exit() the driver reclaims every GPU object anyway, so the skip is a deliberate leak of nothing. The flag is set by a std::atexit handler registered lazily on first registry use - by then every static everywhere has finished constructing, so the handler runs BEFORE any static destructor. A registry-destructor hook was tried first and is wrong: tests and cache resets destroy temporary registry instances mid-run, which latched the flag while the process was alive (caught by DirectGLESBackendTexture.DestructorDeletesIdAndScrubsBindingCache). 421/421 unit tests, the retrace subset exits cleanly on both backends, and the 52-entry integration suite passes. --- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 21 +++++++++++++++++++++ MobileGL/MG_Backend/DirectGLES/Managers.h | 19 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 9e1f1a56..8ae52826 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -114,6 +115,17 @@ namespace MobileGL::MG_Backend::DirectGLES { return source; } + namespace { + Bool g_processTeardown = false; + std::once_flag g_teardownSentinelOnce; + } // namespace + + Bool InProcessTeardown() { return g_processTeardown; } + void EnsureProcessTeardownSentinel() { + std::call_once(g_teardownSentinelOnce, + [] { std::atexit(+[] { g_processTeardown = true; }); }); + } + String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType) { if (shaderType != GL_VERTEX_SHADER || source.find("gl_BaseInstance") == String::npos) { return source; @@ -1388,6 +1400,9 @@ namespace MobileGL::MG_Backend::DirectGLES { } BackendVertexArrayObject::~BackendVertexArrayObject() { + if (InProcessTeardown()) { + return; // see InProcessTeardown(): the driver may be unloaded already + } if (m_backendVAOId != 0) { NoteVAOIdDeleted(m_backendVAOId); g_GLESFuncs.glDeleteVertexArrays(1, &m_backendVAOId); @@ -1641,6 +1656,9 @@ namespace MobileGL::MG_Backend::DirectGLES { } BackendTextureObject::~BackendTextureObject() { + if (InProcessTeardown()) { + return; // see InProcessTeardown(): the driver may be unloaded already + } if (m_backendTextureId == 0) { return; } @@ -3780,6 +3798,9 @@ namespace MobileGL::MG_Backend::DirectGLES { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif + if (InProcessTeardown()) { + return; // see InProcessTeardown(): the driver may be unloaded already + } if (m_backendProgramId != 0) { MGLOG_D("Deleting backend program object with ID: %u", m_backendProgramId); g_GLESFuncs.glDeleteProgram(m_backendProgramId); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 2900c726..d3a27ef2 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -21,9 +21,25 @@ namespace MobileGL::MG_Backend::DirectGLES { String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType); String PromoteDrawParameterGlobalsToUniforms(String source, GLenum shaderType); + // True once the process has entered exit(): past that point the EGL library and + // the driver may already be unloaded, so a backend twin's destructor must not + // call into g_GLESFuncs (the observed crash is a jump through an unmapped driver + // pointer from __run_exit_handlers) nor touch statics in other TUs (cross-TU + // destruction order is unspecified). Deliberate leak: the process is exiting and + // the driver reclaims GPU objects. The flag is set by a std::atexit handler that + // EnsureProcessTeardownSentinel() registers lazily on first registry use - by + // then every static everywhere has finished constructing, so this handler is + // guaranteed to run BEFORE any static destructor (atexit is LIFO). A destructor + // hook on the registry itself was tried first and is WRONG: tests and cache + // resets destroy temporary registry instances mid-run, which would latch the + // flag while the process is very much alive. + Bool InProcessTeardown(); + void EnsureProcessTeardownSentinel(); + template class StateBackendObjectRegistry { public: + using StatePtr = SharedPtr; using StateWeakPtr = std::weak_ptr; using BackendPtr = SharedPtr; @@ -43,6 +59,9 @@ namespace MobileGL::MG_Backend::DirectGLES { BackendPtr& GetOrCreate(const StatePtr& stateObj) { MOBILEGL_ASSERT(stateObj != nullptr, "State object must not be null"); + // Twin creation is the moment a driver-owned id starts needing a guarded + // destructor; cold path, so the once-guard costs nothing per draw. + EnsureProcessTeardownSentinel(); auto& entry = m_entries[stateObj.get()]; if (entry.stateRef.expired()) { // The previous owner of this address is gone and the allocator handed it