Pages

Tuesday, June 13, 2017

GCC - library search dirs

Again, among a lot of detail to pay attention when compiling and linking a program is where the default libraries are being looked for. The general answer found everywhere is: "In the default system locations". But that may be vague, so here's my refinement to that general anwser, for instance, for GCC:

$ libs=$(gcc -m32 -print-search-dirs |grep libraries)
$ libs=$(echo $libs |sed 's/libraries: =//')
$ libs=$(echo $libs |sed 's/:/ /g')

$ for l in $libs; do readlink -e $l; done |sort
/lib
/usr/gcc/4.8/lib
/usr/gcc/4.8/lib/gcc/i386-pc-solaris2.11/4.8.2
/usr/lib

 
And, of course (really?), a similar inspection for 64-bits is possible:

$ libs=$(gcc -m64 -print-search-dirs |grep libraries)
$ libs=$(echo $libs |sed 's/libraries: =//')
$ libs=$(echo $libs |sed 's/:/ /g')

$ for l in $libs; do readlink -e $l; done |sort
/lib
/lib/amd64
/usr/gcc/4.8/lib
/usr/gcc/4.8/lib/amd64
/usr/gcc/4.8/lib/gcc/i386-pc-solaris2.11/4.8.2
/usr/gcc/4.8/lib/gcc/i386-pc-solaris2.11/4.8.2/amd64
/usr/lib
/usr/lib/amd64


NOTE
As it seems, these library search paths may be troublesome at a first glance. For instance, that if you install in /opt/.../lib a newer version of some library already present in the above search paths, say, in /usr/lib, than your building process will most likely ignore the newer version in /opt.../lib, even if you correctly specify a -L build option.

A brute force solution, which may lead to further issues and hence is not recommended, is to supress or somehow rename the conflicting old library found in the library search paths.

The appropriate approach involves getting to know better the compiler and linker in the sense of finding out how the options and environment variables may affect their behavior in the desired way, in this case, tweaking the library search paths.

Again, at first, trying to explore the compiler options, it seems that the best one can find is to try with some combination of following (together with -v for debugging the build-line):

--sysroot
--isysroot
--no-sysroot-suffix

 
But this way you'll soon see yourself caring about linking-in low-level startup files, which isn't a nice workflow addition, unless you're really going after it. In the end this approach gets far too complex and error-prone.

Thus, better investigate what else may affect linking, which means getting to better the linker itself. Move on to ld(1).

For instance, to the library search path in action as well as the default libraries for a standard 64-bit build of the infamous "Hello, world" program:

$ c++ -m64 -march=core2 -std=c++11 -ohello hello.c++
$ file hello
hello: ELF 64-bit LSB executable AMD64 Version 1, ...


$ ldd hello
  libstdc++.so.6 => /usr/lib/64/libstdc++.so.6
  libm.so.2 =>      /lib/64/libm.so.2
  libgcc_s.so.1 =>  /usr/lib/64/libgcc_s.so.1
  libc.so.1 =>      /lib/64/libc.so.1


You know that libc and libm are respectively the C and C math libraries.
And you'll also notice that:

$ ll -d /usr/lib/64
lrwxrwxrwx   ...  /usr/lib/64 -> amd64

But, at first, you may not have noticed why /lib/64  and /usr/lib/64 were selected instead of their amd64 counterparts according to GCC's -print-search-dirs output. As a matter of fact, this is not related to GCC compilers but to the underlying linker that gets invoked on the build process, in which case of Solaris is /usr/bin/ld . And by looking at ld(1) manpage,  in its FILES section we'll find a note about the default LIBPATH (which is not an environment variable) together with an explanation that the 64-bit libraries search path is obtained by appending /64 to the usual 32-bit paths, which are /lib and /usr/lib . And that's why! In fact, this makes sense after all, because from a higher-level perspective, ld is tool present in all platforms and not particularly x86-64 (amd64), hence the more general 64-bit designation of the /64 path suffix.

Due to the "investigation" of the selection of /64 instead of /amd64 suffix in ldd output for 64-bit binaries I came (again) to the LD_LIBRARY_PATH environment variable which is one way (but not the best way) to solve the issue presented on the previously note right above. What I didn't know is that it can also play a role at build-time, affecting where ld searches for libraries for linking into the binary executable being built. And here's its effect on the libraries search order during the linking process:

$ echo LD_LIBRARY_PATH
prepath1:prepath2;postpath1:postpath2

$ ... -Lpath1 ... -LpathN ...

The search path ordering is:

prepath1 prepath2 path1 ... pathN postpath1 postpath2 LIBPATH

Cool! But don't get so excited as this is not how to use LD_LIBRARY_PATH to solve the aforementioned issue because LIBPATH is fixed and is always the last path in the path series.

