Gate link.T to retro_* only; move test exports to link-test.T

Pre-tag review flagged that link.T currently exports a wide internal
symbol surface (DSP*, dsp_*, m68k_*, Jaguar*, jaguar*, GPU*, gpu_*,
JERRY*, TOM*, OP*, jaguarMainRAM, jagMemSpace, regs, sclk, smode,
lowerField, vjs, ...).  These were added so the headless white-box
test harnesses can dlsym into emulator state, but shipping them as
the production ABI is a long-term liability — frontends could pin
themselves to internal symbols and we'd have to keep them stable
across refactors.

Split into two version scripts:

- link.T       : production ABI (retro_* only).  Used by `make`
                  default and the release.yml workflow.
- link-test.T  : the previous wide set, used only when the Makefile
                  is invoked with TEST_EXPORTS=1.  The `test` target
                  re-invokes make with TEST_EXPORTS=1 so the .so
                  produced for the test binaries has the wider
                  symbol set, while the .so produced by `make`
                  (default) hides internal symbols.

Replace `link.T` with `$(LINK_SCRIPT)` in the 5 platform-specific
SHARED definitions in Makefile (unix / classic_armv7_a7 / qnx /
armv* / generic shared-build).  Add an `LINK_SCRIPT` variable at
the top of the Makefile that picks link.T vs link-test.T based on
TEST_EXPORTS=0/1.

Force a re-link of the .so when `make test` is invoked without
TEST_EXPORTS=1 already set: the outer test target rm's the .so
then re-invokes `$(MAKE) TEST_EXPORTS=1 test`.  After `make test`,
the on-disk .so has the wide exports — re-run `make` (no flag) to
restore the production-slim ABI for shipping.

Note: this only takes effect on platforms that link with GNU ld
--version-script (Linux, Windows MSYS2/MinGW, ARM, QNX).  macOS /
iOS / tvOS dylibs ignore --version-script and currently still
export everything with default visibility.  Slimming those needs
-Wl,-exported_symbols_list and a separate exports list — punted to
v2.3.0 (noted in docs/emulation-bug-hunt-todos.md follow-up
section).

Verified locally:
- `make` (default) produces .so without errors.
- `make test` re-links with link-test.T and all tests still pass
  (211 passes in test_hle_bios, full suite green).

Updated docs/emulation-bug-hunt-todos.md follow-up section to
remove the "v2.3 link.T export gating" item from the open list and
mark it done as of v2.2.0; macOS/iOS dylib export-list slimming
is the remaining piece for v2.3.0.
This commit is contained in:
Joseph Mattiello committed 2026-04-30 21:53:52 -04:00
1 parent 95d31b559d
commit 5a50de9a7e
3 files changed
+85 -33

No files matched your search

