使用 dlopen 与共享库绑定不同的数学符号并直接链接到可执行文件 (Linux)

Different math symbol bindings with shared library with dlopen and directly linked into executable (Linux)

我在 Linux 上使用了两个共享库 libA 和 libB,它们有两种使用方式: 1. 作为共享库直接链接到 "offline" 测试可执行文件。 2. 在实际应用中使用:辅助包装器库(libWrapper)链接到libA 和libB,应用程序仅使用系统调用dlopen("libWrapper.so", RTLD_NOW | RTLD_LOCAL) 打开包装器库。

问题:库 运行 复杂的图像分析算法,有时数值结果不相等 。 我应该找到一种方法来确保测试可执行文件给出与真实应用程序相同的结果,但不允许我更改库或实际应用程序,只能更改测试可执行文件。

我使用 LD_DEBUG=bindings 来查找输出中的差异(到 stderr):

$ grep acosf log-bindings.test-executable  # *"offline" test executable*
binding file libB.so to libA.so: normal symbol `acosf.J'
binding file libB.so to libA.so: normal symbol `acosf.A'
binding file libA.so to libA.so: normal symbol `acosf.J' 
binding file libA.so to libA.so: normal symbol `acosf.A' 
binding file libB.so to libA.so: normal symbol `acosf'   <<<<<<<
binding file libA.so to libA.so: normal symbol `acosf'   <<<<<<<


$ grep acosf log-bindings.process   # logging from *real process*
binding file libB.so to libA.so: normal symbol `acosf.J'
binding file libB.so to libA.so: normal symbol `acosf.A'
binding file libB.so to libB.so: normal symbol `_ZSt4acosf'  # std::acosf
binding file libB.so to **libm**.so.6: normal symbol `acosf'      <<<<<<
binding file libA.so to libA.so: normal symbol `acosf.J' 
binding file libA.so to libA.so: normal symbol `acosf.A' 
binding file libA.so to **libm**.so.6: normal symbol `acosf'      <<<<<<

(为清楚起见删除了路径)

这表明,在实际应用中很多数学函数符号(cos、cosf、exp、expf、sin、sinf、acos....) 从系统数学库 libm 中使用,而对于测试可执行文件,绑定是从 libB 到库 libA,以及从 libA 到 libA 本身。这可能是造成差异的原因。

请问我以函数acosf()为例: 使用链接器选项 -y acosf 我们通过将 -Wl,yacosf 传递给编译器在构建期间获得输出:

release/libBdl/lib/libA.so: definition of acosf
release/libBdl/lib/libB.so: reference to acosf 

我使用 nm 工具显示库中的符号:

$ nm  libA/libA.so | grep acosf
00665200 T acosf                          # impl. of acosf (text symbol)
0066c360 T acosf.A
0066c55c T acosf.J
00271fae t _Z13acosf_checkedf             # acosf_checked(float)
00708244 r _Z13acosf_checkedf$$LSDA

$ nm  libB/libB.so | grep acosf
01423780 T acosf                          # impl. of acosf (text symbol)
01424410 T acosf.A
0142460c T acosf.J
004c1b3a W _ZSt4acosf
01547eec r _ZSt4acosf$$LSDA

虽然发布计算机上的数学库没有符号,但我认为libm的方法是相同的:它在库中定义了弱符号expf或acosf,用户应该可以在自己的库中覆盖这些符号带有强符号:

[newer CentOS7 system]$ nm /usr/lib/libm.so|grep acosf
0001b9c0 W acosf      # weak symbol 'acosf'
0001b9c0 t __acosf    # strong symbol / implementation
000176b0 T __acosf_finite
000176b0 t __ieee754_acosf   # called by __acosf in libm

[newer CentOS7 system]$ nm /usr/lib/libm.so|grep expf
0001bc60 W expf       # weak symbol 'expf'
0001bc60 t __expf     # strong symbol / implementation
00017990 i __expf_finite
0002d370 t __expf_finite_ia32
0002d1b0 t __expf_finite_sse2
00017960 i __ieee754_expf      # called by __expf in libm
0002d330 t __ieee754_expf_ia32
0002d1b0 t __ieee754_expf_sse2

readelf -Ws ..| grep acosf 结果:

test-executable:
--

real-application:
--

libWrapper.so:
--

libB.so:
3934: 004c12a6    40 FUNC    WEAK   DEFAULT   10 _ZSt4acosf
5855: 01423b80   506 FUNC    GLOBAL DEFAULT   10 acosf.A
10422: 01423d7c   666 FUNC    GLOBAL DEFAULT   10 acosf.J
14338: 01422ef0    40 FUNC    GLOBAL DEFAULT   10 acosf

libA.so:
2333: 0066c1e8   506 FUNC    GLOBAL DEFAULT   10 acosf.A
4179: 0066c3e4   666 FUNC    GLOBAL DEFAULT   10 acosf.J
5772: 00665088    40 FUNC    GLOBAL DEFAULT   10 acosf

我认为,符号绑定的问题是 "Limitations" 部分 https://en.wikipedia.org/wiki/Weak_symbol 中描述的典型 Unix system-V 问题。使用 dlopen() 时,动态链接器更喜欢带有弱符号的 libm,因为它已经加载,尽管 libA "later" 中有强符号可用。 ~

与LD_DEBUG=全部:

test-executable:

symbol=expf; lookup in file=./test-executable.shared 
symbol=expf; lookup in file=/lib/libdl.so.2
symbol=expf; lookup in file=/home/test/test/bin_NDEBUG/libA/libA.so
binding file libB.so to libA.so: normal symbol `expf'   <<<<

