链接到 .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),我想告诉链接器,它不应该采用 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(或更高版本)。让我们先不考虑这件事的疯狂。我整个项目的解决方法有三方面:
添加到我的 gcc cflags:-include glibc_version_nightmare.h
创建了 glibc_version_nightmare.h
创建了一个 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 扩展,这些库就应该可以工作。以下是我们所做的:
将 glibc 2.35 libmvec.so.1 和 libm.so.6 文件包含在单独的子文件夹中。这些文件包含必要的矢量化数学函数。在“hello codec”应用程序示例中,我们根据 Makefile 找到的发行版、gcc 和 glibc 版本在链接目标中引用这些文件。或多或少,对于任何具有 glibc v2.35 或更高版本的程序,都会引用高性能库,否则会引用较慢的库。
为了处理缺失的符号(本主题),我们使用了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 */
然后,-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
标志,您不需要修改任何原始来源。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件