News, tips, partners, and perspectives for the Oracle Solaris operating system

Static Linking - where did it go?

With Solaris 10 you can no longer build a static executable. It's not that ld(1) doesn't allow static linking, or using archives, it's just that libc.a, the archive version of libc.so.1, is no longer provided. This library provides the interfaces between user land and the kernel, and without this library it is rather hard to create any form of application.

We've been warning users against static linking for some time now, and linking against libc.a has been especially problematic. Every solaris release, or update (even some patches) has resulted in some application that was built against libc.a, failing. The problem is that libc is supposed to isolate an application from the user/kernel boundary, a boundary which can undergo changes from release to release.

If an application is built against libc.a, then any kernel interface it references is extracted from the archive and becomes a part of the application. Thus, this application can only run on a kernel that is in-sync with the kernel interfaces used. Should these interfaces change, the application is treading on shaky ground.

In addition, implementation details of libc, such as localization and the name service switch, require dynamic linking (they dlopen() other objects). As we anticipated libc.a to be used in a static environment, any dynamic capabilities were #ifdef'ed out of the code. libc.a has always been a subset of libc.so.1.

One common failing we have discovered is that many folks built against libc.a but otherwise used dynamic objects to supply other interfaces. These applications, termed partially static, are particularly fragile. The dynamic objects these partially static applications invoke, commonly depend on interfaces contained in libc.so.1. However, at runtime these dynamic objects are bound to the portions of libc that have been statically linked with the executable, leaving any other references to be bound to the dynamic libc.so.1. This combination of inconsistent interfaces can lead to chaos and ruin.

Because of this potential for destroying any application binary interface guarantee, the 64-bit version of Solaris never delivered any 64-bit system archives libraries. We figured we nip that problem in the bud right away.

But why did we wait until Solaris 10 to stop delivering 32-bit system archives? It turns out the merge of libc and libthread put the last nail in the coffin.

In earlier releases of Solaris, threaded applications needed to build against libthread. This library offered< the true thread interfaces that allow you to create and manage threads. However, to allow library developers to create libraries that were thread aware (ie. that could be used in a threading environment and a non-threading environment), libc also provided all the thread interfaces. These interfaces were basically no-op stubs. The gyrations we had to go through to make these two libraries cooperate was a story in itself.

Now, if you built an application against libc.a and happened to reference the thread interfaces, you ended up with the no-op stubs becoming part of the application. This turned out to be a major problem when the application moved to another release and became bound to new dynamic objects.

In Solaris 10, libthread and libc have been merged. Effectively all applications are thread capable, but until any new threads are created, the application remains single threaded. This model simplifies application and especially library development because there are no longer three process models to contend with (threaded, non-threaded, and statically linked). There is now only one model, one that is thread-capable. Libraries can be assured they always operate in a thread-capable application environment. These libraries can take advantage of threading interfaces and features like thread-local storage that cannot be provided in a non-thread-capable environment.

The merger of libthread and libc makes any partially static application doomed to failure, even if a stripped-down, crippled archive version of libc.a were made available.

Therefore, to put to rest the consistent failure of partially static applications from release to release, we've now made it impossible to make such applications.

Some folks thought of static applications as being a means of insulating themselves from system changes. But as I explained above, they were not insulating themselves from user/kernel interface changes.

Note that there is some flexibility even in a dynamic linking environment. Although applications are built to encode the interpretor /usr/lib/ld.so.1, applications can be built to specify an alternative interpretor:

    % cp  /usr/lib/ld.so.1  /local/safe/lib
    % cp  /usr/lib/libc.so.1  /local/safe/lib
    % LD_OPTIONS=-I/local/safe/lib/ld.so.1 \\
        cc -o main main.c -R/local/safe/lib ...

Or, you can invoke an alternative interpretor directly:

    % /local/safe/lib/ld.so.1  main ...

But, even if you create an environment with an alternate runtime linker and dynamic libc, it must be in-sync with the kernel on which it will execute. Otherwise, this technique is no more robust than using an archive version of libc.a.

Join the discussion

Comments ( 3 )
  • Rod Evans Wednesday, December 8, 2004

    Sadly, a number of vocal customers have been
    rather upset when their static binary has

  • Jeff Tuesday, December 19, 2017
    Thanks to this annoying reality I had to cross compile vim from a linux host (since the boxes we have that are running sparc sunos 5.10 didnt have gcc or a means to install it as it is air gapped) as well as dynamically link it! Annoying; rage--- but thanks for the interesting article explaining why you guys did this.
  • Rod Evans Saturday, December 23, 2017
    Sorry Jeff, but this "change" was made over 13 years ago.
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.