Let's have a concrete example of the presented issue. The Solaris 11.3 (GA / Release) includes SQLite version 3.8.8.1. But let's say I need to get the latest version (at the time of this writing, 3.19.3) in order to integrate it with some other software builds requiring not necessarily this newer version but some features that were not compiled-in on the orginal version packaged with the system. After passing by all the usual GNU "process" (which I'll show on this particular post) of getting the tarball, extracting, configuring, building and installing it I get the following:

$ ll -o /opt/sqlite-3.19.3/*
/opt/sqlite-3.19.3/bin:
...
-rwxr-xr-x 1 root ... sqlite3

/opt/sqlite-3.19.3/include:
...
-rw-r--r-- 1 root ... sqlite3.h
-rw-r--r-- 1 root ... sqlite3ext.h

/opt/sqlite-3.19.3/lib:
...
-rwxr-xr-x ... libsqlite3.so.0.8.6
lrwxrwxrwx ... libsqlite3.so.0 -> libsqlite3.so.0.8.6
lrwxrwxrwx ... libsqlite3.so -> libsqlite3.so.0.8.6
-rwxr-xr-x ... libsqlite3.la
-rw-r--r-- ... libsqlite3.a
-rwxr-xr-x ... libtclsqlite3.so
-rw-r--r-- ... pkgIndex.tcl
drwxr-xr-x ... pkgconfig

 
But, by default, the system also has:

$ ll -o /usr/lib/libsqlite*
-rwxr-xr-x ... /usr/lib/libsqlite3.so.0.8.6
lrwxrwxrwx ... /usr/lib/libsqlite3.so.0 -> libsqlite3.so.0.8.6
lrwxrwxrwx ... /usr/lib/libsqlite3.so -> libsqlite3.so.0.8.6


And there you have it! Different versions o the same library with file names that cannot coexist in the same directory. The potential for messing things up if you let the system deduce or use its defaults should be clear. But, if not let me demonstrate how. Let's say I expect that a certain library call behave as returning 1, for instance:

#include <stdio.h>
#include "sqlite3.h"

int ok()
{
  return 

    sqlite3_compileoption_used
    ( 
      "SQLITE_ENABLE_DBSTAT_VTAB" 
    );
}

int main()
{
  printf( "%sOk!\n", ok() ? "" : "Not " );
  return 0;
}


If I proceed very naively, I'll build this program as follows:

$ gcc -I/opt/sqlite-3.19.3/include -lsqlite3 -op1 p1.c

And get:

$ ldd p1
  libsqlite3.so.0 => /usr/lib/libsqlite3.so.0
  libc.so.1       => /lib/libc.so.1


$ ./p1
Not Ok!


As one can see I'm getting the wrong version of the library being resolved at run-time. That version is wrong the for program because it lacks the kind of compiled-in support it expects. This is just an example.

With the previous naive compilation-line, if I set the environment variable LD_LIBRARY_PATH with the path of the correct / expected library, the effect on the run-time link-resolution of the library is immediate. This, of course, is very flexible, but can be very dangerous as well because if this environment variable is used in a system-wide fashion it may turn itself very easily into a single-point-of-failure (SPOF) for all applications that rely on it at run-time.

$ export LD_LIBRARY_PATH=/opt/sqlite-3.19.3/lib

$ ldd p1
  libsqlite3.so.0 => /opt/sqlite-3.19.3/lib/libsqlite3.so.0
  libc.so.1       => /lib/libc.so.1
  libgcc_s.so.1   => /usr/lib/libgcc_s.so.1


To get the potentialy correct linking resolution and be more resilient to LD_LIBRARY_PATH, the compilation-line must be adjusted to include the -R option as follows:

gcc -I/opt/sqlite-3.19.3/include \ 
    -R/opt/sqlite-3.19.3/lib -lsqlite3 -op1 p1.c

$ ./p1
Ok!


This is a better solution. The -R option accepts multiple paths, that is, it's a list of paths, much the same as the PATH environment variable.

NOTE
The values passed to the -R option may be relative paths as well. In any case, absolute or relative, it is critical that these paths exist and that the desired / required libraries can be found on them, otherwise all bets are off as the system will happily revert to its defaults and consider /lib, /usr/lib for 32-bit binaries and their respective /64 counterparts for 64-bit binaries, as well as the LD_LIBRARY_PATH environment variable, again!

NOTE
Be careful when using relative paths with -R option. If you do so be aware that everytime before running the main executable it will be necessary to change paths, so that the current path becomes where the executable resides.

For instance:

If one uses -R./lib and the executable is at /opt/sfw/app, then the follwing won't work:

$ cd /tmp
$ /opt/sfw/app


That's because the run-time linker will search for the library at /tmp/lib and then the system default directories, but none of them resolves to /opt/sfw/app/lib where the libraries actually reside.

By the way, even if such library search path requirements happens indirectly inside other complex software build projects, in many cases the best approach is still to set both the PATH and the compiler flags (CFLAGS and/or CXXFLAGS) environment variables to respectively prepend the apprpriate bin directory and the -R option value. But there are even more complex cases where neither of this will be easily or practically achieved.

To the rescue, enter: The runtime linking environment!