Section 3.5. Library Versioning


3.5. Library Versioning[8],[9]

[8] Refer to the paper "Library Interface Versioning in Solaris and Linux," published in the Proceedings of the 4th Annual Linux Showcase and Conference, Atlanta, Oct 1014, 2000.

[9] Refer to the paper "How to Write Shared Libraries" by Ulrich Drepper, located at http://people.redhat.com/~drepper.

Maintaining binary-level compatibility or an ABI between shared libraries and applications using them is an important requirement. An ABI of a shared library is a set of runtime interfaces that an application may depend on; if the ABI evolves in an upward-compatible way from one release to the next, existing applications compiled on a given release will run on all subsequent releases without change. Library versioning is what Linux and other contemporary systems use to achieve binary compatibility.

Some of the applications we have ported in the past required library versioning support. Other UNIX platforms also implement library versioning, and each may be different in its own way. Linux provides two different techniques to use for library versioning: external and symbol versioning.

3.5.1. External Library Versioning

During link time, the linker (ld) looks for shared library files that end with .so. Files that end with .so are called linker names because of the way they are used in Linux. When an application that depends on a shared library is built, it is the shared library's soname (not the shared library's filename) that is recorded in the application binary as a dependency. It is the library's soname that is used by the runtime linker to locate and load the library depended on by the application. The soname contains only the major number (for example, libfoo.so.1).

When a shared library is updated in such a way that it is not compatible with the older one, the new shared library must have a new external versioned name. In other words, the library's soname needs to be changed. Examples of incompatible updates include the removal of a symbol, the removal of an argument from a function, and the update to the function such that the semantic property no longer matches its original specification and the new version is not binary-compatible with the older one. Consider the following example.

foo.c and message.c are files used to create the shared library libfoo.so.1.1 with a soname of libfoo.so.1:

/* foo.c v1.1 */ extern const char *_message1; extern const char *_message2; void foo1() {       (void) printf(_message1); } void foo2() {       (void) printf(_message2); } /* message.c v1.1 */ const char *_message1 = "this is string message 1\n"; const char *_message2 = "this is string message 2\n"; 


Generate the shared library with a soname of libfoo.so.1:

$ gcc -fPIC -c foo.c message.c $ gcc -shared -Wl,-soname, libfoo.so.1 -o libfoo.so.1.1 foo.o message.o 


The Wl flag is used to pass the arguments soname=libfoo.so.1 to the linker (ld), which gives the library a soname of libfoo.so.1.[10] The file libfoo.so.1.1 is generated, and then ldconfig is run, specifying the library name:

[10] In Linux, use the command objdump x libfoo.so | grep SONAME to display the soname.

$ /sbin/ldconfig -l libfoo.so.1.1 $ ls lrwxrwxrwx 1 x users 13 2004-12-24 15:16 libfoo.so.1 -> libfoo.so.1.1 -rwxr-xr-x 1 x users 6539 2004-12-24 15:15 libfoo.so.1.1 


After running ldconfig, a file with the same name as the soname is generatedlibfoo.so.1. For applications to be able to use and link to the newly created library, a link to the proper linker name libfoo.so needs to be created:

$ ln -s libfoo.so.1 libfoo.so 


The shared library is now ready for use. The following file, main1.c, is used to link with the shared library:

/* main.c */ main() {       foo1();       foo2(); } $ gcc -L. -lfoo main1.c -o main1 $ ./main this is string message 1 this is string message 2 


Using ldd to examine the shared library dependencies of main1 shows the following:

$ ldd ./main1     libfoo.so.1 => ./libfoo.so.1 (0xb7ffe000)     libc.so.6 => /lib/tls/i586/libc.so.6 (0x00b9c000)     /lib/ld-linux.so.2 (0x00b85000) 


As you can see from the output, the ldd command confirms that the soname libfoo.so.1 is recorded as a dependency of the executable main1.

Let's now assume for a moment that we need to change the internal and interface specifications of one of our library functions, foo2(). In this case, we need to create a version 2 of the libfoo.so shared library.

In this example, we change foo2 to print _message3 and take an argument. This represents an internal change to our specification and interface that alters the behavior of the library in a major way.

foo.c now looks like the following:

extern const char * _message1; extern const char * _message2; extern const char * _message3; void foo1() {     (void) printf(_message1); } void foo2(int x) {      int y;     (void) printf(_message3);    /* change to specification */         y = x; } 


message.c is updated to look like this:

const char *_message1 = "this is string message 1\n"; const char *_message2 = "this is string message 2\n"; const char *_message3 = "this is string message 3\n"; 


