Связать статическую библиотеку с общей библиотекой и скрыть экспортированные символы

У меня возникает раздражающая проблема с компоновщиком. Я хочу связать некоторые символы из общей библиотеки со статической библиотекой, но не экспортировать ее символы (т.е. Я не могу просто объединить библиотеки или ссылку с --whole-archive). Я хочу ссылку (как, например, ссылку на исполняемый файл, решение undefined символов) мою общую библиотеку на статическую и удалить символы undefined.

То, что я ищу, вероятно, просто вариант компоновщика, но я не могу на него наложить.

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

EDIT: проблема решена, решение опубликовано в нижней части вопроса

Краткое описание:

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

Эта сторонняя библиотека также содержит символы из еще одной библиотеки, которую я также использую в своей библиотеке, но с другой (несовместимой) версией.

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

Описание упрощенного описания

У меня есть сторонняя библиотека под названием libext.so, для которой у меня нет исходного кода. Это определяет функцию bar и использует функцию foo из другой библиотеки, но символы оба определены там:

$> nm libext.so
0000000000000a16 T bar
00000000000009e8 T foo

Как я уже говорил, foo является внешней зависимостью, для которой я хочу использовать более новую версию. У меня есть обновленная библиотека для этого, позвоните ему libfoo.a:

$> nm libfoo.a
0000000000000000 T foo

Теперь проблема в том, что я хочу создать динамическую библиотеку, которая переопределяет bar, но я хочу, чтобы моя библиотека использовала определение foo из libfoo.a и мне нужны функции из libext.so для вызова функции foo из libext.so. Другими словами, я хочу, чтобы временная привязка моей библиотеки к libfoo.a.

То, что я ищу, - это определить библиотеку, которая использует libfoo.a, но не экспортирует свои символы. Если я свяжу свою библиотеку с libfoo.a, я получаю:

$> nm libmine.so
0000000000000a78 T bar
0000000000000b2c T foo

Это означает, что я перегружаю как foo, так и bar (я не хочу переопределять foo). Если я не связываю свою библиотеку с libfoo.a, я получаю:

$> nm libmine.so
0000000000000a78 T bar
                 U foo

Таким образом, моя библиотека будет использовать свою версию foo, которую я тоже не хочу. Я хочу:

$> nm libmine.so
0000000000000a78 T bar

Где foo связано во время компиляции, а его символ не экспортируется.

Минимальный пример

Вам не нужно читать это, но вы можете использовать его для поиска и поиска решения.

bar.cpp: представляет стороннее приложение. У меня нет кода для:

#include <iostream>
extern "C" void foo(){ std::cerr << "old::foo" << std::endl; }
extern "C" void bar(){ std::cerr << "old::bar" << std::endl; foo(); }

foo.cpp: представляет собой более новую версию функции, используемой как моей, так и третьей стороной:

#include <iostream>
extern "C" void foo(){ std::cerr << "new::foo" << std::endl; }

trap.cpp: код из моей библиотеки, он ловушки bar, вызывает новый foo и вперед:

#include <iostream>
extern "C" {
  #include <dlfcn.h>
}
extern "C" void foo();
extern "C" void bar(){
  std::cerr << "new::bar" << std::endl;
  foo(); // Should be new::foo
  void (*fwd)() = (void(*)())dlsym(RTLD_NEXT, "bar");
  fwd(); // Should use old::foo
}

exec.cpp: фиктивный исполняемый файл для вызова bar:

extern "C" void bar();

int main(){
  bar();
}

Makefile: только Unix, извините

default:
    # The third party library
    g++ -c -o bar.o bar.cpp -fpic
    gcc -shared -Wl,-soname,libext.so -o libext.so bar.o
    # The updated library
    g++ -c -o foo.o foo.cpp -fPIC
    ar rcs libfoo.a foo.o
    # My trapping library
    g++ -c -o trap.o trap.cpp -fPIC
    gcc -shared -Wl,-soname,libmine.so -o libmine.so trap.o -ldl -L. -lfoo
    # The dummy executable
    g++ -o test exec.cpp -L. libext.so

В этом случае bar вызывает foo; нормальное выполнение:

$> ./test
old::bar
old::foo

Предварительная загрузка моей библиотеки перехватывает bar, вызывает мои foo и forward bar, текущее выполнение:

$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
new::foo

Последняя строка неверна, нужный результат:

$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
old::foo

Решение

1) Как указано в принятом ответе, мы можем использовать версию компоновщика script, чтобы изменить область нежелательных символов от глобального к локальному:

BAR {
  global: bar;
  local: *;
};

Компиляция с версией компоновщика показывает, что foo является локальным, и программа теперь ведет себя так, как ожидалось:

$> gcc -shared -Wl,-soname,libmine.so -Wl,--version-script=libmine.version -o libmine.so trap.o -ldl -L. -lfoo
$> nm libmine.so
0000000000000978 T bar
0000000000000000 A BAR  
0000000000000a2c t foo
$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
old::foo

2) Альтернативой является повторная компиляция libfoo.a с атрибутом -fvisibility=hidden и ссылка на него. Видимость экспортируемых символов также локальна, и поведение такое же, как указано выше.

Ответ 1

Вы хотите использовать версию компоновщика script, которая экспортирует нужные символы (bar здесь) и скрывает все остальное.

Пример здесь.