使用 gnu ld 链接器脚本覆盖隐藏的符号可见性

Cra*_*ger 5 linker gcc llvm ld linker-scripts

TL;DR:我可以使用 GNUld链接器--version-script或其他一些方法将具有hidden可见性(由于-fvisibility=hidden或显式__attribute__)的选定符号提升回default可见性,以便它们在共享库的全局符号表中可用?

有什么方法可以告诉 gnu ld version-scriptHIDDENglobal:version-script 部分中的符号提升为DEFAULT可见性?


对于一个相当奇怪的系统,我需要链接一个共享库,以便所有非静态函数符号都具有默认可见性,但所有其他符号都获得“隐藏”可见性,除非用适当的可见性属性明确注释。

如果有办法告诉gcc类似(无效的虚构语法)-fvisibility=hidden -fvisibility-functions=default,那就太完美了。

使用 gcc 编译 -fvisibility=hidden将使所有未注释的符号隐藏可见性。

所以我想我会使用链接器版本脚本将在 ELF 对象中具有全局范围和隐藏可见性的函数提升为默认(全局)可见性。

但是,当在链接器版本脚本global部分中命名符号时,GNU LD 似乎不会更改可见性属性。该local部分中的符号被隐藏,但该部分中已经隐藏的符号global不会被提升为default.

中商会

给定简化对象(宏扩展等):

/* 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) {
};
Run Code Online (Sandbox Code Playgroud)

使用链接脚本:

链接器脚本片段linker-script

{ 
  global:
    exportfunc;
    exportvar;
 local: 
   *; 
};
Run Code Online (Sandbox Code Playgroud)

构建使用Makefile(去标签化以便于复制/粘贴):

.RECIPEPREFIX=~

USE_LLVM?=0

VERBOSE_SYMS?=
EXTRA_CFLAGS?=
LINK_FLAGS?=

# Don't optimise so we retain the unused statics etc in this
# demo code.
EXTRA_CFLAGS+=-O0

ifneq (,$(LINKER_SCRIPT))
LINK_FLAGS+=--version-script=$(LINKER_SCRIPT)
endif

ifeq (1,$(USE_LLVM))
CC=clang -c $(EXTRA_CFLAGS)
LINK_SHARED=ld.lld -shared $(LINK_SHARED)
else
CC=gcc -c $(EXTRA_CFLAGS)
COMMA:=,
LINK_SHARED=gcc -shared $(addprefix -Wl$(COMMA),$(LINK_FLAGS))
endif

all: clean demo.so dumpsyms

clean:
~ @rm -f demo.o demo.so

demo.o: demo.c
~ $(CC) -o $@ $<

demo.so: demo.o
~ $(LINK_SHARED) -o $@ $<

# Show object file and full symbol table if VERBOSE_SYMS=1
ifneq (,$(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)'
~ @echo
else
# Only show dynamic symbols by default
dumpsyms: demo.o demo.so
~ @echo
~ @echo "demo.so:"
~ @readelf --dyn-syms demo.so | egrep '(Symbol table|exportvar|nonexportvar|staticvar|exportfunc|nonexportfunc)'
~ @echo
endif
Run Code Online (Sandbox Code Playgroud)

使用默认标志(不可见,没有链接器脚本)

$ make
gcc -c  -O0  -o demo.o demo.c
gcc -Wall  -O0 -shared -o demo.so demo.o

demo.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
Run Code Online (Sandbox Code Playgroud)

使用链接器脚本和默认可见性

make LINKER_SCRIPT=linker-script
gcc -c  -O0 -o demo.o demo.c
gcc -Wall  -O0  -Wl,--version-script=linker-script -shared -o demo.so demo.o

demo.so:
Symbol table '.dynsym' contains 7 entries:
     5: 0000000000004024     4 OBJECT  GLOBAL DEFAULT   21 exportvar
     6: 00000000000010f9     7 FUNC    GLOBAL DEFAULT   11 exportfunc
Run Code Online (Sandbox Code Playgroud)

nonexportvar 已按预期从动态符号表中消失。

-fvisibility=hidden和没有链接器脚本

$ make EXTRA_CFLAGS="-fvisibility=hidden"
gcc -c -fvisibility=hidden -o demo.o demo.c
gcc -Wall -fvisibility=hidden  -shared -o demo.so demo.o

demo.so:
Symbol table '.dynsym' contains 6 entries:
     5: 0000000000004024     4 OBJECT  GLOBAL DEFAULT   21 exportvar
Run Code Online (Sandbox Code Playgroud)

exportfunc在导出符号表中不可见。这是意料之中的,因为没有链接描述文件会覆盖可见性。

随着-fvisibility=hidden和链接脚本

我们可以使用链接描述文件使隐藏的函数符号可见吗?

$ make EXTRA_CFLAGS="-fvisibility=hidden" LINKER_SCRIPT=linker-script
gcc -c -fvisibility=hidden -o demo.o demo.c
gcc -Wall -fvisibility=hidden  -Wl,--version-script=linker-script -shared -o demo.so demo.o

demo.so:
Symbol table '.dynsym' contains 6 entries:
     5: 0000000000004024     4 OBJECT  GLOBAL DEFAULT   21 exportvar
Run Code Online (Sandbox Code Playgroud)

……好像没有。

为什么?

$ make EXTRA_CFLAGS="-fvisibility=hidden" LINKER_SCRIPT=linker-script VERBOSE_SYMS=1
gcc -c -fvisibility=hidden -o demo.o demo.c
gcc -Wall -fvisibility=hidden  -Wl,--version-script=linker-script -shared -o demo.so demo.o

demo.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 exportfunc

demo.so:
Symbol table '.dynsym' contains 6 entries:
     5: 0000000000004024     4 OBJECT  GLOBAL DEFAULT   21 exportvar
Symbol 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
Run Code Online (Sandbox Code Playgroud)

它看起来像GNUld转向GLOBAL HIDDEN符号在.o进入LOCAL DEFAULT的符号.so

链接描述文件在这里似乎没有效果;无论有没有它,结果都是一样的。

有什么办法可以告诉链接器版本脚本HIDDENglobal部分中的符号提升为DEFAULT可见性吗?

我不能做的事情

不幸的是,我不能放弃默认情况下对函数可见的要求,而所有其他符号只有在明确注释时才应该可见。我需要匹配 Windows 构建的行为,该构建使用生成的.def文件在使用时导出所有函数__declspec__("dllexport")仅导出选定的其他符号。

由于代码库的大小、复杂性、共享控制等,我无法通过合适的属性宏来注释每个函数。

我无法枚举所有要手动导出的符号并维护一个手写的链接器脚本;代码库有太多不同的配置、版本和构建选项。

如果有帮助,我可以使用不同的广泛可用的编译器和工具链,并且工具链版本不是问题。我试过使用 LLVMclangld.lld 得到相同的结果。

帮助?想法?