This article focuses on high-level look at the layout. Where are shared libraries looking for on Linux, BSD *, Mac OS X, Windows on which applications depend? What to do with backward compatibility? How to deal with addiction hell?
It is assumed that the reader is familiar with such character sets as "compiler", "object file", "linker", "static library", "dynamic library", "dynamic loader" and some others, so we will not chew anything.
Static dynamic library loading issues:
')
main.exe depends on version-0.3.dll and bar.dll. bar, in turn, depends on version-0.2.dll, which is binary not compatible with version 0.3 (not just the characters are missing, but the names match, but a different number of arguments, or create objects of different nature, etc.). Do the characters from version-0.2.dll erase them from version-0.3.dll? The same question arises when one static version of the library (say, version-0.2.lib) and dynamic (version-0.3.dll) are used;- creating floating applications: where will the dynamic loader look for version-0.?.dll and bar.dll for the application from the previous item? Will it find the main.exe dependencies if it is moved to another folder? How to build main.exe so that dependencies are searched for relative to the executable file?
- dependency hell: two versions of one library /opt/kde3/lib/libkdecore.so and /opt/kde4/lib/libkdecore.so (with which the plasma does not fall), half of the programs require the first, the other half of the programs - the second. Both libraries cannot be placed in the same scope (one directory). The same problem exists in Section 1, since it is necessary to place two versions of the version library in one directory.
After reading the first paragraph, the reader may exclaim: “Yes, this is a perversion! They don't do that! You need to use one version of the library! ”Yes, it is, but anything can happen in life. The simplest example is: your application uses the a library, and a third-party closed library (I emphasize this,
someone else’s paid product ) b, which cannot use the same version as you, or vice versa.
Another example in the framework of a huge project is the fact that the release of various parts has a different period (what our team faced and what generally caused this text to be written). Thus, the main product may occasionally come out in the configuration described in clause 1.
Dependency hell is more relevant to system library and operating system developers, but may also arise in the application area. Again, suppose that there is a huge project in which there are several executable programs. All of them depend on one library, but different versions. (This is not the same situation as in Section 1: there are two versions of the same library loaded into one process, and here only one is loaded into each process, but each uses its own version).
Escape from hell
The answer is simple: you need to add a version in the library file name. This will allow you to place library files in one directory. It is recommended to add a version of the ABI, rather than an API, thereby creating two parallel branches of the versions and corresponding difficulties.
Version control is a very routine job. Consider the xyz scheme:
- x - major release. There is no question of any compatibility;
- y is a minor release. Liba is compatible at source level, but binary compatibility can be broken;
- z - bugfix. Liba is compatible in both directions.
Then it is reasonable to include xy in the file name. If you keep the compatibility while increasing the minor version, it is enough to make the corresponding symlink:
version-1.1.dll
version-1.0.dll → version-1.1.dll
Applications using version-1.1.0 and those using version-1.0.x will work as well.
If the compatibility is broken, then there will be two files in the system and everything will work again.
If for some reason the compatibility was broken during a bug fix, then the minor version should be increased (and there is nothing to fake, as the team of favorite Qt did
[1] ).
By the way, nobody forbids including the API version at all - then there will be more symbolic links, since compatibility is more often preserved. But in this case, the aforementioned Fayl Qt would be resolved easily and would not force it to increase the minor version.
This is true for all platforms.
Resolving the remaining two issues is different depending on the OS.
ELF & GNU ld (Linux, * BSD, etc)
The so-called SONAME is present in the ELF format shared library.
[2] [3] . This is a character string that is written to a binary file in the DT_SONAME section. You can view SONAME for a library, for example, like this:
$ objdump -p /path/to/file | grep SONAME
If the faz program / library is linked to the baz library, which has SONAME =
baz-0.dll , then the string
baz-0.dll will be hard-coded in the binary faz file in the DT_NEEDED section, and when it starts, the dynamic loader will look for a file called
baz -0.dll . At the same time, no one forbids calling the file differently!
You can view the SONAMEs that the executable file depends on:
$ objdump -x /path/to/file | grep NEEDED
The dynamic loader searches for libraries from the DT_NEEDED section in the following places in this order.
[four] [5] :
- a list of directories in the DT_RPATH section, which is hard coded in the executable file. Supported by most * nix-systems. Ignored if DT_RUNPATH section is present;
- LD_LIBRARY_PATH - environment variable, also contains a list of directories;
- DT_RUNPATH is the same as DT_RPATH, only viewed after LD_LIBRARY_PATH. Supported only on the latest Unix-like systems;
- /etc/ld.so.conf - dynamic loader settings file ld.so, which contains a list of folders with libraries;
- hardwired paths - usually / lib and / usr / lib.
The data format for RPATH, LD_LIBRARY_PATH and RUNPATH is the same as for PATH: a list of paths separated by a colon. You can view RUNPATHs, for example, like this:
$ objdump -x /path/to/file | egrep 'R(|UN)PATH'
R [UN] PATH may contain a special label $ ORIGIN, which the dynamic loader will expand to the full absolute path to the loaded entity. It is worth noting here that some developers add RUNPATH “.” (Full stop). This is not the same as $ ORIGIN! The point will expand into the current working directory, which naturally does not have to match the path to the entity!
To demonstrate what has been written, we will develop the application according to the scheme in Section 1 (link to the repository in the github:
github.com/gshep/linking-sample ). To assemble the entire system, go to the root of the folder and call
./linux_make_good.sh
, the result will be in the
result
folder. Below will be disassembled some assembly steps.
At the stage of building libraries version-0.x set SONAME:
$ gcc -shared -Wl,-soname,version-0.3.dll -o version-0.3.dll version.o
They depend only on the system libraries and therefore do not require the presence of the R [UN] PATH sections.
The bar library already depends on version-0.2, so you need to specify RPATH:
$ gcc -shared -Wl,-rpath-link,/path/to/version-0.2/ -L/path/to/version-0.2/ -l:version-0.2.dll -Wl,-rpath,\$ORIGIN/ -Wl,--enable-new-dtags -Wl,-soname,bar.dll -o bar.dll bar.o
The
--enable-new-dtags
instructs the linker to fill in the DT_RUNPATH section.
The
-Wl,-rpath,...
allows you to fill out the R [UN] PATH section. To specify the list of paths, you can specify a parameter several times, or list all paths separated by a colon:
$ gcc -Wl,-rpath,/path/1/ -Wl,-rpath,/path/2 ... $ gcc -Wl,-rpath,/path/1:/path/2 ...
Now, the entire contents of the result folder as a whole or the folder itself can be moved around the file system as you please, but at startup the dynamic loader will find all the dependencies and the program will be executed:
$ ./result/main.exe Hello World! bar library uses libversion 0.3.0, number = 3 version::get_version result = 0 But I uses liversion 0.3.0 number = 3
Here we come to the problem of overwriting characters! Bar uses version-0.2.dll, in which get_number () returns 2, and the application itself is version-0.3.dll, where the same function returns already 3. It can be seen from the output of the application that one version of the get_number function is erased by another.
The fact
[6; Dynamic Linking and Loading, Comparison of dynamic linking approaches] that GNU ld & ELF does not use SONAME or the file name as the namespace for the imported characters:
if different libraries export entities with the same names, some of them will overwrite others and, at best, the program will crash.
The case when one of the libraries is static is solved simply: all symbols of the static library should be hidden
[7, 2.2.2 Define Global Visibility] .
Unfortunately, in the case of dynamic libraries, not everything is so simple. The GNU linker / loader lacks such functionality as direct binding
[8] . Someone sawed this opportunity in Ghent
[9] , but it seems that everything has died down. In the diesel it is
[ten] [11] , but the diesel fuel itself died ...
One possible option is the versioning of the characters themselves.
[7, 2.2.5 Use Export Maps] . In fact, it is more like decorating characters. (One can only imagine what the reader programming in C ++ is screaming now ...)
This method is to create a so-called versioned script in which to list all exported and hidden entities.
[12] [13] . Example script from version-0.3:
VERSION_0.3 {
global:
get_version;
get_version2;
get_number;
local:
*;
};
At the linking stage, specify this file using the
--version-script=/path/to/version.script
parameter. After this, the application that will be associated with such a library will get into NEEDED version-0.3.dll, and in the import table an undefined character
get_number@@VERSION_0.3
, although in the header files it will still be just int get_number ().
Settal nm on any program that uses glibc, and you will begin to see clearly!
To build an example using character versioning in the version-0.x libraries, run the root
linux_make_good.sh
root script with the
use_version_script
parameter:
$ ./linux_make_good.sh use_version_script $ ./result/main.exe Hello World! bar library uses libversion 0.2.0, number = 2 version::get_version result = 0 But I uses liversion 0.3.0 number = 3 $ nm ./result/main.exe // … U get_number@@VERSION_0.3 U get_version@@VERSION_0.3 0000000000401008 T main U memset@@GLIBC_2.2.5 $ nm ./result/bar.dll // … U get_number@@VERSION_0.2 U get_version2@@VERSION_0.2 0000000000000800 t register_tm_clones U strcat@@GLIBC_2.2.5

