【问题标题】:ld to library without version information in Linuxld 到 Linux 中没有版本信息的库
【发布时间】:2013-12-21 17:57:09
【问题描述】:

在编译 C/C++ 程序时,有没有办法在不使用 ld 嵌入版本信息的情况下链接到 Linux 中的库导出?

我正在构建一个链接到 Firefox 中的 libxul.so 的共享对象(它是一个 Firefox 二进制扩展)。我想构建我的共享对象并链接到 libxul.so,以便在运行时,加载程序不关心 libxul.so 是什么版本。

现在,我的输出 .so 文件具有以下依赖项:

readelf -V myext.so
  0x0080: Version: 1  File: libxul.so  Cnt: 1
  0x0090:   Name: xul24.0  Flags: none  Version: 9

(注意它取决于版本'xul24.0')

导出的函数在 Firefox 版本之间不会改变。所以,我想删除这个版本说明。

尝试加载到 Firefox 26 时,LD_DEBUG=file 提供以下错误:

/usr/lib/firefox/libxul.so: error: version lookup error:
version `xul24.0' not found (required by /.../myext.so) (fatal)

对于 Firefox 26 版本的 libxul.so,版本为'xul26'

那么,如何防止 ld 将版本信息嵌入到我的库中?

【问题讨论】:

  • 当您链接到 libxul24 然后尝试针对 libxul26 运行时,某些函数的 API 和/或 ABI 会发生变化,这就是您不允许这样做的原因。这是一个完整性检查:您想从一个库中使用的函数在另一个库中不一定相同(xul26 本质上就是这样)。
  • @mirabilos 即使没有任何更改,构建工具也有可能自动分配版本号。这是事实库版本与整个产品版本相同的提示。此外,对于一些工具,例如 gettext,我也看到了这一点,其中库版本和功能更改之间没有关联。但是一旦不能保证 API 不会改变,仍然不能依赖安全加载其他库版本...
  • @Netch:当然,但他不是生成 XUL 库的人。在这种情况下,我相信它是一个产品内部库,上游甚至不假装关心 API/ABI 稳定性(不知道是否应该首先链接它)。所以我认为这是 M*zilla 人的故意。
  • Mozilla 强制执行的其他版本兼容性检查(即扩展包中的 minVersion 和 maxVersion 设置),比链接版本号更细粒度和灵活。无论如何,这对 Linux 来说是个大问题,因为相同的代码在 Windows 和 Mac 中运行良好。我们使用 dlopen() 并动态链接到所有 libxul 导出的解决方法将比值得的成本更高。
  • @mirabilos 是的,我已经看到了。尽管如此,我认为即使改变 ABI 也可以附加一个外国图书馆对学术目的有用 - 合适的玩具应该是易碎的,否则它们不会教:)

标签: linker ld


【解决方案1】:

我能够根据需要构建库,而无需版本信息。

我最终做的是使用适当的导出符号创建一个虚拟库——没有版本(或 soname)信息——并在编译和链接我的库时链接到该虚拟库。在运行时,由于我的库不包含真实库的版本信息,加载程序加载真实库 (libxul.so) 不会基于版本控制问题而失败。为了弄清楚我需要哪些导出的符号,我首先链接到真正的 libxul.so,然后使用readelf --dyn-syms 来确定实际需要哪些符号。

其中 ARCH 是 32 或 64(对于 32 位或 64 位编译): gcc -o $ARCH/libxul.so -fPIC -shared -DM$ARCH -m$ARCH xulstubs.c

xulstubs.c:

/*
 2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND NS_Realloc@xul26 (2)
 3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND NS_UTF16ToCString@xul26 (2)
 7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND NS_CStringCloneData@xul26 (2)
13: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND NS_GetMemoryManager@xul26 (2)
...
 */
void NS_Realloc() {}
void NS_UTF16ToCString() {}
void NS_CStringCloneData() {}
void NS_GetMemoryManager() {}
/* ... etc. ... */

现在,从我的库中导入的符号没有版本附件:

23: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND NS_Realloc
29: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND NS_UTF16ToCString
33: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND NS_CStringCloneData
37: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND NS_GetMemoryManager

它会按需要运行。

我仍然想要一个更“优雅”的解决方案,不需要创建一个虚拟库。但是,如果这是使用标准工具链的唯一解决方法,我可以接受。

【讨论】:

  • 很好的解决方案和解释。为避免截断长符号名称,您可以使用readelf --wide --dyn-syms
【解决方案2】:

如果考虑到最广泛的 GNU 工具链,您可以加载任何没有“lib”前缀和版本后缀的共享库,但只能直接从代码中使用 dlopen()。在二进制文件启动或另一个库加载期间自动加载将不起作用,因为链接器将找到一个库文件,从库头中读取其“soname”和“rpath”属性,并在构建目标加载期间应用它们来查找库。 Soname 对于链接器来说比库文件名更重要。更重要的是,在某些系统中,ldconfig 可以重命名公共搜索路径中的库以匹配其中记录的 soname(我在 Linux 上见过这种情况)。

一个小例子。考虑一个文件 t.c,其中 main() 打印来自库函数的值,以及一个库源文件 tt.c,其中函数返回一个常量。制作它们:

$使清洁;制作 rm -f t t.o libtt.so tt.o gcc -o libtt.so -shared -fPIC tt.c gcc -o t t.c -L。 -ltt

使用这些命令,./t 将失败,因为 libtt.so 不在公共搜索路径中,因此需要 LD_LIBRARY_PATH 来启动它。但我们可以解决这个问题:

$ gcc -o libtt.so -shared -fPIC tt.c $ gcc -o t t.c -L。 -ltt -Wl,-rpath=`密码` $ ldd ./吨 ./t: libtt.so => /var2/homes/netch/prog/tests/soname/libtt.so (0x80081a000) libc.so.7 => /lib/libc.so.7 (0x800a1b000)

但是,如果我在构建过程中请求另一个库名称,搜索将失败:

$ gcc -o libtt.so -shared -fPIC tt.c -Wl,-soname=libtt2.so $ gcc -o t t.c -L。 -ltt -Wl,-rpath=`密码` $ ldd ./吨 ./t: libtt2.so => 未找到 (0) libc.so.7 => /lib/libc.so.7 (0x80081a000)

请注意,如果没有看到 libtt.so,链接器将失败,因为它不知道在哪里可以找到未解析的链接。

尽管链接器显示了一些奇怪的东西,但所有这些都适用于所描述样式以外的名称:

$ gcc -o lybtt.so -shared -fPIC tt.c $ gcc -o t t.c lybtt.so -Wl,-rpath=`pwd` $ ./t 233 $ ldd ./吨 ./t: lybtt.so (0x80081a000) libc.so.7 => /lib/libc.so.7 (0x800a1b000)

如果您是库构建者,您可以不加版本地命名它,所有工具都会识别这一点。但这通常被认为是不好的风格,并且在推荐中被禁止。公共搜索路径中的所有库都声明其名称中包含版本,并且强烈建议对任何非家庭使用的软件包打包。

对于你的问题,如果你真的需要自己动手,你可以复制libxul,更改其中的soname并再次放入正确的搜索路径。但我怀疑这就是你想要的。

【讨论】:

  • 感谢您的详细解释。但是,我需要做的不是朝自己(或其他任何人)的脚开枪。当我的库链接在一起时,肯定有某种方法可以省略名称xul24.0……对吗?从概念上讲,这似乎并不难。
  • 是的,有这样的方法。 1. 在运行时,在库目录中找到当前版本,并为其显式调用 dlopen()。您还需要为每个名称执行 dlsym() 来解析它,因为使用自动引用将无法正确链接。 2. 你可以欺骗 RTLD,除非二进制文件是 sugid。如果它在 FreeBSD 上,libmap.conf 和 LD_LIBMAP 允许用另一个库替换任何库。在所有平台上,LD_LIBRARY_PATH 提供另一个目录(但其中的库应使用搜索的名称。) 3. 如前所述,您可以使用更改的 soname 重写库。
猜你喜欢
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 1970-01-01
  • 2014-02-01
  • 1970-01-01
  • 2020-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多