SDL: build: add symbol versioning for SDL

From 1878674477e2b6d4e743319ab5f7dfb4f6d90c8e Mon Sep 17 00:00:00 2001
From: Jan Engelhardt <[EMAIL REDACTED]>
Date: Thu, 24 Nov 2022 20:58:10 +0100
Subject: [PATCH] build: add symbol versioning for SDL
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

If a program built against one version of SDL is run in an
environment where there is an earlier version of the SDL .so library
installed, the result varies depending on platform configuration; in
the best case, it won't start at all, at worst it aborts in the
middle of the user doing "something important" (systems implementing
lazy symbol resolution). verdefs on the other hand are always checked
on startup.

The dependency information present in programs and shared libraries
is not only of value to the dynamic linker, but also to a
distribution's package management. If the dynamic linker is able to
tell that a program is not runnable per the above, a package manager
is able to come to the same conclusion — and block the installation
of a nonfunctional program+library ensemble.

Because there are a lot more symbols than there are libraries (I am
going to throw in "10^4 to 1 or worse"), package managers generally
do not evaluate symbols, but only e.g. the SONAME, NEEDED and VERNEED
fields/blocks. Because the SONAME is the same between two SDL
versions like 2.0.24, and 2.0.26, everything rests on having verdefs.

This patch proposes the addition of verdefs.
---
 Android.mk                |   2 +-
 CMakeLists.txt            |  11 +
 cmake/macros.cmake        |  20 +
 src/dynapi/SDL_dynapi.sym | 871 ++++++++++++++++++++++++++++++++++++++
 src/dynapi/gendynapi.pl   |  11 +
 5 files changed, 914 insertions(+), 1 deletion(-)
 create mode 100644 src/dynapi/SDL_dynapi.sym

diff --git a/Android.mk b/Android.mk
index 6e3ad3d3aa0c..0861ad7af50d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -80,7 +80,7 @@ LOCAL_CFLAGS += -Wno-unused-parameter -Wno-sign-compare
 
 LOCAL_LDLIBS := -ldl -lGLESv1_CM -lGLESv2 -lOpenSLES -llog -landroid
 
-LOCAL_LDFLAGS := -Wl,--no-undefined
+LOCAL_LDFLAGS := -Wl,--no-undefined -Wl,--version-script=$(LOCAL_PATH)/src/dynapi/SDL_dynapi.sym
 
 ifeq ($(NDK_DEBUG),1)
     cmd-strip :=
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e570572e6775..9414af38f33f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -276,6 +276,15 @@ if(WINDOWS)
   set(CMAKE_SHARED_LIBRARY_PREFIX "")
 endif()
 
+check_linker_flag(C "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/dynapi/SDL_dynapi.sym" HAVE_WL_VERSION_SCRIPT)
+if(HAVE_WL_VERSION_SCRIPT)
+  list(APPEND EXTRA_LDFLAGS_BUILD "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/dynapi/SDL_dynapi.sym")
+else()
+  if((LINUX AND LIBC_IS_GLIBC) OR ANDROID)
+    message(FATAL_ERROR "Linker does not support '-Wl,--version-script=xxx.sym'. This is required on the current host platform (${SDL_CMAKE_PLATFORM}).")
+  endif()
+endif()
+
 # Emscripten toolchain has a nonempty default value for this, and the checks
 # in this file need to change that, so remember the original value, and
 # restore back to that afterwards. For check_function_exists() to work in
