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.
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.
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]
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)
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?
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)
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?