c++ - 不同的数学符号绑定与共享库与 dlopen 并直接链接到可执行文件 (Linux)
问题描述
我有两个在 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' <<<<<<
(为清楚起见,删除了路径)
这表明,对于实际应用程序,系统数学库 libm 使用了许多数学函数符号(cos、cosf、exp、expf、sin、sinf、acos ......),而对于测试可执行文件,绑定是从 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,用户应该能够在自己的库中用强符号覆盖它们:
[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
我认为,符号绑定的问题是 “限制”部分中https://en.wikipedia.org/wiki/Weak_symbol中描述的典型 Unix system-V 问题。使用 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 文章描述了类 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 的几个现代标志不可用,并且链接器也错过了例如 --exclude-symbols。LD_DYNAMIC_WEAK 环境变量在旧 Linux 上也不可用。可能唯一的解决方案是将测试可执行文件也重写为使用 dlopen。
任何想法表示赞赏。
解决方案
我不允许更改库或实际应用程序。
如果不允许您更改任何内容,那么您将无法解决问题。
我使用 LD_DEBUG=bindings 来查找差异,发现...
LD_DEBUG
是错误的调试工具。请改用 GDB。
在 eg 上设置断点cos
,运行两个二进制文件,并确认它们实际上正在执行不同的代码。一旦您知道cos
其中一种情况存在libA
(我无法完全解析您的描述,但我认为这就是您声称观察到的),请弄清楚它是如何进入的libA
(使用链接器标志-Wl,-y,cos
来确定)。
符号可见性可能起到一定作用,这也是符号解析表现不同的原因。用于链接 prod-exe、test-exe、libA.so 和 libB.so 的确切命令行可能很重要。跑步readelf -Ws prot-exe test-exe libA.so libB.so | grep ' cos$'
也可能很有启发性。
一旦你掌握了所有信息(假设你仍然无法理解正在发生的事情),提出一个新问题,并记录更详细的观察记录。
推荐阅读
- python - 硬币变化(我的递归逻辑有效,但添加记忆时它不起作用)
- java - 如何让代码知道是手动运行还是通过管道运行?
- google-apps-script - 谷歌脚本复制和粘贴
- css - 为什么我的 sass 在过去几天一直在工作之后现在不起作用
- c# - 报表服务器中的基本每像素调整大小
- r - 如何将列表列表转换为具有差异列表结构的数据框
- asp.net - Crystal Reports ASPX - 由于页面的相关配置数据无效,无法访问请求的资源
- flutter - 无法无条件访问属性“文档”,因为接收者可以为“空”
- javascript - 调用 removeEventListener 后滚动事件仍然有效
- flutter - 通过删除元素刷新列表视图构建器