An explicit, shared object dependency is one that is defined on the command line, either using the path name, or more commonly by using the –l option. Explicit dependencies include those that might be provided by the compiler drivers, such as –lc.
Implicit dependencies are the dependencies of explicit dependencies. Implicit dependencies can be processed as part of a link-edit to complete the closure of all symbol resolution. This symbol closure ensures that the object being built is self-contained, with no unreferenced symbols remaining.
All dynamic objects should define the dependencies they require. This requirement is enforced by default when building a dynamic executable, but not when building a shared object. Use the –z defs option to enforce this requirement when building a shared object.
All dynamic objects should refrain from defining dependencies that they do not require. Loading such unused dependencies at runtime is unnecessary and wasteful.
An explicit dependency is determined to be unused if two conditions are true.
No global symbols that are provided by the dependency are referenced from the object being built.
The dependency does not compensate for the requirements of any implicit dependencies.
Unused dependencies are diagnosed with the –z guidance option. These dependencies should be removed from the link-edit. However, if removing these items is problematic, unused dependencies can be discarded from the object being built by using the –z discard-unused=dependencies option.
Unfortunately, shared objects exist that have not defined all the dependencies they require. In these cases, developers often add the missing dependencies to the executable, or other shared objects they are building, rather than rebuild the original dependency correctly. Such dependencies are referred to as compensating dependencies.
For example, consider a shared object, foo.so, that references the symbol bar() from the shared object bar.so. However, foo.so does not express a dependency upon bar.so. An inspection of foo.so reveals the lack of the required dependency, as the symbol bar() can not be found.
% ldd -r foo.so libc.so.1 => /lib/libc.so.1 symbol not found: bar (foo.so)
Now consider an application developer that wishes to create an executable that references the symbol foo() from the shared object foo.so. The required dependency upon foo.so is specified, but the link-edit of the executable fails.
% cc -Bdirect -o main main.c -L. -lfoo Undefined first referenced symbol in file bar ./libfoo.so ld: fatal: symbol referencing errors
The developer forcibly corrects this situation by adding a compensating dependency on bar.so.
% cc -Bdirect -o main main.c -L. -lfoo -lbar
This correction creates an application that loads all the necessary dependencies at runtime, and therefore appears to resolve the issue. However, the result is fragile. If a future delivery of foo.so is made that does not require a symbol from bar.so, then this application will load bar.so for no reason. The better solution is to correct foo.so by adding the missing dependency bar.so.
The occurrence of a compensating dependency is diagnosed though guidance.
% cc -Bdirect -zguidance -o main main.c -L. -lfoo -lbar ld: guidance: removal of compensating dependency recommended: libbar.so
Compensating dependencies are diagnosed through guidance, but they are not removed under –z discard-unused=dependencies. Although the dependency might be unused in relation to the object being created, the dependency is used by other components of the link-edit. To remove this dependency could result in creating an object that can not be executed at runtime.
The need for compensating dependencies can be eliminated by the systematic use of the –z defs option to build all dynamic objects.
The –z ignore and –z record options are positional options that can be used in conjunction with the –z discard-unused=dependencies option. These positional options turn the discard feature on and off selectively for targeted objects.