Linking against an old version of libc to provide greater application coverage

Linux 二进制文件通常动态链接到核心系统库(libc)。这使得二进制文件的内存占用非常小,但是依赖于最新库的二进制文件不会在旧系统上运行。相反,链接到旧库的二进制文件将在最新的系统上愉快地运行。

因此,为了确保我们的应用程序在发布期间具有良好的覆盖率,我们需要找出我们能够支持的最老的 libc,并将我们的二进制文件与它链接起来。

How should we determine the oldest version of libc we can link to?

45305 次浏览

Glibc 2.2是一个非常常见的最小版本,但是为这个版本寻找一个构建平台可能并不容易。

也许更好的方向是考虑您想要支持的最老的操作系统,并在此基础上进行构建。

找出可执行文件中的哪些符号正在创建对不需要的 glibc 版本的依赖关系。

$ objdump -p myprog
...
Version References:
required from libc.so.6:
0x09691972 0x00 05 GLIBC_2.3
0x09691a75 0x00 03 GLIBC_2.2.5


$ objdump -T myprog | fgrep GLIBC_2.3
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.3   realpath

查看所依赖的库,看看旧版本中是否有可以链接到的符号:

$ objdump -T /lib/libc.so.6 | grep -w realpath
0000000000105d90 g    DF .text  0000000000000021 (GLIBC_2.2.5) realpath
000000000003e7b0 g    DF .text  00000000000004bf  GLIBC_2.3   realpath

我们走运了!

在代码中从 GLIBC_2.2.5请求版本:

#include <limits.h>
#include <stdlib.h>


__asm__(".symver realpath,realpath@GLIBC_2.2.5");


int main () {
realpath ("foo", "bar");
}

注意,GLIBC _ 2.3已经不再需要了:

$ objdump -p myprog
...
Version References:
required from libc.so.6:
0x09691a75 0x00 02 GLIBC_2.2.5


$ objdump -T myprog | grep realpath
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 realpath

有关更多信息,请参见 http://web.archive.org/web/20160107032111/http://www.trevorpounds.com/blog/?p=103

不幸的是,“山姆的解决办法在我的情况下并不奏效。”。但是根据他的方法,我找到了我自己的方法来解决这个问题。

这就是我的处境:

I'm writing a C++ program using the Thrift framework(it's an RPC middleware). I prefer static link to dynamic link, so my program is linked to Libthrift.a statically instead of Libthrift 所以. However, Libthrift.a is dynamically linked to glibc, and since my Libthrift.a is build on my system with glibc 2.15, my Libthrift.a uses Memcpy of version 2.14(Memcpy@GLIBC _ 2.14) provided by glibc 2.15.

但问题是我们的服务器机器只有 glibc 版本2.5,而它只有 Memcpy@GLIBC _ 2.2.5。它比 Memcpy@GLIBC _ 2.14低得多。所以,当然,我的服务器程序不能在这些机器上运行。

我找到了解决办法:

  1. 使用. symver 获取对 Memcpy@GLIBC _ 2.2.5的引用。

  2. 编写我自己的 _ _ wrap _ memcpy函数,它直接调用 Memcpy@GLIBC _ 2.2.5

  3. 当链接我的程序时,将 - Wl,—— wrap= memcpy选项添加到 gcc/g + + 。

步骤1和步骤2中涉及的代码如下: https://gist.github.com/nicky-zs/7541169

为了以更自动化的方式完成这项工作,可以使用下面的脚本创建一个列表,其中包含 GLIBC 中比给定版本更新的所有符号(设置在第2行)。它创建一个包含所有必需的 .symver声明的 glibc.h文件(脚本参数设置的文件名)。然后可以将 -include glibc.h添加到 CFLAGS 中,以确保在编译过程中到处都可以使用它。

如果您不使用任何在没有包含上述代码的情况下编译的静态库,那么这就足够了。如果需要重新编译,并且不想重新编译,可以使用 objcopy创建库的一个副本,其中包含重命名为旧版本的符号。脚本的第二行到最后一行创建了系统 libstdc++.a的一个版本,该版本将链接到旧的 glibc 符号。添加 -L.(或 -Lpath/to/libstdc++.a/)将使您的程序静态链接 libstdc + + ,而不需要链接一堆新的符号。如果你不需要这个,删除最后两行和 printf ... redeff行。

#!/bin/bash
maxver=2.9
headerf=${1:-glibc.h}
set -e
for lib in libc.so.6 libm.so.6 libpthread.so.0 libdl.so.2 libresolv.so.2 librt.so.1; do
objdump -T /usr/lib/$lib
done | awk -v maxver=${maxver} -vheaderf=${headerf} -vredeff=${headerf}.redef -f <(cat <<'EOF'
BEGIN {
split(maxver, ver, /\./)
limit_ver = ver[1] * 10000 + ver[2]*100 + ver[3]
}
/GLIBC_/ {
gsub(/\(|\)/, "",$(NF-1))
split($(NF-1), ver, /GLIBC_|\./)
vers = ver[2] * 10000 + ver[3]*100 + ver[4]
if (vers > 0) {
if (symvertext[$(NF)] != $(NF-1))
count[$(NF)]++
if (vers <= limit_ver && vers > symvers[$(NF)]) {
symvers[$(NF)] = vers
symvertext[$(NF)] = $(NF-1)
}
}
}
END {
for (s in symvers) {
if (count[s] > 1) {
printf("__asm__(\".symver %s,%s@%s\");\n", s, s, symvertext[s]) > headerf
printf("%s %s@%s\n", s, s, symvertext[s]) > redeff
}
}
}
EOF
)
sort ${headerf} -o ${headerf}
objcopy --redefine-syms=${headerf}.redef /usr/lib/libstdc++.a libstdc++.a
rm ${headerf}.redef