剥去膨胀可执行文件(GCC)的未使用的运行时函数

时间:2013-09-13 16:22:58

标签: c++ gcc embedded cross-compiling

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

虽然当我正在寻找目标ELF(例如使用nm)时,有很多符号(显然来自libstdc ++)在我不希望找到的地方(例如std::exception,{{ 1}}等。)。

为什么会出现这种情况,如何摆脱这些问题以减少目标的std::ios_base区域大小?

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

.text

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


更新
好吧,我发现了一个非常愚蠢的错误,删除了我想知道的大部分内容,修复它时: 在其中一个源文件中留下了namespace __gnu_cxx { void __verbose_terminate_handler() { for (;;) ; } } 语句(虽然没有从那里调用)。这当然会链接到静态#include <iostream>std::cinstd::cout实例以及随附的所有内容。
删除std::cerr语句会将#include <iostream>段减少为另一个&gt; 100KB部分。


尽管如此:
我还在想.textstd::exception这些东西:

std::basic_string

使用的代码大小并不多,只有大约100个字节,所以我可以忽视它,但如果我能摆脱它,我会很感激。

因为我明确地使用了没有异常,所以我想知道为什么这些在链接时仍然被实例化。是否在运行时确定是否使用异常?!?
我现在留下的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> >' 命名空间中唯一剩下的就是

__gnu_cxx

这是另一个异常类。


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

Type         Size Class 
T              58 '__gnu_cxx::recursive_init_error'

后面的标志用于编译libstdc ++,与用于构建目标代码的标记基本相同(无论如何这是一个合理的操作)。事后引用的异常引用(包括--enable-gold=yes --enable-lto --enable-cxx-flags='-fno-exceptions -ffunction-sections -fno-omit-frame-pointer' )。

最后一件事是,我在代码库中发现__gnu_cxx::recursive_init_error的意外使用。修好后,对std::string的引用也消失了。

所以我现在对结果很满意,libstdc ++没有更多不必要的,意外的开销,没有理由不优先使用C ++而不是C语言。

4 个答案:

答案 0 :(得分:2)

你永远不会知道使用一个库函数会引入什么。好吧,实际上你可以通过使用其中一个工具创建调用图。那你使用c ++ std库的哪一部分?

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

  1. 使用-ffunction-sections切换,然后使用-gc-sections链接。这会将每个函数放入它自己的部分,然后告诉链接器删除未使用的部分。
  2. 使用链接时间优化(-flto,有关详细信息,请参阅gcc手册)。这使编译器可以看到“整个”程序,就像它是单一来源一样,可能会让它删除未使用的函数。
  3. 你已经在使用-Os了,对吧?

答案 1 :(得分:2)

一些CFLAGS:

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

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

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

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

如果你没有使用线程,你可能会从-fno-threadsafe-statics中找到一个小的好处,它会删除执行静态的线程安全初始化的代码。

链接器

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

答案 2 :(得分:2)

  

为什么会出现这种情况,如何摆脱这些问题以减少目标的.text区域大小?

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

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

另外仔细查看代码库中某些STL类的不必要/意外使用情况(例如std::string),它们可能会编译并链接而不会出现错误或警告,尽管某些功能(例如new())不是你的裸机平台确实支持。

  

我可以覆盖更多此类存根吗?

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

答案 3 :(得分:1)

快速修复

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

第二,嵌入式开发提示减少C或C ++的大小 - 杀死浮点库,在许多应用程序中你可以只使用定点数学,但有些人因为可协商的原因(比如希望使用“% f“在printf)。

您的同事给出的提示可能非常特定于您的平台,可能它有硬件监视器?所以对于那些不熟悉你的人来说很难 用于提供此类快速修复建议的硬件,代码或应用程序。

深入挖掘

您是否实际上添加了这些库的符号,并且这是您消耗大部分.txt部分的地方?

问题可能不一定是你从那个图书馆没有使用的,但你是什么。您“需要”的库很可能有自己的依赖项。

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

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

方法2:   - 我从来没有使用C ++,只使用C libs,但理论应该保持 - 同时删除libstdc ++的链接 - 一次从libstdc ++添加一个对象,你可以从代码构建并明确使用目标文件或你可以尝试使用arar -t等工具列出对象和ar -xv来提取)。请注意,我不建议你搞砸你的链接命令,只是如果你想要划分和征服,看看你实际使用的模块需要这些模块的原因 - 在一天结束时,你可能能够重建库带有一组“无”标志。阅读手册页或arnmobjdump可能有助于自动化其中一些如果花费太长时间,我已经完成了多年前的事情,所以我不喜欢我的备忘单很方便。