How To Name A Solaris Shared Object

Ali Bahrami — Friday October 15, 2010

Surfing with the Linker-Aliens

In order to add a new shared object to Solaris, you need to know how to name it. As obvious as this sounds, there is a lot of confusion surrounding this subject.

Solaris follows a standard set of rules for shared object naming, and largely serves as a example of how we intend things to work. Unfortunately, some poor examples have also crept into the system over the years, no doubt adding to the confusion.

The Linker and Libraries Guide contains the basic information, but we seem to be missing a concise description of how Solaris shared objects are supposed to be named. Without that, people will end up trying to intuit what they should be doing by looking about and guessing. The occasional misstep is almost inevitable.

I hope this discussion will fill that gap. I will describe the rules we follow under Solaris, and explain the reasoning behind them.

Naming Of Native Solaris Objects To Be Linked Against With ld

The largest category of shared object are those objects that are intended to be linked against executables and other shared objects via the link-editor (ld). This is typically done using the ld -l command line option. Native shared objects that are intended to be linked against with ld on Solaris are expected to follow the following conventions:
  1. The object should have the fully versioned name.
  2. The object should have an SONAME, set via the ld -h option, that includes the version number.
  3. If this is a public object intended for general use, a symbolic link with the non-versioned name should point at the object.
The C runtime library demonstrates this:
% ls -alF /lib/libc.*
lrwxrwxrwx 1 root root       9 Mar 22  2010 /lib/libc.so -> libc.so.1*
-rwxr-xr-x 1 root bin  1721888 Oct  4 10:08 /lib/libc.so.1*
% elfdump -d /lib/libc.so.1 | grep SONAME
       [4]  SONAME        0xb8bc          libc.so.1
Each shared object is therefore accessible by its fully versioned name, or via a symbolic link that makes it available via a generic non-versioned name. Although libc does not show this, there can be more than one version of a given object. This happens when a shared object is changed in a backward incompatible manner, something that we try very hard to prevent under Solaris. The generic symbolic link always points at the most current version of the object. This is the version that newly built code should use.

The non-versioned and versioned names serve the differing needs of the link-editor (ld), and runtime linker (ld.so.1):

The non-versioned symbolic link is called a compilation symlink, because it is the name seen by the link-editor (ld) at compile/link time. When ld sees an argument of the form '-lXXX', it searches the library path for files with the name 'libXXX.so'. For example, to link a program against the C runtime library, you specify the -lc option. This mechanism allows ld to find the desired library via its generic compilation symlink.

Having arranged for ld to find the object via its generic name, it is now necessary to ensure that the runtime linker will look for it via its fully versioned name. This does not happen by default:

This is why you must explicitly use ld -h to specify an SONAME when you build your object. It ensures that the runtime linker will search for the object via it's fully versioned name at runtime.

For example, we saw above that the SONAME for the C runtime library is "libc.so.1". /bin/ls is linked against libc using the ld -lc command line option. Without the SONAME in libc, we'd expect the NEEDED entry to contain "libc.so", but instead we see the desired result:

% elfdump -d /bin/ls | grep libc.so
       [8]  NEEDED        0x60f           libc.so.1
Despite being linked via the generic name, the runtime linker searches for libc.so.1, and not libc.so, when the program is actually run, as shown by ldd:
% ldd /bin/ls | grep libc.so
        libc.so.1 =>     /lib/libc.so.1
To summarize:
  1. The link-editor uses the generic object name to locate the object at link-time.

  2. The runtime linker uses the versioned object name to locate the object at runtime.

  3. This happens not by default, but through the systematic application of the three rules listed at the beginning of this section.

Compilation Symlinks and Private Objects

The compilation symlink exists solely for the benefit of the link-editor (ld), and plays no role in finding the object at runtime. If the compilation symlink is not present, the object is effectively rendered invisible to ld. Solaris takes advantage of this fact to protect users from accidentally linking against objects.