- Hey Dart! Our libX will support Linux!
- Noooooooooooooooooooooooooo!
Yes, after the file our team encountered, the captain made a willful decision and now only one version is used (precisely because of Linux).
How are things on the Mac?
Mac Axis uses the Mach-o format for executable files, and to search for characters, a two-level namespace
[14, Two-level namespace] [16] . This is the default now, but you can build it with a flat namespace or disable it altogether when you start the program.
[15, FORCE_FLAT_NAMESPACE] . To find out if the binary is built with the support of the namespace, the command will help:
$ otool -hv /path/to/binary/file
That is, do not bathe with any additional decoration of names - just include the version in the file name!
And what about the search for addictions?
In makosi, almost everything is the same, just called differently.
Instead of SONAME there is a library id or install name. You can view, for example, like this:
$ otool -D /usr/lib/libstdc++.dylib
You can change it using install_name_tool.
When linking with the library, its id is written in the binary.
You can view the dependencies of a binary like this:
$ otool -L /path/to/main.exe
or
$ dyldinfo -dylibs /path/to/main.exe
Starting dyld tries to open a file with the name “id”
[15, DYNAMIC LIBRARY LOADING] , i.e., considers the install name as the absolute path to the dependency. If it fails, it searches for a file with the name / suffix "id" in the directories listed in the environment variable DYLD_LIBRARY_PATH (full analogue of LD_LIBRARY_PATH).
If the search in DYLD_LIBRARY_PATH did not return any results, then dyld similarly scans a couple more environment variables.
[15] , after which he will look for lib in standard catalogs.
Such a scheme does not allow to build relocatable applications, so a special label was introduced that can be entered in id: @ executable_path /. This label at boot time will be expanded to the absolute path to the executable file.
Further, you can change the dependencies of the finished binaries:
$ install_name_tool -change /usr/lib/libstdc++.dylib @executable_path/libstdc++.dylib main.exe
Now the loader will first look for this lib in the same folder as main.exe. In order not to change in a ready-made binary, you need to slip libstdc ++. Dylib, which has id = @ executable_path / libstdc ++. Dylib, during the build.
Further, one problem arises, or rather two. Let there be such a hierarchy:
- main.bin
- tools /
- library.dll
main.bin depends on library.dll, but also tools / auxiliary.bin depends on it.
At the same time, the id is = @ executable_path / library.dll, and both binaries were simply linked with it. Then, when running auxiliary.bin, the loader will search for /path/to/tools/library.dll and naturally will not find it! Of course, you can correct the tools / auxiliary.bin with handles after the layout, or throw a soft link, but again inconvenience!
Even better, the problem manifests itself when it comes to plugins:
1.plugin has the @ executable_path / helper.dylib entry, but during launch it will expand to the absolute path to main.bin, not 1.plugin!
To solve this problem, the Yabloko from the version of Axis 10.4 introduced a new marker: @ loader_path /. During a dependency download, this marker will unfold in the absolute path to the binary, which pulls the dependency.
The last difficulty is that two versions of linked libraries are needed: some will be installed into the system, and have id = /usr/lib/libfoo.dylib, and others are used to build projects, and their id = @ loader_path / libfoo.dylib. This is easy to solve with install_name_tool, but tedious; therefore, since version 10.5, the @ rpath / label was entered. The library is built with id = @ rpath / libfoo.dylib and is copied anywhere. The binary is compiled with a list of paths to look for dependencies, in which it is allowed to use @ {executable, loader} _path /:
$ gcc ... -Xlinker -rpath -Xlinker '@executable_path/libs' -Xlinker -rpath -Xlinker '/usr/lib' ...
This is similar to RPATH / RUNPATH for ELF. When the binary is run, the @ rpath / libfoo.dylib line will be expanded to @ executable_path / libs / libfoo.dylib, which will already be expanded to an absolute path. Or it will unfold in /usr/lib/libfoo.dylib.
You can view rpaths sewn into the binary as follows:
$ otool -l main.bin | grep -A 2 -i lc_rpath
You can remove, change or add rpaths using install_name_tool.
Check by example:
$ ./macosx_make_good.sh Building version-0.2 Building version-0.3 Building bar Building fooapp $ ./result/main.exe Hello World! bar library uses libversion 0.2.0, number = 2 version::get_version result = 0 But I uses liversion 0.3.0 number = 3
On the iOS is still the same.
As you can see from the example, Mac OS X in terms of dynamic libraries is better than Linux & Co.
And finally, Windows!
It's all good here too
[6; Dynamic Linking and Loading, Comparison of dynamic linking approaches] . You just need to add the version in the file name and ... there are no symlinks! That is, they are there, but many complain about them and they only work on NTFS
(Windows XP can be installed on the FAT partition for sure) . Consequently, backward compatibility can cost decent disk space ... Well, okay. )
To build an example on Windows, you will need to start the Visual Studio console, in which the environment will already be configured. Next build and run:
> .\windows_make_good.bat // ... >.\result\main.exe Hello World! bar library uses libversion 0.2.0, number = 2 version::get_version result = 0 But I uses liversion 0.3.0 number = 3
Scans are sought only
[17] . One of the possible ways to change the dependency search algorithm is to use the application configuration file and the privatePath properties of the probing
[18] . However, this method is applicable only starting with Windows 7 / Server 2008 R2.
And then there are WinSxS and so-called assemblies.
[19] . This is the topic of a separate article. However, while this article was being written, an insight and understanding descended that these same assemblies are needed only for (at least, Sishnikam and C ++ nicknames) so that all applications link, say, with comdlg32.dll, but all use different versions.
Conclusion
All major platforms make it relatively easy to create applications that can be installed by regular copying. However, the problems of dependency hell, backward compatibility and mashing of characters, developers must solve on their own.
The main decision is the choice of the correct versioning and control over it.
While
Curiosity is plowing through the Martian expanses, the author tried to tell here how to avoid mashing of characters, there have been articles on Habré for a long time telling how to specifically achieve the opposite:
habrahabr.ru/post/106107 ,
habrahabr.ru/post/ 115558 .
P.S. While work was being done on this article, the author attended the conference “On the strike!”, Where he listened to the report of K. Nazarov from Parallels on versioning
[20] . Nothing unexpected or unusual was said there, but it was nice to hear that in such a well-known company they realized the problem and made the right conclusions. From the author himself, he made a link from there:
semver.org .
Taking this opportunity, I want to thank my colleagues Alexander Sidorov and Alexander Prokofiev for constructive criticism and valuable comments!
Links
- ^ QtMultimedia changes-5.0.1
- ^ http://en.wikipedia.org/wiki/Soname .
- ^ Program Library HOWTO, 3.1.1. Shared Library Names .
- ^ man ld-linux.so.
- ^ http://en.wikipedia.org/wiki/Rpath .
- ^ 1 2 Linkers and Loaders by John R. Levine, http://www.iecc.com/linker/ .
- ^ 1 2 How To Write Shared Libraries by Ulrich Drepper, http://www.akkadia.org/drepper/dsohowto.pdf (pdf).
- ^ http://en.wikipedia.org/wiki/Direct_binding .
- ^ https://bugs.gentoo.org/show_bug.cgi?id=114008 .
- ^ https://blogs.oracle.com/msw/date/20050614 .
- ^ http://cryptonector.com/2012/02/dll-hell-on-linux-but-not-solaris/ .
- ^ https://sourceware.org/binutils/docs/ld/VERSION.html .
- ^ http://www.tux.org/pub/tux/eric/elf/docs/GNUvers.txt .
- ^ man ld.
- ^ 1 2 3 man dyld.
- ^ http://en.wikipedia.org/wiki/Mach-O#Mach-O_file_layout .
- ^ MSDN, Dynamic-Link Library Search Order, http://msdn.microsoft.com/en-us/library/windows/desktop/ms682586%28v=vs.85%29.aspx .
- ^ http://stackoverflow.com/a/10390305/1758733 .
- ^ http://en.wikipedia.org/wiki/WinSXS .
- ^ Nazarov K., Extremely preconceived look at the versioning of software products, http://nastachku.ru/lectures#lecture_178 .
- Oracle, Linker and libraries Guide, http://docs.oracle.com/cd/E19683-01/817-1983/index.html .
- Beginner's guide to the linker operation, http://habrahabr.ru/post/150327/ .