Wednesday, 24 November 2010

ELF. ELF. ELF. Dont do it!

This is interesting...to me at least. How to create a "universal"
Linux binary. A universal binary is one which can be created
on one kernel/glibc distro but run on all prior and future releases
(or as many as I have or can catalog).

Its common to want the latest/greatest distro on your main machine.
(I currently run Ubuntu 10.10). But if I build software
(such as CRiSP) on that host, I want it to run on all
prior Ubuntu/RedHat/whatever systems. Building on each
one when there is close to zero difference is a waste of energy
and time.

I experimented a while back and found that glibc makes judicious
incompatible changes to the headers and libc, enticing applications
to only run on the glibc it was built on or later.

On investigating what was triggering this, I found it was
things I didnt care about. E.g. <ctype.h> which has always
been implemented as #defines and bitmap indexing into an array
(isdigit(), isalpha(), etc) was replaced by libc calls (to handle
Unicode or widechar types). Since I dont use these, I dont need that
headache. After trying to run my binary on a failing older glibc, I
worked out the headers to avoid, and wrote a tool to patch the
ELF executable to patch the GLIBC version requirements. This works well.

At least it did.

But Ubuntu 10.10 is running glibc 12.1. Here, when I create
my universal binary, I find it doesnt work on an earlier glibc. Instead
I get a cryptic "error loading shared library: glibc 2.5 or later dynamic linker"
is required.

What on earth happened? Where is this "information" telling the
/lib/ld-linux.so.2 that my binary needs such a thing?

Heres a good link from Ali Bahrami:
http://blogs.sun.com/ali/entry/gnu_hash_elf_sections.

The binutils people have decided to break ELF backwards compatibility
(or is that forward compatibility?). Instead of a ".hash" ELF
section - required by the rt.ld dynamic linker (/lib/ld-linux.so.2), they
create a new style of hash table, as described in that link above.

Now consider an older dynamic linker. It is expecting a ".hash"
section. But executables on glibc 12.1 (could be earlier releases
too - it may have existed on Ubuntu 10.04, but I havent verified
when this occurred yet), dont have a ".hash" section.

Instead we have a ".gnu.hash" section.


$ objdump -h ~/bin/fcterm | grep hash
3 .gnu.hash 00000268 0804818c 0804818c 0000018c 2**2


So, I am asserting by doing this, the older runtime linker gets
confused, and refuses to run the executable. You are really
SOL here, as its not as if you can use LD_LIBRARY_PATH or
LD_PRELOAD to bypass /lib/ld-linux.so.2. You could do this with
a chroot/jail, but that gets fiddly and may require root access - or
any number of things to send your customers scurry for the Windows
installation disks.

So - for my next trick, lets trying patching in an ELF conformant
".hash" section and see if we can get an older glibc to "like" my
executables.

I'll post an update if I am successful or not.




Post created by CRiSP v10.0.2c-b5917


4 comments:

  1. Why don't you keep around an ancient Ubuntu (virtual?) machine, with an old glibc et al., to do your builds on?

    ReplyDelete
  2. Do you realize that you can link with the -Wl,--hash-style=sysv option and get a .hash section instead of .gnu.hash ?

    ReplyDelete
  3. Also, about the symbol version problem, you can add these to your program:
    __asm__(".symver func,func@GLIBC_x.y.z");

    ReplyDelete
  4. Oracle has removed support for the old sun.com blog addresses - Bahrami's blog article is now at:

    https://blogs.oracle.com/ali/entry/gnu_hash_elf_sections

    ReplyDelete