There are objects in the Solaris system that exist as implementation details, to provide support for the parts of the system that are publically documented and committed. Such objects are subject to unannounced change, or even removal, so the lack of a compilation symlink saves a great deal of trouble. For example, Solaris ships with the following object:

% ls -alF /lib/libavl.so*
-rwxr-xr-x 1 root bin 14K Oct  4 10:07 /lib/libavl.so.1*
Without a compilation symlink named libavl.so, this object will be ignored by the link-editor when it builds new objects. Your programs will not accidentally find it even if you specify -lavl to ld, because ld will not be able to locate a file named libavl.so. If libavl becomes public and committed someday, a compilation symlink will be added for it.

I mentioned before that Solaris contains some shared objects that do not faithfully follow the rules we are discussing. By far, the most common error is to deliver a compilation symlink with a private object. The mere presence of a compilation symlink should not be taken as evidence that the object is public and safe to use. A public object will have manpages documenting the library and the functions it contains, and those manpages will include an ATTRIBUTES section that details the commitment level of the interfaces it provides.

Objects That Have More Than A Major Version

Solaris shared objects use a single version number, referred to as the major version. In the case of libc, as shown above, this version is 1. Non-native shared objects often use a versioning scheme that includes additional sub-version numbers. To handle such objects, we need to generalize our rules.

Although Solaris uses a single version number, our history includes a time when we used more. Those of you who remember SunOS 4.x may recall that those systems had major and minor numbers, as evidenced by the BCP objects still delivered with sparc systems:

% ls -alF /usr/4lib/libc.so*
-rwxr-xr-x 1 root bin 411820 Jan 22 2005 /usr/4lib/libc.so.1.9*
-rwxr-xr-x 1 root bin 411080 Jan 22 2005 /usr/4lib/libc.so.2.9*
Note that there is no compilation symlink — we supply these old objects so that customers can continue to run their ancient (now approaching 20 years) SunOS 4.x executables, but we don't want anyone linking new code to them!

Shared objects were first added to SunOS in version 4.0. As with today's system, a change in the major number reflected an incompatible interface change, and when that happened, the older objects would continue to be supplied for the benefit of old executables, and a separate new object would be delivered with the new code. A change in minor number reflected a compatible change, but the runtime linker would print warning messages when you ran an old executable against a newer minor version. This quickly proved to be a bad idea, as it needlessly annoyed users.

We learned two lessons from SunOS 4.x shared objects:

As a result, the minor number concept was dropped in Solaris 2.x (SunOS 5.x), and has not been missed.

Other bodies of code do utilize additional (minor, micro, etc) version numbers, and you will see them under Solaris in software that originates elsewhere. This is done in order to match name used by the community that produces the software in question, and not necessarily for object versioning. When building such software for Solaris, you must follow a slightly more general version of our rules:

  1. The object should have the fully versioned name.
  2. The object should have an SONAME, set via the ld -h option, that includes only the major version number, and not the minor or smaller version numbers.
  3. A symbolic link matching the SONAME should point at the object.
  4. If this is a public object intended for general use, a symbolic link with the non-versioned name should point at the object.

For example:

% ls -alF /usr/gnu/lib/libncurses.so*
lrwxrwxrwx 1 root root   15 Mar 22  2010
        /usr/gnu/lib/libncurses.so -> libncurses.so.5*
lrwxrwxrwx 1 root root   17 Mar 22  2010
        /usr/gnu/lib/libncurses.so.5 -> libncurses.so.5.7*
-rwxr-xr-x 1 root bin  351K Jun 10 11:53 /usr/gnu/lib/libncurses.so.5.7*
% elfdump -d /usr/gnu/lib/libncurses.so.5.7 | grep SONAME
       [2]  SONAME        0x27a1          libncurses.so.5
This GNU object, delivered with Solaris, retains its original version number (5.7). However, it still follows our basic rules. The object has the fully versioned named, the SONAME contains only the major number, and a compilation symlink is supplied.