After making changes to the message.c and foo.c files, recompile and then create the shared library with a different soname, as follows:

$ gcc -fPIC -c foo.c message.c $ gcc -shared -Wl,-soname, libfoo.so.2 -o libfoo.so.2.1 foo.o message.o 


Notice that we incremented the major number of the soname and the resulting library filename. After creating the shared library with the soname of libfoo.so.2, run ldconfig, which creates the link to libfoo.so.2. Link libfoo.so.2 to libfoo.so, and it is ready to use:

$ /sbin/ldconfig -l libfoo.so.2.1 $ ln -s libfoo.so.2 libfoo.so 


Compile a program that uses the new libfoo.so, as in the following example:

/* main.c */ main() {     foo1();     foo2(4); } 


After compiling a program linked with the new libfoo.so, examine the shared library dependencies and notice the dependency on libfoo.so.2:

$ gcc -L. -lfoo main.c -o main2 $ ldd main2     linux-gate.so.1 => (0xffffe000)     libfoo.so.2 => ./libfoo.so.2 (0x40018000)     libc.so.6 => /lib/tls/libc.so.6 (0x4002a000)     /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) 


Run the new executable to determine whether it uses the right library:

$ ./main2 this is string message 1 this is string message 3 


Test the original executable to determine whether it still successfully locates the original versioned library:

$ ./main1 this is string message 1 this is string message 2 


Notice that the executable main1 from the previous example still runs even though the link to libfoo.so now points to libfoo.so.2. If main1 is executed in an environment in which there is no libfoo.so.1, you get this error:

./main: error while loading shared libraries: libfoo.so.1: cannot open shared object file: No such file or directory 


3.5.2. Symbol Versioning

As mentioned earlier, we increment the minor number of a shared library when we make changes to the library that are upward-compatible. This change involves adding some new interfaces while maintaining existing interfaces. However, there is an important issue associated with minor revision change. An application built with a given minor version of a shared library cannot be certain whether it can run with an earlier minor version of the library. This is because the application may have used one or more of the interfaces added to the library at a later minor version, and not present in the earlier one. Symbol versioning is introduced to solve this problem by allowing the shared library to record the information about what has been added in each minor revision.

In Linux, the GNU ld permits creation of shared libraries with symbol versioning through the linker switch -version-script option. This compiler option Wl,--version-script=mapfile instructs the linker which symbols to export from the generated shared library. Each symbol can belong to one of these two classes: global (exported) or local (not exported). Take, for example, the following program, foo.c. It has one function, foo1. This file will be used to create version 1.1 of the shared library.

Example:

$cat foo.c /* foo.c v1.1 */ extern const char *_message1; void foo1() {       (void) printf(_message1); } $cat message.c /* message.c v1.1 */ const char *_message1 = "this is string message 1\n"; 


A script file named scriptfile[11] is created, as shown here:

[11] Refer to the GNU ld manual at www.gnu.org/manual (and select the ld link). Or go directly to www.gnu.org/software/binutils/manual/ld-2.9.1/ld.html.

$cat scriptfile LX_1.1 {                global:                foo1;                local:                      *; }; 


In the scriptfile example, symbol foo1 is public and is exported as it is under the global stanza. And all other symbols (matched by the *) within version 1.1 of the library will not be exported.[12]

[12] This can be used to prevent symbols from being exported from the library by default.

Build the library:

$gcc -o libfoo.so.1 -shared -Wl,--version-script=scriptfile foo.o message.o 


Run objdump to examine the library:

$objdump -x libfoo.so.1 | egrep "foo1|message1" 00001640 l   O .data 00000004             _message1 0000049c g   F .text 0000002b             foo1 


Notice that the symbol foo1 is the only global symbol (the symbol _message1 is listed as local). readelf V displays the contents of the version sections in the object file:

$ readelf -V libfoo.so.1 Version symbols section '.gnu.version' contains 17 entries:  Addr: 00000000000002c6 Offset: 0x0002c6 Link: 2 (.dynsym)  000:  0 (*local*)    0 (*local*)    0 (*local*)    0 (*local*)  004:  0 (*local*)    0 (*local*)    0 (*local*)    0 (*local*)  008:  0 (*local*)    0 (*local*)    0 (*local*)    2 (LX_1.1)  00c:  2 (LX_1.1)    3 (GLIBC_2.0)   4 (GLIBC_2.1.3)  0 (*local*)  010:  0 (*local*) Version definition section '.gnu.version_d' contains 2 entries:  Addr: 0x00000000000002e8 Offset: 0x0002e8 Link: 3 (.dynstr)  000000: Rev: 1 Flags: BASE  Index: 1 Cnt: 1 Name: libfoo.so.1  0x001c: Rev: 1 Flags: none Index: 2 Cnt: 1 Name: LX_1.1 Version needs section '.gnu.version_r' contains 1 entries:  Addr: 0x0000000000000320 Offset: 0x000320 Link to section: 3 (.dynstr)  000000: Version: 1 File: libc.so.6 Cnt: 2  0x0010:  Name: GLIBC_2.1.3 Flags: none Version: 4  0x0020:  Name: GLIBC_2.0 Flags: none Version: 3 


