Avoiding libtool minefields when cross-compiling

If you've ever tried to cross-compile a free software project for a different architecture on GNU/Linux, you may well have run into the situation where you compile a library with your cross toolchain, install it in a “staging” directory that will hold your root filesystem (for example, you compile the library to go into /usr/lib, but you install it in /tmp/rootfs/usr/lib, because that's where you're building your new root filesystem. You do this correctly: specify your --prefix=/usr when you configure, but provide DESTDIR=/tmp/rootfs when you make install), and you try to compile another library that links against that library. Everything goes fine (the author of the program may have even written his configure script properly, so you can configure it to use the correct toolchain), but at the link step, you get this:

/bin/sh libtool --mode=link target-gcc -c -O2 -o libbar.so ... -lfoo
target-gcc -c -O2 -o libbar.so ... /usr/lib/libfoo.so
/usr/lib/libfoo.so: could not read symbols: File in wrong format
collect2: ld returned 1 exit status

Now, why is the command trying to link against /usr/lib/libfoo.so, which is your system's version of libfoo, and not /tmp/rootfs/usr/lib/libfoo.so? You might play with compiler and linker flags, try to hack and understand the libtool shell script (best of luck if you can decipher it), and end up cursing the ancestors of those who ever came up with such a dumb system.

The problem turns out to be that libfoo and libbar in this example both use libtool, and that the prefix happens to conflict with the system's library locations; when libtool installs a library, you'll see it installs libfoo.la.

What's with that file? You may, reasonably, expect to see one or two different kinds of library file: libfoo.a, which is just an archive of object files, and which you can statically link into your program; and libfoo.so, a dynamically loadable, sharable library. What's a .la? Just cat one, and you'll see:

$ cat /usr/lib/libesd.la
# libesd.la - a libtool library file
# Generated by ltmain.sh - GNU libtool 1.5.6 (1.1220.2.95 2004/04/11 05:50:42) Debian: 224 $
#
# Please DO NOT delete this file!
# It is necessary for linking the library.

# The name that we can dlopen(3).
dlname='libesd.so.0'

# Names of this library.
library_names='libesd.so.0.2.36 libesd.so.0 libesd.so'

# The name of the static archive.
old_library='libesd.a'

# Libraries that this one depends upon.
dependency_libs=' -L/usr/lib /usr/lib/libaudiofile.la -lm'

# Version information for libesd.
current=2
age=2
revision=36

# Is this an already installed library?
installed=yes

# Should we warn about portability when linking against -modules?
shouldnotlink=no

# Files to dlopen/dlpreopen
dlopen=''
dlpreopen=''

# Directory that this library needs to be installed in:
libdir='/usr/lib'

Notice the last line? It says that libesd is installed in /usr/lib, so when libtool finds your libfoo.la in your temporary root filesystem tree, that file tells it to go look in /usr/lib. So, you have to prevent the libtool file from confusing the libtool script at link time.

You can probably just remove the libtool library file, and rely on your linker to figure out what you mean by -lfoo (even though the .la strongly admonishes you not to delete it), but perhaps better is to just update libdir in this file to point to where your library is temporarily installed. Using GNU sed you can do this:

sed -i~ -e "s;/usr;/tmp/rootfs/usr;" /tmp/rootfs/usr/lib/libfoo.la

I do this just after installing the cross-compiled library into my temporary root filesystem tree.

You likely won't need the libtool files any more when you install (if you're targeting an embedded Linux system), so you don't care if those files are correct once installed on the target. You'll probably just remove them from the final image, anyway.

I wrote this because I spent a long night of fighting libtool, Googling for help and finding none, and finally figuring this out. This is the second time I've even figured this out, having forgotten the solution the first time. Hopefully, this will help you out if you encounter this issue.


The wrong-gcc problem

For some packages, you seem to also run into trouble where libtool can't figure out what you're doing:

$ libtool --mode=compile target-gcc -g -O2 -c foo.c
libtool: compile: unable to infer tagged configuration
libtool: compile: specify a tag with `--tag'

OK, so it couldn't figure out that we're compiling C code. No problem, just add the tag --tag=CC. Works when we compile:

$ libtool --tag=CC --mode=compile target-gcc -g -O2 -c foo.c
target-gcc -g -O2 -c foo.c -fPIC -DPIC -o .libs/foo.o
target-gcc -g -O2 -c foo.c -o foo.o >/dev/null 2>&1

But when we link, libtool tries to call the native gcc:

$ libtool --tag=CC --mode=link target-gcc -rpath /usr/lib -o libfoo.la foo.lo
gcc -shared .libs/foo.o -Wl,-soname -Wl,libfoo.so.0 -o .libs/libfoo.so.0.0.0
/usr/bin/ld: .libs/foo.o: Relocations in generic ELF (EM: 40)
/usr/bin/ld: .libs/foo.o: Relocations in generic ELF (EM: 40)
/usr/bin/ld: .libs/foo.o: Relocations in generic ELF (EM: 40)
/usr/bin/ld: .libs/foo.o: Relocations in generic ELF (EM: 40)
/usr/bin/ld: .libs/foo.o: Relocations in generic ELF (EM: 40)
/usr/bin/ld: .libs/foo.o: Relocations in generic ELF (EM: 40)
.libs/foo.o: could not read symbols: File in wrong format
collect2: ld returned 1 exit status

One rumored workaround (I haven't tried this, since I haven't had to cross compile anything in a while) is to specify a proper rpath in LDFLAGS:

-Wl,-rpath -Wl,/usr/lib -Wl,-rpath-link -Wl,${STAGING_DIR}/usr/lib -L${STAGING_DIR}/lib -L${STAGING_DIR}/usr/lib

Thanks to Waldemar Brodkorb for this workaround. Let me know if this works for you.

Another solution to this problem is to just install a cross libtool, which you'll use in preference to your system's libtool. It's easy enough to do; go grab the distribution, unpack it, and install it with:

$ tar xzf libtool-1.5.22.tar.gz
$ cd libtool-1.5.22
libtool-1.5.22 $ ./configure --prefix=/opt/Toolchain --host=target --program-prefix=target-
configure output...
libtool-1.5.22 $ make
make output...
libtool-1.5.22 $ sudo make install

Then, you can just use target-libtool when cross compiling. That libtool (which is still just a shell script, so you needn't worry about running it on the host) will be set up properly for using your target-gcc. You usually just have to add an environment variable LIBTOOL=target-libtool when you configure your package.

Happy Hacking.


Copyright © 2007, 2009  Casey Marshall
casey dot s dot marshall at gmail dot com

This is the version of March 7, 2009.

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 United States License.