【问题标题】:gcc ld: method to determine link order of static librariesgcc ld:确定静态库链接顺序的方法
【发布时间】:2016-03-13 21:17:17
【问题描述】:

我的可执行文件与许多静态库链接,在 Linux 上通常有 50 到 100 个档案。这些档案中偶尔会有依赖循环。这些库出现在链接命令行上的顺序很重要,请参阅here。尝试手动订购这么多库至少很耗时,尤其是在存在循环时。

问题:是否有一种实用程序或技术可以分析代码库并生成正确的链接命令行顺序?

【问题讨论】:

  • 我明白了。通常,人们希望自动解决它们,而不是为自己找到正确的顺序。我以为你问过你想避免手动修复订单。请问您为什么要知道订单?是不是担心 --start-group -- --end-group 会变慢?
  • @l3x,它与性能有关。预先确定顺序允许链接器仅扫描每个库一次。当您尝试为可执行文件链接 70 个库时,这可能会产生很大的不同。
  • 如果存在依赖循环,则没有正确的顺序。您必须隔离在它们之间具有这些循环的库并仅在这些库周围使用开始/结束组,或者将这些库和 tsort 组合在一起。
  • @ELP,是的,但是可以使用--start-group/--end-group 识别和隔离循环,其余的库是拓扑排序的。这是通过反复试验所做的。

标签: c++ gcc ld static-linking


【解决方案1】:

您需要拓扑排序

tsort 程序可以做到这一点,但您需要做更多的工作才能使用它[准备编写 perl/python 脚本]。此外,还有另一种方法。而且,我进入下面的“howto”,因为我以前做过这种事情。


简短的回答:使用--start-group liblist --end-group 并完成它。

有几个原因:

ld 组是智能。它不只是循环文件。它首先通过组,但记住符号。因此,在随后的传递中,它使用缓存的符号表信息,因此速度非常快。

对于复杂的交互,您可能能够通过拓扑排序摆脱所有循环,因此您仍然需要一个组,即使 liblist 已经过拓扑排序。

我们谈论了多少时间?而且,您认为会节省多少时间?您将如何衡量事物以证明您确实需要它。


争取金牌

考虑使用ld.gold,而不是使用ld。它已从头开始重写,使用 libbfd [这很慢] 并直接对 ELF 文件进行操作。创建它的主要动机是简单和速度


如何对库列表进行拓扑排序

如果我们使用info coreutils,tsort 部分将给出如何对符号表进行拓扑排序的示例。

但是,在我们开始之前,我们需要获取符号。对于.a 文件,nm 可以提供列表:nm -go <liblist>

输出将如下所示:

libbfd.a:
libbfd.a:archive.o:0000000000000790 T _bfd_add_bfd_to_archive_cache
libbfd.a:archive.o:                 U bfd_alloc
libbfd.a:archive.o:0000000000000c20 T _bfd_append_relative_path
libbfd.a:archive.o:                 U bfd_assert
libbfd.a:archive.o:                 U bfd_bread
libbfd.a:archive.o:00000000000021b0 T _bfd_bsd44_write_ar_hdr
libbfd.a:archive.o:                 U strcpy
libbfd.a:archive.o:                 U strlen
libbfd.a:archive.o:                 U strncmp
libbfd.a:archive.o:                 U strncpy
libbfd.a:archive.o:                 U strtol
libbfd.a:archive.o:                 U xstrdup
libbfd.a:bfd.o:                 U __asprintf_chk
libbfd.a:bfd.o:00000000000002b0 T _bfd_abort
libbfd.a:bfd.o:0000000000000e40 T bfd_alt_mach_code
libbfd.a:bfd.o:                 U bfd_arch_bits_per_address
libbfd.a:bfd.o:0000000000000260 T bfd_assert
libbfd.a:bfd.o:0000000000000000 D _bfd_assert_handler
libbfd.a:bfd.o:0000000000000450 T bfd_canonicalize_reloc
libbfd.a:bfd.o:                 U bfd_coff_get_comdat_section
libbfd.a:bfd.o:0000000000000510 T _bfd_default_error_handler
libbfd.a:bfd.o:0000000000000fd0 T bfd_demangle
libbfd.a:bfd.o:                 U memcpy
libbfd.a:bfd.o:                 U strchr
libbfd.a:bfd.o:                 U strlen
libbfd.a:opncls.o:0000000000000a50 T bfd_openr
libbfd.a:opncls.o:0000000000001100 T bfd_openr_iovec
libbfd.a:opncls.o:0000000000000b10 T bfd_openstreamr
libbfd.a:opncls.o:0000000000000bb0 T bfd_openw
libbfd.a:opncls.o:0000000000001240 T bfd_release
libbfd.a:opncls.o:                 U bfd_set_section_contents
libbfd.a:opncls.o:                 U bfd_set_section_size
libbfd.a:opncls.o:0000000000000000 B bfd_use_reserved_id
libbfd.a:opncls.o:00000000000010d0 T bfd_zalloc
libbfd.a:opncls.o:00000000000011d0 T bfd_zalloc2

