Search code examples
c++g++glibc

What factors determine which version of glibc is required for binary built with g++?


I manage a complex C++ project

I am getting field reports that a binary built on one system will not run on another system because of glibc version mismatch.

I just want to verify: is the version of glibc required by the binary completely determined by which version of g++ is used to compile it?

In this case, the binary is built with g++ 11, and run on another system that doesn't have g++ 11 installed.


Solution

  • I just want to verify: is the version of glibc required by the binary completely determined by which version of g++ is used to compile it?

    Not at all. The version of GLIBC required at runtime is determined by several factors:

    1. The version of GLIBC used at link time.
    2. The features of GLIBC actually used (which in turn depend on the compiler version, and possibly compilation flags).
    3. Whether or not any of the features used have had changes in their ABI.

    In this case, the binary is built with g++ 11

    You can install g++-11 on a system with GLIBC-2.15, and on a system with GLIBC-2.31. For non-trivial source, the resulting binaries are likely to have different requirements on GLIBC at runtime.

    See this answer to understand how symbol versioning comes into play.

    TL;DR: if you want to build an executable or a shared library which is portable to different Linux distributions, select the oldest version of GLIBC you are willing to support, and build on a system with that version (you don't have to have a physical machine for this -- a VM or a docker container work find).

    Due to backwards compatibility guarantees, your binary will work on all systems with the same or newer version of GLIBC.

    Update:

    Let's say that you build libfoo.a on GLIBC-NN, and the end user links a.out on GLIBC-MM, where MM < NN.

    This is a very dangerous practice: the program may silently corrupt its data, and finding the root cause of such corruption will be very difficult.

    Now suppose the end user links a.out on GLIBC-MM where NN <= MM (this is the actual situation according to comments), then runs a.out on a system with GLIBC-JJ, where JJ < NN.

    In that case, I the possibility of "silent" corruption is minimized, but not completely eliminated.

    Consider the following hypothetical example (foo() is part of libfoo.a you provide):

    struct passwd *foo()
    {
       struct passwd *pw = getpwuid(42);
       if (pw->field_a == 123)              // 1
         pw->field_a = 1234;                // 2
       return pw;
    }
    

    Now suppose that in GLIBC-NN field_a exists as part of struct passwd, but in GLIBC-JJ it does not. In that case, at point 1 above the program may read past an end of allocated buffer, and at point 2 it may write past the end.

    Note: the program above is already violating "application shall not modify the structure to which the return value points" requirement and so has undefined behavior anyway; it's just an example of how bugs due to mismatched GLIBC may come about.