您需要拓扑排序。
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 输出一个依赖行:<curfile> <symfile>。我们对所有文件和符号重复此操作。
请注意,这有点浪费,因为我们可以输出许多相同的 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