Oracle Blog

Solaris Linkers and Linking Accessories

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:

The object should have the fully versioned name.

The object should have an SONAME, set via the
ld -h option, that includes the version number.

If this is a public object intended for general use,
a symbolic link with the non-versioned name should point
at the object.

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:

The link-editor uses the generic object name to locate
the object at link-time.

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

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:

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:

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:

The object should have the fully versioned name.

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.

A symbolic link matching the SONAME should point at the object.

If this is a public object intended for general use,
a symbolic link with the non-versioned name should point
at the object.

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:

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.