A weak version definition can be created by updating local objects without introducing new interfaces. Examples of such changes are bug fixes and performance improvements. From the previous example, let's update the symbol _message1:

$ cat message.c /* message.c v1.1 */ const char *_message1 = "this is an updated string message 1\n"; 


Our version script file, scriptfile, has to be changed as follows:

$cat scriptfile LX_1.1 {         global:             foo1;         local:             *; }; LX_1.1.1 { } ; 


Now, rebuild libfoo.so.1:

$gcc -fPIC -c foo.c message.c $gcc -o libfoo.so.1 -shared -Wl,--version-script=scriptfile foo.o message.o 


The command readelf V libfoo.so.1 displays the following:

$ readelf -V libfoo.so.1 Version symbols section '.gnu.version' contains 18 entries:  Addr: 00000000000002e6 Offset: 0x0002e6 Link: 2 (.dynsym)  000:  0 (*local*)    0 (*local*)    0 (*local*)    0 (*local*)  004:  0 (*local*)    0 (*local*)    0 (*local*)    0 (*local*)  008:  0 (*local*)    0 (*local*)    0 (*local*)    3 (LX_1.1.1)  00c:  2 (LX_1.1)    2 (LX_1.1)    4 (GLIBC_2.0)   5 (GLIBC_2.1.3)  010:  0 (*local*)    0 (*local*) Version definition section '.gnu.version_d' contains 3 entries:  Addr: 0x0000000000000308 Offset: 0x000308 Link: 3 (.dynstr)  000000: Rev: 1 Flags: BASE  Index: 1 Cnt: 1 Name: libfoo.so.1  0x001c: Rev: 1 Flags: none Index: 2 Cnt: 1 Name: LX_1.1  0x0038: Rev: 1 Flags: WEAK  Index: 3 Cnt: 1 Name: LX_1.1.1 Version needs section '.gnu.version_r' contains 1 entries:  Addr: 0x0000000000000360 Offset: 0x000360 Link to section: 3 (.dynstr)  000000: Version: 1 File: libc.so.6 Cnt: 2  0x0010:  Name: GLIBC_2.1.3 Flags: none Version: 5  0x0020:  Name: GLIBC_2.0 Flags: none Version: 4 


Note that this time the Version definition section displays the weak version (see text box) definition.

Next, let's update libfoo.so. 1 by adding a function foo2:

$cat foo.c /* foo.c v 1.2 */ extern const char * _message1; extern const char * _message2; void foo1() {       (void) printf(_message1); } void foo2() {       (void) printf(_message2); } 


Update message.c:

$cat message.c /* message.c v1.2 */ const char *_message1 = "this is an updated string message 1\n"; const char *_message2 = "this is a string message 2\n"; 


Update the scriptfile:

LX_1.1 {         global:             foo1;         local:             *; }; LX_1.1.1 { } ; LX_1.2 {         global:             foo2; } LX_1.1.1; 


Build the library:

$gcc -fPIC -c foo.c message.c $gcc -o libfoo.so.1 -shared -Wl,--version-script=scriptfile foo.o message.o 


The command readelf V libfoo.so.1 displays the following:

