c-在.so fi中链接到较旧的符号版本

在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(如Trevor Pounds的博客的Web存档中所述),因为这将要求我确保symver位于所有使用memcpy的代码之前,这将非常困难(带有第三方代码的复杂代码库)
  • 使用旧库维护构建环境; 仅仅因为我想在我的桌面系统上进行开发,而在网络中同步内容将是皮塔饼。

考虑链接器的所有工作时,似乎很难实现,毕竟它也有一些代码可以找出符号的默认版本。

只要不像编辑所生成的二进制文件那样怪异的技巧,任何其他与简单链接程序命令行具有相同复杂性级别的想法(例如创建简单的链接脚本等)也将受到欢迎。

编辑:为了保护以后的读者,除了以下思想外,我还为链接器找到了--wrap选项,该选项有时也很有用。

PlasmaHH asked 2020-08-10T23:15:53Z
10个解决方案
46 votes

我找到了以下可行的解决方案。 首先创建文件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链接。

anight answered 2020-08-10T23:16:37Z
18 votes

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

另外,您可以简单地自己实现memcpy,尽管glibc中的手工调整程序集版本可能更有效

请注意,memcpy @ GLIBC_2.2.5映射到memmove(以可预测的方向一致地复制了memcpy的旧版本,这有时导致应在使用memmove时误用了它),这是造成版本颠簸的唯一原因-您 在这种情况下,只需在代码中将memcpy替换为memmove即可。

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

Random832 answered 2020-08-10T23:16:12Z
7 votes

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

我也扭曲了memcpy_wrap.o命令,但是我不得不使用稍微不同的方法,因为@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);
}

么么成品油_wrap.马屁:

GLIBC_2.2.5 {
   memcpy;
};

构建包装器:

gcc -c memcpy_wrap.c -o memcpy_wrap.o

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

  • memcpy_wrap.o
  • memcpy_wrap.o

这样您将得到类似以下内容的结果:

g++ <some flags> -Wl,--version-script memcpy_wrap.map <some .o files> memcpy_wrap.o <some libs>
Ortwin Angermeier answered 2020-08-10T23:17:23Z
5 votes

我有一个类似的问题。 尝试在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)

这对我有用。 我希望它也为您做到。

avarvit answered 2020-08-10T23:18:14Z
4 votes

我显然对此回应晚了一点,但是最近我将Linux操作系统升级了(不再升级的更多理由)到新的libc随附的XUbuntu 14.04。 我在我的机器上编译了一个共享库,供出于任何正当理由未从10.04升级其环境的客户端使用。 我编译的共享库不再加载到其环境中,因为gcc依赖于memcpy glibc v 2.14(或更高版本)。 让我们抛弃这种精神错乱。 我整个项目的解决方法有三方面:

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

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;
Oliver K. answered 2020-08-10T23:18:56Z
2 votes

我认为您可以制作一个包含symver语句的简单C文件,以及也许一个调用memcpy的伪函数。 然后,只需确保生成的目标文件是分配给链接器的第一个文件。

zvrba answered 2020-08-10T23:19:16Z
2 votes

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

我的解决方案是呼叫记忆。 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
Gilles Olivier Vollant answered 2020-08-10T23:19:41Z
1 votes

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

Pete Wilson answered 2020-08-10T23:20:01Z
1 votes

可运行的最小自包含示例

GitHub上游。

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;
}

交流

#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;
}

#ifndef A_H
#define A_H

int a(void);

#endif

a.map

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

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

生成文件

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上测试。

0 votes

这可能是由于旧的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,并且崩溃已解决。

jeanerpp answered 2020-08-10T23:21:15Z
translate from https://stackoverflow.com:/questions/8823267/linking-against-older-symbol-version-in-a-so-file