【问题标题】:TCL extensions that depends on each other相互依赖的 TCL 扩展
【发布时间】:2011-11-30 17:56:48
【问题描述】:

我的问题有点类似于this,但它是关于 TCL 扩展的。

我在 Linux (gcc) 上使用 C,并且我有一个包含三个模块 A、B 和 C 的包。模块 A 包含函数并定义(不仅是声明)全局变量。我将模块 A 编译并链接到动态库 (libA.so) 中。

现在,我希望 B 和 C 是 TCL 扩展。两者都使用来自 A 的函数和全局变量,而 C 也使用来自 B 的函数。我制作了 B 和 C 共享库(B.so 和 C.so),但没有使用“-Wl -soname”。我使 B.so 依赖于 A.so,而 C.so 没有用户依赖项。虽然这很奇怪,但机器人扩展已加载并正常工作。这是我所拥有的(A=libbiddy.so,B=bddscout.so,C=bddscoutIFIP.so):

meolic@meolic:/usr/lib/bddscout$ ldd *.so
bddscout.so:
    linux-gate.so.1 =>  (0x00177000)
    libbiddy.so.1 => /usr/lib/libbiddy.so.1 (0x00eca000)
    libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00342000)
    /lib/ld-linux.so.2 (0x0061f000)
bddscoutIFIP.so:
    linux-gate.so.1 =>  (0x00fc2000)
    libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00110000)
    /lib/ld-linux.so.2 (0x00c75000)

meolic@meolic:/usr/lib/bddscout$ wish
% puts $tcl_patchLevel
8.5.8
% load ./bddscout.so
% load ./bddscoutIFIP.so
% info loaded
{./bddscoutIFIP.so Bddscoutifip} {./bddscout.so Bddscout} {{} Tk}

问题是,完全相同的包并非在所有地方都可以使用。在新的计算机上,扩展 C.so 无法加载。

meolic@altair:/usr/lib/bddscout$ ldd *.so
bddscout.so:
    linux-gate.so.1 =>  (0xb76ef000)
    libbiddy.so.1 => /usr/lib/libbiddy.so.1 (0xb76c9000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb754d000)
    /lib/ld-linux.so.2 (0xb76f0000)
bddscoutIFIP.so:
    linux-gate.so.1 =>  (0xb7780000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75e8000)
    /lib/ld-linux.so.2 (0xb7781000)

meolic@altair:/usr/lib/bddscout$ wish
% puts $tcl_patchLevel
8.5.10
% load ./bddscout.so
% load ./bddscoutIFIP.so
couldn't load file "./bddscoutIFIP.so": ./bddscoutIFIP.so: undefined symbol: biddy_termFalse

报告的未定义符号是 A 中的全局变量之一。问题 1:我的方法是否正确,因为它适用于某些系统?问题2:为什么它在新系统上不起作用?

【问题讨论】:

  • +1:关于复杂主题的好问题。

标签: c linux linker dependencies tcl


【解决方案1】:

Tcl 的load 命令在后台使用dlopen()(在Linux 上;当然在其他平台上有所不同),并将它与RTLD_LOCAL 标志一起使用;库中的符号导出到应用程序的其余部分。因此,一个动态加载的库中的未绑定符号将无法解析另一个库;这会增强隔离性,但会迫使您做更多的工作,以使您希望这种依赖项实际存在的地方都能正常运行。

您的选择是:

  1. 如果libscoutIFIP.so 依赖于libbiddy.so 的符号,请在构建库时将此告知链接器,动态链接器引擎将对其进行排序,以免依赖项多次加载。也就是说,如果一个库依赖于另一个库中的一个符号,它应该显式地将该库列为依赖项。
  2. 安排libbiddy.so 通过 Tcl 的包 API (Tcl_PkgProvide()) 将其符号导出为存根表(即,指向函数/变量的指针的结构)。然后当libscoutIFIP.sobiddy 包执行Tcl_PkgRequireEx() 时,它将获得指向该存根表的指针,并且可以使用其中的引用而不是直接链接。这就是 Tcl 的存根机制的工作原理,其出色的可移植让您可以进行相当复杂的 API 版本管理(如果需要)。不过,设置起来需要做更多的工作。 Tcler's Wiki 对此主题进行了更深入的探讨。

如果选项 1 对您有用,请继续;对于 Linux 特定的代码应该没问题,因为系统动态链接器不是非常密集(与 Windows 上的情况不同)。


[编辑]:请注意,旧版本的 Tcl(最高 8.5.9)使用 RTLD_GLOBAL 代替。似乎此更改应该在发行说明中标记为 ***POTENTIAL INCOMPATIBILITY*** 并进行更广泛的跟踪。代表 Tcl 开发人员道歉。

【讨论】:

  • +1 关于 Tcl 的存根很棒。尤其是与该领域的 Python 产品相比时 (python.org/dev/peps/pep-0384)。
  • 你没有解释为什么同一个包在 tcl 8.5.8 的系统上工作,在 tcl 8.5.10 的系统上不工作。 8.5.8 加载扩展时是否使用了RTLD_GLOBAL
  • @Employed:是的,由于Bug #3216070。与该错误相关的discussion 的摘要是没有选择是完美的——如果有一个正确的选择,我们会选择它——但 RTLD_LOCAL 使缺陷更加本地化。抱歉,更改给您带来了问题。
  • 谢谢。只是最后的评论。模块 A 也用于其他非 TCL 项目(因此它必须是一个库)。模块 B 和 C 只是 TCL 扩展(没有必要创建系统库)。此外,C 是 B 的扩展,这是主要问题。这不是微不足道的,但现在,我可以用两种不同的方式解决这个问题。第一个版本:模块 B 和 C 都与 A 动态链接,而 B 也将其符号导出为存根表。第二版:模块 B 与 A 静态链接(以避免依赖于 A)并将其符号导出为存根表,而 C 与 B 动态链接。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-18
  • 2013-04-06
  • 1970-01-01
  • 1970-01-01
  • 2011-01-25
  • 2016-07-22
相关资源
最近更新 更多