+33 -5
View File
@@ -52,6 +52,22 @@ BUILD_TIMESTAMP := " debug $(shell date -u +%Y-%m-%dT%H:%M:%SZ)"
CFLAGS += -DBUILD_TIMESTAMP=\"$(BUILD_TIMESTAMP)\"
endif
# GNU-ld --version-script choice.
# link.T : production ABI (retro_* only).
# link-test.T : wide symbol set used by white-box test harnesses.
# The `test` target re-invokes make with TEST_EXPORTS=1 so the
# shipped .so on `make` (default) hides internal symbols, while
# `make test` produces a .so the test binaries can dlsym into.
# Only effective on platforms that link with GNU ld --version-script
# (Linux, Windows MSYS2, ARM, etc.); macOS / iOS / tvOS dylibs and
# static archives ignore this and currently still export everything
# with default visibility.
ifeq ($(TEST_EXPORTS),1)
LINK_SCRIPT := link-test.T
else
LINK_SCRIPT := link.T
endif
# Unix
ifeq ($(platform), unix)
TARGET := $(TARGET_NAME)_libretro.so
@@ -59,7 +75,7 @@ ifeq ($(platform), unix)
ifneq ($(findstring SunOS,$(shell uname -a)),)
SHARED := -shared -z defs -z gnu-version-script-compat
else
SHARED := -shared -Wl,--no-undefined -Wl,--version-script=link.T
SHARED := -shared -Wl,--no-undefined -Wl,--version-script=$(LINK_SCRIPT)
endif
# Classic Platforms ####################
@@ -71,7 +87,7 @@ ifeq ($(platform), unix)
else ifeq ($(platform), classic_armv7_a7)
TARGET := $(TARGET_NAME)_libretro.so
fpic := -fPIC
SHARED := -shared -Wl,--no-undefined -Wl,--version-script=link.T
SHARED := -shared -Wl,--no-undefined -Wl,--version-script=$(LINK_SCRIPT)
CFLAGS += -Ofast \
-flto=4 -fwhole-program -fuse-linker-plugin \
-fdata-sections -ffunction-sections -Wl,--gc-sections \
@@ -173,7 +189,7 @@ else ifeq ($(platform), theos_ios)
else ifeq ($(platform), qnx)
TARGET := $(TARGET_NAME)_libretro_$(platform).so
fpic := -fPIC
SHARED := -shared -Wl,--no-undefined -Wl,--version-script=link.T
SHARED := -shared -Wl,--no-undefined -Wl,--version-script=$(LINK_SCRIPT)
CC = qcc -Vgcc_ntoarmv7le
CXX = QCC -Vgcc_ntoarmv7le_cpp
@@ -181,7 +197,7 @@ else ifeq ($(platform), qnx)
else ifneq (,$(findstring armv,$(platform)))
TARGET := $(TARGET_NAME)_libretro.so
fpic := -fPIC
SHARED := -shared -Wl,--no-undefined -Wl,--version-script=link.T
SHARED := -shared -Wl,--no-undefined -Wl,--version-script=$(LINK_SCRIPT)
ARCH = arm
# Nintendo Switch (libnx)
@@ -517,7 +533,7 @@ else
TARGET := $(TARGET_NAME)_libretro.dll
CC ?= gcc
CXX ?= g++
SHARED := -shared -Wl,--no-undefined -Wl,--version-script=link.T
SHARED := -shared -Wl,--no-undefined -Wl,--version-script=$(LINK_SCRIPT)
LDFLAGS += -static-libgcc -static-libstdc++ -lwinmm
endif
@@ -655,6 +671,18 @@ ifneq (,$(findstring msvc,$(platform)))
test:
@echo "make test requires GCC/Clang flags; use MSYS2/Unix or compile test/test_cheat.c manually."
@false
else ifneq ($(TEST_EXPORTS),1)
# When `make test` is invoked without TEST_EXPORTS=1, the shipped .so
# was linked with link.T (production-slim, retro_* only) and the
# white-box test binaries can't dlsym into JaguarReset / DSPGetRAM /
# etc. Force a re-link with link-test.T by removing the .so and
# re-invoking make with TEST_EXPORTS=1 so the wider symbol set is
# exported just for this build. After `make test` finishes, the .so
# in the working tree has the wider exports — re-run `make` (no flag)
# to restore the production-slim ABI.
test:
@rm -f $(TARGET)
@$(MAKE) TEST_EXPORTS=1 test
else
test: test/test_cheat test/test_event_queue test/test_blitter_simd test/test_dsp_mac40 \
$(TARGET) test/test_m68k_ops test/test_gpu_ops test/test_dsp_ops \
+40
View File
@@ -0,0 +1,40 @@
/* Wide symbol export script for white-box test builds.
*
* The release / production ABI is link.T, which exports only retro_*.
* This script is used when the Makefile is invoked with
* TEST_EXPORTS=1 (set automatically by the `test` target). It
* exposes the internal symbols our headless test harnesses dlsym
* into for white-box testing of the emulator state.
*
* If you add a new symbol here, that's an extension to the test ABI
* only — it does NOT change the production-shipped .so / .dll.
*/
{
global:
retro_*;
DSP*;
dsp_*;
m68k_*;
Jaguar*;
jaguar*;
Halfline*;
jaguarMainRAM;
jaguarMainROM;
jagMemSpace;
pcQueue;
pcQPtr;
a6Queue;
d0Queue;
GPU*;
gpu_*;
JERRY*;
TOM*;
OP*;
tomRam8;
regs;
sclk;
smode;
lowerField;
vjs;
local: *;
};
+12 -28
View File
@@ -1,33 +1,17 @@
/* Extra symbols beyond retro_* are exported intentionally so that
test harnesses (test_cd_boot, test_gpu_ops, test_rom_smoke, etc.)
can dlsym() into internal emulator state for white-box testing. */
/* Production ABI export script.
*
* Only the retro_* libretro entry points are exported from the
* shipped .so / .dll. Internal emulator state (DSP, GPU, blitter,
* 68K registers, jaguarMainRAM, etc.) is hidden so frontends and
* future refactors don't accidentally pin themselves to internal
* symbols.
*
* White-box test harnesses need the wider symbol set; the Makefile
* switches to link-test.T when TEST_EXPORTS=1 (which the `test`
* target sets automatically).
*/
{
global:
retro_*;
DSP*;
dsp_*;
m68k_*;
Jaguar*;
jaguar*;
Halfline*;
jaguarMainRAM;
jaguarMainROM;
jagMemSpace;
pcQueue;
pcQPtr;
a6Queue;
d0Queue;
GPU*;
gpu_*;
JERRY*;
TOM*;
OP*;
tomRam8;
regs;
sclk;
smode;
lowerField;
vjs;
local: *;
};