Search code examples
buildglibc

“Version `GLIBC_2.16' not found” target host error after upgrading build environment to Ubuntu 14.4


After upgrading build environment to Ubuntu 14.4, main executable refuses to start on hosts with older Linux version with the following message:

/lib/i386-linux-gnu/libc.so.6: version `GLIBC_2.16' not found (required by ./executable_name)

In order to safely distribute my package to hosts, which have older Glibc, should I try:

  • linking statically against libc?
  • downgrading libc on the build machine?
  • install older glibc along with one Ubuntu distributive goes? with?

Note: - I don't need to use any binaries from older Ubuntu versions - I don't need to build on older Ubuntu version


Solution

  • After long investigation I finally found solution for the issue.

    First of all I looked at the executable file dependencies:

    ldd -v <executable_name>
    

    My build is built with Cmake and only its Release version has the following dependency:

    Version information: 
        ./build/Release/products/<executable_name>: 
        libc.so.6 (GLIBC_2.16) => /lib/i386-linux-gnu/libc.so.6
    

    On analyzing this file with objdump, I retrieved that it needs __poll_chk symbol:

    00000000       F *UND*  00000000              __poll_chk@@GLIBC_2.16
    

    Though Glibc uses so called _symbol versioning _, this particular function was added only in Glibc 2.16.

    Therefore I tried investigating what caused the difference between Debug and Release builds.

    When CMAKE_BUILD_TYPE is set, Cmake defines domestic variables, which determine compiler flags. For GCC 4.8.4 they are:

    • Debug: -g
    • Release: -NDEBUG -O3

    Glibc poll.h includes poll2.h, which contains tricky _poll_chk yet unavailable in GLibc 2.16. This include goes under _USE_FORTIFY_LEVEL define.

    And according to Linux man pages (see quotes below) in Release build I have -D_FORTIFY_SOURCE=2 due to -O3 level.

    man gcc
    

    NOTE: In Ubuntu 8.10 and later versions, -D_FORTIFY_SOURCE=2 is set by default, and is activated when -O is set to 2 or higher. This enables additional compile-time and run-time checks for several libc functions. To disable, specify either -U_FORTIFY_SOURCE or -D_FORTIFY_SOURCE=0.

    man feature_test_macros
    

    _FORTIFY_SOURCE (since glibc 2.3.4) Defining this macro causes some lightweight checks to be performed to detect some buffer overflow errors when employing various string and memory manipulation functions. Not all buffer overflows are detected, just some common cases. In the current implementation, checks are added for calls to memcpy(3), mempcpy(3), memmove(3), memset(3), stpcpy(3), strcpy(3), strncpy(3), strcat(3), strncat(3), sprintf(3), snprintf(3), vsprintf(3), vsnprintf(3), and gets(3). If _FORTIFY_SOURCE is set to 1, with compiler optimization level 1 (gcc -O1) and above, checks that shouldn't change the behavior of conforming programs are performed. With _FORTIFY_SOURCE set to 2 some more checking is added, but some conforming programs might fail. Some of the checks can be performed at compile time, and result in compiler warnings; other checks take place at run time, and result in a run-time error if the check fails. Use of this macro requires compiler support, available with gcc(1) since version 4.0.

    or just use

    man -K _FORTIFY_SOURCE
    

    I checked every static library my executable includes, whose code uses poll function and ultimately had found it:

    objdump -t  lib.a | grep poll
    00000000         *UND*  00000000 Curl_poll
    00000000 l    d  .text.Curl_poll    00000000 .text.Curl_poll
    00000000         *UND*  00000000 poll
    00000000         *UND*  00000000 __poll_chk
    00000000 g     F .text.Curl_poll    0000025c Curl_poll
    

    This optimization may be disabled by adding -U_FORTIFY_SOURCE to the compiler flags within target CmakeLists.txt. This eliminate any lately detected GLIBC2.16 dependencies:

    Version information:
    products/<executable>:
        ld-linux.so.2 (GLIBC_2.3) => /lib/ld-linux.so.2
        librt.so.1 (GLIBC_2.2) => /lib/i386-linux-gnu/librt.so.1
        libdl.so.2 (GLIBC_2.0) => /lib/i386-linux-gnu/libdl.so.2
        libdl.so.2 (GLIBC_2.1) => /lib/i386-linux-gnu/libdl.so.2
        libpthread.so.0 (GLIBC_2.2) => /lib/i386-linux-gnu/libpthread.so.0
        libpthread.so.0 (GLIBC_2.3.2) => /lib/i386-linux-gnu/libpthread.so.0
        libpthread.so.0 (GLIBC_2.1) => /lib/i386-linux-gnu/libpthread.so.0
        libpthread.so.0 (GLIBC_2.0) => /lib/i386-linux-gnu/libpthread.so.0
        libpulse.so.0 (PULSE_0) => /usr/lib/i386-linux-gnu/libpulse.so.0
        libsndfile.so.1 (libsndfile.so.1.0) => /usr/lib/i386-linux-gnu/libsndfile.so.1
        libc.so.6 (GLIBC_2.15) => /lib/i386-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.11) => /lib/i386-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.1.3) => /lib/i386-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.2.4) => /lib/i386-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.4) => /lib/i386-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.1) => /lib/i386-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.3) => /lib/i386-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.2) => /lib/i386-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.7) => /lib/i386-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.0) => /lib/i386-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.3.4) => /lib/i386-linux-gnu/libc.so.6
    

    The only thing I can't get is why I donn't have such problems in other static libraries which invokes GLibc poll as well?

    To make the situation clear I used special GCC flag to display preprocessor output:

    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -E")
    

    .c.o includes poll.h and poll2.h and its content goes as following:

    # 1 "/usr/include/i386-linux-gnu/bits/poll2.h" 1 3 4 
    # 24 "/usr/include/i386-linux-gnu/bits/poll2.h" 3 4 
    
    
    extern int __poll_alias (struct pollfd *__fds, nfds_t __nfds, int __timeout) __asm__ ("" "poll") 
                                   ; 
    extern int __poll_chk (struct pollfd *__fds, nfds_t __nfds, int __timeout, 
             unsigned int __fdslen); 
    extern int __poll_chk_warn (struct pollfd *__fds, nfds_t __nfds, int __timeout, unsigned int __fdslen) __asm__ ("" "__poll_chk") 
    
    
      __attribute__((__warning__ ("poll called with fds buffer too small file nfds entries"))); 
    
    extern __inline __attribute__ ((__always_inline__)) __attribute__ ((__gnu_inline__)) __attribute__ ((__artificial__)) int 
    poll (struct pollfd *__fds, nfds_t __nfds, int __timeout) 
    { 
      if (__builtin_object_size (__fds, 2 > 1) != (unsigned int) -1) 
        { 
          if (! __builtin_constant_p (__nfds)) 
     return __poll_chk (__fds, __nfds, __timeout, __builtin_object_size (__fds, 2 > 1)); 
          else if (__builtin_object_size (__fds, 2 > 1) / sizeof (*__fds) < __nfds) 
     return __poll_chk_warn (__fds, __nfds, __timeout, __builtin_object_size (__fds, 2 > 1)); 
        } 
    
      return __poll_alias (__fds, __nfds, __timeout); 
    }
    

    But library object file still has only upper poll symbol:

    objdump -t  lib.a | grep poll 
    00000000 UND    00000000 poll
    

    So far I can't explain why tricky __poll_chk symbol is not added to the other libraries. But now my binary runs on any of target Linux hosts.