#重复 #二进制 #确定 #函数 #如何 #名称 #查找

app duplicate-function-checker

工具,用于确定二进制文件中有多少重复函数

1 个不稳定版本

0.1.0 2024年6月7日

#895解析器实现

MIT/Apache

20KB
268

重复符号检查器

此工具旨在确定编译的Rust二进制文件中有多少是由相同的函数组成的。

它依赖于调试符号来查找函数,因此您的二进制文件不能被剥离。

它通过读取每个函数的指令,对这些指令进行归一化,以适应仅由于函数的基本地址而产生的差异,然后根据结果指令字节进行分组。

目前它仅支持x86_64二进制文件,并且仅在Linux上进行了测试。

已识别的重复函数有几个不同的来源

  • 即使函数来自代码库的不同部分,它们也可能偶然相同。
  • 通用函数在单态化后,即使具有不同的通用参数也可能相同。例如,函数可能仅依赖于通用参数的大小,这意味着所有具有相同大小的类型的单态化最终都会相等。
  • 通用函数可能多次使用相同的参数进行单态化,但后来并没有进行去重。

最后一个是我在乎的。不幸的是,目前很难区分这最后两种情况。Rustc似乎有时会在符号名称中包含通用参数,但通常它会在符号名称的末尾使用不同的哈希值。

推荐用法

cargo run --release -- --verbose --demangle /path/to/bin

样本输出

现在我将展示一些从运行ripgrep发布构建的该工具的样本输出。由于输出相当长,我没有包含所有输出,只包含一些值得进一步讨论的部分。

Function size: 103
Copies: 21
Excess bytes: 2060
Names:
  1x `alloc::sync::Arc<T,A>::drop_slow::hd706a4fa915b4d89`
  1x `alloc::sync::Arc<T,A>::drop_slow::h87093f1f9dea2d0e`
  1x `alloc::sync::Arc<T,A>::drop_slow::h10d0dcce72958fd8`
  1x `alloc::sync::Arc<T,A>::drop_slow::h8c617ac2d907e2aa`
  1x `alloc::sync::Arc<T,A>::drop_slow::hd71eeda01817a536`
  1x `alloc::sync::Arc<T,A>::drop_slow::he520a5dd6ca64703`
  1x `alloc::sync::Arc<T,A>::drop_slow::h628a33a33ecac575`
  1x `alloc::sync::Arc<T,A>::drop_slow::h8cec9ca0439c3711`
  1x `alloc::sync::Arc<T,A>::drop_slow::h4cd5ea407012db46`
  1x `alloc::sync::Arc<T,A>::drop_slow::h224d6f2371018a1c`
  1x `alloc::sync::Arc<T,A>::drop_slow::h990f0b7fc7e3af11`
  1x `alloc::sync::Arc<T,A>::drop_slow::h73ba588d5943ac7a`
  1x `alloc::sync::Arc<T,A>::drop_slow::h2dc0bbd1c9c62e26`
  1x `alloc::sync::Arc<T,A>::drop_slow::h517054e3fb2dbac5`
  1x `alloc::sync::Arc<T,A>::drop_slow::hdf2cd5f474fa2393`
  1x `alloc::sync::Arc<T,A>::drop_slow::h61f7d6c3b84da1e9`
  1x `alloc::sync::Arc<T,A>::drop_slow::h2487201382634f65`
  1x `alloc::sync::Arc<T,A>::drop_slow::hfd3d412a64e719d1`
  1x `alloc::sync::Arc<T,A>::drop_slow::h127b8fb68b2d8622`
  1x `alloc::sync::Arc<T,A>::drop_slow::h545a994a083aa1dc`
  1x `alloc::sync::Arc<T,A>::drop_slow::he1370168d3fba403`

这里我们可以看到有21个删除Arc的函数副本。我们不知道Arc里有什么。很可能每个这些函数都是为了删除具有不同 Arc<T> 的不同 T 而创建的,但机器代码最终是相同的。

Function size: 236
Copies: 26
Excess bytes: 5900
Names:
  3x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::h0ede7a90cb4e4caf`
  3x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::h3dc26697a761e8f9`
  3x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::h3801b4f9aaad7fc2`
  5x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::h9eb8ddad156565f6`
  5x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::hc517e495ab2a88a4`
  4x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::hde4f9e64fcdf6bea`
  3x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::hd22a22559e751911`

这里我们有7个不同的函数名,它们之间的区别仅在于它们的哈希值。编译器已经替换了类型参数,因此我们知道这些函数都在丢弃相同的类型。每个函数名然后都有几个副本。这些额外的符号副本来自不同的代码生成单元。我们可以通过重建具有 codegen-units=1 的二进制文件来确定这一点,然后我们得到以下内容

Function size: 236
Copies: 7
Excess bytes: 1416
Names:
  1x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::h92c782dcb35669b7`
  1x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::h67d0912fdfd31563`
  1x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::h11e9189330999400`
  1x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::h3242a4cd1700d56b`
  1x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::h17293025138ff46d`
  1x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::h085ce04cd90a2c3f`
  1x `core::ptr::drop_in_place<regex_automata::meta::wrappers::PikeVMCache>::h419132e2cb015325`

当编译不同的crate时,这些7个副本都被单形化了。我们可以通过运行没有 --demangle 的工具,然后使用grep定位包含该符号的 .rlib 来验证这一点。这些符号中的每一个都出现在不同的rlib中。

典型结果

对于使用 rustc 1.78.0 编译的 ripgrep 的发布版本,我观察到以下情况

配置 % 重复函数
默认发布 5.8
禁用LTO的发布 6.4
带有胖LTO的发布 2.1
codegen-units=1的发布 1.9
codegen-units=1 + 胖LTO的发布 1.8
codegen-units=1 + 胖LTO + -Zshare-generics的发布 0.9
默认调试 (-Zshare-generics 默认开启) 6.6
默认调试带有 -Zshare-generics=off 7.1

如果尝试重现这些数字,我测试了 commit 601e122e9f。

对于具有更多依赖项的二进制文件,我以自己的crate Rust REPL evcxr为例。在其发布版本中,10%的可执行字节来自重复函数的额外副本。通过 codegen-units=1,这降低到1%。

许可证

根据您的选择,许可协议为 Apache License, Version 2.0MIT license

除非您明确说明,否则您提交给 Wild 的任何贡献,根据 Apache-2.0 许可证定义,将按照上述方式双许可,无需任何额外的条款或条件。

依赖项

~18–30MB
~475K SLoC