通过LD_PRELOAD为memcpy@glibc_2.14提供替代

时间:2018-11-02 12:18:20

标签: gcc memcpy ld-preload

这是我们的老朋友“找不到版本'GLIBC_2.14'”。客户实际上需要使用仅提供glibc版本2.11的较旧的Linux。而且我对预编译的库感到困惑。

Linus LD_PRELOAD workaround根本不起作用。我想这是因为在我的情况下,该库明确需要memcpy@GLIBC_2.14

所以我尝试了另一种方法。首先,find the function that needs the newer version

$ export LD_LIBRARY_PATH=/home/kremers/experiment/lib
$ /home/kremers/experiment/runner
/home/kremers/experiment/runner: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by /home/kremers/experiment/runner)
/home/kremers/experiment/runner: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by /home/kremers/experiment/lib/libfoo.so)

现在,我尝试从GLIBC_2.14

中找到确切的符号
$ readelf -s /home/kremers/experiment/lib/libfoo.so | grep GLIBC_2.14
       174: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@GLIBC_2.14 (12)
      1242: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@@GLIBC_2.14

我很幸运。只有memcpy!因此,我尝试构建自己的memcpy@GLIBC_2.14,类似于[https://stackoverflow.com/a/33275588/1210825]

memcpy.c:

/* this was taken from https://bugzilla.redhat.com/show_bug.cgi?id=638477#c55
 * */

#include <sys/types.h>
void *memcpy(void *dst, const void *src, size_t size)
{
void *orig = dst;
asm volatile("rep ; movsq"
:"=D" (dst), "=S" (src)
:"0" (dst), "1" (src), "c" (size >> 3)
:"memory");
asm volatile("rep ; movsb"
:"=D" (dst), "=S" (src)
:"0" (dst), "1" (src), "c" (size & 7)
:"memory");
return orig;
}

memcpy.map:

GLIBC_2.14 {
   memcpy;
};

我从中建立一个共享库:

$ gcc -shared -fPIC -fno-builtin -c memcpy.c
$ gcc -shared -fPIC -Wl,--version-script memcpy.map -o libmemcpy-2.14.so memcpy.o -lc
$ cp libmemcpy-2.14.so ../lib/.

检查是否根据需要显示符号:

$ readelf -sW /home/kremers/experiment/lib/libmemcpy-2.14.so | grep GLIBC
     4: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (3)
     5: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  ABS GLIBC_2.14
    10: 000000000000061c   112 FUNC    GLOBAL DEFAULT   13 memcpy@@GLIBC_2.14
    53: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  ABS GLIBC_2.14
    54: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@@GLIBC_2.2.5

$ readelf -s /home/kremers/experiment/lib/libfoo.so | grep GLIBC_2.14
   174: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@GLIBC_2.14 (12)
  1242: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@@GLIBC_2.14

请注意libfoo需要memcpy@GLIBC_2.14memcpy@@GLIBC_2.14的区别。 libmemcpy仅提供memcpy@GLIBC_2.14。而且我不知道该如何更改。

$ export LD_LIBRARY_PATH=/home/kremers/experiment/lib
$ export LD_PRELOAD=/home/kremers/experiment/lib/libmemcpy-2.14.so
$ /home/kremers/experiment/runner
/home/kremers/experiment/runner: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by /home/kremers/experiment/runner)
/home/kremers/experiment/runner: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by /home/kremers/experiment/lib/libfoo.so)

似乎什么都没有改变。我只能验证libmemcpy是否已加载:

$ ldd lib/libfoo.so
lib/libfoo.so: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by lib/libfoo.so)
lib/libfoo.so: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by lib/libfoo.so)
        linux-vdso.so.1 =>  (0x00007fff46d99000)                 
        /home/kremers/experiment/lib/libmemcpy-2.14.so (0x00007fada31f7000)
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007fada2ee5000)
        libm.so.6 => /lib64/libm.so.6 (0x00007fada2c8e000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fada2a78000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fada270a000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fada3669000)

结论 我觉得这应该可行,但是我缺少一个细节。我的第一个猜测是memcpy@GLIBC_2.14memcpy@@GLIBC_2.14的不匹配。

PS 我很难寻找线索,因为必应或Google似乎不太支持搜索“ @”和“ @@”。尽管readelf的文档很多,但是每个文档仅覆盖一小部分。通常遗漏我想要的东西。因此,如果解决方案在某处有详细记录,请与我同在。

1 个答案:

答案 0 :(得分:1)

glibc动态链接器当前不支持此功能。原始的GNU symbol versioning specification需要进行多次一致性检查,这些检查无法关闭,并且无法通过预加载将全新的符号版本添加到现有库中。

首先,您应该简单地插入一个未版本化的符号。所有符号版本都将绑定到它。由于您无法安排行为上的任何可观察到的差异(只需调用memmove),因此这里不需要细粒度的控制。符号memcpy@GLIBC_2.14不会插入任何内容,因此原始规范要求动态链接程序检查符号定义的soname是否与符号引用中包含的soname匹配,并且在预加载时不匹配。

但这不能解决您的问题,因为还要进行另一项一致性检查:库(由符号版本参考中的soname标识)必须使用此符号版本来定义 any 符号。简而言之,绑定符号版本本身并不是一件容易的事。即使由于从未预装符号版本也未使用符号版本而导致失败,由于预加载或实际的符号引用是惰性的且从未绑定。

我们可能应该删除第一个检查(由于它,我们在glibc本身中遇到了问题),并添加一个旋钮以关闭第二个检查。但是您必须将该更改回移植到2.11派生的glibc中,此时,您可以简单地对其进行重建,并为memcpy添加别名,并使用版本符号memcpy@GLIBC_2.14。抱歉。