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

2024-10-24 08:51:00
admin
原创
55
摘要:问题描述:在 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标志,您不需要修改任何原始来源。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用