Intro to Linux Shared Libraries (How to Create Shared Libraries)
In this article we will discuss specifically about Shared Libraries.
Shared Libraries
Shared Libraries are the libraries that can be linked to any program at run-time. They provide a means to use code that can be loaded anywhere in the memory. Once loaded, the shared library code can be used by any number of programs. So, this way the size of programs(using shared library) and the memory footprint can be kept low as a lot of code is kept common in form of a shared library.
Shared libraries provide modularity to the development environment as the library code can be changed, modified and recompiled without having to re-compile the applications that use this library. For example, for any change in the pthread library code, no change is required in the programs using pthread shared library. A shared library can be accessed through different names :
- Name used by linker (‘lib’ followed by the library name, followed by ‘.so’ . For example libpthread.so)
- Fully qualified name or soname ( ‘lib’ followed by the library name, followed by ‘.so’, followed by ‘.’ and a version number. For example : libpthread.so.1)
- Real name (‘lib’ followed by the library name, followed by ‘.so’, followed by ‘.’ and a version number, followed by a ‘.’ and a minor number, followed by a ‘.’ and a release number. Release number is optional. For example, libpthread.so.1.1)
A version number is changed for a shared library when the changes done in the code make the shared library incompatible with the previous version. For example, if a function is completely removed then a new version of the library is required.
A minor number is changed in case there is a modification in the code that does not make the shared library incompatible with the previous version being used. For example, a small bug fix won’t break the compatibility of the existing shared library so only a minor number is changed while version remains the same.
Now, one may wonder why so many names for a shared library?
Well, these naming conventions help multiple versions of same shared library to co-exist in a system. The programs linking with the shared library do not need to take care about the latest version of the shared library installed in the system. Once the latest version of the shared library is installed successfully, all the programs automatically start linking to the latest version.
The name used by linker is usually a symbolic link to the fully qualified soname which in turn is a symbolic link to the real name.
Placement in File System
There are mainly three standard locations in the filesystem where a library can be placed.
We will go by the Filesystem Hierarchy standards(FHS) here. According to the FHS standards, All the libraries which are loaded at start up and running in the root filesystem are kept in /lib. While the libraries that are used by system internally are stored at /usr/lib. These libraries are not meant to be directly used by users or shell scripts. There is a third location /usr/local/lib( though it is not defined in the latest version of FHS ). If it exists, it contains all the libraries that are not part of standard distribution. These non-standard libraries are the one’s which you download and could be possibly buggy.
Using ldconfig
Once a shared library is created, copy the shared library to directory in which you want the library to reside (for example /usr/local/lib or /usr/lib). Now, run ldconfig command in this directory.
You remember that we discussed earlier that a linker name for shared library is a symbolic link to the fully qualified soname which in turn is a symbolic link to the real name. Well, this command does exactly the same.
When you run an ELF executable, by default the loader is run first. The loader itself is a shared object file /lib/ld-linux.so.X where ‘X’ is a version number. This loader in turn finds and loads all the shared libraries on which our program depends.
All the directories that are searched by the loader in order to find the libraries is stored in /etc/ld.so.conf. Searching all the directories specified in /etc/ld.so.conf file can be time consuming so every time ldconfig command is run, it sets up the required symbolic links and then creates a cache in file /etc/ld.so.cache where all the information required for executable is written. Reading information from cache is very less time consuming. The catch here is that ldconfig command needs to be run every-time a shared library is added or removed. So on start-up the program uses /etc/ld.so.cache to load the libraries it requires.
Using Non Standard Library Locations
When using non standard library locations. One of the following three steps could be carried out :
Add the path to /etc/ld.so.conf file. This file contains paths to all the directories in which the library is searched by the loader. This file could sometime contain a single line like :
In that case, just create a conf file in the same directory. You can directly add a directory to cache by using the following command :
ldconfig -n [non standard directory path containing shared library]
Note that this is a temporary change and will be lost once the system is rebooted. Update the environment variable LD_LIBRARY_PATH to point to your directory containing the shared library. Loader will use the paths mentioned in this environment variable to resolve dependencies.
Note that on some Unix systems the name of the environment variable could differ.
Note: On a related topic, as we explained earlier, there are four main stages through which a source code passes in order to finally become an executable.
Example (How to Create a Shared Library)
Lets take a simple practical example to see how we can create and use shared libraries. The following is the piece of code (shared.c) that we want to put in a shared library :
#include «shared.h» unsigned int add(unsigned int a, unsigned int b)
#include extern unsigned int add(unsigned int a, unsigned int b);
Lets first make shared.c as a shared library.
1. Run the following two commands to create a shared library :
gcc -c -Wall -Werror -fPIC shared.c gcc -shared -o libshared.so shared.o
The first command compiles the code shared.c into position independent code which is required for a shared library.
The second command actually creates a shared library with name ‘libshared.so’.
2. Here is the code of the program that uses the shared library function ‘add()’
#include #include»shared.h» int main(void)
3. Next, run the following command :
gcc -L/home/himanshu/practice/ -Wall main.c -o main -lshared
This command compiles the main.c code and tells gcc to link the code with shared library libshared.so (by using flag -l) and also tells the location of shared file(by using flag -L).
4. Now, export the path where the newly created shared library is kept by using the following command :
export LD_LIBRARY_PATH=/home/himanshu/practice:$LD_LIBRARY_PATH
The above command exports the path to the environment variable ‘LD_LIBRARY_PATH’.
5. Now run the executable ‘main’ :
# ./main Inside add() The result is [3]
So we see that shared library was loaded and the add function inside it was executed.
Where do executables look for shared objects at runtime?
I understand how to define include shared objects at linking/compile time. However, I still wonder how do executables look for the shared object ( *.so libraries) at execution time. For instance, my app a.out calls functions defined in the lib.so library. After compiling, I move lib.so to a new directory in my $HOME . How can I tell a.out to go look for it there?
A quick solution is to use g++ -Wl,-R. this will force loader to look into the current folder for so libraries. Or -Wl,-R$HOME/path to specidy some other fixed folder.
4 Answers 4
The shared library HOWTO explains most of the mechanisms involved, and the dynamic loader manual goes into more detail. Each unix variant has its own way, but most use the same executable format (ELF) and have similar dynamic linkers¹ (derived from Solaris). Below I’ll summarize the common behavior with a focus on Linux; check your system’s manuals for the complete story.
(Terminology note: the part of the system that loads shared libraries is often called “dynamic linker”, but sometimes “dynamic loader” to be more precise. “Dynamic linker” can also mean the tool that generates instructions for the dynamic loader when compiling a program, or the combination of the compile-time tool and the run-time loader. In this answer, “linker” refers to the run-time part.)
In a nutshell, when it’s looking for a dynamic library ( .so file) the linker tries:
- directories listed in the LD_LIBRARY_PATH environment variable ( DYLD_LIBRARY_PATH on OSX);
- directories listed in the executable’s rpath;
- directories on the system search path, which (on Linux at least) consists of the entries in /etc/ld.so.conf plus /lib and /usr/lib .
The rpath is stored in the executable (it’s the DT_RPATH or DT_RUNPATH dynamic attribute). It can contain absolute paths or paths starting with $ORIGIN to indicate a path relative to the location of the executable (e.g. if the executable is in /opt/myapp/bin and its rpath is $ORIGIN/../lib:$ORIGIN/../plugins then the dynamic linker will look in /opt/myapp/lib and /opt/myapp/plugins ). The rpath is normally determined when the executable is compiled, with the -rpath option to ld , but you can change it afterwards with chrpath .
In the scenario you describe, if you’re the developer or packager of the application and intend for it to be installed in a …/bin , …/lib structure, then link with -rpath=’$ORIGIN/../lib’ . If you’re installing a pre-built binary on your system, either put the library in a directory on the search path ( /usr/local/lib if you’re the system administrator, otherwise a directory that you add to $LD_LIBRARY_PATH ), or try chrpath .