【问题标题】:Strip unused runtime functions which bloat executable (GCC)剥离导致可执行文件膨胀的未使用的运行时函数 (GCC)
【发布时间】:2013-09-18 09:58:54
【问题描述】:

我已经为 ARM (cortex-m3) 构建了 GCC4.7.1 交叉工具链。现在我正在从 C/C++ 代码链接一个可执行文件,该代码肯定不使用某些特定的 STL 类(例如std::string)。此外,异常和 RTTI 被关闭。

虽然当我在寻找目标 ELF(例如使用 nm)时,有很多符号(显然来自 libstdc++)链接在我不希望在那里找到(例如std::exceptionstd::ios_base、等等)。

为什么会出现这种情况,我怎样才能摆脱这些东西以减少目标的 .text 部分大小?

一位同事给了我一个技巧来覆盖一些 GCC 特定的存根函数:

namespace __gnu_cxx
{
    void __verbose_terminate_handler()
    {
        for (;;)
            ;
    }
}

仅此一项就减少了大约 20KB 的代码大小。
我可以覆盖更多这样的存根吗?


更新:
好的,我发现一个非常愚蠢的错误,在修复它时删除了我想知道的大部分内容:
在其中一个源文件中有一个#include <iostream> 语句(尽管没有从那里调用)。这当然会链接到静态 std::cinstd::coutstd::cerr 实例以及随之而来的所有内容。
删除 #include <iostream> 语句将 .text 段减少了大约另一个 > 100KB 的部分。


尽管如此:
我还想知道std::exceptionstd::basic_string 的东西:

Namespace summaries:
==============================================================================
Type         Size Namespace 
T             774 'std'
W             184 'std::string::_Rep'
W             268 'std'
W             472 'std::string'
Class summaries:
==============================================================================
Type         Size Class 
T              50 'std::error_category'
T              52 'std::type_info'
T              54 'std::bad_exception'
T              54 'std::exception'
T              68 'std::bad_alloc'
T              98 'std::length_error'
T             214 'std::logic_error'
W             268 'std::basic_string<char, std::char_traits<char>, std::allocator<char> >'

实际上并没有使用太多的代码大小,只有大约 100 个字节,所以我可以忽略它,但如果我也能摆脱它,我将不胜感激。

由于我明确地不使用任何异常,我想知道为什么在链接时仍然会实例化这些异常。在运行时无法真正确定是否使用异常?!?
我现在剩下的 __gnu_cxx 命名空间中唯一剩下的是

Type         Size Class 
T              58 '__gnu_cxx::recursive_init_error'

这是另一个异常类。


终于:
我使用了一些额外的标志来配置 GCC4.7 交叉构建:

--enable-gold=yes 
--enable-lto 
--enable-cxx-flags='-fno-exceptions -ffunction-sections -fno-omit-frame-pointer'

后面的标志用于编译 libstdc++,与用于构建目标代码的标志基本相同(无论如何这是一个合理的操作)。之后的异常引用(包括__gnu_cxx::recursive_init_error)。

最后一件事是,我在我们的代码库中发现了 std::string 的意外使用。修复后,对std::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt; 的引用也消失了。

所以我现在对结果很满意,libstdc++ 不再有不必​​要的意外开销,没有理由不优先使用 C++。

【问题讨论】:

  • @CarlNorum 对于这个项目,我什至使用 C++11 ;-) ...
  • 说真的...如果您的编译器在您的程序中添加了 20k(!) 段纯废话,我强烈建议您更改编译器或将编程语言切换为 C。C++ 语言中没有任何功能可以可以证明这么多无用的开销是合理的。
  • @Lundin 我认为这只是正确调整存根的问题。
  • 如果您可以升级到 GCC 4.8,那么使用 --disable-libstdcxx-verbose 构建 GCC 会禁用详细终止处理程序,因此您不需要将其存根。使用-fno-rtti(通过--enable-cxx-flags 选项)构建libstdc++ 本身将减少库大小。
  • 使用 -fno-threadsafe-statics 编译将摆脱 __recursive_init_error 依赖,但如果您使用多个线程,请不要指望一切正常

