强制链接器失败并出现多重定义错误,即使包括 --whole-archive

Force a linker to fail with a multiple definition error, even if including --whole-archive

本文关键字:错误 定义 --whole-archive 包括 链接 失败      更新时间:2023-10-16

此示例由多个文件组成:

// baz.cxx
int wat = 0;
int counter = ++wat;
// foo.cxx (empty)
// bar.cxx (empty)
// main.cxx
#include <iostream>
extern int wat;
int main() {
std::cout << wat << 'n';
}
// makefile
run : main.cxx foo.so bar.so
g++ -std=c++11 $^ -o $@
baz.a : baz.cxx
g++ -std=c++11 -c $^ -o baz.o -fPIC
ar rcs $@ baz.o
%.so : %.cxx baz.a
g++ -std=c++11 $< -Wl,--whole-archive baz.a -Wl,--no-whole-archive -o $@ -shared -fPIC

按原样,如果您只是运行make && LD_LIBRARY_PATH=. ./run,则编译、构建、链接、运行和输出的所有内容2。这是因为foo.sobar.so都提供wat,并且counter的初始化运行两次。

在这种情况下,有没有办法以某种方式强制run无法与多重定义错误链接,同时仍然确保foo.sobar.so都有wat的定义?

您可以使用链接器脚本libfoo.solibbar.so静态部分,这会产生符号冲突(并将实际的 DSO 安装在一些不明显的地方,以便-lfoo-lbar不会拾取它们(。

像这样:

  • libfoo.so

    INPUT(conflict.o)
    INPUT(./foo.so)
    
  • libbar.so

    INPUT(conflict.o)
    INPUT(./bar.so)
    
  • conflict.cxx

    int conflict;
    

这导致:

g++ -std=c++11 main.cxx -o run -L. -lfoo -lbar
conflict.o:(.bss+0x0): multiple definition of `conflict'
conflict.o:(.bss+0x0): first defined here
collect2: error: ld returned 1 exit status

这只会检测链接编辑器的同一运行中的冲突。 您仍然可以使用-lfoo-lbar链接两个不同的 DSO,并将这两个链接到应用程序中,而不会从链接编辑器出错。

ABI 注释解决了类似的问题,但我认为这些注释需要特定的ld更改。

如果运行时检查是可以接受的,则可以具有由需要冲突的每个 DSO 定义的弱符号列表,并实现一个 ELF 构造函数,如果定义了多个 ELF 构造函数,该构造函数将中止该过程。 我不知道有什么东西可以在静态链接时与 GNU 工具链实现同样可靠的东西。 也许这值得针对binutils的RFE错误。

我认为解决问题的唯一正确方法是将解决方案的内容移动到某个共享库bar.a从而解决钻石继承问题......但我认为这超出了这个问题的范围。

我唯一能为您考虑的就是创建在链接最终可执行文件之前执行的"自定义"验证器。像这样:

nm *.so |  # get all symbols
grep " [B|T] " |  # only exported ones
egrep -v "__bss_start|_end|_fini|_init" |  # filter out some commons, probably to be extended
awk '{print $3}' |  # get only symbol name
sort | uniq -c |  # sort and count
egrep -v "^[ ]+1 " # get only those that have multiple definitions

这将打印多次定义的库中的所有强(导出(符号。您可以轻松地将其包装在一个脚本中,如果输出不为空并显示有意义的消息,则返回错误状态代码。

修补的生成文件的实验版本如下所示:

run: main.cxx foo.so bar.so
! nm foo.so bar.so | grep " [B|T] " | egrep -v "__bss_start|_end|_fini|_init" | awk '{print $3}' | sort | uniq -c | egrep -v "^[ ]+1 "
g++ -std=c++11 $^ -o $@

(注意!以反转最终 grep 的退出代码,该代码搜索任何不以 bare 1 开头的uniq -c输出(

我知道它确实是黑客,丑陋且不可移植的解决方案,但我认为它在像您这样的极端情况下可能具有一定的价值。