gcc ld: 确定link 静态库顺序的方法

gcc ld: method to determine link order of static libraries

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

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

You want a topological sort.

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


The short answer: Use --start-group liblist --end-group and be done with it.

有几个原因:

ld组聪明。它不只是在文件上循环。它首先通过该组,但会记住这些符号。所以,在随后的传递中,它使用缓存的符号 table 信息,所以速度非常快。

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

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


Go for the gold

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


How to topologically sort a library list

如果我们这样做 info coreutils,tsort 部分将举例说明如何对符号进行拓扑排序 table。

但是,在开始之前,我们需要先获取符号。对于 .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,符号类型(例如T、D、B、U) , 以及 符号 .

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

请注意,当我们构建符号 table 时,一个库可能有多个 U [在不同的 .o 中] 引用由其中另一个 .o 定义的符号。所以,我们只记录符号一次,如果我们看到一个非 U 类型,我们就 "promote" 它(例如,如果我们看到 U foo 然后又看到 T foo 我们改变 [= 的类型85=]foo 到 T [同样适用于 D 和 B]。

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

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

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

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


Convert some .a files to .o files

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

这类似于创建共享库 .so 文件的方法,但 "big".o 仍然可以静态 linked。

现在,你有一个比 .a 快 link 的 .o,因为库内 links 已经被解析。此外,它会对依赖周期有所帮助。

topo 脚本的轻微扩展可以告诉您哪些库适合此。

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

BSD 世界有一个围绕 nm 的包装器来完成生成名为 lorder. It's really just a shell script, and you can see the source code in FreeBSDtsort 适当输入的工作。它完全按照现有答案所说的进行操作,但是以两个文件的形式用于符号和参考。 (还有一些输入文件名中的空格处理不当,但我离题了。)

lorder是古代。它自 AT&T Unix 版本 7 (1979) 以来一直存在。


至于使用更现代的 ld.gold 提高速度的建议, 考虑使用 LLVM 的 ld.lld. LLD is easier to use since it does not require a good link order at all. And it's faster still.