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

2024-10-24 08:51:00
admin
原创
292
摘要:问题描述:在 x86_64 linux 上使用 gcc 和 ld 时,我需要链接到较新版本的库 (glibc 2.14),但可执行文件需要在较旧版本 (2.5) 的系统上运行。由于唯一不兼容的符号是 memcpy (需要 memcpy@GLIBC_2.2.5,但库提供 memcpy@GLIBC_2.14),我...

问题描述:

在 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 博客的网络档案中所示),因为这需要我确保 symver 位于所有使用 memcpy 的代码之前,这将非常困难(带有第三方代码的复杂代码库)

  • 使用旧库来维护构建环境;只是因为我想在我的桌面系统上进行开发,而在我们的网络中同步东西会很麻烦。

当考虑链接器所做的所有工作时,它似乎不是一件难以实现的事情,毕竟它也有一些代码来找出符号的默认版本。

任何与简单链接器命令行具有相同复杂程度的其他想法(例如创建简单的链接器脚本等)也受到欢迎,只要它们不是像编辑生成的二进制文件这样的奇怪的黑客行为......

编辑:
为了给未来的读者保存这个,除了下面的想法之外,我还发现了链接器的选项--wrap,有时它也可能很有用。


解决方案 1:

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

解决方案 2:

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

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

请注意,memcpy@GLIBC_2.2.5已映射到 memmove(旧版本的 memcpy 始终以可预测的方向复制,这导致它在应该使用 memmove 时有时被误用),这是版本升级的唯一原因 - 您可以在代码中针对这种特定情况简单地将 memcpy 替换为 memmove。

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

解决方案 3:

我遇到了类似的问题。我们使用的第三方库需要旧的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>

解决方案 4:

我遇到了类似的问题。尝试在 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.map 文件,将我们的 memcpy 导出为 memcpy@GLIBC_2.14:

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)

这对我来说很管用。我希望它对你也有用。

解决方案 5:

我显然有点迟才回复这个问题,但我最近将我的 Linux 操作系统升级到 XUbuntu 14.04(这是永远不升级的更多理由),它带有新的 libc。我在我的计算机上编译了一个共享库,客户出于某种正当理由没有将其环境从 10.04 升级,因此使用该共享库。我编译的共享库不再加载到他们的环境中,因为 gcc 依赖于 memcpy glibc v. 2.14(或更高版本)。让我们先不考虑这件事的疯狂。我整个项目的解决方法有三方面:

  1. 添加到我的 gcc cflags:-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
";
$status = 1;
}

while (<SYMS>) {
next unless /@@GLIBC/;
chomp;
my ($symbol, $verstr) = (m/^s+.s(.*)@@GLIBC_(.*)/);
die "unable to parse version from $libname in $_
"
    unless $verstr;
my @ver = split(/./, $verstr);
complain $symbol, $verstr
    if ($ver[0] > 2 || $ver[1] > 10);
}
close SYMS;

exit $status;

解决方案 6:

最小可运行的独立示例

GitHub 上游。

主程序

#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

地图

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

解决方案 7:

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

我的解决方案是调用 memmove。memove 所做的工作与 memcpy 完全相同。唯一的区别是当 src 和目标区域重叠时,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

解决方案 8:

对于 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在某些时候将其移除。而且它似乎有效。

解决方案 9:

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

解决方案 10:

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

解决方案 11:

我们遇到了类似的问题,但我们不必提供一个较旧的 GLIBC 符号,而是必须在 .so 库中提供具有必要功能的新符号和我们的库可能引用但不可用的旧符号。出现这种情况的原因是,我们正在向客户提供具有矢量化数学函数的高性能编解码器库,并且我们无法对他们使用的 OS 发行版、gcc 或 glibc 版本施加要求。只要他们的机器具有适当的 SSE 和 AVX 扩展,这些库就应该可以工作。以下是我们所做的:

  1. 将 glibc 2.35 libmvec.so.1 和 libm.so.6 文件包含在单独的子文件夹中。这些文件包含必要的矢量化数学函数。在“hello codec”应用程序示例中,我们根据 Makefile 找到的发行版、gcc 和 glibc 版本在链接目标中引用这些文件。或多或少,对于任何具有 glibc v2.35 或更高版本的程序,都会引用高性能库,否则会引用较慢的库。

  2. 为了处理缺失的符号(本主题),我们使用了Ortwin Anermeier解决方案的修改版,该解决方案又基于anight的解决方案,但没有使用 -Wl,--wrap=xxx 选项。

.map文件如下所示:

GLIBC_2.35 {
   hypot;
   :
   : (more function symbols as needed)
};

GLIBC_2.32 {
   exp10;
   :
   :  (more function symbols as needed)
};

 :
 : (more version nodes as needed)

并在“stublib”中,所以我们有:

#define _GNU_SOURCE
#include <math.h>

asm(".symver hypot_235, hypot@GLIBC_2.35");
asm(".symver exp10_232, exp10f@GLIBC_2.32");
/* ... more as needed */

double hypot_235(double x, double y) { return hypot(x, y); }
double exp10_232(double x) { return exp10(x); }
/* ... more as needed */
  1. 然后,-lstublib.so 被包含在应用程序构建中作为最后的链接项,即使在 -lm 之后。

这个答案和这个答案也提供了线索,但它们没有处理一般情况。因此,它不够灵活,无法在各种各样的系统上使用。

解决方案 12:

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

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

当我使用 ld 2.19.1 时,memset 被重定位为:memset@@GLIBC_2.0,导致崩溃。升级到 2.25 后,它变为:memset@plt,崩溃问题得到解决。

解决方案 13:

相当简单的解决方案,创建一个包含您需要的所有 symver 定义的头文件,例如:

我的标题

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

然后只需通过 gcc/g++ 标志将此标头包含在所有编译命令中-include myheader.h

这相当简单和直接——由于这个-include标志,您不需要修改任何原始来源。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用