link a static library to a shared library and hide exported symbols
You want to use a linker version script, which exports the symbol(s) you want (bar
here) and hides everything else.
Example here.
Related videos on Youtube
Thibaut
Updated on October 09, 2022Comments
-
Thibaut over 1 year
I am having an annoying problem with the linker. I want to link some symbols from a shared library to a static library, but not export its symbols (ie, I cannot simply merge the libraries or link with
--whole-archive
). What I want is link (as in, like linking an executable, solving undefined symbols) my shared library to a static one and remove the undefined symbols.The thing I am looking for is probably just a linker option but I can't put my finger on it.
I'll try to describe the problem the best I can (it's not that easy) and then provide a toy minimal example to play with.
EDIT: The problem has been solved, solution posted at the bottom of the question
Quick description:
I want to use the
LD_PRELOAD
trick to trap some function calls in an executable. This executable is linked against a third party shared library, which contains the function definition of the functions I want to trap.This third party library also contains symbols from yet another library, which I am also using in my library, but with a different (non-compatible) version.
What I want to do is compile my shared library and link it at compile time with the definitions of the last (static) library, without exporting the symbols, so that my shared library uses a different version from the one I want to trap.
Simplified problem description
I have a third party library called
libext.so
, for which I don't have the source code. This defines a functionbar
and uses a functionfoo
from another library, but the symbols are both defined there:$> nm libext.so 0000000000000a16 T bar 00000000000009e8 T foo
As I mentioned,
foo
is an external dependency, for which I want to use a newer version. I have an updated library for it, let's call itlibfoo.a
:$> nm libfoo.a 0000000000000000 T foo
Now the problem is that I want to create a dynamic library which re-defines
bar
, but I want my library to use the the definition offoo
fromlibfoo.a
and i want the functions fromlibext.so
to call the functionfoo
fromlibext.so
. In other words, I want a compile time linkage of my library tolibfoo.a
.What I am looking for is define a library which uses
libfoo.a
but doesn't export its symbols. If I link my library tolibfoo.a
, I get:$> nm libmine.so 0000000000000a78 T bar 0000000000000b2c T foo
Which means I overload both
foo
andbar
(i don't want to overridefoo
). If i don't link my library tolibfoo.a
, I get:$> nm libmine.so 0000000000000a78 T bar U foo
So my library will use their version of
foo
, which I don't want either. What I want is:$> nm libmine.so 0000000000000a78 T bar
Where
foo
is linked at compile time and its symbol not exported.Minimal example
You don't need to read this, but you can use it to play around and find a solution.
bar.cpp
: represents the third party app I don't have the code for:#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
: represents a newer version of a function used by both my lib and the third party:#include <iostream> extern "C" void foo(){ std::cerr << "new::foo" << std::endl; }
trap.cpp
: the code from my library, it trapsbar
, calls the newfoo
and forwards:#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
: a dummy executable to callbar
:extern "C" void bar(); int main(){ bar(); }
Makefile
: Unix only, sorrydefault: # 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
In this case,
bar
callsfoo
; the normal execution is:$> ./test old::bar old::foo
Preloading my library intercepts
bar
, calls myfoo
and forwardsbar
, the current execution is:$> LD_PRELOAD=libmine.so ./test new::bar new::foo old::bar new::foo
The last line is wrong, the desired output is:
$> LD_PRELOAD=libmine.so ./test new::bar new::foo old::bar old::foo
Solution
1) As pointed out in the accepted answer, we can use a linker version script to change the scope of the undesired symbols from global to local:
BAR { global: bar; local: *; };
Compiling with the linker version shows
foo
is local and the program now behaves as expected:$> 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) An alternative is to re-compile
libfoo.a
with the attribute-fvisibility=hidden
and link against that. The visibility of the exported symbols is then local too, and the behavior is the same as above. -
Thibaut about 10 yearsThat worked. It's weird that there is no linker option to lower the visibility of the linked dependencies directly at link time.