У меня возникает раздражающая проблема с компоновщиком. Я хочу связать некоторые символы из общей библиотеки со статической библиотекой, но не экспортировать ее символы (т.е. Я не могу просто объединить библиотеки или ссылку с --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
и ссылка на него. Видимость экспортируемых символов также локальна, и поведение такое же, как указано выше.