When a program or application start on Linux, shared libraries are loaded. So if the shared library is installed properly in the system,  all programs that start afterwards automatically use the new shared library. In managing your system’s applications, you need to understand libraries and, more specifically, shared libraries.

In this guide, we will look at how to manage shared libraries on Linux.

Library Principles

A collection of items, such as program functions is called system library. Self-contained code modules that perform a specific task within an application, such as opening and reading a data file are called functions.

There is an advantage of splitting functions into separate library files is that multiple applications that use the same functions can share the same library files. These files full of functions make it easier to distribute applications.

Linux supports two different types of libraries;

  • Static libraries or Statically linked libraries

These are libraries which are loaded into an application when it is compiled.

  • Shared libraries or Dynamic libraries

These are library functions which are loaded into memory and bound to the application when the program is launched.

On Linux/Unix systems, like application packages, library files have naming conventions. A shared library file uses the following filename format:

libLIBRARYNAME.so.VERSION

Locating Library Files

In locating library files, the system will search for the function’s library file in a specific order when a program is using a shared function in directories stored within;

  1. LD_LIBRARY_PATH environment variable
  2. Program’s PATH environment variable
  3. /etc/ld.so.conf.d/ folder
  4. /etc/ld.so.conf file
  5. /lib/ and /usr/lib/ folders

Note: The order of no. 3 & 4 may alternate on your system. The reason is because the /etc/ld.so.conf file loads configuration files from the /etc/ld.so.conf.d/ folder.

Displaying the /etc/ld.so.conf file contents on Linux:

$ cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf

Files listing:

$ ls -1 /etc/ld.so.conf.d/
fakeroot-x86_64-linux-gnu.conf
libc.conf
x86_64-linux-gnu.conf

It is important to know that:

  • The /lib*/ folders, such as /lib/ and /lib64/,are for libraries needed by system utilities that reside in the /bin/ and /sbin/ directories
  • The /usr/lib*/ folders, such as /usr/lib/ and /usr/lib64/, are for libraries needed by additional software, such as database utilities like MariaDB.

Files within the /etc/ld.so.conf.d/ folder contains a shared library directory name and inside that directory are the shared library files needed by an application.

Looking at the /etc/ld.so.conf.d/ file contents on Ubuntu:

$ cat /etc/ld.so.conf.d/x86_64-linux-gnu.conf
# Multiarch support
/usr/local/lib/x86_64-linux-gnu
/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu

Listing:

$ ls /usr/lib/x86_64-linux-gnu
libpytalloc-util.cpython-38-x86-64-linux-gnu.so.2
libpytalloc-util.cpython-38-x86-64-linux-gnu.so.2.3.0
libsamba-policy.cpython-38-x86-64-linux-gnu.so.0
libsamba-policy.cpython-38-x86-64-linux-gnu.so.0.0.1

Loading Shared Libraries Dynamically

Dynamic linker also called dynamic linker/loader is responsible for finding the program’s needed library functions when a program is started. After they are located, the dynamic linker will copy them into memory and bind them to the program.

The dynamic linker executable has a name like ld.so and ld-linux.so* or you may use locate command to find the right name based on your distribution, in my case it is Ubuntu distro.

Locating the dynamic linker executable on Ubuntu.

$ locate ld-linux
/snap/core/10859/lib/ld-linux.so.2
/snap/core/10859/lib/i386-linux-gnu/ld-linux.so.2
/snap/core/10859/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/snap/core/10859/lib64/ld-linux-x86-64.so.2
/snap/core/10908/lib/ld-linux.so.2
/snap/core/10908/lib/i386-linux-gnu/ld-linux.so.2
/snap/core/10908/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/snap/core/10908/lib64/ld-linux-x86-64.so.2
/snap/core18/1932/lib/ld-linux.so.2
/snap/core18/1932/lib/i386-linux-gnu/ld-linux.so.2
/snap/core18/1932/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/snap/core18/1932/lib64/ld-linux-x86-64.so.2
/snap/core18/1988/lib/ld-linux.so.2
/snap/core18/1988/lib/i386-linux-gnu/ld-linux.so.2
/snap/core18/1988/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/snap/core18/1988/lib64/ld-linux-x86-64.so.2
/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/usr/lib64/ld-linux-x86-64.so.2
/usr/share/man/man8/ld-linux.8.gz
/usr/share/man/man8/ld-linux.so.8.gz

Once you have located the dynamic linker utility, you can use it to manually load a program and its libraries (it will run the program as well)

Example, On Ubuntu distribution using ls command to list the contents of ~/Music directory.

$ /usr/lib64/ld-linux-x86-64.so.2 /usr/bin/ls ~/Music
config	THM

You find that I have config and THM files in my ~/Music directory

Managing the Library Cache

Library cache is a catalog of library directories and all the various libraries contained within them. The system reads this cache file to quickly find needed libraries when it is loading programs. This makes it much faster for loading libraries than searching through all the possible directory locations for a particular required library file.

When new libraries or library directories are added to the system, this library cache file must be updated. Thus, ldconfig must be run every time configuration files are added or updated.

