Removing Unused Dependencies
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 an 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 -B direct -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 -B direct -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 -B direct -z guidance -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.