Coder Perfect

Multiple glibc libraries on a single host


My linux (SLES-8) server now has glibc-2.2.5-235, but I have a software that requires glibc-2.3.3 and won’t run with this version.

Is it possible to have multiple glibcs installed on the same host?

When I run my application on the old glibc, I receive the following error:

./myapp: /lib/i686/ version `GLIBC_2.3' not found (required by ./myapp)
./myapp: /lib/i686/ version `GLIBC_2.3.2' not found (required by ./myapp)
./myapp: /lib/i686/ version `GLIBC_2.3' not found (required by ./
./myapp: /lib/ version `GLIBC_2.3' not found (required by ./
./myapp: /lib/i686/ version `GLIBC_2.3' not found (required by ./

As a result, I made a new directory called newglibc and copied in the following files: ->



However, I receive the following error:

./myapp: /lib/ version `GLIBC_PRIVATE' not found (required by ./newglibc/
./myapp: /lib/ version `GLIBC_2.3' not found (required by
./myapp: /lib/ version `GLIBC_PRIVATE' not found (required by ./newglibc/
./myapp: /lib/ version `GLIBC_2.3' not found (required by ./newglibc/
./myapp: /lib/ version `GLIBC_PRIVATE' not found (required by ./newglibc/

So they’re still connecting to /lib and not picking up where I put them.

Asked by dogbane

Solution #1

On the same system, several versions of glibc are highly likely (we do that every day).

However, you should be aware that glibc is made up of numerous elements (over 200 shared libraries), all of which must be compatible. is one of the parts, and it must match or you’ll get the errors you’re seeing.

The absolute path to is hard-coded into the executable at link time, and it cannot be modified easily after the link is completed (Update: can be done with patchelf; see this answer below).

To create an executable that works with the latest glibc, follow these steps:

g++ main.o -o myapp ... \
   -Wl,--rpath=/path/to/newglibc \

The -rpath linker option tells the runtime loader to look for libraries in /path/to/newglibc (so you don’t have to adjust LD LIBRARY PATH before executing it), and the -dynamic-linker option tells the runtime loader to “bake” the path to the right

Not all is lost if you can’t relink the myapp application (for example, since it’s a third-party binary), but it gets difficult. Setting up an appropriate chroot environment for it is one solution. Using rtldi and a binary editor is another option. Update: Alternatively, patchelf can be used.

Answered by Employed Russian

Solution #2

This is an old question, and the other replies are also old. The response of “Employed Russian” is excellent and instructive, however it only works if you have the source code. If you don’t, the alternatives were difficult to come by back then. Fortunately, we now have a simple solution to this problem (as one of his comments pointed out), which is patchelf. All you have to do is complete the following steps:

$ ./patchelf --set-interpreter /path/to/newglibc/ --set-rpath /path/to/newglibc/ myapp

After that, you can just run your file:

$ ./myapp

Thankfully, there’s no need to chroot or manually change binaries. If you’re not sure what you’re doing, backup your binary before patching it because it affects your binary file. After you patch it, you can’t restore the old path to interpreter/rpath. If it doesn’t work, you’ll have to keep patching it until you find the path that will actually work… Well, it doesn’t have to be a trial-and-error process. For example, in OP’s example, he needed GLIBC_2.3, so you can easily find which lib provides that version using strings:

$ strings /lib/i686/ | grep GLIBC_2.3
$ strings /path/to/newglib/ | grep GLIBC_2.3

The first grep should return empty because the system libc does not have the version he wants, but the second should return GLIBC 2.3 because that is the version myapp is using, indicating that we can patchelf our binary using that path. Read the notice at the end if you receive a segmentation error.

When you start a binary on Linux, it first tries to load the linker, then the libraries, which should all be in the path and/or in the correct location. If you have a linker problem and want to figure out which path your binary is looking for, use the following command:

$ readelf -l myapp | grep interpreter
  [Requesting program interpreter: /lib/]                                                                                                                                                                                   

If your issue is with the libs, the following commands will show you which libs are being used:

$ readelf -d myapp | grep Shared
$ ldd myapp 

This will list the libs that your binary requires, but you’re probably already aware of the problematic ones because, as in the case of the OP, they’re already causing issues.

