I’ve been chasing some linker errors in a fairly complex product, and it’s pretty crazy what I’ve ran it down to. Below is a very minimal example that exhibits the behavior that concerns me. I am trying to understand this behavior. If the change in behavior is intentional can it be traced to a gcc changelog so I can rely on it in the future?
This is really a link ordering problem, and it appears that linkers of different versions behave differently in unexpected ways.
First shared library (libtest1.so):
void test2();
void test1()
{
test2();
}
Second shared library (libtest2.so):
#include <iostream>
void test2()
{
std::cout << “calling test 2” << std::endl;
}
Main program:
#include “test1.hpp”
int main()
{
test1();
}
When I build the shared libs, I don’t make one library depend on the other. I’m just going to leave unresolved symbols in test1, and then put both test1 and test2 in the link settings on compilation of main. Don’t worry about rpath and LD_LIBRARY_PATH kinds of things; those don’t matter in the discussion.
Using gcc 7.5.0
g++ -std=c++11 main.cpp -o main -L. -ltest1 -ltest2 //compiles OK
g++ -std=c++11 main.cpp -o main -L. -ltest2 -ltest1 //link fails; symbol test2 is unresolved
// This makes 100% good sense to me. The linker is a single pass linker and order matters.
// We know and understand this.
Using gcc 8.3.0 or gcc 9.2.0
g++ -std=c++11 main.cpp -o main -L. -ltest1 -ltest2 //compiles OK
g++ -std=c++11 main.cpp -o main -L. -ltest2 -ltest1 //compiles OK
// This totally breaks my brain!!! This makes it seem like the linker in the newer gcc toolchains
// is a multi pass linker which is doing a lot more work to find the symbols that are needed to
// correctly link. This seems like a HUGE change in the way the linker works. However, I have
// scoured the change logs for the compilers and I haven’t been able to find any mention that the
// way the linker operates has changed in any significant way. This scares me and makes me worry
// that we can’t depend on this behavior consistently.
So what's the story here? I don't see how the more recent linker versions can succeed with making multiple passes of the object files. Has the gcc linker gotten smarter? If so, how much smarter and will it stay smarter moving forward? Any help you can provide would be greatly appreciated.
Both compiler are likely using the same linker, so the linker isn't getting any smarter. It's all in the linker options that the compiler passes to the linker by default. -v
is your friend.
These commands work identically in either version of the compiler.
g++ -Wl,-as-needed -std=c++11 main.cpp -o main -L. -ltest2 -ltest1 # fails
g++ -Wl,-no-as-needed -std=c++11 main.cpp -o main -L. -ltest2 -ltest1 #succeeds