symbol=acosf; lookup in file=./test-executable.shared
symbol=acosf; lookup in file=/lib/libdl.so.2
symbol=acosf; lookup in file=/home/test/test/bin_NDEBUG/libA/libA.so
binding file libA.so to libA.so: normal symbol `acosf'   <<<<



real-application:

symbol=expf; lookup in file=real-application
symbol=expf; lookup in file=/home/test/lib/libX1.so
symbol=expf; lookup in file=/home/test/lib/libX2.so
symbol=expf; lookup in file=/home/test/lib/libX3.so
symbol=expf; lookup in file=/home/test/lib/libX4.so 
symbol=expf; lookup in file=/lib/libdl.so.2 
symbol=expf; lookup in file=/usr/lib/libstdc++.so.5 
symbol=expf; lookup in file=/home/test/lib/libX5.so
symbol=expf; lookup in file=/lib/i686/libm.so.6
binding file libA.so to libm.so.6: normal symbol `expf'    <<<<<<<


symbol=acosf; lookup in file=real-application
symbol=acosf; lookup in file=/home/test/lib/libX1.so
symbol=acosf; lookup in file=/home/test/lib/libX2.so
symbol=acosf; lookup in file=/home/test/lib/libX3.so
symbol=acosf; lookup in file=/home/test/lib/libX4.so
symbol=acosf; lookup in file=/lib/libdl.so.2
symbol=acosf; lookup in file=/usr/lib/libstdc++.so.5
symbol=acosf; lookup in file=/home/test/lib/libX5.so 
symbol=acosf; lookup in file=/lib/i686/libm.so.6
binding file libA.so to libm.so.6: normal symbol `acosf'  <<<<<<

辅助库 "libWrapper" 链接到 libA 和 libB 但没有符号 acosf。

该平台是旧的 32 位 Linux,使用内核 2.4 和 glibc 2.2.5(是的,2001!)。

库 A 和 B 是使用带有选项 -O3、NDEBUG 的英特尔 Icc 编译器构建的。使用 DEBUG 似乎没有问题。与共享链接相比,静态/存档构建的结果略有不同。

测试可执行文件使用 g++(或 icc,没有区别)直接链接到共享库 libA 和 libB。 我努力让测试可执行文件也通过使用 LD_PRELOAD 或各种链接器标志将数学符号绑定到 libm,但这并没有改变任何东西。

我的假设: 实际应用程序中的 dlopen 调用确实晚得多,在加载常用库(和 libm)并启动应用程序之后。如果已经在先前加载的库中找到符号,则首选符号,尽管该符号在 libA 中有一个弱符号和一个强符号。可能这只是旧 Linux 的行为,但 "Limitations" 部分中的 Wikipedia article on weak symbols 描述了类似 Unix system-V 系统的链接器的这种弱点。

我试过了

linker option -Wl,--no-whole-archive 
define LD_BIND_NOW 
define LD_PRELOAD=libm.so 

用于测试可执行文件,但这对符号绑定没有影响:

symbol=acosf;  lookup in file=./test-executable.shared
symbol=acosf;  lookup in file=/lib/i686/libm.so.6
symbol=acosf;  lookup in file=/lib/libdl.so.2
symbol=acosf;  lookup in file=libA.so
binding file libA.so to libA.so: normal symbol `acosf'

我的问题:为什么,即使使用 LD_PRELOAD,测试可执行文件也不会改变并坚持(libA)的库内实现,但使用 dlopen 它使用libm 符号?!?我怎样才能强制测试可执行文件与实际应用程序表现得一样,即使用 libm 符号?

遗憾的是,dlopen 的几个现代标志不可用,而且链接器也未命中,例如--排除符号。 另外 LD_DYNAMIC_WEAK 环境变量在旧的 Linux 上不可用。 可能唯一的解决方案是重写测试可执行文件以也使用 dlopen。

欢迎任何想法。

I am not permitted to change the libraries or the real application.

如果不允许您更改任何内容,则无法解决问题。

I used LD_DEBUG=bindings to find differences, and found that ...

LD_DEBUG 是错误的调试工具。请改用 GDB。

设置一个断点,例如cos、运行 这两个二进制文件,并确认它们实际上在执行不同的代码。一旦你知道 cos 在其中一个案例中位于 libA 中(我不能完全解析你的描述,但我 认为 这就是你声称的观察),弄清楚如何它进入libA(使用linker标志-Wl,-y,cos来确定)。

符号可见性可能是符号解析行为不同的原因之一。用于 link prod-exe、test-exe、libA.so 和 libB.so 的确切命令行可能很重要。 运行 readelf -Ws prot-exe test-exe libA.so libB.so | grep ' cos$' 也很有启发性。

获得所有信息后(假设您仍然无法理解发生了什么),请提出一个新问题并提供更详细的观察记录。

我想我可以自己回答这个问题。

实际应用程序中的 dlopen 调用确实晚得多,在加载常用库(和 libm)并开始执行应用程序之后。如果已经在先前加载的库中找到符号,则首选符号,尽管该符号有一个弱符号,而 libA 中有一个强符号(稍后在程序执行中通过 dlopen 加载)。 Wikipedia articleweak symbols 部分 "Limitations" 中描述了动态链接器 ld-linux.so 对于类似 Unix system-V 的系统的这种弱点(在这种情况下 Linux)。 使用 LD_DEBUG=all 您可以看到链接器如何搜索符号。

在这种情况下,不能更改原始应用程序和共享库(链接器标志、导出方式和导出的符号),唯一的解决方案仍然是重写测试可执行文件以也使用 dlopen(作为真正的申请)。