“patchelf” solves a variety of difficulties relating to these two issues that you may experience while trying to run a program. If you get the error ELF file OS ABI invalid, for example, you may repair it by changing the loader (the —set-interpreter component of the program), as I explain here. Another example is when you run a file that is present and executable and you get No such file or directory, as shown here. In that scenario, the OP was missing a link to the loader; however, you may not have root access and hence be unable to build the link. Your difficulty could be solved by installing a new interpreter.

Thank you for the insight and solution, Employed Russian and Michael Pankov!

Note about segmentation faults: it’s possible that myapp uses numerous libs, some of which are fine but others of which aren’t; then you patchelf it to a new dir and get a segmentation fault. When you patchelf your binary, you modify the path of multiple libs, even though some of them were in a different path originally. Take a look at the following example:

$ ldd myapp
./myapp: /usr/lib/x86_64-linux-gnu/ version `GLIBCXX_3.4.20' not found (required by ./myapp)
./myapp: /usr/lib/x86_64-linux-gnu/ version `GLIBCXX_3.4.21' not found (required by ./myapp) =>  (0x00007fffb167c000) => /lib/x86_64-linux-gnu/ (0x00007f9a9aad2000) => /lib/x86_64-linux-gnu/ (0x00007f9a9a8ce000) => /lib/x86_64-linux-gnu/ (0x00007f9a9a6af000) => /usr/lib/x86_64-linux-gnu/ (0x00007f9a9a3ab000) => /lib/x86_64-linux-gnu/ (0x00007f9a99fe6000)
        /lib64/ (0x00007f9a9adeb000) => /lib/x86_64-linux-gnu/ (0x00007f9a99dcf000)

