TL;DR: Can I use a GNU ld
linker --version-script
or some other method to promote selected symbols with hidden
visibility (due to -fvisibility=hidden
or an explicit __attribute__
) back to default
visibility, so they are available in the global symbol table of a shared library?
Is there any way I can tell a gnu ld version-script to promote the HIDDEN
symbols in the global:
version-script section to DEFAULT
visibility?
For a rather weird system I need to link a shared library such that all non-static function symbols have default visibility, but all other symbols get "hidden" visibility unless explicitly annotated with appropriate visibility attributes.
If there was a way to tell gcc
something like the (invalid imaginary syntax) -fvisibility=hidden -fvisibility-functions=default
, that'd be perfect.
Compiling with gcc's -fvisibility=hidden
will give all non-annotated symbols hidden visibility.
So I thought I'd promote the functions that have global scope and hidden visibility in the ELF objects to default (global) visibility using a linker version-script.
However it seems like GNU LD doesn't change the visibility attribute when a symbol is named in the linker version-script global
section. Symbols in the local
section get hidden, but already-hidden symbols in the global
section don't get promoted to default
.
SCCCE
Given simplified object (macros expanded, etc):
/* Public global variables are annotated */extern int exportvar __attribute__ ((visibility ("default")));int exportvar;/* Internal ones are not annotated */extern int nonexportvar;int nonexportvar;/* Nor are static vars obviously */static int nonexportvar;/* For various weird reasons we can't annotate functions with visibility information */extern void exportfunc(void);void exportfunc(void) {};static void staticfunc(void) {};
with linker script:
Linker script snippet linker-script
:
{ global: exportfunc; exportvar; local: *; };
built using Makefile
(de-tabified for easy copy/paste):
.RECIPEPREFIX=~USE_LLVM?=0VERBOSE_SYMS?=EXTRA_CFLAGS?=LINK_FLAGS?=# Don't optimise so we retain the unused statics etc in this# demo code.EXTRA_CFLAGS+=-O0ifneq (,$(LINKER_SCRIPT))LINK_FLAGS+=--version-script=$(LINKER_SCRIPT)endififeq (1,$(USE_LLVM))CC=clang -c $(EXTRA_CFLAGS)LINK_SHARED=ld.lld -shared $(LINK_SHARED)elseCC=gcc -c $(EXTRA_CFLAGS)COMMA:=,LINK_SHARED=gcc -shared $(addprefix -Wl$(COMMA),$(LINK_FLAGS))endifall: clean demo.so dumpsymsclean:~ @rm -f demo.o demo.sodemo.o: demo.c~ $(CC) -o $@ $<demo.so: demo.o~ $(LINK_SHARED) -o $@ $<# Show object file and full symbol table if VERBOSE_SYMS=1ifneq (,$(VERBOSE_SYMS))dumpsyms: demo.o demo.so~ @echo~ @echo "demo.o:"~ @readelf --syms demo.o | egrep '(Symbol table|exportvar|nonexportvar|staticvar|exportfunc|nonexportfunc)'~ @echo~ @echo "demo.so:"~ @readelf --syms demo.so | egrep '(Symbol table|exportvar|nonexportvar|staticvar|exportfunc|staticfunc)'~ @echoelse# Only show dynamic symbols by defaultdumpsyms: demo.o demo.so~ @echo~ @echo "demo.so:"~ @readelf --dyn-syms demo.so | egrep '(Symbol table|exportvar|nonexportvar|staticvar|exportfunc|nonexportfunc)'~ @echoendif
With default flags (no visibility, no linker script)
$ makegcc -c -O0 -o demo.o demo.cgcc -Wall -O0 -shared -o demo.so demo.odemo.so:Symbol table '.dynsym' contains 8 entries: 5: 00000000000010f9 7 FUNC GLOBAL DEFAULT 11 exportfunc 6: 0000000000004028 4 OBJECT GLOBAL DEFAULT 21 nonexportvar 7: 0000000000004024 4 OBJECT GLOBAL DEFAULT 21 exportvar
With linker script and default visibility
make LINKER_SCRIPT=linker-scriptgcc -c -O0 -o demo.o demo.cgcc -Wall -O0 -Wl,--version-script=linker-script -shared -o demo.so demo.odemo.so:Symbol table '.dynsym' contains 7 entries: 5: 0000000000004024 4 OBJECT GLOBAL DEFAULT 21 exportvar 6: 00000000000010f9 7 FUNC GLOBAL DEFAULT 11 exportfunc
nonexportvar
has vanished from the dynamic symbol table as expected.
With -fvisibility=hidden
and no linker script
$ make EXTRA_CFLAGS="-fvisibility=hidden"gcc -c -fvisibility=hidden -o demo.o demo.cgcc -Wall -fvisibility=hidden -shared -o demo.so demo.odemo.so:Symbol table '.dynsym' contains 6 entries: 5: 0000000000004024 4 OBJECT GLOBAL DEFAULT 21 exportvar
exportfunc
is not visible in the export symbol table. That's expected, since no linker script would override visibility.
With -fvisibility=hidden
and linker script
Can we use the linker script to make the hidden function symbols visible?
$ make EXTRA_CFLAGS="-fvisibility=hidden" LINKER_SCRIPT=linker-scriptgcc -c -fvisibility=hidden -o demo.o demo.cgcc -Wall -fvisibility=hidden -Wl,--version-script=linker-script -shared -o demo.so demo.odemo.so:Symbol table '.dynsym' contains 6 entries: 5: 0000000000004024 4 OBJECT GLOBAL DEFAULT 21 exportvar
... it seems not.
Why?
$ make EXTRA_CFLAGS="-fvisibility=hidden" LINKER_SCRIPT=linker-script VERBOSE_SYMS=1gcc -c -fvisibility=hidden -o demo.o demo.cgcc -Wall -fvisibility=hidden -Wl,--version-script=linker-script -shared -o demo.so demo.odemo.o:Symbol table '.symtab' contains 13 entries: 5: 0000000000000008 4 OBJECT LOCAL DEFAULT 3 staticvar 10: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 exportvar 11: 0000000000000004 4 OBJECT GLOBAL HIDDEN 3 nonexportvar 12: 0000000000000000 7 FUNC GLOBAL HIDDEN 1 exportfuncdemo.so:Symbol table '.dynsym' contains 6 entries: 5: 0000000000004024 4 OBJECT GLOBAL DEFAULT 21 exportvarSymbol table '.symtab' contains 52 entries: 33: 000000000000402c 4 OBJECT LOCAL DEFAULT 21 staticvar 39: 00000000000010f9 7 FUNC LOCAL DEFAULT 11 exportfunc 42: 0000000000004028 4 OBJECT LOCAL DEFAULT 21 nonexportvar 50: 0000000000004024 4 OBJECT GLOBAL DEFAULT 21 exportvar
It looks like GNU ld
turned GLOBAL HIDDEN
symbols in the .o
into LOCAL DEFAULT
symbols in the .so
.
The linker script appears to have no effect here; the result is the same with or without it.
Is there any way I can tell the linker version-script to promote the HIDDEN
symbols in the global
section to DEFAULT
visibility?
Things I can't do
I cannot unfortunately drop the requirement for functions to be visible by default, while all other symbols should be visible only when explicitly annotated as such. I need to match the behaviour of a Windows build that uses a generated .def
file to export all functions while using __declspec__("dllexport")
to export only selected other symbols.
Due to codebase size, complexity, shared control, etc I can't go through and annotate every function with a suitable attribute macro.
I can't enumerate all symbols to be exported manually and maintain a handwritten linker script; the codebase has too many different configurations, versions and build options.
I could use a different widely-available compiler and toolchain if that'd help though, and toolchain versions are not a problem. I've tried using LLVM's clang
and ld.lld
with identical results.
Help? Ideas?