19
Oct 09
Linking to Older Versioned Symbols (glibc)


In my last post I gave a brief overview of the mechanism used internally by glibc for versioning symbols within shared libraries. As an addendum to my previous article I would like to discuss a simple way to force linking against older glibc symbols. Why would you do this you may ask? Well, suppose you have several GNU/Linux systems with varying glibc installs across them but you want to deliver a binary that would be compatible across them. One option is to statically link your binary, my vote :) , the other is to link to an older symbol within the shared library. I would like to mention that using an older symbol has the obvious drawback, possible advantage, of using something that was deprecated for a reason in the past (i.e. broken, behavior changes, performed poorly, new arch support, etc.). The reason I say it is possibly advantageous to link against an older symbol may be because it is known to behave in a desired way, broken or not! I present the following merely for education purposes, so use with care.

To force linking against a particular symbol you need to use the same .symver pseudo-op that is used for defining versioned symbols in the first place. In the following example I make use of glibc’s realpath, but want to make sure it is linked against an older 2.2.5 version.

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
 
__asm__(".symver realpath,realpath@GLIBC_2.2.5");
int main()
{
   char* unresolved = "/lib64";
   char  resolved[PATH_MAX+1];
 
   if(!realpath(unresolved, resolved))
      { return 1; }
 
   printf("%s\n", resolved);
 
   return 0;
}

If you were to use objdump on the resulting binary you would see that it is indeed using realpath@GLIBC_2.2.5! Also note that other symbols have been resolved to their defaults so you need to make sure you add a .symver pseudo-op for each symbol you want to force to an older version.

0000000000000000      F *UND*  0000000000000000         realpath@GLIBC_2.2.5
...
0000000000000000      F *UND*  0000000000000000         __stack_chk_fail@@GLIBC_2.4

The .symver pseudo-op can be used this way to force any symbol to be linked against an older one so long as it is valid. To ease linking against older glibc versions I’ve provided a simple header which can be used to force linking against the minimum glibc version for a give x86 architecture. The minimum versions I am using were taken from shlib-versions file of the glibc git tree.

#ifndef __GLIBC_COMPAT_SYMBOL_H__
#define __GLIBC_COMPAT_SYMBOL_H__ 1
 
/**
 * add other architecures below
 */
#ifdef __amd64__
   #define GLIBC_COMPAT_SYMBOL(FFF) __asm__(".symver " #FFF "," #FFF "@GLIBC_2.2.5");
#else
   #define GLIBC_COMPAT_SYMBOL(FFF) __asm__(".symver " #FFF "," #FFF "@GLIBC_2.0");
#endif /*__amd64__*/
 
#endif /*__GLIBC_COMPAT_SYMBOL_H__*/

To use the glibc compatible header with the realpath example above, we merely use the GLIBC_COMPAT_SYMBOL macro with the appropriate symbol:

GLIBC_COMPAT_SYMBOL(realpath)

12 comments

  1. Great, I’m looking for it. I checked ld’s manual, but because a small mistake, typing one more ‘@’ in .symver, I failed. After google and google it again with different keywords, I find this, by google “link against glibc symbol version” :o )

  2. Very useful post that saves me a lot of time.
    I wanted an executable to be runnable on a system that has older glibc version. My exe depended on GLIBC_2.4 and the target system was GLIBC_2.3.
    The thing is to find what symbol in Glibc makes the newest version dependence.
    With a ‘objdump -T exename’ you can collect all symbols and their versions, as listed above the __stack_chk_fail symbol was the bugger.
    Recompiling my exe with -fno-stack-protector gcc option made the trick.
    NOTE: w/o this option you expose the exe to security risks. You must know what you do here.
    Anyway, the objdump utility is your tool to find out symbol version dependencies.

  3. Thanks for posting this, I’ve found it is an incredibly useful technique for moving my locally compiled binaries onto a cluster using a somewhat dated OS version.

    I do have one question however, if I have a supplied archive file with a set of unversioned glibc calls which I link with my own compiled .o’s (with symbol versions) to create a shared library, then the calls from the .a file resolve to the default glibc version. Is there a way to enforce the symver behaviour at link time, or somehow transform the archive symbols to a given versioning?

    Thanks, Paul.

  4. @Paul: Glad this post was of help to you. Regarding your question, I’m not sure if this is possible without rebuilding the archive file. You can try reading up on the man pages for ar/objcopy from binutils to see if they can do what you need.

  5. Dear Trevor,
    do you know if this trick can also be applied to a program compiled with ifort?

    Greetings,
    Martino

  6. @Martino: I’m not familiar with the Inter IFORT compiler. It might be possible if you are able to get IFORT to understand the inline assembly primitives and have it link the object files with the GNU linker.

  7. I think that using asm directly in source is not a good practice. The best way is to add ‘Wa,–defsym,realpath=realpath@GLIBC_2.2.5′ option to gcc’s cmdline. BTW, all the required static libraries used in binary should be also build with this option too.

  8. @Vitamin: I agree that instrumenting the source code inline is ugly and non-portable. That said, I do not recommend using any of the techniques discussed here unless they are truly needed. In the general case I find it better to static link any dependencies to avoid unintentional side effects.

  9. @Trevor: everyting-is-static idiom causes a big problem while using system .so files (loaded directly). So I am continue to investigate the issue- I need to get statically linked binary that works on the most distros while built on quite recent one.

  10. @Vitamin: I can see running into problems if you application is using dlopen to load libraries dynamically. Are you running into another type of issue?

  11. If you are just interested in supporting a given older (dynamic) glibc version then a simpler way is to get a local copy and specify that manually on the command line. E.g. ‘gcc -o myprog mycode.c /home/myuser/oldlibs/libc.so.6′. A sort of lighter-weight version of the chroot technique.

  12. @Michael: Yep. Building and linking to an older version of libc or using a chroot is a much better, less error prone approach.

Leave a comment

*