ldconfig has the following useful options:

  1. -v--verbose, Display the library version numbers, the name of each directory, and the links that are created:
$ sudo ldconfig -v
/usr/local/lib:
/lib/x86_64-linux-gnu:
	libsndio.so.7.0 -> libsndio.so.7.0
	libcups.so.2 -> libcups.so.2
	libx264.so.155 -> libx264.so.155
	libdv.so.4 -> libdv.so.4.0.3
	libbd_part.so.2 -> libbd_part.so.2.0.0
	libspice-client-gtk-3.0.so.5 -> libspice-client-gtk-3.0.so.5.0.0

In the example above, we see how libsndio.so.7.0 is linked to the actual shared object file libsndio.so.7.0

2. -p--print-cache, Print the lists of directories and candidate libraries stored in the current cache:

$ sudo ldconfig -p
1005 libs found in cache `/etc/ld.so.cache'
	libzvbi.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzvbi.so.0
	libzvbi-chains.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzvbi-chains.so.0
	libzstd.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzstd.so.1
	libzip.so.5 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzip.so.5
	libz.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libz.so.1
	libyelp.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libyelp.so.0
	libyaml-0.so.2 (libc6,x86-64) => /lib/x86_64-linux-gnu/libyaml-0.so.2
	libyajl.so.2 (libc6,x86-64) => /lib/x86_64-linux-gnu/libyajl.so.2
	libx265.so.179 (libc6,x86-64) => /lib/x86_64-linux-gnu/libx265.so.179

Now we can see how the cache uses the fully qualified soname (shared object name) of the links:

$ sudo ldconfig -p |grep libfuse
	libfuse.so.2 (libc6,x86-64) => /lib/x86_64-linux-gnu/libfuse.so.2

Let’s now long list /lib/x86_64-linux-gnu/libfuse.so.2, we will find the reference to the actual shared object file libfuse.so.2.9.9 which is stored in the same directory:

$ ls -l /lib/x86_64-linux-gnu/libfuse.so.2
lrwxrwxrwx 1 root root 16 Sep  1  2020 /lib/x86_64-linux-gnu/libfuse.so.2 -> libfuse.so.2.9.9

We can add new paths for shared libraries temporarily using LD_LIBRARY_PATH i.e to add /usr/local/mynewlib to the library path in the current shell session, use:

$ LD_LIBRARY_PATH=/usr/local/mynewlib

Now let’s check the new added library,

$ echo $LD_LIBRARY_PATH
/usr/local/mynewlib

To add /usr/local/mynewlib  permanently to the library path in the current shell session and have it exported to all child processes spawned from that shell, use;

$ export LD_LIBRARY_PATH=/usr/local/mynewlib

To remove the LD_LIBRARY_PATH environment variable, use;

$ unset LD_LIBRARY_PATH

Searching Dependencies of a Particular Executable

To look up the shared libraries required by a specific program, use the ldd command followed by the absolute path to the program. The output shows the path of the shared library file as well as the hexadecimal memory address at which it is loaded:

$ ldd /usr/bin/ssh
	linux-vdso.so.1 (0x00007ffffe1c5000)
	libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007fef16f5e000)
	libcrypto.so.1.1 => /lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007fef16c88000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fef16c82000)
	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fef16c66000)
	libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007fef16c4a000)
	libgssapi_krb5.so.2 => /lib/x86_64-linux-gnu/libgssapi_krb5.so.2 (0x00007fef16bfd000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fef16a09000)
	libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007fef16979000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fef17065000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fef16956000)
	libkrb5.so.3 => /lib/x86_64-linux-gnu/libkrb5.so.3 (0x00007fef16879000)
	libk5crypto.so.3 => /lib/x86_64-linux-gnu/libk5crypto.so.3 (0x00007fef16848000)
	libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 (0x00007fef16841000)
	libkrb5support.so.0 => /lib/x86_64-linux-gnu/libkrb5support.so.0 (0x00007fef16830000)
	libkeyutils.so.1 => /lib/x86_64-linux-gnu/libkeyutils.so.1 (0x00007fef16829000)

We also use ldd to search for the dependencies of a shared object:

$ ldd /lib/x86_64-linux-gnu/libkrb5support.so.0
	linux-vdso.so.1 (0x00007ffd2b3e0000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f77ce49a000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f77ce2a8000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f77ce4c5000)

With the -u (or --unused) option ldd prints the unused direct dependencies (if they exist).

$ ldd -u /usr/bin/ssh

The reason for unused dependencies is related to the options used by the linker when building the binary. Although the program does not need an unused library, it was still linked and labelled as NEEDED in the information about the object file. You can investigate this using commands such as readelf or objdump.

Note: Sometimes a library is dependent on another library. So when you are troubleshooting a missing library file, you may need to use the ldd command on the libraries listed for the application in order to get to the root of the problem.

Conclusion

This is the the of our guide on how to Manage Shared Libraries on Linux, I hope this guide has been helpful.

Here is a list of other articles you can also check:

LPIC 101 – Managing Software Packages on Linux

LPIC 101 – Managing Processes in Linux Systems

Grep vs Awk vs Sed Commands in Linux

How To Run Docker Containers using Vagrant