The use of an SONAME that contains only the major version number is done in order to preserve the principle that you should be able to replace an object with a newer version of the same object as long as the major number does not change, and that it will not be necessary to recompile objects linked against such an object in order to run them. To put this in more concrete terms, we expect that libncurses.so.5.7 can be replaced by libncurses.so.5.8, and that any program linked against libncurses.so.5.7 can use libncurses.5.8, without the need to rebuild.

Related to this point, it is worth noting that we now have two symbolic links rather than the single link we use for native Solaris objects, and the purpose of these two links is different, and unrelated:

Rules for Objects Used Via dlopen()

The discussion to this point has been limited to objects that are linked to other objects via the link-editor (ld). Now, let's consider objects that are loaded under program control, via the dlopen() function.

Many objects are used both via ld, and dlopen(). For such objects, you must follow the rules described above that allow the object to work properly with ld. The advice given in this section is only for objects that will never be linked to via ld.

An object that is not linked to via ld does not have to follow any of the rules we've discussed so far:

As you can see, dlopen() imposes no naming requirements on an object. However, Solaris employs the following conventions for the benefit of human observers:

For example, the elfedit utility is delivered with a set of runtime loadable support modules:

% ls -alFR /usr/lib/elfedit/
/usr/lib/elfedit/:
total 1185
drwxr-xr-x   3 root     bin           13 Sep  2 15:09 ./
drwxr-xr-x 169 root     bin         2023 Oct  4 10:09 ../
lrwxrwxrwx   1 root     root           1 Jun 19 13:52 32 -> ./
lrwxrwxrwx   1 root     root           5 Jun 19 13:52 64 -> amd64/
drwxr-xr-x   2 root     bin           10 Sep  2 15:09 amd64/
-rwxr-xr-x   1 root     bin        62868 Sep  2 15:09 cap.so*
-rwxr-xr-x   1 root     bin        97948 Sep  2 15:09 dyn.so*
-rwxr-xr-x   1 root     bin        92412 Sep  2 15:09 ehdr.so*
-rwxr-xr-x   1 root     bin        56248 Sep  2 15:09 phdr.so*
-rwxr-xr-x   1 root     bin        65428 Sep  2 15:09 shdr.so*
-rwxr-xr-x   1 root     bin        41760 Sep  2 15:09 str.so*
-rwxr-xr-x   1 root     bin        71268 Sep  2 15:09 sym.so*
-rwxr-xr-x   1 root     bin        47688 Sep  2 15:09 syminfo.so*

/usr/lib/elfedit/amd64:
total 1447
drwxr-xr-x   2 root     bin           10 Sep  2 15:09 ./
drwxr-xr-x   3 root     bin           13 Sep  2 15:09 ../
-rwxr-xr-x   1 root     bin        90928 Sep  2 15:09 cap.so*
-rwxr-xr-x   1 root     bin       130992 Sep  2 15:09 dyn.so*
-rwxr-xr-x   1 root     bin       125064 Sep  2 15:09 ehdr.so*
-rwxr-xr-x   1 root     bin        78368 Sep  2 15:09 phdr.so*
-rwxr-xr-x   1 root     bin        83968 Sep  2 15:09 shdr.so*
-rwxr-xr-x   1 root     bin        54240 Sep  2 15:09 str.so*
-rwxr-xr-x   1 root     bin        98592 Sep  2 15:09 sym.so*
-rwxr-xr-x   1 root     bin        70056 Sep  2 15:09 syminfo.so*

Installing them under /usr/lib/elfedit makes it obvious what application they support, while the use of the .so extension shows that they are sharable objects.

These elfedit objects are private modules delivered together with the elfedit utility, and not used by anything else. In addition, elfedit includes a module version in the handshake it completes with each module as the module is loaded. Therefore, adding version numbers to the file names would add no value, and is not done.

Surfing with the Linker-Aliens

Published Elsewhere

https://blogs.oracle.com/ali/entry/how_to_name_a_solaris/
https://blogs.oracle.com/ali/how-to-name-a-solaris-shared-object/

Surfing with the Linker-Aliens

[15] New Mapfile Syntax
Blog Index (ali)
[17] Solaris 11