У меня раздражающая проблема с компоновщиком. Я хочу связать некоторые символы из общей библиотеки со статической библиотекой, но не экспортировать ее символы (т.е. я не могу просто объединить библиотеки или связать их с --whole-archive
). Я хочу связать (например, связать исполняемый файл, решить неопределенные символы) мою разделяемую библиотеку со статической и удалить неопределенные символы.
То, что я ищу, — это, вероятно, просто опция компоновщика, но я не могу это понять.
Я постараюсь описать проблему как можно лучше (это не так просто), а затем приведу минимальный игрушечный пример для игры.
РЕДАКТИРОВАТЬ: проблема была решена, решение размещено в нижней части вопроса
Краткое описание:
Я хочу использовать 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
и вперед 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) Как указано в принятом ответе, мы можем использовать скрипт версии компоновщика, чтобы изменить область действия нежелательных символов с глобального на локальный:
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
и ссылка против этого. Видимость экспортированных символов также локальна, и поведение такое же, как и выше.
Вы хотите использовать скрипт версии компоновщика, который экспортирует символы, которые вы хотите (bar
здесь) и шкуры все остальное.
пример Вот.
Других решений пока нет …