How To Name A Solaris Shared Object

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):

  • At link time, it is appropriate to refer to objects generically. When we build a program, we wish to link against the current version of the libraries we intend to use, and to always get the best/latest versions without having to explicitly specify those versions.

  • At runtime, a program must use the specific version of the shared objects that it was linked against. If a program was linked against libXXX.so.1, it must continue to find libXXX.so.1 even if the system has added libXXX.so.2 since the last time the program was built. This means that at runtime, the fully versioned name must be used.
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:

  • When ld builds an object, it puts a NEEDED entry in the dynamic section for each object it links to it.

  • If the linked-to object contains an SONAME entry in its dynamic section, the value of the SONAME entry is placed in the NEEDED entry of the linking object.

  • If the linked-to object does not contain an SONAME entry in its dynamic section, ld uses the name that it found the object under. As we've seen above, this will be the generic compilation symlink name.
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:

  • Never, ever, issue a warning if the end user is not supposed to care about the issue being warned about and no harm can come from not knowing about it.

  • Since all libraries with the same major number are compatible, the minor number conveys nothing of interest and need not exist.
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:

  • libncurses.so is the compilation symlink, present for the benefit of the link-editor (ld), as discussed above. The compilation symlink operates as previously discussed, and should be omitted when delivering a private object.

  • libncurses.so.5 exists to bridge the gap between the SONAME for the object (libncurses.so.5) and the name of the object file (libncurses.so.5.7). This link is not optional. Without it, the runtime linker will not be able to locate the object.

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:

  • It does not require or benefit from a compilation symlink.

  • It does not require an SONAME, since the runtime linker will not look at it in this context.

  • It can have any can have any arbitrary name, rather than following the libXXX.so.x naming scheme. In particular, it is common to omit the 'lib' prefix from such objects.
As you can see, dlopen() imposes no naming requirements on an object. However, Solaris employs the following conventions for the benefit of human observers:
  • Give all sharable objects a '.so' extension.

  • Only use a version number (i.e. XXX.so.1) if multiple versions may be delivered and co-exist, or for which a version number would be meaningful to the end user.

  • Use a common application specific prefix or install objects together in a directory hierarchy that makes their purpose clear.

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.

Comments:

Post a Comment:
Comments are closed for this entry.
About

I work in the core Solaris OS group on the Solaris linkers. These blogs discuss various aspects of linking and the ELF object file format.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
Feeds