GNU libtool is a generic library support script used by many software packages as a portable interface for creating shared libraries. In contrast to the conventional approach where the build process results in binary programs and libraries being created, libtool produces, along with each binary, a wrapper script which is used to invoke that binary from the source tree. The wrapper invokes the binary in such a way as to guarantee that shared objects from the local source tree are preferred over those already installed in the system.
It has been discovered that under certain conditions libtool
creates wrappers that prefer installed versions of the shared objects
over those built in the source tree. As a result, any tests run in
the source tree produce unreliable results.
This article describes two such cases. Minimal test suites are provided. Patches for libtool are proposed, both for the current stable version and the recent commit in the repository.
The first version of this article was published on December 9, 2019. The fixes were accepted by the libtool team in August 2024.
The issues were finally fixed in Libtool version 2.5.3, released on September 25, 2024.
The status if this article is historic.
Imagine a project consisting of at least one installable shared library (libltb1) and an installable binary program (ltb1), which is linked with that library. If the ‘ltb1_LDFLAGS’ variable contains one or more -Wl,-rpath linker options, the created wrapper script will prefer directories listed in them over locations in the build tree. If a previous version of the libltb1 library is already installed in one of these directories, the ltb1 wrapper will use the installed version of the library, instead of the one from the source tree. This leads to undesirable consequences, e.g. if the package provides a test suite, it will likely fail, since it will use the old library version.
The issue was observed when building GNU mailutils with MIT Kerberos. The Mailutils configure script uses the krb5-config tool to obtain the linker options needed for linking with the library. The tool is invoked as ‘krb5-config --libs’ and its output is then added to the ‘*_LDADD’ variable of several Makefile.am’s via the substitution variable. The output in question looks like:
$ krb5-config --libs -L/usr/lib64 -Wl,--enable-new-dtags -Wl,-rpath -Wl,/usr/lib64 -lkrb5 -lk5crypto -lcom_err
When the -Wl,-rpath option makes its way to the ‘*_LDADD’ variable, the wrapper scripts created for mailutils programs use libraries from the prior version of mailutils (installed in /usr/lib64).
The problem is attested on GNU/Linux and FreeBSD systems (see affected systems).
The problem is illustrated by a testcase available for download from ltbug-1.tar.gz. It consists of the following principal parts:
A library providing a single test function ‘ltb_version()’, which prints on stdout an identifier string defined at compile time.
A binary linked with libltb1.la and invoking ‘ltb_version()’.
The ltb1_LDFLAGS
variable contains the expansion of the variable
CF_LIB_FLAGS
, defined in configure.ac and containing
-Wl.-rpath -Wl,$(libdir).
libtool
2.4.6.
Unpack ltbug-1.tar.gz and run sh
ltbug-1/test.sh
. This test script does the following:
Workspace directory ltb-test1 is created in the current working directory. The installation directory with the same name is created in /tmp.
The identifier string ‘installed’ will be printed by the
ltb_version
function if called from the installed version of
the library.
The identifier string ‘local’ will be printed by the
ltb_version
function if the library from the local source tree
is used.
src/lbt1
.
The test produces the following output:
Building and installing project Building local copy of the project Running test program Test failed: --- expout 2019-12-13 11:46:13.489649518 +0200 +++ stdout 2019-12-13 11:46:13.517649520 +0200 @ -1 +1 @ -ltbug: local +ltbug: installed Keeping the directories in place. When no longer needed, run: rm -rf /tmp/ltb-test1 rm -rf ltb-test1
This shows that the installed copy of the library is used when the local wrapper src/ltb1 is run.
The test script leaves two directories for examination: the installation prefix directory /tmp/ltb-test1, and working directory ltb-test1. The latter contains the following files and directories:
A directory where the installed version of the package was built.
Local build directory. The test runs build_local/src/ltb1
.
Expected standard output.
Obtained standard output.
Standard output generated by the build process.
Standard output generated by the build process.
The test script can be used with the following command line options:
Set root directory for the installation prefix. The prefix directory is dir/ltb-test1.
Set test working directory root. Default is current working directory. The test directory name is constructed as dir/ltb-test1.
Keep test and installation directories for inspection. This is the default if the test fails.
Display short usage summary.
Let a project consist of two installable libraries and a binary (ltb2) linked to both of them. One of the libraries depends on an external library, which has a libtool archive installed along with the shared library. The figure below illustrates this scheme:
ltb2 <---+-- liba <==== libdir/libdep.la | +-- libb
Then, for GNU/Linux systems the wrapper script would issue the relink command that ends with the following options (split along multiple lines for readability):
-Wl,-rpath -Wl,srcdir/liba/.libs -Wl,-rpath -Wl,libdir -Wl,-rpath -Wl,srcdir/libb/.libs -Wl,-rpath -Wl,libdir
For FreeBSD, the LD_LIBRARY_PATH
used by the wrapper will look
like:
srcdir/liba/.libs:libdir:srcdir/libb/.libs:$LD_LIBRARY_PATH
In both cases, the libdir directory appearing before
libb will cause the libb library to be searched in
libdir first. If a prior version of the project is
installed in the same prefix as the libdep library, then the
ltb2
wrapper will use the version of liba from the
local source tree, but the version of libb installed on the
system.
The issue was initially discovered when building GNU mailutils on FreeBSD with support for OpenLDAP enabled. The latter incurred dependency on libldap, which was installed (along with libldap.la) in /usr/local/lib.
The package built in these conditions failed most tests because it was using mailutils libraries from prior version, installed in /usr/local/lib.
The issue has been confirmed to exist on GNU/Linux as well (see affected systems).
A minimal testcase is available for download from
ltbug-2.tar.gz. It consists of two subpackages:
ltb2dep implementing the dependency library, and the main
test package ltb2. The latter builds and installs two libraries:
liba and libb, and a test program ltb2
, linked
with both of these. The liba library provides the function
‘ltb2a_version’. The libb library implements a similar
function ‘ltb2b_version’. Both functions print on the standard
output the value of the ‘LTBUG_ID’ macro defined at the compile
time. The ltb2
program calls both functions in order.
The test script first builds and installs the dependency library
ltb2dep. Then, it builds the package with
LTBUG_ID="installed"
and installs it. When run, the installed
ltb2
program will output
ltb2a: installed ltb2b: installed
Finally, the test rebuilds the package with LTBUG_ID="local"
and runs the ltb2
program from the source tree. The program
then outputs:
ltb2a: local ltb2b: installed
This shows that the command uses libltb2a from the local source tree and libltb2b previously installed on the system.
Follow these steps to run the test:
tar xf ltbug-2.tar.gz
sh ltbug-2/test.sh
or
make -C ltbug-2 check
If the bug is present, you will see the following output:
Building dependency library Building and installing project Building local copy of the project Running test program Test failed: --- expout 2019-12-12 13:45:53.193782824 +0200 +++ stdout 2019-12-12 13:45:53.222782826 +0200 @ -1,2 +1,2 @ ltb2a: local -ltb2b: local +ltb2b: installed Keeping the directories in place. When no longer needed, run: rm -rf /tmp/ltb-test2 rm -rf ltb-test2 make: *** [check] Error 1
The directory ltb-test2 will be left on disk for examination. It will contain:
Build directory for the dependency library libltb2dep.
A directory where the installed version of the package was built.
Local build directory. The test runs build_local/src/ltb2
.
Expected standard output.
Obtained standard output.
Standard output generated by the build process.
Standard output generated by the build process.
The test script takes the same command line options as described above for the ltb1 test.
The following systems are known to be affected:
host-triplet: x86_64-pc-linux-gnu shell: /bin/sh compiler: gcc compiler flags: -g -O2 linker: /usr/x86_64-slackware-linux/bin/ld -m elf_x86_64 (gnu? yes) version: libtool (GNU libtool) 2.4.6 automake: automake (GNU automake) 1.15 autoconf: autoconf (GNU Autoconf) 2.69
host-triplet: x86_64-pc-linux-gnu shell: /bin/sh compiler: gcc compiler flags: -g -O2 linker: /usr/x86_64-slackware-linux/bin/ld -m elf_x86_64 (gnu? yes) version: libtool (GNU libtool) 2.4.6.44-b9b4 automake: automake (GNU automake) 1.15 autoconf: autoconf (GNU Autoconf) 2.69
host-triplet: amd64-portbld-freebsd12.0 shell: /bin/sh compiler: cc compiler flags: -O2 -pipe -fstack-protector-strong -fno-strict-aliasing linker: /usr/bin/ld (gnu? yes) version: libtool (GNU libtool) 2.4.6 automake: automake (GNU automake) 1.16.1 autoconf: autoconf (GNU Autoconf) 2.69
Patches are available both for the current stable and for the repository version of GNU libtool. Both patched versions successfully pass libtool tests as well as the ltb1 and ltb2 tests.
These patches were built over commit b9b44533fb. At the time of this writing (2024-06-11), they apply cleanly (with offset 28 lines) to version 2.4.7 (commit b9b44533fb).
The patches should be applied in order.
Cumulative patch: libtool-2.4.6-ltb1-2.patch.
Patch for ltmain.sh: ltmain.sh-2.4.6.patch
Cumulative patch: libtool-2.4.7-ltb1-2.patch.
Patch for ltmain.sh: ltmain.sh-2.4.7.patch
This document was generated on September 25, 2024 using makeinfo.
Copyright (C) 2019 — 2024 Sergey Poznyakoff
Content is available under GNU Free Documentation License 1.3.