标签: c++ gcc embedded cross-compiling


【解决方案1】:

您永远无法知道使用 一个 库函数会带来什么。实际上,您可能可以通过使用其中一种工具创建调用图来实现。那么你使用的是 c++ std 库的哪一部分呢?

除此之外,我已经成功地通过使用两种方法从可执行文件中删除了不需要的函数(在 ARM 上都没有,但方法不是 ARM 特定的):

  1. 使用 -ffunction-sections 开关编译,然后使用 -gc-sections 链接。这会将每个函数放入它自己的部分,然后告诉链接器删除未使用的部分。
  2. 使用链接时间优化(-flto,详见 gcc 手册)。这让编译器可以看到“整个”程序,就好像它是单一来源的一样,并且可能会让它删除未使用的函数。

你已经在使用 -Os 了吧?

【讨论】:

  • 1. 中的选项已被使用。使用 fltoOs 并没有改变任何东西。
  • '那么你使用的是 c++ 标准库的哪一部分?' 到目前为止,我只使用了std::arraystd::tuple
  • 如果您使用的是数组,那么我想其中也存在异常也就不足为奇了(因为 out_of_range 可能会被抛出)。顺便说一句:如果你还没有,也看看这个 (infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0474c/…)。
  • 您的链接与我的工具链 (GCC) 无关,但我会看看它是否支持类似的选项。
  • 请看看我的更新。目前你的回答并不能真正回答我的问题。
【解决方案2】:

一些 CFLAGS:

如果您的代码中没有使用dynamic_casttype_id,请尝试添加-fno-rtti。这将删除一些与类层次结构相关的代码,并且可能会删除代码中每个类的几十个字节。

如果您在函数规范中经常使用throw(),请尝试添加-fnothrow-opt。这将鼓励 GCC 将 throw() 视为更严格的 noexcept 规范(来自 C++11),这将大大减少许多领域的代码大小,因为它不必构造异常帧。

作为-fnothrow-opt 的伴侣,添加-Wnoexcept。这会在找到添加throw()noexcept 的位置时向您发出警告,并进一步减少必须构造异常帧的位置。

同时作为上述内容的补充,将您的代码库(如果有)中的所有new 实例替换为new (nothrow),并在必要时将-fcheck-new 添加到您的CFLAGS。

如果您不使用线程,您可能会从 -fno-threadsafe-statics 中发现一点好处,它删除了执行线程安全的静态初始化代码。

链接器

新的(好吧,最近几年)binutils 链接器有一个名为gold 的插件,它执行许多链接时优化,包括删除死代码。我不知道 ARM 是否支持 gold,但这将有助于大大减少您导入的标准库的占用空间。

【讨论】:

  • 谢谢,听起来不错!我已经设置了-fno-rtti 选项。将尽快尝试您的其他建议! (顺便说一句:我自己的代码中没有使用throw()specs,也没有使用new())。
  • 我已经尝试了 -fnothrow-opt 并没有看到代码大小有任何变化,但无论如何我们都没有在自己的代码中使用 throw() 规范。
  • 我一直在使用 GCC 配置选项启用 gold,它构建良好(请参阅我的更新)...
【解决方案3】:

为什么会出现这个问题,我怎样才能摆脱这些东西以减少目标的 .text 部分大小?

对于从libstdc++ 链接的内容可能存在(静态)引用,无论它们是否在实际执行的代码路径中被调用或引用,都将被实例化。
仔细搜索包含此类引用声明的#include 语句的用法(例如#include &lt;iostream&gt;)。

libstdc++ 在安装GCC 的过程中使用不同的C++ 编译标志构建最终预期目标时,可能会有不需要的东西链接或实例化(例如__gnu_cxx::recursive_init_error 异常类,尽管使用了@987654328 @ 旗帜)。在构建工具链时,使用--enable-cxx-flags 配置选项与预期的目标编译标志同步。