libglib-2.0.a:
libglib-2.0.a:libglib_2_0_la-gallocator.o:0000000000000100 T g_allocator_free
libglib-2.0.a:libglib_2_0_la-gallocator.o:00000000000000f0 T g_allocator_new
libglib-2.0.a:libglib_2_0_la-gallocator.o:0000000000000150 T g_blow_chunks
libglib-2.0.a:libglib_2_0_la-gallocator.o:0000000000000160 T g_list_push_allocator
libglib-2.0.a:libglib_2_0_la-gallocator.o:0000000000000060 T g_mem_chunk_alloc
libglib-2.0.a:libglib_2_0_la-gallocator.o:0000000000000090 T g_mem_chunk_alloc0
libglib-2.0.a:libglib_2_0_la-gallocator.o:0000000000000110 T g_mem_chunk_clean
libglib-2.0.a:libglib_2_0_la-gallocator.o:0000000000000120 T g_mem_chunk_reset
libglib-2.0.a:libglib_2_0_la-gallocator.o:00000000000001b0 T g_node_pop_allocator
libglib-2.0.a:libglib_2_0_la-gallocator.o:00000000000001a0 T g_node_push_allocator
libglib-2.0.a:libglib_2_0_la-gallocator.o:                 U g_return_if_fail_warning
libglib-2.0.a:libglib_2_0_la-gallocator.o:                 U g_slice_alloc
libglib-2.0.a:libglib_2_0_la-gallocator.o:                 U g_slice_alloc0
libglib-2.0.a:libglib_2_0_la-gallocator.o:                 U g_slice_free1
libglib-2.0.a:libglib_2_0_la-gallocator.o:0000000000000190 T g_slist_pop_allocator
libglib-2.0.a:libglib_2_0_la-gslice.o:                 U g_private_get
libglib-2.0.a:libglib_2_0_la-gslice.o:                 U g_private_set
libglib-2.0.a:libglib_2_0_la-gslice.o:                 U g_return_if_fail_warning
libglib-2.0.a:libglib_2_0_la-gslice.o:00000000000010d0 T g_slice_alloc
libglib-2.0.a:libglib_2_0_la-gslice.o:0000000000001770 T g_slice_alloc0
libglib-2.0.a:libglib_2_0_la-gslice.o:00000000000017a0 T g_slice_copy
libglib-2.0.a:libglib_2_0_la-gslice.o:00000000000017e0 T g_slice_free1
libglib-2.0.a:libglib_2_0_la-gslice.o:0000000000001ae0 T g_slice_free_chain_with_offset

所以,语法是:

<libname.a>:<objname.o>:<address> [TDB] <symbol>
<libname.a>:<objname.o>:          U     <symbol>

我们需要提取libname.a、符号type(例如T、D、B、U)和符号.

我们创建一个文件列表。在每个文件结构中,我们都会记住所有符号及其类型。任何 U [未定义符号] 的类型都将定义该符号。

