Runtime Path

From Crypto++ Wiki
Jump to navigation Jump to search

A chronic problem with Linux distributions is compiling and linking against one version of a library, and then runtime linking against a different version of a library. Other operating systems managed to fix the problems for users, including AIX, BSDs, OS X, Solaris and even Windows. Linux made a bone headed decision back in the 1990's and Linux users have been suffering the problem ever since. The idiots who decided the default behavior deserve a Darwin award. This wiki article will tell you how to fix the problem.

The discussion below assumes a Linux distro provides Crypto++ in /usr/include and /usr/lib; while the user installed a newer version of the library at /usr/local/include and /usr/local/lib. This is a common scenario when the user wants the latest version of the library for new classes.

A related wiki page is Linux (Command Line). The page discusses how to build the library and user programs. The Crypto++ test program does not suffer the problem because it uses static linking, but user programs may suffer the problem if dynamic libraries are used like with Debian or Fedora.

RPATH vs RUNPATH

There are two different "runtime path" options available to a program. The old one is rpath and the new one is runpath. The difference between rpath and runpath is, rpath is not overridden by LD_LIBRARY_PATH; while runpath can be overridden by LD_LIBRARY_PATH.

Using linker option --enable-new-dtags provides the new runtime path. It is usually recommended you use runpath, which means you use --enable-new-dtags.

Local Install

A new or different version of the library will be installed at /usr/local/include and /usr/local/lib by default. You can chage the default location; see GNUmakefile. The steps to perform a local install using the default locations are shown below.

$ cd cryptopp
$ make
$ make test
$ sudo make install

You can verify the installation by running the newly installed cryptest.exe program.

$ /usr/local/bin/cryptest.exe v
...
All tests passed!

Seed used was 1555002643
Test started at Thu Apr 11 13:10:43 2019
Test ended at Thu Apr 11 13:10:47 2019

Test Program

The test program used in the examples is shown below. There is not much to it. It generates two random 64-bit integers, and then prints their product.

$ cat myprog.cxx
#include <cryptopp/cryptlib.h>
#include <cryptopp/osrng.h>
#include <cryptopp/integer.h>
#include <iostream>
#include <iomanip>

int main(int argc, char* argv[])
{
    using namespace CryptoPP;

    AutoSeededRandomPool prng;
    Integer x(prng, 64), y(prng, 64);

    std::cout << "x = " << std::hex << x << std::endl;
    std::cout << "y = " << std::hex << y << std::endl;
    std::cout << "x*y = " << std::hex << x*y << std::endl;

    return 0;
}

Static Linking

The easiest way to sidestep runtime linking problems is to avoid runtime linking. The commands below shows you how to static link.

$ g++ -I/usr/local/include myprog.cxx /usr/local/lib/libcryptopp.a -o myprog.exe

Or you can compile and link in two separate commands.

$ g++ -I/usr/local/include myprog.cxx -c
$ g++ myprog.o /usr/local/lib/libcryptopp.a -o myprog.exe

Running the program results output similar to the following.

$ ./myprog.exe
x = ac469320f16f659ah
y = 76661cb77ff066fh
x*y = 4fad3f43030d1c99248c2401614a9c6h

And more importantly, no runtime dependencies on the Crypto++ library.

$ ldd myprog.exe
    linux-vdso.so.1 (0x00007ffeeb951000)
    libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fe9b69f8000)
    libm.so.6 => /lib64/libm.so.6 (0x00007fe9b6874000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fe9b6859000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fe9b6693000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fe9b6ba9000)

You can also use -L and -l:libcryptopp.a to achieve the same effect. Also see --library=namespec in the ld(1) man page.

$ g++ -I/usr/local/include myprog.cxx -o myprog.exe -L/usr/local/lib -l:libcryptopp.a
$ ldd myprog.exe
    linux-vdso.so.1 (0x00007f817cc2f000)
    libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f817ca7b000)
    libm.so.6 => /lib64/libm.so.6 (0x00007f817c8f7000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f817c8dc000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f817c716000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f817cc30000)

Dynamic Linking

Sometimes you must use dynamic linking, like when two components both depend on Crypto++. That is, your program and another library depend on Crypto++. In this case you should use the shared object to avoid subtle memory problems.

To avoid compiling and linking against one version of a library, and then runtime linking against a different version of a library, you should use a Runtime Path or RUNPATH on Linux. The proper way to use a runtime path is use both -Wl,-R,<path> and -Wl,--enable-new-dtags. The -Wl is used to pass options through the compiler to the linker. The -R is used from -rpath. -R works on Linux and Solaris, while -rpath does not work on Solaris. You must also use -L during compile.

$ g++ -I/usr/local/include myprog.cxx -o myprog.exe -L/usr/local/lib \
    -Wl,-R,/usr/local/lib -lcryptopp -Wl,--enable-new-dtags

Running the program results output similar to the following.

$ ./myprog.exe
x = beafc80177632095h
y = 6ace56e66bf3641ah
x*y = 4f8e7afe3c2410b1f82cf85d0d3a8322h

Now there is a runtime dependency on the Crypto++ library.

$ ldd myprog.exe
    linux-vdso.so.1 (0x00007ffea199d000)
    libcryptopp.so.8 => /usr/local/lib/libcryptopp.so.8 (0x00007f431cb39000)
    libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f431c98a000)
    libm.so.6 => /lib64/libm.so.6 (0x00007f431c806000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f431c7eb000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f431c625000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f431c603000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f431d04b000)

And there is a DT_RUNPATH section present in the ELF headers.

$ objdump -x myprog.exe | egrep '(RPATH|RUNPATH)'
  RUNPATH              /usr/local/lib

LD_LIBRARY_PATH

If you use dynamic linking and omit -Wl,-R,<path> then you have to hack things using LD_LIBRARY_PATH. You may also need LD_LIBRARY_PATH to run self-tests in-place before an install. LD_LIBRARY_PATH is probably the worst contraption to work around the chronic Linux path problems but it is all you have.

An example of using LD_LIBRARY_PATH is shown below. First compile with a RUNPATH.

$ g++ -I/usr/local/include test.cxx -o test.exe -L/usr/local/lib -lcryptopp

However, when we check the runtime libraries we see the wrong library will be used at runtime. Notice /usr/lib/libcryptopp.so.8 is used instead of /usr/local/lib/libcryptopp.so.8. Derp...

$ ldd myprog.exe
    linux-vdso.so.1 (0x00007ffe6299c000)
    libcryptopp.so.8 => /usr/lib/libcryptopp.so.8 (0x00007ff85924d000)
    libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007ff85909e000)
    libm.so.6 => /lib64/libm.so.6 (0x00007ff858f1a000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007ff858eff000)
    libc.so.6 => /lib64/libc.so.6 (0x00007ff858d39000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007ff858d17000)
    /lib64/ld-linux-x86-64.so.2 (0x00007ff85975f000)

To fix the path problem you need to provide a wrapper shell script that sets LD_LIBRARY_PATH.

$ cat myprog.sh
#!/usr/bin/env bash

export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH"
shift
./myprog.exe "$@"

Running the program results output similar to the following.

$ ./myprog.sh
x = 221f84259480140ah
y = 721486335b095d59h
x*y = f34c53266a615abf5db91a16a28997ah