【问题标题】:Different math symbol bindings with shared library with dlopen and directly linked into executable (Linux)不同的数学符号绑定与共享库与 dlopen 并直接链接到可执行文件 (Linux)
【发布时间】: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


    【解决方案1】:

    我不允许更改库或实际应用程序。

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

    我使用 LD_DEBUG=bindings 查找差异,发现...

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

    在例如设置断点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$' 也可能很有启发性。

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

    【讨论】:

    • 感谢您的评论。明天早上我会测试gdb和readelf。关于 LD_DEBUG,它在 ld.so 中列为“输出有关动态链接器的详细调试信息”,这似乎是我想要的。我在问题中添加了更多信息,我希望现在更清楚。我可以更改测试可执行文件,但不能更改真正的应用程序或库 A 和 B。
    • gdb 在目标系统上不起作用:“b acosf”--> 错误:“无法访问地址 0x4 处的内存.....”
    • readelf -Ws ... | grep acosf 结果:test-executable.shared:(无)真实应用程序:(无)libWrapper.so:(无)libB.so:3934:004c12a6 40 FUNC WEAK DEFAULT 10 _ZSt4acosf 5855:01423b80 506 FUNC GLOBAL DEFAULT 10 acosf。 A10422:01423D7C 666 FUNC全球默认10 ACOSF.J 14338:01422EF0 40 FUNC全球默认10 ACOSF LIBA.SO:2333:0066C1E8 506 FUNC全球默认10 ACOSF.A 4179:0066C3E4 666 FUNC全球默认10 ACOSF.J 5772:00665088 40 FUNC 全局默认值 10 acosf
    • readelf -Ws ..| grep acosf 结果:测试可执行文件:--真实应用程序:-- libWrapper.so:-- libB.so:3934:004c12a6 40 FUNC WEAK DEFAULT 10 _ZSt4acosf 5855:01423b80 506 FUNC GLOBAL DEFAULT 10 acosf.A10422:01423d7c 66全球默认10 ACOSF.J 14338:01422EF0 40 Func全球默认10 ACOSF LIBA.SO:2333:0066C1E8 506 Func全球默认10 ACOSF.A 4179:0066C3E4 666 Func全球默认10 ACOSF.J 5772:00665088 40 Func全球默认10 ACOSF 
    • @karsten 请编辑问题。注释格式使其无法阅读。
    【解决方案2】:

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

    实际应用程序中的dlopen 调用确实要晚得多,在加载常用库(和 libm)并开始应用程序执行之后。如果已在先前加载的库中找到符号,则首选符号,尽管该符号是弱符号,并且在 libA 中可用强符号(稍后在程序执行中通过 dlopen 加载)。在“限制”部分的弱符号上的Wikipedia article 描述了动态链接器 ld-linux.so 对 Unix system-V 类系统(在本例中为 Linux)的这种弱点。 使用 LD_DEBUG=all,您可以查看链接器如何搜索符号。

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

    【讨论】:

      猜你喜欢
      • 2022-01-08
      • 1970-01-01
      • 2015-09-05
      • 2023-03-15
      • 2014-03-06
      • 2011-03-22
      • 2019-12-11
      • 2013-02-22
      • 2016-01-02
      相关资源
      最近更新 更多