请注意,当我们构建符号表时,库可能有多个 U [在各种 .o 中],它们引用由其中另一个 .o 定义的符号。因此,我们只记录一次符号,如果我们看到非 U 类型,我们“提升”它(例如,如果我们看到 U foo,然后看到 T foo,我们更改 foo 的类型到T [同样适用于 D 和 B]。

现在我们遍历文件列表(例如curfile)。对于文件符号表中的每个符号,如果它的类型为U [undefined],我们会扫描所有文件以查找非U 符号定义。如果我们找到一个(在symfile(例如)中),我们可以为 tsort 输出一个依赖行:&lt;curfile&gt; &lt;symfile&gt;。我们对所有文件和符号重复此操作。

请注意,这有点浪费,因为我们可以输出许多相同的 file 依赖行,因为上面将为每个 symbol 生成一行。因此,我们应该跟踪输出的行,并且只输出唯一文件对的依赖行。另外,请注意,foo bar bar foo可能的。也就是说,实际上是一个循环。虽然我们只想要一份foo bar 和/或bar foo 的副本,但它们不应该相互排斥。

好的,现在将上面的输出提供给tsort,它将为我们提供我们想要的拓扑排序版本的liblist

很明显,脚本解析可能需要一些时间,因此 tsort 输出应缓存在一个文件中,并根据 liblist 的依赖项列表在 makefile 中重新构建


将部分 .a 文件转换为 .o 文件

如果给定库使用了所有[或大部分] .o 文件,而不是使用ar rv libname.a ...,请考虑使用ld -r libname.o ...

这与创建共享库 .so 文件的方法类似,但“大”.o 仍然可以静态链接。

现在,您有一个比 .a 链接速度更快的 .o 文件,因为库内链接已被解析。此外,它还会对依赖周期有所帮助。

对 topo 脚本的轻微扩展可以告诉您哪些库是很好的候选者。

即使无法更改正常的构建 makefile,“最终”顶层也可以采用 .a,或者将其提取到 .o 中,或者使用带有 -r 的 ld 强制加载选项来获得“大” .o

【讨论】:

  • 过去我们使用ar tvf libXXX.a `nm -no *.o|tsort` 之类的东西,但我忘记了细节,已经有 25 年或更长时间了。
  • @EJP 是的。 25 年是关于我必须在记忆中挖掘算法的时间:-)。叹。我记得几年前,当我第一次发现 ld 组时,我是“哇,不要再进行拓扑排序了!”并更改了我所有的构建脚本以使用它们。似乎,在这里,“旧的又是新的......”
  • 非常好的答案。一个问题是,nm 生成的名称是否应该被分解。我想将它们弄乱会提供更精确的匹配,但对人类来说更难阅读,对吗?
  • @ThomasMcLeod 人类能读或不能读的内容无关紧要。输出直接通过管道传输到tsort,然后可能从那里传输到uniq,然后肯定会删除除文件名之外的所有内容。可以记住细节,但它曾经在man 1 ar上列出。人类根本不需要阅读它。
  • @ThomasMcLeod ld [and nm] 只知道错位的名称,因此按原样使用它们更合适。更快[拆解很慢]。而且,更便携,因为我们不必预测创建它们的工具链的修改方案并将其反转。如果我们猜错了,我们可能会发生碰撞。如果我希望 topo 脚本打印进度消息,我会进行去修饰,但在尝试符号匹配时我仍然会使用损坏的名称。
【解决方案2】:

BSD 世界有一个围绕nm 的包装器来完成生成tsort 的工作,该输入称为lorder。它实际上只是一个shell脚本,你可以看到source code in FreeBSD。它完全按照现有答案所说的那样做,但是以两个文件的形式用于符号和参考。 (还有一些输入文件名中的空白处理不佳,但我离题了。)

lorder 是古老的。自 AT&T Unix 第 7 版(1979 年)以来,它就一直存在。


关于使用更现代的ld.gold 来提高速度的建议,考虑使用 LLVM 的ld.lld。 LLD 更易于使用,因为它根本不需要良好的链接顺序。它是faster still

【讨论】:

    猜你喜欢
    • 2020-02-27
    • 1970-01-01
    • 2012-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多