还要仔细查看代码库中某些 STL 类的不必要/意外使用(例如 std::string),尽管某些功能(例如 new())并不真正受支持,但它们可能会在没有错误或警告的情况下编译和链接在您的裸机平台上。

还有更多我可以覆盖的存根吗?

根据为工具链构建“newlib”的方式(请参阅--en/disable-newlib-supplied-syscalls 配置选项),可能需要覆盖那里提供的某些或所有存根。

【讨论】:

  • 感兴趣的读者可能还想看这里:Building GCC 4.7.1 ARM cross toolchain on Suse 12.2
  • 你没有在问题中提到你没有动态内存,你从 C++ 中得到什么而不是 C?类型安全?
  • @nhed 我尽量避免它是的,但这是一个小问题(如果我需要它,我会实现它)。类型安全当然是一个方面,但在这里使用 C++ 而不是 C 的主要原因是模板和模板元编程的使用。如果有些东西可以在编译时计算,我更喜欢这种方式,而不是在运行时计算或使用晦涩的宏。
【解决方案4】:

快速修复

首先,根据您的目标的限制 - 您可能需要考虑以上@Carl 的建议。我们决定不在我们的 cortex m3 平台中使用 C++。

第二,减少 C 或 C++ 大小的嵌入式开发技巧 - 取消浮点库,在许多应用程序中,您可以只使用定点数学,但有些出于可协商的原因(例如希望使用“% f" 在printf)。

您的同事给出的提示可能非常特定于您的平台,可能它有一个硬件看门狗?因此,对于不熟悉您的其他人来说,这将是困难的 提供此类快速修复建议的硬件、代码或应用程序。

深入挖掘

您是否真的将这些库的符号加起来,而这正是您消耗大部分 .txt 部分的地方?

问题可能不一定是您没有从该库中使用什么,而是您是什么。您“需要”的库很可能有自己的依赖项。

这是一个痛苦的过程(也许有更好的方法),但如果我是你,我会尝试删除你链接的库,看看你真正引入了什么(STL 标头可能需要其中几个符号)。如果那里没有意外,并且您的应用只提取预期的库,您需要深入挖掘:

方法一: - 分析 libstdc++ 代码(可能太长,无法立即跳转)

方法二: - 我从来没有用 C++ 做过这个,只是用 C 库,但理论应该成立 - 在删除 libstdc++ 的链接的同时 - 从 libstdc++ 一次添加一个对象,您可以从代码构建并显式使用对象文件,或者您可以尝试使用ar 等工具(ar -t 列出对象,ar -xv 提取)。请注意,我不建议您拼凑您的链接命令,只是如果您想分而治之,看看为什么您实际使用的模块需要这些模块 - 在一天结束时,您可能能够重建库带有一组“无”标志。阅读手册页或arnmobjdump 可能有助于自动化其中的一些工作,如果它花费的时间太长,我几年前就做过类似的事情,所以我手边没有备忘单.

【讨论】:

  • 请看看我的更新。目前你的回答并不能真正回答我的问题。
  • 这个答案并不真正符合我最初提出的问题,但它是对找到未使用/不需要的代码来自何处的过程的最接近的描述。
  • @g-makulik 我明白了,我根据原始问题回答,不确定节省几百字节的意义,但如果你需要看看是谁把它拉进来......至少在您的每个目标文件上使用nm -uC 并查看哪个需要它,然后您可以更深入地挖掘......
  • 我一直在使用nm + 我自己的工具nmalyzr 以各种方式累积符号的大小。
  • 很抱歉重画了接受,但我试图为这个问题给出一个更规范的答案。把赏金点留给你,如果这能让你舒服的话......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-28
  • 1970-01-01
  • 2011-08-30
  • 2011-05-09
  • 2017-04-12
  • 1970-01-01
相关资源
最近更新 更多