Shared Libraries: Understanding Dynamic Loading

In this post, I will attempt to explain the inner workings of how dynamic loading of shared libraries works in Linux systems. This post is long - for a TL;DR, please read the debugging cheat sheet.


This is a companion discussion topic for the original entry at http://amir.rachum.com/blog/2016/09/17/shared-libraries/

Hi Amir, Very well and precisely explained. Thanks for sharing.

Thanks! Very useful & concise article.

The best tutorial I have read for a long time.
Perfect.
Thanks for the help and your time

Registered just to say thank you. Your article presumably saved me at least half a day of research.

Thank you very much for the great article!

It helped me troubleshoot a very peculiar issue. I just want to point out, that ldd -v which lists recursive dependencies, does not list the ones that are not found.

So the only true way to find out where a (missing) dependency is coming from is to use readelf -d <exe or lib> | grep 'NEEDED\|RPATH\|RUNPATH' and trace every dependency by hand.

Also keep in mind that for a given shared lib/executable its dependencies are searched according to the RPATH/RUNPATH specified by that dependency. So even if a path is in your executable RUNPATH, a library inside that runpath can still be missing, because its an indirect dependency of another library, which does not have RUNPATH (i.e it assumes that libs are put inside a globally accessible /etc/ld.so.conf location).

Hi, I am facing a issue which I will best try and describe as below.
I have 2 libraries in my environment which both exposes the same api.
libraryone : is a pc library,
librarytwo : is the library which is part of the embedded software which runs on a embedded system when HW is used, otherwise it runs on the same PC when run in simulation mode.
Problem:
When my application is executed, during initialization a PC library is expected to call API in usr/lib/i386…/libraryone, but it ends up calling API in myApp/libs/librarytwo. (in simulation mode). This is crashing the simulation.
Temporarily I changed api names in Library two (to which i have access), library one is part of standard installation of a package. With this the simulation runs and executes correctly.

Can you tell me how can I make the pc library call the api in usr/lib/i386…/libraryone and not the one in librarytwo.

Thanks for such a detailed explanation.
I have one issue, when I move my executable from one folder to another folder, I got follwoing error
./final: error while loading shared libraries: libprintMsg.so: cannot open shared object file: No such file or directory

But according to the explanation, I should not be getting this because I used ORGINAL as you can see

vagrant@ubuntu-xenial:/vagrant/c_pp_test$ readelf -d final | grep path
0x000000000000000f (RPATH) Library rpath: [$ORIGIN]

Can you tell me why I am getting error while moving the executable in another folder?

$ORIGIN is relative to the directory of the executable.
You should move your library to the same folder as well.

I am trying to compile a complete list of dependencies for the firefox (60.9) 32-bit executable. After reading this tutorial I can see that firefox must have been linked using the rpath flag:

[root@kilauea4 ~]# cd /usr/lib/firefox
[root@kilauea4 firefox]# readelf -d firefox | grep RPATH
0x0000000f (RPATH) Library rpath: [/usr/lib/firefox/bundled/lib]
[root@kilauea4 firefox]#

Within the /usr/lib/firefox/bundled/lib directory there are a number of .so files that firefox needs to run, however, ldd does not report any of these dependencies:

[root@kilauea4 firefox]# ldd firefox
linux-gate.so.1 => (0x00168000)
libpthread.so.0 => /lib/libpthread.so.0 (0x00de1000)
libdl.so.2 => /lib/libdl.so.2 (0x00963000)
librt.so.1 => /lib/librt.so.1 (0x00862000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00169000)
libm.so.6 => /lib/libm.so.6 (0x00c93000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x002fa000)
libc.so.6 => /lib/libc.so.6 (0x00318000)
/lib/ld-linux.so.2 (0x007b9000)
[root@kilauea4 firefox]#

I want to be able to compile a complete list of all dependencies, including those .so files that are in RPATH. Is there a way to do this? Thanks!

Thank you for the good summary on shared libraries!

Just a small correction: You described Dynamic Linking. Dynamic Loading is done programatically using dlopen().
A process may use dlopen()/dlclose() to dynamically load/unload a shared library at any time, possibly using a dynamically supplied string as the filename. Use cases are plugins or speeding up process startup if the library code is not always/immediately used.
Consequently, ldd can not show dynamically loaded libraries.

ldd can’t show shared libraries that a process loads dynamically using dlopen(). Use pldd on the running process.

I test your code using g++ on a Ubuntu 20.04 machine. And I found that I am able to run ./main directly. So, ld would also search the lib in the current path, right?

 ldd main
linux-vdso.so.1 (0x00007fff14d69000)
librandom.so (0x00007fb810318000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fb81011b000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb80ff29000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fb80fdda000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb810324000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fb80fdbf000)

Great summary, thank you!

I agree with @Unhold. In that case, looks like with the new behavior (e.g. newer OS have the new ld linker which has --enabled-new-dtags enabled by default). One has to always set the LD_LIBRARY_PATH for dynamically loaded libraries or use --disable-new-dtags to search in the rpath (which IMO feels like a workaround). Do you know of another way to obtain the same old behavior without using the aforementioned approaches?

thanks amir insallah