LD_ASSUME_KERNEL

To quote the ld.so man page,

The LD_ASSUME_KERNEL environment variable overrides the kernel version used by the dynamic linker to determine which library to load.

An ELF executable has its minimum compatible OS ABI version written into its .note.ABI-tag ELF section at compile-time. The dynamic linker (e.g. /lib/ld-linux.so.2) compares the kernel ABI to that version and errors out if the current environment isn’t sufficient.

Ulrich Drepper has a short write-up on the mechanism: http://www.akkadia.org/drepper/assumekernel.html

The ABI version is written into the .note.ABI-tag ELF section. You can get the ABI version using objdump and interpreting the hex manually, or you can install and use eu-readelf, which will pretty-print the information for you.

Environment note

All work in this post was done on an Ubuntu Natty machine:

$ uname -a
Linux kid-charlemagne 2.6.38-13-generic #53-Ubuntu SMP Mon Nov 28 19:33:45 UTC 2011 x86_64 x86_64 x86_64 GNU/Linux

Using objdump

$ objdump -s --section=.note.ABI-tag /bin/ls

/bin/ls:     file format elf64-x86-64

Contents of section .note.ABI-tag:
 400254 04000000 10000000 01000000 474e5500  ............GNU.
 400264 00000000 02000000 06000000 0f000000  ................

The Linux Standard Base Specification describes the format of the section:

The first 32-bit word of the desc field must be 0 (this signifies a Linux executable). The second, third, and fourth 32-bit words of the desc field contain the earliest compatible kernel version.

In our case, 2, 6, and f mean our earliest compatible kernel version is 2.6.15.

Using eu-readelf

eu-readelf is part of the elfutils package on Ubuntu.

$ eu-readelf -n /bin/ls

Note section [ 2] '.note.ABI-tag' of 32 bytes at offset 0x254:
  Owner          Data size  Type
  GNU                   16  VERSION
    OS: Linux, ABI: 2.6.15

Note section [ 3] '.note.gnu.build-id' of 36 bytes at offset 0x274:
  Owner          Data size  Type
  GNU                   20  GNU_BUILD_ID
    Build ID: 3e6f3159144281f709c3c5ffd41e376f53b47952

And we see that eu-readelf agrees with our interpretation of the objdump output.

ABI mismatch

What happens if your kernel ABI isn’t sufficient to run an executable?

Since the minimum compatible OS version for this ls we’ve been examining is 2.5.15, let’s set LD_ASSUME_KERNEL to a lower version number and find out:

$ export LD_ASSUME_KERNEL=2.5.14
$ ls
ls: error while loading shared libraries: librt.so.1: cannot open shared object file: No such file or directory

This is not that helpful an error message, since it’s not true that librt.so.1 doesn’t exist:

$ ldd /bin/ls
	linux-vdso.so.1 =>  (0x00007fff653ff000)
	libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007fa3f87e7000)
	librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007fa3f85df000)
	libacl.so.1 => /lib/x86_64-linux-gnu/libacl.so.1 (0x00007fa3f83d6000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa3f8042000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fa3f7e3e000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fa3f8a2f000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fa3f7c1f000)
	libattr.so.1 => /lib/x86_64-linux-gnu/libattr.so.1 (0x00007fa3f7a1a000)

tells us that it is in /lib/x86_64-linux-gnu/.

We can see why this happens, though, in elf/dl-load.c in the glibc source, where errno is set to ENOENT on version mismatch:

osversion = (abi_note[5] & 0xff) * 65536
	  + (abi_note[6] & 0xff) * 256
	  + (abi_note[7] & 0xff);                                                                                                                                                                                
 (abi_note[4] != __ABI_TAG_OS
  || (GLRO(dl_osversion) && GLRO(dl_osversion) < osversion))
{
close_and_out:
  __close (fd);                                                                                                                                                                                                  
  __set_errno (ENOENT);                                                                                                                                                                                          
  fd = -1;                                                                                                                                                                                                       
}