Version symbols section '.gnu.version' contains 20 entries:  Addr: 0000000000000318 Offset: 0x000318 Link: 2 (.dynsym)  000:  0 (*local*)    0 (*local*)    0 (*local*)    0 (*local*)  004:  0 (*local*)    0 (*local*)    0 (*local*)     0 (*local*)  008:  0 (*local*)    0 (*local*)    0 (*local*)    4 (LX_1.2)  00c:  3 (LX_1.1.1)   2 (LX_1.1)    2 (LX_1.1)     5 (GLIBC_2.0)  010:  6 (GLIBC_2.1.3)  4 (LX_1.2)    0 (*local*)    0 (*local*) Version definition section '.gnu.version_d' contains 4 entries:  Addr: 0x0000000000000340 Offset: 0x000340 Link: 3 (.dynstr)  000000: Rev: 1 Flags: BASE  Index: 1 Cnt: 1 Name: libfoo.so.1  0x001c: Rev: 1 Flags: none Index: 2 Cnt: 1 Name: LX_1.1  0x0038: Rev: 1 Flags: WEAK  Index: 3 Cnt: 1 Name: LX_1.1.1  0x0054: Rev: 1 Flags: none Index: 4 Cnt: 2 Name: LX_1.2  0x0070: Parent 1: LX_1.1.1 Version needs section '.gnu.version_r' contains 1 entries:  Addr: 0x00000000000003b8 Offset: 0x0003b8 Link to section: 3 (.dynstr)  000000: Version: 1 File: libc.so.6 Cnt: 2  0x0010:  Name: GLIBC_2.1.3 Flags: none Version: 6  0x0020:  Name: GLIBC_2.0 Flags: none Version: 5 


The output shows that the version definition LX_1.2 has a dependency on the version definition LX_1.1.1.

Update main.c:

$cat main.c /* main.c v1.2 */ main() {       foo1();       foo2(); } $ln -s libfoo.so.1 libfoo.so $gcc -o main main.c -L. -lfoo 


Examine main using the ldd v command:

$ ldd -v main     libfoo.so => ./libfoo.so (0xb7ffe000)     libc.so.6 => /lib/tls/i586/libc.so.6 (0x00b9c000)     /lib/ld-linux.so.2 (0x00b85000)     Version information:     ./main:         libfoo.so (LX_1.1) => ./libfoo.so         libfoo.so (LX_1.2) => ./libfoo.so         libc.so.6 (GLIBC_2.0) => /lib/tls/i586/libc.so.6     ./libfoo.so:         libc.so.6 (GLIBC_2.1.3) => /lib/tls/i586/libc.so.6         libc.so.6 (GLIBC_2.0) => /lib/tls/i586/libc.so.6     /lib/tls/i586/libc.so.6:         ld-linux.so.2 (GLIBC_2.1) => /lib/ld-linux.so.2         ld-linux.so.2 (GLIBC_2.3) => /lib/ld-linux.so.2         ld-linux.so.2 (GLIBC_PRIVATE) => /lib/ld-linux.so.2         ld-linux.so.2 (GLIBC_2.0) => /lib/ld-linux.so.2 


Notice that the main executable references both LX_1.1 and LX_1.2 of the versioned library, as shown in the box.

If main.c looked liked the following:

main() {       foo1(); } gcc -o main main.c -L. -lfoo 


the resulting main executable will still be linked to version 1.2 of libfoo.so but will only show the reference to version 1.1 if examined, because it only needs to resolve symbols from that version of the library:

$ ldd -v main     libfoo.so => ./libfoo.so (0xb7ffe000)     libc.so.6 => /lib/tls/i586/libc.so.6 (0x00b9c000)     /lib/ld-linux.so.2 (0x00b85000)     Version information:     ./main:         libfoo.so (LX_1.1) => ./libfoo.so         libc.so.6 (GLIBC_2.0) => /lib/tls/i586/libc.so.6     ./libfoo.so:         libc.so.6 (GLIBC_2.1.3) => /lib/tls/i586/libc.so.6         libc.so.6 (GLIBC_2.0) => /lib/tls/i586/libc.so.6     /lib/tls/i586/libc.so.6:         ld-linux.so.2 (GLIBC_2.1) => /lib/ld-linux.so.2         ld-linux.so.2 (GLIBC_2.3) => /lib/ld-linux.so.2         ld-linux.so.2 (GLIBC_PRIVATE) => /lib/ld-linux.so.2         ld-linux.so.2 (GLIBC_2.0) => /lib/ld-linux.so.2 


Notice that this time only version LX_1.1 of the versioned library is referenced by the executable.

GNU ld also lets you bind a symbol to a version in the source file where the symbol is defined rather than in the versioning script. In addition, GNU ld allows multiple versions of the same function to appear in a given shared library. For detailed information, refer to the GNU ld manual[13] and the paper "How to Write Shared Libraries" by Ulrich Drepper.

[13] www.gnu.org/software/binutils/manual/ld-2.9.1/ld.html

Symbol versioning has been implemented in glibc since version 2.1. Symbol versioning is also a part of the Linux Standard Base Specification 1.2 and higher.




UNIX to Linux Porting. A Comprehensive Reference
UNIX to Linux Porting: A Comprehensive Reference
ISBN: 0131871099
EAN: 2147483647
Year: 2004
Pages: 175

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net