-
Notifications
You must be signed in to change notification settings - Fork 7
Pin exported surface to intended API and shrink #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,21 +22,98 @@ all: $(TARGET) | |
| # segfaults at offset 0x50. -Bsymbolic (not just -Bsymbolic-functions) | ||
| # binds *both* function and data references to the local library at | ||
| # link time. Mach-O ld on macOS does not accept the flag, so gate it | ||
| # on Linux only. | ||
| LDFLAGS_LIB := | ||
| # on Linux only. The version script below now hides those same lock | ||
| # globals outright (they are in no manifest, so local: keeps them out | ||
| # of the dynamic table entirely), which is strictly stronger than | ||
| # -Bsymbolic; the flag stays as belt-and-suspenders for the exported | ||
| # symbols' own intra-library references. | ||
|
|
||
| # Pin the exported surface to the intended public API. The library defaults to | ||
| # exporting every non-static symbol, which leaks ~400 internal helpers into the | ||
| # dynamic table; restricting the export set to the enforced manifests also lets | ||
| # the link garbage-collect code no exported symbol reaches (e.g. the Xft/Fc copy | ||
| # that only libXft-compat consumes). See scripts/gen-export-list.sh. | ||
| X11_EXPORT_MANIFESTS := tests/api-symbols.txt tests/shim-symbols.txt \ | ||
| tests/private-symbols.txt tests/whitebox-symbols.txt | ||
|
|
||
| # GLX (and the export FORMAT) toggle the exported surface, so a GLX=0->1 flip | ||
| # without make clean must not reuse a map that hid glX* as local. That identity | ||
| # is tracked once by X11_LINK_CONFIG below: the defined-syms, the export list, | ||
| # and the linked libraries all take it as a prerequisite, so a change to GLX or | ||
| # FORMAT regenerates the map and relinks. The artifact names stay unversioned. | ||
| # The core link splits in two: LDFLAGS_LIB_COMMON is the binding every core .so | ||
| # needs (notably -Bsymbolic on Linux), LDFLAGS_LIB_PIN is the export-surface | ||
| # restriction only the shipped library wants. The whitebox test twin below | ||
| # reuses COMMON but drops PIN, so it keeps -Bsymbolic (without which the system | ||
| # libX11.so.6 that SDL2 loads on Linux interposes our lock/event globals) while | ||
| # exporting every internal the tests reach. The @loader_path rpath and the | ||
| # @rpath install_name / $ORIGIN rpath come from shared_lib_rpath_ldflags, keyed | ||
| # on $@ so each library carries its own soname. | ||
| LDFLAGS_LIB_COMMON := | ||
| X11_RPATH_FLAGS = $(call shared_lib_rpath_ldflags,$(notdir $@)) | ||
| ifeq ($(UNAME_S),Linux) | ||
| LDFLAGS_LIB += -Wl,-Bsymbolic $(call shared_lib_rpath_ldflags,$(notdir $(TARGET))) | ||
| X11_EXPORT_LIST := $(OUT)/libX11-compat.map | ||
| X11_EXPORT_FORMAT := elf | ||
| LDFLAGS_LIB_COMMON += -Wl,-Bsymbolic | ||
| LDFLAGS_LIB_PIN := -Wl,--version-script=$(X11_EXPORT_LIST) -Wl,--gc-sections | ||
| endif | ||
| ifeq ($(UNAME_S),Darwin) | ||
| # @loader_path lets the dylib find sibling compat shared libraries | ||
| # (libXt-compat, libXpm-compat, etc.) at the same directory level | ||
| # without requiring the consumer to bake in an absolute rpath. | ||
| LDFLAGS_LIB += -Wl,-install_name,@rpath/$(notdir $(TARGET)) \ | ||
| -Wl,-rpath,@loader_path | ||
| X11_EXPORT_LIST := $(OUT)/libX11-compat.exports | ||
| X11_EXPORT_FORMAT := macho | ||
| LDFLAGS_LIB_PIN := -Wl,-exported_symbols_list,$(X11_EXPORT_LIST) -Wl,-dead_strip | ||
| endif | ||
|
|
||
| $(TARGET): $(OBJS) $(SDL_WRAPPER_TARGETS) | $(OUT) | ||
| X11_LINK_CONFIG := $(OUT)/libX11-compat.link-config | ||
| .PHONY: FORCE | ||
| $(X11_LINK_CONFIG): FORCE | $(OUT) | ||
| $(Q){ printf 'GLX=%s\n' '$(GLX)'; \ | ||
| printf 'FORMAT=%s\n' '$(X11_EXPORT_FORMAT)'; } > $@.tmp | ||
| $(Q)if test -r $@ && cmp -s $@.tmp $@; then \ | ||
| rm -f $@.tmp; \ | ||
| else \ | ||
| mv $@.tmp $@; \ | ||
| fi | ||
|
|
||
| # The symbols the core objects actually define, so the export list can be | ||
| # intersected against them (see scripts/gen-export-list.sh for why). Darwin nm | ||
| # spells C symbols with a leading underscore; strip it so names match the | ||
| # manifests. Undefined entries (type U) are dropped. | ||
| X11_EXPORT_DEFINED := $(OUT)/libX11-compat.defined-syms | ||
| $(X11_EXPORT_DEFINED): $(OBJS) $(X11_LINK_CONFIG) | $(OUT) | ||
| @echo " GEN $@" | ||
| $(Q)nm -g $(OBJS) 2>/dev/null \ | ||
| | awk '$$1 ~ /^[0-9a-fA-F]+$$/ { print $$NF }' \ | ||
| | $(if $(filter Darwin,$(UNAME_S)),sed 's/^_//',cat) \ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: On macOS the underscore ABI is silently dropped from the exported set. The defined-syms step strips the leading underscore from every nm symbol ( Prompt for AI agents |
||
| | LC_ALL=C sort -u > $@ | ||
| $(Q)test -s $@ || { echo " ERROR $@ empty (nm found no defined symbols)" >&2; exit 1; } | ||
|
|
||
| # X11_EXPORT_FORMAT and GLX are quoted because a make GLX= override leaves GLX | ||
| # empty; unquoted it would vanish from the argv and shift every later positional, | ||
| # so the script would read the defined-syms path as the glx flag. Matching only | ||
| # lines whose first nm field is a hex address above keeps undefined-weak (type | ||
| # w/v) references out of the defined set, not just type U. | ||
| $(X11_EXPORT_LIST): $(X11_EXPORT_MANIFESTS) $(X11_EXPORT_DEFINED) \ | ||
| scripts/gen-export-list.sh $(X11_LINK_CONFIG) | $(OUT) | ||
| @echo " GEN $@" | ||
| $(Q)scripts/gen-export-list.sh "$(X11_EXPORT_FORMAT)" "$(GLX)" \ | ||
| $(X11_EXPORT_DEFINED) $(X11_EXPORT_MANIFESTS) > $@ | ||
|
|
||
| $(TARGET): $(OBJS) $(SDL_WRAPPER_TARGETS) $(X11_EXPORT_LIST) \ | ||
| $(X11_LINK_CONFIG) | $(OUT) | ||
| @echo " LD $@" | ||
| $(Q)$(CC) $(LDFLAGS) $(LDFLAGS_LIB_COMMON) $(X11_RPATH_FLAGS) \ | ||
| $(LDFLAGS_LIB_PIN) -shared -o $@ $(OBJS) $(LDLIBS) | ||
|
|
||
| # Fat, unpinned twin of the core library for the whitebox tests (see mk/tests.mk | ||
| # for why they cannot link the pinned .so or the bare objects). Same objects and | ||
| # the same COMMON binding as $(TARGET), so -Bsymbolic still shields our globals | ||
| # from the system libX11.so.6, but with no export list every internal stays | ||
| # reachable. Built only as a prerequisite of the whitebox test binaries, never | ||
| # by all and never installed. | ||
| X11_TEST_LIB := $(OUT)/libX11-compat-test.so | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: The whitebox test twin is hardcoded to Prompt for AI agents |
||
| $(X11_TEST_LIB): $(OBJS) $(SDL_WRAPPER_TARGETS) $(X11_LINK_CONFIG) | $(OUT) | ||
| @echo " LD $@" | ||
| $(Q)$(CC) $(LDFLAGS) $(LDFLAGS_LIB) -shared -o $@ $(OBJS) $(LDLIBS) | ||
| $(Q)$(CC) $(LDFLAGS) $(LDFLAGS_LIB_COMMON) $(X11_RPATH_FLAGS) \ | ||
| -shared -o $@ $(OBJS) $(LDLIBS) | ||
|
|
||
| endif # native .so build | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| #!/bin/sh | ||
|
|
||
| # Emit the linker export list that pins libX11-compat's exported surface to its | ||
| # intended API. Everything else (internal helpers, the duplicated Xft/Fc copy) | ||
| # is hidden, which also lets the linker dead-strip code no exported symbol | ||
| # reaches. The symbol set is the union of the manifests already enforced by | ||
| # tests/check-api-symbols.py plus tests/private-symbols.txt (the core->libXft | ||
| # private contract) and tests/whitebox-symbols.txt (the whitebox test surface), | ||
| # so the manifests stay the single source of truth. | ||
| # | ||
| # The union is intersected with <defined-syms>, the symbols the core objects | ||
| # actually define, so the list never names a symbol absent from this build. That | ||
| # matters because a manifest carries symbols that only exist on one platform or | ||
| # feature build (the libx11Compat* shims are macOS-only; glX* drop when GLX=0): | ||
| # a stray name is a hard error under ld64 and under lld's --no-undefined-version | ||
| # default, and a silent no-op only under GNU ld. Pinning to the defined set | ||
| # keeps every linker happy without per-platform manifests. | ||
| # | ||
| # Usage: gen-export-list.sh <elf|macho> <glx:0|1> <defined-syms> <manifest>... | ||
| set -eu | ||
|
|
||
| # comm below needs both inputs collated identically to the sort that produced | ||
| # them; pin C collation everywhere so the intersection is deterministic | ||
| # regardless of the caller's LC_* (a UTF-8 locale orders _/case differently). | ||
| export LC_ALL=C | ||
|
|
||
| format=$1 | ||
| glx=$2 | ||
| defined=$3 | ||
| shift 3 | ||
|
|
||
| # Read every manifest in one checked step: a missing or unreadable file must | ||
| # abort the build, not silently yield an empty (API-omitting) map. A sed failure | ||
| # propagates through this assignment under set -e; the later grep -v / sort only | ||
| # exit non-zero on the harmless all-blank case. | ||
| manifest_lines=$(sed -e 's/#.*//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' "$@") | ||
| syms=$(printf '%s\n' "$manifest_lines" | grep -v '^$' | sort -u) | ||
|
|
||
| # grep here reads a pipe, so it can only exit non-zero by matching nothing (a | ||
| # legal empty filter, e.g. GLX=0 against a glX-only set): tolerate that. | ||
| if [ "$glx" != "1" ]; then | ||
| syms=$(printf '%s\n' "$syms" | grep -v '^glX' || true) | ||
| fi | ||
|
|
||
| # Keep only symbols this build actually defines. comm reads $defined, so a read | ||
| # error there is real and must fail the build; comm already returns 0 for an | ||
| # empty intersection, so no explicit fallback is needed for the legitimate empty | ||
| # case. | ||
| syms=$(printf '%s\n' "$syms" | comm -12 - "$defined") | ||
|
|
||
| # Always export the libX11 underscore ABI the core defines (_Xdebug, | ||
| # _XrmInternalStringToQuark, ...). Real libX11 exports these _X* globals, and | ||
| # legacy clients link against them directly (violawww's libIMG references | ||
| # _Xdebug); the manifests only enumerate the X[A-Z]* public names, so without | ||
| # this the underscore ABI would be hidden and those clients fail to link. Drawn | ||
| # from $defined, so every entry is guaranteed present in this build. | ||
| abi=$(grep -E '^_X' "$defined" || true) | ||
| syms=$(printf '%s\n%s\n' "$syms" "$abi" | grep -v '^$' | sort -u) | ||
|
|
||
| case "$format" in | ||
| macho) | ||
|
|
||
| # Mach-O -exported_symbols_list: one C symbol per line, leading | ||
| # underscore. | ||
| printf '%s\n' "$syms" | grep -v '^$' | sed 's/^/_/' | ||
| ;; | ||
| elf) | ||
| # ELF version script: named globals, everything else local (hidden). | ||
| printf '{\n global:\n' | ||
| printf '%s\n' "$syms" | grep -v '^$' | sed 's/^/ /; s/$/;/' | ||
| printf ' local:\n *;\n};\n' | ||
| ;; | ||
| *) | ||
| echo "gen-export-list.sh: unknown format '$format'" >&2 | ||
| exit 2 | ||
| ;; | ||
| esac |
Uh oh!
There was an error while loading. Please reload this page.