A couple of notes on the various version variables in the above code snippet:

  • __ABI_TAG_OS describes the platform (i.e. Linux, Solaris, FreeBSD) and is derived from data in abi-tags. As mentioned in the LSB spec, Linux has an __ABI_TAG_OS of 0, which is checked against the 5th word in the ABI note (which we verified was 0 in the objdump above).
  • In sysdeps/unix/sysv/linux/dl-sysdep.c, the dynamic linker attempts to set dl_osversion based on the following sources:
    1. a version given in the PT_NOTE ELF section for the “kernel-supplied DSO”. I’m not sure which DSO that glibc comment is specifying.
    2. the uname system call
    3. /proc/sys/kernel/osrelease

Miscellaneous notes

  • The assembly that writes the ABI version into the .note.ABI-tag ELF section lives in csu/abi-note.S in the glibc source:
            .section ".note.ABI-tag", "a"
            .p2align 2
            .long 1f - 0f           /* name length */
            .long 3f - 2f           /* data length */
            .long  1                /* note type */
    0:      .asciz "GNU"            /* vendor name */
    1:      .p2align 2
    2:      .long __ABI_TAG_OS      /* note data: the ABI tag */
            .long __ABI_TAG_VERSION
    3:      .p2align 2              /* pad out section */
  • A lot of shared libraries don’t have a .note.ABI-tag section. Here’s a small shell script to print out the OS version for those that do in the main shared library directories:
    for dir in /lib /lib64 /lib/x86_64-linux-gnu /lib64/x86_64-linux-gnu /usr/lib /usr/lib64
    do
        for elt in `ls $dir/*.so*`
        do
            res=`eu-readelf -n $elt | grep "OS"`;
            echo $elt;
            if ! [ -z "$res" ]; then
                echo "    "$res;
            fi
        done
    done

    With almost no exception, all shared libraries with .note.ABI-tag sections are in /lib/x86_64-linux-gnu and /lib64/x86_64-linux-gnu (although not all shared libraries in those directories have an ABI tag). This is presumably on purpose, although I couldn’t find a document describing when you do or don’t have an ABI tag or why shared libraries in /lib or /usr/lib never have an ABI tag.

    The one exception was /usr/lib/libvdpau_nvidia.so / /usr/lib64/libvdpau_nvidia.so, which has the odd OS minimum version of 2.3.99.

  • I ran this script on RHEL 4 (2.6.9), Precise (3.2.0), and Fedora 16 (3.3.0) machines for comparison. The RHEL 4 shared libraries that have ABI tags all require 2.2.5, Precise required 2.6.24, and Fedora 16 required 2.6.32. I’m not sure what the relationship is between the minimum OS version written into ABI tags as built for a particular environment and the actual kernel ABI.
  • The script caught a couple of .so files that were not actually shared libraries. They all in fact ended up being linker scripts. Here’s a partial list:
    • /usr/lib/libc.so, provided by glibc-devel
    • /usr/lib/libbfd.so, provided by binutils-devel
    • /usr/lib/libcurses.so, provided by ncurses-devel
    • /usr/lib/libfl.so, provided by flex
    • /usr/lib/libpthread.so, provided by glibc-devel
    • /usr/lib/libtermcap.so, provided by ncurses-devel

    You can determine which package provides a file with dpkg -S on dpkg-based sytems and rpm -qf on rpm-based systems.

The Architecture of Open Source Applications, Volume II

I was honored to be approached last year by editors Amy Brown and Greg Wilson to write a chapter on Twisted for The Architecture of Open Source Applications Volume II: Structure, Scale, and a Few More Fearless Hacks. This was my first-ever contribution to a book, and a great introduction to the writing, review, and editing cycles for a technical book.

The book was released as a paperback on May 8th and is now available in a number of formats. Other chapters include: Firefox release engineering, GDB, Git, Matplotlib, Mediawiki, Puppet, and PyPy.

As with Volume I, the material is under a Creative Commons Attribution 3.0 Unported license, and all royalties are donated to Amnesty International.

Twisted logo

Ways to enjoy the book:

Thank you Glyph and JP for letting me pick their brains for hours while researching my chapter, and to Glyph and Adam for their reviews of the numerous drafts.

The 6th Boston Python Workshop

The 6th Boston Python Workshop ran the weekend of March 30th at MIT. It marked a full year of diversity outreach with the Boston Python user group and was the second workshop to utilize our grant from the Python Software Foundation Outreach and Education Committee.

Boston Python Workshop 6, Friday night

Additional resources: