Issue
My situation is as follows: our program depends on a ton of shared (and static) libraries. I want to add a new dependency, which is a static library. The program compiles with no issues, but it crashes during runtime. As it turns out, the new static library defines a symbol foo
which is available in one of the many shared libraries. So the linker links to the shared lib's foo
instead of the new static library's foo
-- then the wrong foo
is called during runtime, causing the crash.
I created a minimal example to emulate this: https://gitlab.com/luizromario/linker_example
There, we have:
- A lib called
libstatic_old
, containing a single functionprint_thing()
which printsold static lib
- A lib called
libstatic_new
, containing a single functionprint_thing()
which printsnew static lib
- A shared lib called
libdynamic
that links tolibstatic_old
. It contains a single functiondo_things()
which:- Prints the following message:
about to print thing from dynamic lib (should print "old static lib"):
- Calls
print_thing()
- Prints the following message:
- An executable called
executable
that links to libdynamic and libstatic_new. It:- Prints:
about to do things from executable
- Calls
do_things()
- Prints:
about to print thing from executable (should print "new static lib"):
- Calls
print_thing()
- Prints:
If I link dynamic
first, then static_new
, this is the output:
about to do things from executable
about to print thing from dynamic lib (should print "old static lib"): old static lib
about to print thing from executable (should print "new static lib"): old static lib
If I link static_new
first, then dynamic
, this is the output:
about to do things from executable
about to print thing from dynamic lib (should print "old static lib"): new static lib
about to print thing from executable (should print "new static lib"): new static lib
In both cases, I'm not able to manage telling the linker that, for the executable, it should look for print_thing
in static_new
and, for the shared lib, that it should look for print_thing
in static_old
. Even though we have the code for static_new
baked into the executable (right?) and the code for static_old
baked into the shared lib, the linker can only link, for the entire executable, to either one or the other.
Of course, I could simply not link to two different libraries that define the same symbol, but unfortunately, in the real scenario, the shared lib is a prebuilt binary which I cannot build again. So, when compiling executable
, is there a way I can tell the linker not to look for print_thing
in libdynamic
? Or some way to remove the print_thing
symbol from libdynamic
?
EDIT: someone else described a similar issue here: Linux/C++ shared libaries: Can I edit the sybol table, i.e. which symbols are exported?
I might try that, but I'd really prefer not to edit the shared library.
EDIT 2: after some trial and error, I managed to edit one of the print_thing
s in the libdynamic.so
binary into print_thong
and that did the trick:
about to do things from executable
about to print thing from dynamic lib (should print "old static lib"): old static lib
about to print thing from executable (should print "new static lib"): new static lib
That's really unreliable, though, and I can't simply find and replace all print_thing
s in the binary because there's another print_thing
in the binary which I can't edit (otherwise the execution fails):
about to do things from executable
./executable: symbol lookup error: /home/c/luizromario/local/linker_example/build/libdynamic.so: undefined symbol: print_thong
I'd still prefer to tell the linker not to look for print_thing
inside of libdynamic
EDIT 3: I might be inching closer to a solution.
I found out about the --exclude-libs
linker option and used it like so:
target_link_options(dynamic PRIVATE "-Wl,--exclude-libs,libstatic_old.a")
The end result is exactly what I want:
about to do things from executable
about to print thing from dynamic lib (should print "old static lib"): old static lib
about to print thing from executable (should print "new static lib"): new static lib
Unfortunately, I need a non-invasive solution since, as I mentioned, I can't rely on being able to recompile the dynamic lib
Solution
Looking into ld
's manual, I noticed the following option:
--exclude-libs lib,lib,...
Specifies a list of archive libraries from which symbols should not
be automatically exported. The library names may be delimited by
commas or colons. Specifying "--exclude-libs ALL" excludes symbols
in all archive libraries from automatic export.
I managed to use it to do what I want. It's tricky, but it works reliably enough and I didn't need to tamper with libdynamic.so
.
- Add the
--exclude-libs
link option to the executable:
target_link_options(executable PRIVATE "-Wl,--exclude-libs,ALL")
- Link to
static_new
first, then link todynamic
.
target_link_libraries(executable static_new dynamic)
Done!
about to do things from executable
about to print thing from dynamic lib (should print "old static lib"): old static lib
about to print thing from executable (should print "new static lib"): new static lib
Note: The link order matters. Linking dynamic
first then static_new
will make the execution fail:
about to do things from executable
about to print thing from dynamic lib (should print "old static lib"): old static lib
about to print thing from executable (should print "new static lib"): old static lib
What's going on
From what I understand, --exclude-libs ALL
tells the linker to, for each linked library, exclude every symbol the linked library exports. So, in my example, what ld
is doing is:
- Link
executable
tostatic_new
- The call to
print_thing
inexecutable
will point tostatic_new
'sprint_thing
- The call to
- Exclude all symbols exported by
static_new
from the resultingexecutable
print_thing
is no longer available in theexecutable
binary
- Link
executable
todynamic
- The call to
print_thing
will certainly point todynamic
'sprint_thing
, since we've excludedstatic_new
'sprint_thing
in the previous step
- The call to
- Exclude all symbols exported by
dynamic
Why the order matters
Linking dynamic
first fails because ld
will then do the following:
- Link
executable
todynamic
- Since
dynamic
exports bothprint_thing
(fromstatic_old
) anddo_thing
, both references to these symbols inexecutable
will point to the code baked intolibdynamic.so
- Since
- Exclude all symbols exported by
dynamic
from the resultingexecutable
- It doesn't matter,
print_thing
has already been linked
- It doesn't matter,
- Link
executable
tostatic_new
- Nothing happens,
print_thing
was already defined in step 1.
- Nothing happens,
- Exclude all symbols exported by
static_new
Unfortunately, I still haven't managed to solve the problem in my original project because of issues with link ordering, but I'm closing this because the specific matter of linking has been solved.
Answered By - Romário Answer Checked By - Cary Denson (WPSolving Admin)