The most of the libraries are located in /lib/x86 64-linux-gnu/, but the problematic one ( is located in /usr/lib/x86 64-linux-gnu. I received a segmentation error after patchelf’ing myapp to point to /path/to/mylibs. The libs aren’t completely compatible with the binaries for some reason. I transferred the original libs from /lib/x86 64-linux-gnu/ to /path/to/mylibs2, as well as from /path/to/mylibs, because myapp didn’t complain about them. Myapp now works after I patchelf’ed it to /path/to/mylibs2. It’s possible that you won’t be able to fix your situation if your binary uses separate libraries and you have different versions.:( However, if it is possible, mixing libs may be the way to go. It isn’t perfect, but it might work. Best of luck!

Answered by msb

Solution #3

LD PRELOAD should be used: Place your library under one of the man lib directories, and then run:

LD_PRELOAD='' program

See the Wikipedia article for further information.

Answered by PiedPiper

Solution #4

The linker is, first and foremost, the most crucial dependence of any dynamically linked program. All libraries must be compatible with the linker’s version.

Consider the following example: I have a fresh Ubuntu machine on which I run a few programs (in my case it is D compiler – ldc2). I’d like to run it on the old CentOS, but it’s not possible due to the older glibc library. I was given

ldc2-1.5.0-linux-x86_64/bin/ldc2: /lib64/ version `GLIBC_2.15' not found (required by ldc2-1.5.0-linux-x86_64/bin/ldc2)
ldc2-1.5.0-linux-x86_64/bin/ldc2: /lib64/ version `GLIBC_2.14' not found (required by ldc2-1.5.0-linux-x86_64/bin/ldc2)

I need to copy all of Ubuntu’s dependencies to Centos. The correct procedure is as follows:

Let’s start by double-checking all dependencies:

ldd ldc2-1.5.0-linux-x86_64/bin/ldc2 =>  (0x00007ffebad3f000) => /lib/x86_64-linux-gnu/ (0x00007f965f597000) => /lib/x86_64-linux-gnu/ (0x00007f965f378000) => /lib/x86_64-linux-gnu/ (0x00007f965f15b000) => /lib/x86_64-linux-gnu/ (0x00007f965ef57000) => /lib/x86_64-linux-gnu/ (0x00007f965ec01000) => /lib/x86_64-linux-gnu/ (0x00007f965e9ea000) => /lib/x86_64-linux-gnu/ (0x00007f965e60a000)
    /lib64/ (0x00007f965f79f000)

We don’t need to worry about because it isn’t a genuine library.

The linker /lib64/ is used by Linux to link the executable with all dynamic libraries.

The rest of the files are real libraries, and they must all be copied someplace in the centos, along with the linker.

Assume that all of the libraries and linker are located in the “/mylibs” directory.

As I previously stated, the linker is It’s a static executable rather than a dynamic library. You can run it and notice that it has certain parameters, such as —library-path (which I’ll come back to).

On Linux, a dynamically linked program can be launched simply by typing its name, for example.


Linux loads a program like this into RAM and verifies which linker is assigned to it. On a 64-bit machine, it’s usually /lib64/ (in your filesystem it is symbolic link to the real executable). The linker is then used by Linux to load dynamic libraries.

You can also alter this slightly and perform the following trick:

/mylibs/ /bin/ldc2

It’s a way of forcing Linux to use a specific linker.

We may now revert to the previously specified parameter —library-path.

/mylibs/ --library-path /mylibs /bin/ldc2

It will load dynamic libraries from /mylibs using ldc2.

This is the way for calling the executable with custom libraries (rather than the system defaults).

Answered by Arkadiusz Rychliński

Solution #5

This setup might work and is rapid because it only recompiles glibc, not the entire GCC toolchain.

glibc provides the.o and crtn.o files. against glibc in an installed location Those objects are responsible for glibc’s early setup, so I wouldn’t be surprised if things crashed in amazing and awesomely subtle ways.

See Configuration 2 below for a more stable setup.

Glibc should be built and installed locally:

export glibc_install="$(pwd)/glibc/build/install"

git clone git://
cd glibc
git checkout glibc-2.28
mkdir build
cd build
../configure --prefix "$glibc_install"
make -j `nproc`
make install -j `nproc`


#define _GNU_SOURCE
#include <assert.h>
#include <gnu/libc-version.h>
#include <stdatomic.h>
#include <stdio.h>
#include <threads.h>

atomic_int acnt;
int cnt;

int f(void* thr_data) {
    for(int n = 0; n < 1000; ++n) {
    return 0;

int main(int argc, char **argv) {
    /* Basic library version check. */
    printf("gnu_get_libc_version() = %s\n", gnu_get_libc_version());

    /* Exercise thrd_create from -pthread,
     * which is not present in glibc 2.27 in Ubuntu 18.04.
     * */
    thrd_t thr[10];
    for(int n = 0; n < 10; ++n)
        thrd_create(&thr[n], f, NULL);
    for(int n = 0; n < 10; ++n)
        thrd_join(thr[n], NULL);
    printf("The atomic counter is %u\n", acnt);
    printf("The non-atomic counter is %u\n", cnt);

Compile and execute test as follows:

#!/usr/bin/env bash
set -eux
gcc \
  -L "${glibc_install}/lib" \
  -I "${glibc_install}/include" \
  -Wl,--rpath="${glibc_install}/lib" \
  -Wl,--dynamic-linker="${glibc_install}/lib/" \
  -std=c11 \
  -o test_glibc.out \
  -v \
  test_glibc.c \
  -pthread \
ldd ./test_glibc.out

The software generates the following results:

gnu_get_libc_version() = 2.28
The atomic counter is 10000
The non-atomic counter is 8674 is a command modified from Compile against glibc in an installed location failed due to —sysroot:

cannot find /home/ciro/glibc/build/install/lib/ inside /home/ciro/glibc/build/install

As a result, I removed it.

ldd output confirms that the ldd and libraries that we’ve just built are actually being used as expected:

+ ldd test_glibc.out (0x00007ffe4bfd3000) => /home/ciro/glibc/build/install/lib/ (0x00007fc12ed92000) => /home/ciro/glibc/build/install/lib/ (0x00007fc12e9dc000)
        /home/ciro/glibc/build/install/lib/ => /lib64/ (0x00007fc12f1b3000)

As previously noted, the gcc compilation debug output shows that my host runtime objects were used, which is undesirable, but I don’t know how to work around it, for example, it contains:


Now we’ll change glibc with:

diff --git a/nptl/thrd_create.c b/nptl/thrd_create.c
index 113ba0d93e..b00f088abb 100644
--- a/nptl/thrd_create.c
+++ b/nptl/thrd_create.c
@@ -16,11 +16,14 @@
    License along with the GNU C Library; if not, see
    <>.  */

+#include <stdio.h>
 #include "thrd_priv.h"

 thrd_create (thrd_t *thr, thrd_start_t func, void *arg)
+  puts("hacked");
   _Static_assert (sizeof (thr) == sizeof (pthread_t),
                   "sizeof (thr) != sizeof (pthread_t)");

After that, recompile and reinstall glibc, as well as our program:

cd glibc/build
make -j `nproc`
make -j `nproc` install

As expected, we see hacked printed a couple times.

This confirms that we utilized the glibc we compiled rather than the one provided by the host.

Ubuntu 18.04 was used for testing.

This is an alternative to setup 1, and it is the most correct setup I’ve achieved far: everything is correct as far as I can observe, including the C runtime objects such as crt1.o, crti.o, and crtn.o.

We’ll generate a whole specialized GCC toolchain that uses the glibc we want in this situation.

The sole disadvantage of this method is that it takes longer to construct. But with anything less, I wouldn’t risk a production setup.

GCC, glibc, and binutils are among the programs that crosstool-NG downloads and compiles from source for us.

Yes, the GCC build system is so awful that it requires its own project.

This setup is only not perfect because crosstool-NG does not support building the executables without extra -Wl flags, which feels weird since we’ve built GCC itself. But everything seems to work, so this is only an inconvenience.

Obtain crosstool-NG, configure it, and build it as follows:

git clone
cd crosstool-ng
git checkout a6580b8e8b55345a5a342b5bd96e42c83e640ac5
export CT_PREFIX="$(pwd)/.build/install"
export PATH="/usr/lib/ccache:${PATH}"
./configure --enable-local
make -j `nproc`
./ct-ng x86_64-unknown-linux-gnu
./ct-ng menuconfig
env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc`

The construction time ranges from thirty minutes to two hours.

Making it match your host kernel version to use the correct kernel headers is the only mandatory configuration choice that I can see. Find out what version of the host kernel you’re running with:

uname -a

which shows me:


So here’s what I do in menuconfig:

so I select:


which is the first version that is equivalent to or older. Because the kernel is backwards compatible, it must be older.

The.config file we created with./ct-ng x86 64-unknown-linux-gnu has the following information:


To do so, go to menuconfig d and alter it.

Continue with the build after saving the.config.

Alternatively, if you want to use your own glibc source, such as glibc from the most recent git, follow these steps:

glibc has been cloned as:

git clone git://
cd glibc
git checkout glibc-2.28

Once you’ve created the toolchain you desire, put it to the test with:

#!/usr/bin/env bash
set -eux
PATH="${PATH}:${install_dir}/bin" \
  x86_64-unknown-linux-gnu-gcc \
  -Wl,--dynamic-linker="${install_dir}/x86_64-unknown-linux-gnu/sysroot/lib/" \
  -Wl,--rpath="${install_dir}/x86_64-unknown-linux-gnu/sysroot/lib" \
  -v \
  -o test_glibc.out \
  test_glibc.c \
  -pthread \
ldd test_glibc.out

Everything appears to work as it did in Setup 1, with the exception that the correct runtime objects were used this time:


As mentioned below, it does not appear to be possible with crosstool-NG.

If you simply rebuild;

env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc`

then your changes to the custom glibc source location are taken into account, but it builds everything from scratch, making it unusable for iterative development.

If we do:

./ct-ng list-steps

it gives a nice overview of the build steps:

Available build steps, in order:
  - companion_tools_for_build
  - companion_libs_for_build
  - binutils_for_build
  - companion_tools_for_host
  - companion_libs_for_host
  - binutils_for_host
  - cc_core_pass_1
  - kernel_headers
  - libc_start_files
  - cc_core_pass_2
  - libc
  - cc_for_build
  - cc_for_host
  - libc_post_cc
  - companion_libs_for_target
  - binutils_for_target
  - debug
  - test_suite
  - finish
Use "<step>" as action to execute only that step.
Use "+<step>" as action to execute up to that step.
Use "<step>+" as action to execute from that step onward.

As a result, we can see that glibc steps are entwined with numerous GCC stages, the most notable of which is libc start files, which comes before cc core pass 2, which, along with cc core pass 1, is likely the most expensive step.

In order to build just one step, you must first set the “Save intermediate steps” in .config option for the intial build:

After that, you could try:

env -u LD_LIBRARY_PATH time ./ct-ng libc+ -j`nproc`

However, as indicated at, the + is essential.

That basically makes the rebuild too sluggish to be useful for development, and I don’t see any way to fix it without changing crosstool-NG.

Furthermore, starting from the libc step did not appear to copy the source back over from the Custom source location, rendering this procedure useless.

If you’re also interested in the C++ standard library, here’s a bonus: How can I edit the GCC libstdc++ C++ standard library source and rebuild it?

Answered by Ciro Santilli 新疆再教育营六四事件法轮功郝海东

Post is based on