Переопределение СКРЫТОЙ видимости символов с помощью скрипта компоновщика gnu ld

TL;DR: Могу ли я использовать ld компоновщик GNU --version-script или какой-либо другой метод, чтобы повысить видимость выбранных символов с hidden (из-за -fvisibility=hidden или явного __attribute__) до default видимости, чтобы они были доступны в глобальная таблица символов общей библиотеки?

Могу ли я каким-либо образом сказать gnu ld version-script повысить видимость символов HIDDEN в разделе global: version-script до DEFAULT видимости?


Для довольно странной системы мне нужно связать общую библиотеку, чтобы все нестатические функциональные символы имели видимость по умолчанию, но все остальные символы получали «скрытую» видимость, если явно не аннотированы соответствующими атрибутами видимости.

Если бы был способ сказать gcc что-то вроде (недопустимый воображаемый синтаксис) -fvisibility=hidden -fvisibility-functions=default, это было бы идеально.

Компиляция с помощью gcc -fvisibility=hidden сделает видимыми все неаннотированные символы.

Поэтому я подумал, что буду продвигать функции, которые имеют глобальную область видимости и скрытую видимость в объектах ELF, до видимости по умолчанию (глобальной), используя скрипт версии компоновщика.

Однако похоже, что GNU LD не меняет атрибут видимости, когда символ указан в разделе global версии сценария компоновщика. Символы в разделе local скрываются, но уже скрытые символы в разделе global не повышаются до default.

SCCCE

Учитывая упрощенный объект (расширенные макросы и т. д.):

/* 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) {
};

со скриптом компоновщика:

Фрагмент скрипта компоновщика linker-script:

{ 
  global:
    exportfunc;
    exportvar;
 local: 
   *; 
};

построено с использованием 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

С флагами по умолчанию (нет видимости, нет скрипта компоновщика)

$ 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

Со скриптом компоновщика и видимостью по умолчанию

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

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

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

... вроде нет.

Почему?

$ 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

Похоже, что GNU ld превратил GLOBAL HIDDEN символов в .o в LOCAL DEFAULT символов в .so.

Сценарий компоновщика здесь не работает; результат один и тот же с ним или без него.

Могу ли я каким-либо образом указать скрипту версии компоновщика повысить видимость символов HIDDEN в разделе global до DEFAULT видимости?

Вещи, которые я не могу сделать

К сожалению, я не могу отказаться от требования, чтобы функции были видны по умолчанию, в то время как все остальные символы должны быть видны только тогда, когда они явно аннотированы как таковые. Мне нужно соответствовать поведению сборки Windows, которая использует сгенерированный файл .def для экспорта всех функций, а использует __declspec__("dllexport") для экспорта только выбранных других символов.

Из-за размера кодовой базы, сложности, совместного управления и т. д. я не могу просмотреть и аннотировать каждую функцию подходящим макросом атрибутов.

Я не могу перечислить все символы, которые нужно экспортировать вручную, и поддерживать рукописный скрипт компоновщика; кодовая база имеет слишком много разных конфигураций, версий и вариантов сборки.

Я мог бы использовать другой широкодоступный компилятор и цепочку инструментов, если это поможет, и версии цепочки инструментов не являются проблемой. Я пробовал использовать LLVM clang и ld.lld с идентичными результатами.

Помощь? Идеи?


person Craig Ringer    schedule 05.06.2020    source источник
comment
Я также пытался использовать __attribute__(section(".globals"))) для перемещения явно экспортированных переменных в отдельный раздел ELF, но это не работает, потому что тегируются как инициализированные, так и неинициализированные, константные и неконстантные переменные. Итак, gcc задыхается от var1 causes a section type conflict with var2. В любом случае, было бы больно сопоставлять.   -  person Craig Ringer    schedule 05.06.2020
comment
Связано: stackoverflow.com/q/8129782/398670   -  person Craig Ringer    schedule 05.06.2020