链接.so文件中的旧符号版本

时间:2012-01-11 16:54:19

标签: c linux gcc linker ld

在x86_64 linux上使用gcc和ld我需要链接到较新版本的库(glibc 2.14),但可执行文件需要在具有旧版本(2.5)的系统上运行。由于唯一不兼容的符号是memcpy(需要memcpy@GLIBC_2.2.5,但提供memcpy@GLIBC_2.14的库),我想告诉链接器,不应该使用memcpy的默认版本,它应该采用我指定的旧版本

我找到了一种非常笨拙的方法:只需在链接器命令行中指定旧.so文件的副本即可。这工作正常,但我不喜欢有多个.so文件的想法(我只能通过指定我链接到的所有旧库,也有memcpy的引用)来检查svn并且我的构建系统需要

所以我正在寻找一种告诉链接器采用旧版本符号的方法。

对我来说不起作用的替代方案是:

  • 使用asm .symver(如Web Archive of Trevor Pounds' Blog所示),因为这需要我确保symver在所有使用memcpy的代码之前,这将非常困难(具有第三方代码的复杂代码库)
  • 使用旧库维护构建环境;仅仅因为我想在我的桌面系统上进行开发,并且在我们的网络中同步这些东西将会很有用。

当考虑链接器所做的所有工作时,看起来似乎并不困难,毕竟它还有一些代码可以找出符号的默认版本。

与简单的链接器命令行具有相同复杂性级别的任何其他想法(如创建简单的链接描述文件等)也是受欢迎的,只要它们不像编辑生成的二进制文件那样奇怪的黑客...... / p>

修改 为了保护未来的读者,除了以下想法之外,我还在链接器中找到了--wrap选项,这有时也很有用。

11 个答案:

答案 0 :(得分:43)

我找到了以下工作解决方案。首先创建文件memcpy.c:

#include <string.h>

/* some systems do not have newest memcpy@@GLIBC_2.14 - stay with old good one */
asm (".symver memcpy, memcpy@GLIBC_2.2.5");

void *__wrap_memcpy(void *dest, const void *src, size_t n)
{
    return memcpy(dest, src, n);
}

编译此文件不需要额外的CFLAGS。然后使用 -Wl, - wrap = memcpy 链接您的程序。

答案 1 :(得分:18)

只需静态链接memcpy - 从libc.a ar x /path/to/libc.a memcpy.o拉出memcpy.o(无论版本 - memcpy几乎都是一个独立的函数)并将其包含在最终链接中。请注意,如果您的项目是分发给公众而非开源的,静态链接可能会使许可问题复杂化。

或者,您可以自己实现memcpy,尽管glibc中手动调整的汇编版本可能更高效

请注意memcpy@GLIBC_2.2.5映射到memmove(旧版本的memcpy在可预测的方向上一致地复制,导致它有时在应用memmove时被误用),这是该版本的唯一原因bump - 你可以在代码中用memmove替换memcpy用于这个特定的情况。

或者您可以转到静态链接,或者您可以确保网络上的所有系统都具有与构建计算机相同或更好的版本。

答案 2 :(得分:6)

我有类似的问题。我们使用的第三方库需要旧的memcpy@GLIBC_2.2.5。我的解决方案是@anight发布的扩展方法。

我也使memcpy命令变形,但我必须使用稍微不同的方法,因为@anight发布的解决方案对我不起作用。

<强> memcpy_wrap.c:

#include <stddef.h>
#include <string.h>

asm (".symver wrap_memcpy, memcpy@GLIBC_2.2.5");
void *wrap_memcpy(void *dest, const void *src, size_t n) {
  return memcpy(dest, src, n);
}

<强> memcpy_wrap.map:

GLIBC_2.2.5 {
   memcpy;
};

构建包装器:

gcc -c memcpy_wrap.c -o memcpy_wrap.o

现在终于在链接程序时添加

  • -Wl,--version-script memcpy_wrap.map
  • memcpy_wrap.o

这样你最终会得到:

g++ <some flags> -Wl,--version-script memcpy_wrap.map <some .o files> memcpy_wrap.o <some libs>

答案 3 :(得分:5)

我有类似的问题。试图在RHEL 7.1上安装一些oracle组件,我得到了这个:

$ gcc -o /some/oracle/bin/foo .... -L/some/oracle/lib ... 
/some/oracle/lib/libfoo.so: undefined reference to `memcpy@GLIBC_2.14'

似乎(我的)RHEL的glibc只定义了memcpy@GLIBC_2.2.5:

$ readelf -Ws /usr/lib/x86_64-redhat-linux6E/lib64/libc_real.so | fgrep memcpy@
   367: 000000000001bfe0    16 FUNC    GLOBAL DEFAULT    8 memcpy@@GLIBC_2.2.5
  1166: 0000000000019250    16 FUNC    WEAK   DEFAULT    8 wmemcpy@@GLIBC_2.2.5

所以,我设法绕过这个,首先创建一个没有包装的memcpy.c文件,如下所示:

#include <string.h>
asm (".symver old_memcpy, memcpy@GLIBC_2.2.5");       // hook old_memcpy as memcpy@2.2.5
void *old_memcpy(void *, const void *, size_t );
void *memcpy(void *dest, const void *src, size_t n)   // then export memcpy
{
    return old_memcpy(dest, src, n);
}

以及将memcpy导出为memcpy@GLIBC_2.14的memcpy.map文件:

GLIBC_2.14 {
   memcpy;
};

然后我将自己的memcpy.c编译成一个共享库,如下所示:

$ gcc -shared -fPIC -c memcpy.c
$ gcc -shared -fPIC -Wl,--version-script memcpy.map -o libmemcpy-2.14.so memcpy.o -lc

,将libmemcpy-2.14.so移动到/ some / oracle / lib(在我的链接中由-L参数指向),并再次通过

链接
$ gcc -o /some/oracle/bin/foo .... -L/some/oracle/lib ... /some/oracle/lib/libmemcpy-2.14.so -lfoo ...

(编译时没有错误)并通过以下方式验证:

$ ldd /some/oracle/bin/foo
    linux-vdso.so.1 =>  (0x00007fff9f3fe000)
    /some/oracle/lib/libmemcpy-2.14.so (0x00007f963a63e000)
    libdl.so.2 => /lib64/libdl.so.2 (0x00007f963a428000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f963a20c000)
    librt.so.1 => /lib64/librt.so.1 (0x00007f963a003000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f9639c42000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f963aa5b000)

这对我有用。我希望它也适合你。

答案 4 :(得分:4)

我显然有点迟到了,但我最近将我的Linux操作系统升级(更多原因从未升级)到新的libc附带的XUbuntu 14.04。我在我的机器上编译了一个共享库,这个库由客户使用,出于任何合法原因,他们没有从10.04升级他们的环境。我编译的共享库不再在其环境中加载,因为gcc依赖于memcpy glibc v.2.14(或更高版本)。让我们抛开这种疯狂。我整个项目的解决方法有三个:

  1. 添加到我的gcc cflags:-include glibc_version_nightmare.h
  2. 创建了glibc_version_nightmare.h
  3. 创建了一个perl脚本来验证.so
  4. 中的符号

    glibc_version_nightmare.h:

    #if defined(__GNUC__) && defined(__LP64__)  /* only under 64 bit gcc */
    #include <features.h>       /* for glibc version */
    #if defined(__GLIBC__) && (__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 14)
    /* force mempcy to be from earlier compatible system */
    __asm__(".symver memcpy,memcpy@GLIBC_2.2.5");
    #endif
    #undef _FEATURES_H      /* so gets reloaded if necessary */
    #endif
    

    perl脚本片段:

    ...
    open SYMS, "nm $flags $libname |";
    
    my $status = 0;
    
    sub complain {
    my ($symbol, $verstr) = @_;
    print STDERR "ERROR: $libname $symbol requires $verstr\n";
    $status = 1;
    }
    
    while (<SYMS>) {
    next unless /\@\@GLIBC/;
    chomp;
    my ($symbol, $verstr) = (m/^\s+.\s(.*)\@\@GLIBC_(.*)/);
    die "unable to parse version from $libname in $_\n"
        unless $verstr;
    my @ver = split(/\./, $verstr);
    complain $symbol, $verstr
        if ($ver[0] > 2 || $ver[1] > 10);
    }
    close SYMS;
    
    exit $status;
    

答案 5 :(得分:2)

对于nim-lang,我详细介绍了使用C编译器--include=标志找到的解决方案,如下所示:

使用以下文件创建文件 symver.h

__asm__(".symver fcntl,fcntl@GLIBC_2.4");

使用nim c ---passC:--include=symver.h

构建程序

至于我,我也正在交叉编译。我使用nim c --cpu:arm --os:linux --passC:--include=symver.h ...进行编译,可以使用arm-linux-gnueabihf-objdump -T ../arm-libc.so.6 | grep fcntl

获得符号版本

我不得不在某个时候删除~/.cache/nim。似乎可行。

答案 6 :(得分:1)

我认为你可以创建一个包含symver语句的简单C文件,也可以是一个调用memcpy的虚函数。然后,您只需确保生成的目标文件是第一个提供给链接器的文件。

答案 7 :(得分:1)

我建议您静态链接memcpy();或找到memcpy()的来源并将其编译为您自己的库。

答案 8 :(得分:1)

此解决方法似乎与-flto编译选项不兼容。

我的解决方案是调用memmove。 memove与memcpy完全相同的工作。 唯一的区别是当src和dest区域重叠时,memmove是安全的,memcpy是不可预测的。所以我们可以安全地总是调用memmove而不是memcpy

#include <string.h>

#ifdef __cplusplus
extern "C" {
#endif

    void *__wrap_memcpy(void *dest, const void *src, size_t n)
    {
        return memmove(dest, src, n);
    }

#ifdef __cplusplus
}
#endif

答案 9 :(得分:0)

它可能是由旧的ld(gnu链接)版本引起的。 对于以下简单问题:

#include <string.h>
#include <stdio.h>
int main(int argc,char **argv)
{
    char buf[5];
    memset(buf,0,sizeof(buf));
    printf("ok\n");
    return 0;
}

当我使用ld 2.19.1时,memset被重新定位到:memset @@ GLIBC_2.0,并导致崩溃。 升级到2.25后,它是:memset @ plt,崩溃解决了。

答案 10 :(得分:0)

最小的可运行自包含示例

GitHub upstream

main.c

#include <assert.h>
#include <stdlib.h>

#include "a.h"

#if defined(V1)
__asm__(".symver a,a@LIBA_1");
#elif defined(V2)
__asm__(".symver a,a@LIBA_2");
#endif

int main(void) {
#if defined(V1)
    assert(a() == 1);
#else
    assert(a() == 2);
#endif
    return EXIT_SUCCESS;
}

a.c

#include "a.h"

__asm__(".symver a1,a@LIBA_1");
int a1(void) {
    return 1;
}

/* @@ means "default version". */
__asm__(".symver a2,a@@LIBA_2");
int a2(void) {
    return 2;
}

a.h

#ifndef A_H
#define A_H

int a(void);

#endif

a.map

LIBA_1{
    global:
    a;
    local:
    *;
};

LIBA_2{
    global:
    a;
    local:
    *;
};

Makefile

CC := gcc -pedantic-errors -std=c89 -Wall -Wextra

.PHONY: all clean run

all: main.out main1.out main2.out

run: all
    LD_LIBRARY_PATH=. ./main.out
    LD_LIBRARY_PATH=. ./main1.out
    LD_LIBRARY_PATH=. ./main2.out

main.out: main.c libcirosantilli_a.so
    $(CC) -L'.' main.c -o '$@' -lcirosantilli_a

main1.out: main.c libcirosantilli_a.so
    $(CC) -DV1 -L'.' main.c -o '$@' -lcirosantilli_a

main2.out: main.c libcirosantilli_a.so
    $(CC) -DV2 -L'.' main.c -o '$@' -lcirosantilli_a

a.o: a.c
    $(CC) -fPIC -c '$<' -o '$@'

libcirosantilli_a.so: a.o
    $(CC) -Wl,--version-script,a.map -L'.' -shared a.o -o '$@'

libcirosantilli_a.o: a.c
    $(CC) -fPIC -c '$<' -o '$@'

clean:
    rm -rf *.o *.a *.so *.out

在Ubuntu 16.04上测试。