【发布时间】:2019-12-29 03:20:17
【问题描述】:
我有两个在 Linux 上使用的共享库 libA 和 libB,它们有两种使用方式:
1. 作为共享库直接链接到“离线”测试可执行文件。
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 的方法是相同的:它在 lib 中定义了弱符号 expf 或 acosf,用户应该能够在他自己的 lib 中使用强符号:
[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
我认为,符号绑定的问题是典型的 Unix system-V 问题,在“限制”部分的https://en.wikipedia.org/wiki/Weak_symbol 中描述。使用 dlopen() 动态链接器更喜欢带有弱符号的 libm,因为它已经加载,尽管“稍后”在 libA 中可以使用强符号。 ~
使用 LD_DEBUG=all:
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 的行为,但“限制”一节中的 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。
感谢任何想法。
【问题讨论】:
标签: c++ binding linker shared-libraries dlopen