@@ -3182,6 +3191,8 @@ if(SDL_SHARED)
   # alias target for in-tree builds
   add_library(SDL3::SDL3 ALIAS SDL3)
   set_target_properties(SDL3 PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
+  set_target_properties(SDL3 PROPERTIES LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/dynapi/SDL_dynapi.sym")
+  set_target_properties(SDL3 PROPERTIES INTERFACE_LINK_DEPENDS "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/dynapi/SDL_dynapi.sym>")
   if(NOT SDL_LIBC)
     if(MSVC AND SDL_CPU_X86)
       # FIXME: should be added for all architectures (missing symbols for ARM)
diff --git a/cmake/macros.cmake b/cmake/macros.cmake
index c703abf6b35e..891d2019fdcc 100644
--- a/cmake/macros.cmake
+++ b/cmake/macros.cmake
@@ -110,6 +110,26 @@ else()
   endif()
 endif()
 
+if(CMAKE_VERSION VERSION_LESS 3.18)
+  function(check_linker_flag LANG FLAG VAR)
+    cmake_push_check_state()
+    list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${FLAG} )
+    if(LANG STREQUAL "C")
+      include(CheckCSourceCompiles)
+      check_c_source_compiles("int main(int argc,char*argv[]){(void)argc;(void)argv;return 0;}" ${VAR} FAIL_REGEX "warning")
+    elseif(LANG STREQUAL "CXX")
+      include(CheckCXXSourceCompiles)
+      check_cxx_source_compiles("int main(int argc,char*argv[]){(void)argc;(void)argv;return 0;}" ${VAR} FAIL_REGEX "warning")
+    else()
+      message(FATAL_ERROR "Unsupported language: ${LANG}")
+    endif()
+    cmake_pop_check_state()
+  endfunction()
+else()
+  cmake_policy(SET CMP0057 NEW)  # Support new if() IN_LIST operator. (used inside check_linker_flag, used in CMake 3.18)
+  include(CheckLinkerFlag)
+endif()
+
 if(APPLE)
   check_language(OBJC)
   if(NOT CMAKE_OBJC_COMPILER)
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
new file mode 100644
index 000000000000..1ecd8eabcc7b
--- /dev/null
+++ b/src/dynapi/SDL_dynapi.sym
@@ -0,0 +1,871 @@
+SDL3_0.0.0 {
+  global:
+    SDL_OpenURL;
+    SDL_LoadObject;
+    SDL_LoadFunction;
+    SDL_UnloadObject;
+    SDL_RWFromFile;
+    SDL_RWFromMem;
+    SDL_RWFromConstMem;
+    SDL_AllocRW;
+    SDL_FreeRW;
+    SDL_RWsize;
+    SDL_RWseek;
+    SDL_RWtell;
+    SDL_RWread;
+    SDL_RWwrite;
+    SDL_RWclose;
+    SDL_LoadFile_RW;
+    SDL_LoadFile;
+    SDL_ReadU8;
+    SDL_ReadLE16;
+    SDL_ReadBE16;
+    SDL_ReadLE32;
+    SDL_ReadBE32;
+    SDL_ReadLE64;
+    SDL_ReadBE64;
+    SDL_WriteU8;
+    SDL_WriteLE16;
+    SDL_WriteBE16;
+    SDL_WriteLE32;
+    SDL_WriteBE32;
+    SDL_WriteLE64;
+    SDL_WriteBE64;
+    SDL_RecordGesture;
+    SDL_SaveAllDollarTemplates;
+    SDL_SaveDollarTemplate;
+    SDL_LoadDollarTemplates;
+    SDL_GetNumAudioDrivers;
+    SDL_GetAudioDriver;
+    SDL_AudioInit;
+    SDL_AudioQuit;
+    SDL_GetCurrentAudioDriver;
+    SDL_OpenAudio;
+    SDL_GetNumAudioDevices;
+    SDL_GetAudioDeviceName;
+    SDL_GetAudioDeviceSpec;
+    SDL_GetDefaultAudioInfo;
+    SDL_OpenAudioDevice;
+    SDL_GetAudioStatus;
+    SDL_GetAudioDeviceStatus;
+    SDL_PauseAudio;
+    SDL_PauseAudioDevice;
+    SDL_LoadWAV_RW;
+    SDL_FreeWAV;
+    SDL_BuildAudioCVT;
+    SDL_ConvertAudio;
+    SDL_NewAudioStream;
+    SDL_AudioStreamPut;
+    SDL_AudioStreamGet;
+    SDL_AudioStreamAvailable;
+    SDL_AudioStreamFlush;
+    SDL_AudioStreamClear;
+    SDL_FreeAudioStream;
+    SDL_MixAudio;
+    SDL_MixAudioFormat;
+    SDL_QueueAudio;
+    SDL_DequeueAudio;
+    SDL_GetQueuedAudioSize;
+    SDL_ClearQueuedAudio;
+    SDL_LockAudio;
+    SDL_LockAudioDevice;
+    SDL_UnlockAudio;
+    SDL_UnlockAudioDevice;
+    SDL_CloseAudio;
+    SDL_CloseAudioDevice;
+    SDL_SetWindowsMessageHook;
+    SDL_Direct3D9GetAdapterIndex;
+    SDL_RenderGetD3D9Device;
+    SDL_RenderGetD3D11Device;
+    SDL_RenderGetD3D12Device;
+    SDL_DXGIGetOutputInfo;
+    SDL_LinuxSetThreadPriority;
+    SDL_LinuxSetThreadPriorityAndPolicy;
+    SDL_iPhoneSetAnimationCallback;
+    SDL_iPhoneSetEventPump;
+    SDL_AndroidGetJNIEnv;
+    SDL_AndroidGetActivity;
+    SDL_GetAndroidSDKVersion;
+    SDL_IsAndroidTV;
+    SDL_IsChromebook;
+    SDL_IsDeXMode;
+    SDL_AndroidBackButton;
+    SDL_AndroidGetInternalStoragePath;
+    SDL_AndroidGetExternalStorageState;
+    SDL_AndroidGetExternalStoragePath;
+    SDL_AndroidRequestPermission;
+    SDL_AndroidShowToast;
+    SDL_AndroidSendMessage;
+    SDL_WinRTGetFSPathUNICODE;
+    SDL_WinRTGetFSPathUTF8;
+    SDL_WinRTGetDeviceFamily;
+    SDL_IsTablet;
+    SDL_OnApplicationWillTerminate;
+    SDL_OnApplicationDidReceiveMemoryWarning;
+    SDL_OnApplicationWillResignActive;
+    SDL_OnApplicationDidEnterBackground;
+    SDL_OnApplicationWillEnterForeground;
+    SDL_OnApplicationDidBecomeActive;
+    SDL_OnApplicationDidChangeStatusBarOrientation;
+    SDL_GDKGetTaskQueue;
+    SDL_AtomicTryLock;
+    SDL_AtomicLock;
+    SDL_AtomicUnlock;
+    SDL_MemoryBarrierReleaseFunction;
+    SDL_MemoryBarrierAcquireFunction;
+    SDL_AtomicCAS;
+    SDL_AtomicSet;
+    SDL_AtomicGet;
+    SDL_AtomicAdd;
+    SDL_AtomicCASPtr;
+    SDL_AtomicSetPtr;
+    SDL_AtomicGetPtr;
+    SDL_GetKeyboardFocus;
+    SDL_GetKeyboardState;
+    SDL_ResetKeyboard;
+    SDL_GetModState;
+    SDL_SetModState;
+    SDL_GetKeyFromScancode;
+    SDL_GetScancodeFromKey;
+    SDL_GetScancodeName;
+    SDL_GetScancodeFromName;
+    SDL_GetKeyName;
+    SDL_GetKeyFromName;
+    SDL_StartTextInput;
+    SDL_IsTextInputActive;
+    SDL_StopTextInput;
+    SDL_ClearComposition;
+    SDL_IsTextInputShown;
+    SDL_SetTextInputRect;
+    SDL_HasScreenKeyboardSupport;
+    SDL_IsScreenKeyboardShown;
+    SDL_LogSetAllPriority;
+    SDL_LogSetPriority;
+    SDL_LogGetPriority;
+    SDL_LogResetPriorities;
+    SDL_Log;
+    SDL_LogVerbose;
+    SDL_LogDebug;
+    SDL_LogInfo;
+    SDL_LogWarn;
+    SDL_LogError;
+    SDL_LogCritical;
+    SDL_LogMessage;
+    SDL_LogMessageV;
+    SDL_LogGetOutputFunction;
+    SDL_LogSetOutputFunction;
+    SDL_GUIDToString;
+    SDL_GUIDFromString;
+    SDL_PumpEvents;
+    SDL_PeepEvents;
+    SDL_HasEvent;
+    SDL_HasEvents;
+    SDL_FlushEvent;
+    SDL_FlushEvents;
+    SDL_PollEvent;
+    SDL_WaitEvent;
+    SDL_WaitEventTimeout;
+    SDL_PushEvent;
+    SDL_SetEventFilter;
+    SDL_GetEventFilter;
+    SDL_AddEventWatch;
+    SDL_DelEventWatch;
+    SDL_FilterEvents;
+    SDL_EventState;
+    SDL_RegisterEvents;
+    SDL_GetTicks;
+    SDL_GetTicks64;
+    SDL_GetPerformanceCounter;
+    SDL_GetPerformanceFrequency;
+    SDL_Delay;
+    SDL_AddTimer;
+    SDL_RemoveTimer;
+    SDL_Vulkan_LoadLibrary;
+    SDL_Vulkan_GetVkGetInstanceProcAddr;
+    SDL_Vulkan_UnloadLibrary;
+    SDL_Vulkan_GetInstanceExtensions;
+    SDL_Vulkan_CreateSurface;
+    SDL_Vulkan_GetDrawableSize;
+    SDL_GetPreferredLocales;
+    SDL_GetPixelFormatName;
+    SDL_PixelFormatEnumToMasks;
+    SDL_MasksToPixelFormatEnum;
+    SDL_AllocFormat;
+    SDL_FreeFormat;
+    SDL_AllocPalette;
+    SDL_SetPixelFormatPalette;
+    SDL_SetPaletteColors;
+    SDL_FreePalette;
+    SDL_MapRGB;
+    SDL_MapRGBA;
+    SDL_GetRGB;
+    SDL_GetRGBA;
+    SDL_malloc;
+    SDL_calloc;
+    SDL_realloc;
+    SDL_free;
+    SDL_GetOriginalMemoryFunctions;
+    SDL_GetMemoryFunctions;
+    SDL_SetMemoryFunctions;
+    SDL_GetNumAllocations;
+    SDL_getenv;
+    SDL_setenv;
+    SDL_qsort;
+    SDL_bsearch;
+    SDL_abs;
+    SDL_isalpha;
+    SDL_isalnum;
+    SDL_isblank;
+    SDL_iscntrl;
+    SDL_isdigit;
+    SDL_isxdigit;
+    SDL_ispunct;
+    SDL_isspace;
+    SDL_isupper;
+    SDL_islower;
+    SDL_isprint;
+    SDL_isgraph;
+    SDL_toupper;
+    SDL_tolower;
+    SDL_crc16;
+    SDL_crc32;
+    SDL_memset;
+    SDL_memset4;
+    SDL_memcpy;
+    SDL_memmove;
+    SDL_memcmp;
+    SDL_wcslen;
+    SDL_wcslcpy;
+    SDL_wcslcat;
+    SDL_wcsdup;
+    SDL_wcsstr;
+    SDL_wcscmp;
+    SDL_wcsncmp;
+    SDL_wcscasecmp;
+    SDL_wcsncasecmp;
+    SDL_strlen;
+    SDL_strlcpy;
+    SDL_utf8strlcpy;
+    SDL_strlcat;
+    SDL_strdup;
+    SDL_strrev;
+    SDL_strupr;
+    SDL_strlwr;
+    SDL_strchr;
+    SDL_strrchr;
+    SDL_strstr;
+    SDL_strcasestr;
+    SDL_strtokr;
+    SDL_utf8strlen;
+    SDL_utf8strnlen;
+    SDL_itoa;
+    SDL_uitoa;
+    SDL_ltoa;
+    SDL_ultoa;
+    SDL_lltoa;
+    SDL_ulltoa;
+    SDL_atoi;
+    SDL_atof;
+    SDL_strtol;
+    SDL_strtoul;
+    SDL_strtoll;
+    SDL_strtoull;
+    SDL_strtod;
+    SDL_strcmp;
+    SDL_strncmp;
+    SDL_strcasecmp;
+    SDL_strncasecmp;
+    SDL_sscanf;
+    SDL_vsscanf;
+    SDL_snprintf;
+    SDL_vsnprintf;
+    SDL_asprintf;
+    SDL_vasprintf;
+    SDL_acos;
+    SDL_acosf;
+    SDL_asin;
+    SDL_asinf;
+    SDL_atan;
+    SDL_atanf;
+    SDL_atan2;
+    SDL_atan2f;
+    SDL_ceil;
+    SDL_ceilf;
+    SDL_copysign;
+    SDL_copysignf;
+    SDL_cos;
+    SDL_cosf;
+    SDL_exp;
+    SDL_expf;
+    SDL_fabs;
+    SDL_fabsf;
+    SDL_floor;
+    SDL_floorf;
+    SDL_trunc;
+    SDL_truncf;
+    SDL_fmod;
+    SDL_fmodf;
+    SDL_log;
+    SDL_logf;
+    SDL_log10;
+    SDL_log10f;
+    SDL_pow;
+    SDL_powf;
+    SDL_round;
+    SDL_roundf;
+    SDL_lround;
+    SDL_lroundf;
+    SDL_scalbn;
+    SDL_scalbnf;
+    SDL_sin;
+    SDL_sinf;
+    SDL_sqrt;
+    SDL_sqrtf;
+    SDL_tan;
+    SDL_tanf;
+    SDL_iconv_open;
+    SDL_iconv_close;
+    SDL_iconv;
+    SDL_iconv_string;
+    SDL_GetMouseFocus;
+    SDL_GetMouseState;
+    SDL_GetGlobalMouseState;
+    SDL_GetRelativeMouseState;
+    SDL_WarpMouseInWindow;
+    SDL_WarpMouseGlobal;
+    SDL_SetRelativeMouseMode;
+    SDL_CaptureMouse;
+    SDL_GetRelativeMouseMode;
+    SDL_CreateCursor;
+    SDL_CreateColorCursor;
+    SDL_CreateSystemCursor;
+    SDL_SetCursor;
+    SDL_GetCursor;
+    SDL_GetDefaultCursor;
+    SDL_FreeCursor;
+    SDL_ShowCursor;
+    SDL_SetClipboardText;
+    SDL_GetClipboardText;
+    SDL_HasClipboardText;
+    SDL_SetPrimarySelectionText;
+    SDL_GetPrimarySelectionText;
+    SDL_HasPrimarySelectionText;
+    SDL_CreateThread;
+    SDL_CreateThreadWithStackSize;
+    SDL_CreateThread;
+    SDL_CreateThreadWithStackSize;
+    SDL_GetThreadName;
+    SDL_ThreadID;
+    SDL_GetThreadID;
+    SDL_SetThreadPriority;
+    SDL_WaitThread;
+    SDL_DetachThread;
+    SDL_TLSCreate;
+    SDL_TLSGet;
+    SDL_TLSSet;
+    SDL_TLSCleanup;
+    SDL_CreateSurface;
+    SDL_CreateSurfaceFrom;
+    SDL_FreeSurface;
+    SDL_SetSurfacePalette;
+    SDL_LockSurface;
+    SDL_UnlockSurface;
+    SDL_LoadBMP_RW;
+    SDL_SaveBMP_RW;
+    SDL_SetSurfaceRLE;
+    SDL_HasSurfaceRLE;
+    SDL_SetColorKey;
+    SDL_HasColorKey;
+    SDL_GetColorKey;
+    SDL_SetSurfaceColorMod;
+    SDL_GetSurfaceColorMod;
+    SDL_SetSurfaceAlphaMod;
+    SDL_GetSurfaceAlphaMod;
+    SDL_SetSurfaceBlendMode;
+    SDL_GetSurfaceBlendMode;
+    SDL_SetClipRect;
+    SDL_GetClipRect;
+    SDL_DuplicateSurface;
+    SDL_ConvertSurface;
+    SDL_ConvertSurfaceFormat;
+    SDL_ConvertPixels;
+    SDL_PremultiplyAlpha;
+    SDL_FillRect;
+    SDL_FillRects;
+    SDL_UpperBlit;
+    SDL_LowerBlit;
+    SDL_SoftStretch;
+    SDL_SoftStretchLinear;
+    SDL_UpperBlitScaled;
+    SDL_LowerBlitScaled;
+    SDL_SetYUVConversionMode;
+    SDL_GetYUVConversionMode;
+    SDL_GetYUVConversionModeForResolution;
+    SDL_HasIntersection;
+    SDL_IntersectRect;
+    SDL_UnionRect;
+    SDL_EnclosePoints;
+    SDL_IntersectRectAndLine;
+    SDL_HasIntersectionF;
+    SDL_IntersectFRect;
+    SDL_UnionFRect;
+    SDL_EncloseFPoints;
+    SDL_IntersectFRectAndLine;
+    SDL_CreateShapedWindow;
+    SDL_IsShapedWindow;
+    SDL_SetWindowShape;
+    SDL_GetShapedWindowMode;
+    SDL_hid_init;
+    SDL_hid_exit;
+    SDL_hid_device_change_count;
+    SDL_hid_enumerate;
+    SDL_hid_free_enumeration;
+    SDL_hid_open;
+    SDL_hid_open_path;
+    SDL_hid_write;
+    SDL_hid_read_timeout;
+    SDL_hid_read;
+    SDL_hid_set_nonblocking;
+    SDL_hid_send_feature_report;
+    SDL_hid_get_feature_report;
+    SDL_hid_close;
+    SDL_hid_get_manufacturer_string;
+    SDL_hid_get_product_string;
+    SDL_hid_get_serial_number_string;
+    SDL_hid_get_indexed_string;
+    SDL_hid_ble_scan;
+    SDL_GetWindowWMInfo;
+    SDL_GetNumRenderDrivers;
+    SDL_GetRenderDriverInfo;
+    SDL_CreateWindowAndRenderer;
+    SDL_CreateRenderer;
+    SDL_CreateSoftwareRenderer;
+    SDL_GetRenderer;
+    SDL_RenderGetWindow;
+    SDL_GetRendererInfo;
+    SDL_GetRendererOutputSize;
+    SDL_CreateTexture;
+    SDL_CreateTextureFromSurface;
+    SDL_QueryTexture;
+    SDL_SetTextureColorMod;
+    SDL_GetTextureColorMod;
+    SDL_SetTextureAlphaMod;
+    SDL_GetTextureAlphaMod;
+    SDL_SetTextureBlendMode;
+    SDL_GetTextureBlendMode;
+    SDL_SetTextureScaleMode;
+    SDL_GetTextureScaleMode;
+    SDL_SetTextureUserData;
+    SDL_GetTextureUserData;
+    SDL_UpdateTexture;
+    SDL_UpdateYUVTexture;
+    SDL_UpdateNVTexture;
+    SDL_LockTexture;
+    SDL_LockTextureToSurface;
+    SDL_UnlockTexture;
+    SDL_RenderTargetSupported;
+    SDL_SetRenderTarget;
+    SDL_GetRenderTarget;
+    SDL_RenderSetLogicalSize;
+    SDL_RenderGetLogicalSize;
+    SDL_RenderSetIntegerScale;
+    SDL_RenderGetIntegerScale;
+    SDL_RenderSetViewport;
+    SDL_RenderGetViewport;
+    SDL_RenderSetClipRect;
+    SDL_RenderGetClipRect;
+    SDL_RenderIsClipEnabled;
+    SDL_RenderSetScale;
+    SDL_RenderGetScale;
+    SDL_RenderWindowToLogical;
+    SDL_RenderLogicalToWindow;
+    SDL_SetRenderDrawColor;
+    SDL_GetRenderDrawColor;
+    SDL_SetRenderDrawBlendMode;
+    SDL_GetRenderDrawBlendMode;
+    SDL_RenderClear;
+    SDL_RenderDrawPoint;
+    SDL_RenderDrawPoints;
+    SDL_RenderDrawLine;
+    SDL_RenderDrawLines;
+    SDL_RenderDrawRect;
+    SDL_RenderDrawRects;
+    SDL_RenderFillRect;
+    SDL_RenderFillRects;
+    SDL_RenderCopy;
+    SDL_RenderCopyEx;
+    SDL_RenderDrawPointF;
+    SDL_RenderDrawPointsF;
+    SDL_RenderDrawLineF;
+    SDL_RenderDrawLinesF;
+    SDL_RenderDrawRectF;
+    SDL_RenderDrawRectsF;
+    SDL_RenderFillRectF;
+    SDL_RenderFillRectsF;
+    SDL_RenderCopyF;
+    SDL_RenderCopyExF;
+    SDL_RenderGeometry;
+    SDL_RenderGeometryRaw;
+    SDL_RenderReadPixels;
+    SDL_RenderPresent;
+    SDL_DestroyTexture;
+    SDL_DestroyRenderer;
+    SDL_RenderFlush;
+    SDL_GL_BindTexture;
+    SDL_GL_UnbindTexture;
+    SDL_RenderGetMetalLayer;
+    SDL_RenderGetMetalCommandEncoder;
+    SDL_RenderSetVSync;
+    SDL_Init;
+    SDL_InitSubSystem;
+    SDL_QuitSubSystem;
+    SDL_WasInit;
+    SDL_Quit;
+    SDL_ShowMessageBox;
+    SDL_ShowSimpleMessageBox;
+    SDL_SetMainReady;
+    SDL_RegisterApp;
+    SDL_UnregisterApp;
+    SDL_WinRTRunApp;
+    SDL_UIKitRunApp;
+    SDL_GDKRunApp;
+    SDL_GDKSuspendComplete;
+    SDL_GetBasePath;
+    SDL_GetPrefPath;
+    SDL_GetNumVideoDrivers;
+    SDL_GetVideoDriver;
+    SDL_VideoInit;
+    SDL_VideoQuit;
+    SDL_GetCurrentVideoDriver;
+    SDL_GetNumVideoDisplays;
+    SDL_GetDisplayName;
+    SDL_GetDisplayBounds;
+    SDL_GetDisplayUsableBounds;
+    SDL_GetDisplayDPI;
+    SDL_GetDisplayOrientation;
+    SDL_GetNumDisplayModes;
+    SDL_GetDisplayMode;
+    SDL_GetDesktopDisplayMode;
+    SDL_GetCurrentDisplayMode;
+    SDL_GetClosestDisplayMode;
+    SDL_GetPointDisplayIndex;
+    SDL_GetRectDisplayIndex;
+    SDL_GetWindowDisplayIndex;
+    SDL_SetWindowDisplayMode;
+    SDL_GetWindowDisplayMode;
+    SDL_GetWindowICCProfile;
+    SDL_GetWindowPixelFormat;
+    SDL_CreateWindow;
+    SDL_CreateWindowFrom;
+    SDL_GetWindowID;
+    SDL_GetWindowFromID;
+    SDL_GetWindowFlags;
+    SDL_SetWindowTitle;
+    SDL_GetWindowTitle;
+    SDL_SetWindowIcon;
+    SDL_SetWindowData;
+    SDL_GetWindowData;
+    SDL_SetWindowPosition;
+    SDL_GetWindowPosition;
+    SDL_SetWindowSize;
+    SDL_GetWindowSize;
+    SDL_GetWindowBordersSize;
+    SDL_GetWindowSizeInPixels;
+    SDL_SetWindowMinimumSize;
+    SDL_GetWindowMinimumSize;
+    SDL_SetWindowMaximumSize;
+    SDL_GetWindowMaximumSize;
+    SDL_SetWindowBordered;
+    SDL_SetWindowResizable;
+    SDL_SetWindowAlwaysOnTop;
+    SDL_ShowWindow;
+    SDL_HideWindow;
+    SDL_RaiseWindow;
+    SDL_MaximizeWindow;
+    SDL_MinimizeWindow;
+    SDL_RestoreWindow;
+    SDL_SetWindowFullscreen;
+    SDL_GetWindowSurface;
+    SDL_UpdateWindowSurface;
+    SDL_UpdateWindowSurfaceRects;
+    SDL_SetWindowGrab;
+    SDL_SetWindowKeyboardGrab;
+    SDL_SetWindowMouseGrab;
+    SDL_GetWindowGrab;
+    SDL_GetWindowKeyboardGrab;
+    SDL_GetWindowMouseGrab;
+    SDL_GetGrabbedWindow;
+    SDL_SetWindowMouseRect;
+    SDL_GetWindowMouseRect;
+    SDL_SetWindowOpacity;
+    SDL_GetWindowOpacity;
+    SDL_SetWindowModalFor;
+    SDL_SetWindowInputFocus;
+    SDL_SetWindowHitTest;
+    SDL_FlashWindow;
+    SDL_DestroyWindow;
+    SDL_IsScreenSaverEnabled;
+    SDL_EnableScreenSaver;
+    SDL_DisableScreenSaver;
+    SDL_GL_LoadLibrary;
+    SDL_GL_GetProcAddress;
+    SDL_EGL_GetProcAddress;
+    SDL_GL_UnloadLibrary;
+    SDL_GL_ExtensionSupported;
+    SDL_GL_ResetAttributes;
+    SDL_GL_SetAttribute;
+    SDL_GL_GetAttribute;
+    SDL_GL_CreateContext;
+    SDL_GL_MakeCurrent;
+    SDL_GL_GetCurrentWindow;
+    SDL_GL_GetCurrentContext;
+    SDL_EGL_GetCurrentEGLDisplay;
+    SDL_EGL_GetCurrentEGLConfig;
+    SDL_EGL_GetWindowEGLSurface;
+    SDL_EGL_SetEGLAttributeCallbacks;
+    SDL_GL_GetDrawableSize;
+    SDL_GL_SetSwapInterval;
+    SDL_GL_GetSwapInterval;
+    SDL_GL_SwapWindow;
+    SDL_GL_DeleteContext;
+    SDL_GetNumTouchDevices;
+    SDL_GetTouchDevice;
+    SDL_GetTouchName;
+    SDL_GetTouchDeviceType;
+    SDL_GetNumTouchFingers;
+    SDL_GetTouchFinger;
+    SDL_GetCPUCount;
+    SDL_GetCPUCacheLineSize;
+    SDL_HasRDTSC;
+    SDL_HasAltiVec;
+    SDL_HasMMX;
+    SDL_Has3DNow;
+    SDL_HasSSE;
+    SDL_HasSSE2;
+    SDL_HasSSE3;
+    SDL_HasSSE41;
+    SDL_HasSSE42;
+    SDL_HasAVX;
+    SDL_HasAVX2;
+    SDL_HasAVX512F;
+    SDL_HasARMSIMD;
+    SDL_HasNEON;
+    SDL_HasLSX;
+    SDL_HasLASX;
+    SDL_GetSystemRAM;
+    SDL_SIMDGetAlignment;
+    SDL_SIMDAlloc;
+    SDL_SIMDRealloc;
+    SDL_SIMDFree;
+    SDL_SetError;
+    SDL_GetError;
+    SDL_GetErrorMsg;
+    SDL_ClearError;
+    SDL_Error;
+    SDL_LockSensors;
+    SDL_UnlockSensors;
+    SDL_NumSensors;
+    SDL_SensorGetDeviceName;
+    SDL_SensorGetDeviceType;
+    SDL_SensorGetDeviceNonPortableType;
+    SDL_SensorGetDeviceInstanceID;
+    SDL_SensorOpen;
+    SDL_SensorFromInstanceID;
+    SDL_SensorGetName;
+    SDL_SensorGetType;
+    SDL_SensorGetNonPortableType;
+    SDL_SensorGetInstanceID;
+    SDL_SensorGetData;
+    SDL_SensorGetDataWithTimestamp;
+    SDL_SensorClose;
+    SDL_SensorUpdate;
+    SDL_SetHintWithPriority;
+    SDL_SetHint;
+    SDL_ResetHint;
+    SDL_ResetHints;
+    SDL_GetHint;
+    SDL_GetHintBoolean;
+    SDL_AddHintCallback;
+    SDL_DelHintCallback;
+    SDL_ClearHints;
+    SDL_GetPlatform;
+    SDL_CreateMutex;
+    SDL_LockMutex;
+    SDL_TryLockMutex;
+    SDL_UnlockMutex;
+    SDL_DestroyMutex;
+    SDL_CreateSemaphore;
+    SDL_DestroySemaphore;
+    SDL_SemWait;
+    SDL_SemTryWait;
+    SDL_SemWaitTimeout;
+    SDL_SemPost;
+    SDL_SemValue;
+    SDL_CreateCond;
+    SDL_DestroyCond;
+    SDL_CondSignal;
+    SDL_CondBroadcast;
+    SDL_CondWait;
+    SDL_CondWaitTimeout;
+    SDL_ComposeCustomBlendMode;
+    SDL_LockJoysticks;
+    SDL_UnlockJoysticks;
+    SDL_NumJoysticks;
+    SDL_JoystickNameForIndex;
+    SDL_JoystickPathForIndex;
+    SDL_JoystickGetDevicePlayerIndex;
+    SDL_JoystickGetDeviceGUID;
+    SDL_JoystickGetDeviceVendor;
+    SDL_JoystickGetDeviceProduct;
+    SDL_JoystickGetDeviceProductVersion;
+    SDL_JoystickGetDeviceType;
+    SDL_JoystickGetDeviceInstanceID;
+    SDL_JoystickOpen;
+    SDL_JoystickFromInstanceID;
+    SDL_JoystickFromPlayerIndex;
+    SDL_JoystickAttachVirtual;
+    SDL_JoystickAttachVirtualEx;
+    SDL_JoystickDetachVirtual;
+    SDL_JoystickIsVirtual;
+    SDL_JoystickSetVirtualAxis;
+    SDL_JoystickSetVirtualButton;
+    SDL_JoystickSetVirtualHat;
+    SDL_JoystickName;
+    SDL_JoystickPath;
+    SDL_JoystickGetPlayerIndex;
+    SDL_JoystickSetPlayerIndex;
+    SDL_JoystickGetGUID;
+    SDL_JoystickGetVendor;
+    SDL_JoystickGetProduct;
+    SDL_JoystickGetProductVersion;
+    SDL_JoystickGetFirmwareVersion;
+    SDL_JoystickGetSerial;
+    SDL_JoystickGetType;
+    SDL_JoystickGetGUIDString;
+    SDL_JoystickGetGUIDFromString;
+    SDL_GetJoystickGUIDInfo;
+    SDL_JoystickGetAttached;
+    SDL_JoystickInstanceID;
+    SDL_JoystickNumAxes;
+    SDL_JoystickNumBalls;
+    SDL_JoystickNumHats;
+    SDL_JoystickNumButtons;
+    SDL_JoystickUpdate;
+    SDL_JoystickEventState;
+    SDL_JoystickGetAxis;
+    SDL_JoystickGetAxisInitialState;
+    SDL_JoystickGetHat;
+    SDL_JoystickGetBall;
+    SDL_JoystickGetButton;
+    SDL_JoystickRumble;
+    SDL_JoystickRumbleTriggers;
+    SDL_JoystickHasLED;
+    SDL_JoystickHasRumble;
+    SDL_JoystickHasRumbleTriggers;
+    SDL_JoystickSetLED;
+    SDL_JoystickSendEffect;
+    SDL_JoystickClose;
+    SDL_JoystickCurrentPowerLevel;
+    SDL_Metal_CreateView;
+    SDL_Metal_DestroyView;
+    SDL_Metal_GetLayer;
+    SDL_Metal_GetDrawableSize;
+    SDL_GameControllerAddMappingsFromRW;
+    SDL_GameControllerAddMapping;
+    SDL_GameControllerNumMappings;
+    SDL_GameControllerMappingForIndex;
+    SDL_GameControllerMappingForGUID;
+    SDL_GameControllerMapping;
+    SDL_IsGameController;
+    SDL_GameControllerNameForIndex;
+    SDL_GameControllerPathForIndex;
+    SDL_GameControllerTypeForIndex;
+    SDL_GameControllerMappingForDeviceIndex;
+    SDL_GameControllerOpen;
+    SDL_GameControllerFromInstanceID;
+    SDL_GameControllerFromPlayerIndex;
+    SDL_GameControllerName;
+    SDL_GameControllerPath;
+    SDL_GameControllerGetType;
+    SDL_GameControllerGetPlayerIndex;
+    SDL_GameControllerSetPlayerIndex;
+    SDL_GameControllerGetVendor;
+    SDL_GameControllerGetProduct;
+    SDL_GameControllerGetProductVersion;
+    SDL_GameControllerGetFirmwareVersion;
+    SDL_GameControllerGetSerial;
+    SDL_GameControllerGetAttached;
+    SDL_GameControllerGetJoystick;
+    SDL_GameControllerEventState;
+    SDL_GameControllerUpdate;
+    SDL_GameControllerGetAxisFromString;
+    SDL_GameControllerGetStringForAxis;
+    SDL_GameControllerGetBindForAxis;
+    SDL_GameControllerHasAxis;
+    SDL_GameControllerGetAxis;
+    SDL_GameControllerGetButtonFromString;
+    SDL_GameControllerGetStringForButton;
+    SDL_GameControllerGetBindForButton;
+    SDL_GameControllerHasButton;
+    SDL_GameControllerGetButton;
+    SDL_GameControllerGetNumTouchpads;
+    SDL_GameControllerGetNumTouchpadFingers;
+    SDL_GameControllerGetTouchpadFinger;
+    SDL_GameControllerHasSensor;
+    SDL_GameControllerSetSensorEnabled;
+    SDL_GameControllerIsSensorEnabled;
+    SDL_GameControllerGetSensorDataRate;
+    SDL_GameControllerGetSensorData;
+    SDL_GameControllerGetSensorDataWithTimestamp;
+    SDL_GameControllerRumble;
+    SDL_GameControllerRumbleTriggers;
+    SDL_GameControllerHasLED;
+    SDL_GameControllerHasRumble;
+    SDL_GameControllerHasRumbleTriggers;
+    SDL_GameControllerSetLED;
+    SDL_GameControllerSendEffect;
+    SDL_GameControllerClose;
+    SDL_GameControllerGetAppleSFSymbolsNameForButton;
+    SDL_GameControllerGetAppleSFSymbolsNameForAxis;
+    SDL_ReportAssertion;
+    SDL_SetAssertionHandler;
+    SDL_GetDefaultAssertionHandler;
+    SDL_GetAssertionHandler;
+    SDL_GetAssertionReport;
+    SDL_ResetAssertionReport;
+    SDL_NumHaptics;
+    SDL_HapticName;
+    SDL_HapticOpen;
+    SDL_HapticOpened;
+    SDL_HapticIndex;
+    SDL_MouseIsHaptic;
+    SDL_HapticOpenFromMouse;
+    SDL_JoystickIsHaptic;
+    SDL_HapticOpenFromJoystick;
+    SDL_HapticClose;
+    SDL_HapticNumEffects;
+    SDL_HapticNumEffectsPlaying;
+    SDL_HapticQuery;
+    SDL_HapticNumAxes;
+    SDL_HapticEffectSupported;
+    SDL_HapticNewEffect;
+    SDL_HapticUpdateEffect;
+    SDL_HapticRunEffect;
+    SDL_HapticStopEffect;
+    SDL_HapticDestroyEffect;
+    SDL_HapticGetEffectStatus;
+    SDL_HapticSetGain;
+    SDL_HapticSetAutocenter;
+    SDL_HapticPause;
+    SDL_HapticUnpause;
+    SDL_HapticStopAll;
+    SDL_HapticRumbleSupported;
+    SDL_HapticRumbleInit;
+    SDL_HapticRumblePlay;
+    SDL_HapticRumbleStop;
+    SDL_GetVersion;
+    SDL_GetRevision;
+    SDL_GetPowerInfo;
+    # extra symbols go here (don't modify this line)
+  local: *;
+};
diff --git a/src/dynapi/gendynapi.pl b/src/dynapi/gendynapi.pl
index 5ac07e4ec94e..f9cba30ac528 100755
--- a/src/dynapi/gendynapi.pl
+++ b/src/dynapi/gendynapi.pl
@@ -33,6 +33,7 @@
 chdir(dirname(__FILE__) . '/../..');
 my $sdl_dynapi_procs_h = "src/dynapi/SDL_dynapi_procs.h";
 my $sdl_dynapi_overrides_h = "src/dynapi/SDL_dynapi_overrides.h";
+my $sdl_dynapi_sym = "src/dynapi/SDL_dynapi.sym";
 
 my %existing = ();
 if (-f $sdl_dynapi_procs_h) {
@@ -48,6 +49,10 @@
 open(SDL_DYNAPI_PROCS_H, '>>', $sdl_dynapi_procs_h) or die("Can't open $sdl_dynapi_procs_h: $!\n");
 open(SDL_DYNAPI_OVERRIDES_H, '>>', $sdl_dynapi_overrides_h) or die("Can't open $sdl_dynapi_overrides_h: $!\n");
 
+open(SDL_DYNAPI_SYM, '<', $sdl_dynapi_sym) or die("Can't open $sdl_dynapi_sym: $!\n");
+read(SDL_DYNAPI_SYM, my $sdl_dynapi_sym_contents, -s SDL_DYNAPI_SYM);
+close(SDL_DYNAPI_SYM);
+
 opendir(HEADERS, 'include/SDL3') or die("Can't open include dir: $!\n");
 while (my $d = readdir(HEADERS)) {
     next if not $d =~ /\.h\Z/;
@@ -76,6 +81,8 @@
 
             next if $existing{$fn};   # already slotted into the jump table.
 
+            $sdl_dynapi_sym_contents =~ s/# extra symbols go here/$fn;\n    # extra symbols go here/;
+
             my @params = split(',', $7);
 
             #print("rc == '$rc', fn == '$fn', params == '$params'\n");
@@ -144,4 +151,8 @@
 close(SDL_DYNAPI_PROCS_H);
 close(SDL_DYNAPI_OVERRIDES_H);
 
+open(SDL_DYNAPI_SYM, '>', $sdl_dynapi_sym) or die("Can't open $sdl_dynapi_sym: $!\n");
+print SDL_DYNAPI_SYM $sdl_dynapi_sym_contents;
+close(SDL_DYNAPI_SYM);
+
 # vi: set ts=4 sw=4 expandtab: