8

我已经为 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>语句(尽管没有从那里调用)。这当然会链接到 staticstd::cin和实例std::cout以及std::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<char, std::char_traits<char>, std::allocator<char> >也消失了。

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

4

4 回答 4

2

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

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

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

您已经在使用 -Os 了,对吧?

于 2013-09-16T07:39:06.630 回答
2

一些CFLAGS:

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

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

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

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

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

链接器

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

于 2013-09-16T20:00:55.990 回答
2

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

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

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

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

我可以覆盖更多这样的存根吗?

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

于 2013-09-17T20:41:00.203 回答
1

快速修复

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

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

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

深层发掘

你真的把这些库的符号加起来了吗?那是你消耗大部分 .txt 部分的地方?

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

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

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

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

于 2013-09-16T00:45:15.513 回答