【问题标题】:Process for reducing the size of an executable减小可执行文件大小的过程
【发布时间】:2010-09-17 01:11:37
【问题描述】:

我正在生成一个 hex 文件以在我希望保持在 32K 以下的 ARM 处理器上运行。它目前比这大得多,我想知道是否有人可以就什么是瘦身的最佳方法提供一些建议?

这是我到目前为止所做的

  1. 所以我在它上面运行了'size'来确定十六进制文件有多大。
  2. 然后再次“调整大小”以查看每个目标文件有多大,该链接用于创建十六进制文件。似乎大部分大小来自外部库。
  3. 然后我使用“readelf”查看哪些函数占用的内存最多。
  4. 我搜索了代码,看看是否可以消除对这些函数的调用。

这就是我卡住的地方,有些函数我不直接调用(例如_vfprintf),我找不到调用它的函数,所以我可以删除调用(因为我认为我不需要它)。

那么接下来的步骤是什么?

对答案的回应:

  • 正如我所见,调用的函数会占用大量内存。但是我找不到它的名字。
  • 我想省略这些函数(如果可能的话),但我找不到调用它们的原因!我猜可以从任意数量的库函数中调用。
  • 链接器工作正常,我想,它只包括相关的库文件。你怎么知道是否只包含相关功能?你可以为此设置一个标志或其他东西吗?
  • 我正在使用 GCC

【问题讨论】:

标签: embedded arm


【解决方案1】:

一般列表:

  • 确保禁用编译器和链接器调试选项
  • 在所有大小选项打开的情况下编译和链接(gcc 中的-Os)
  • 在可执行文件上运行strip
  • 生成地图文件并检查您的函数大小。您可以让链接器生成映射文件(使用 ld 时为-M),或者您可以在最终的可执行文件上使用 objdump(请注意,这仅适用于未剥离的可执行文件!)这实际上并不能解决问题,但它会让您知道最严重的违规者。
  • 使用nm 调查从每个目标文件中调用的符号。这应该有助于找出谁在调用您不想调用的函数。

最初的问题是关于仅包含相关功能的子问题。 gcc 将包含所使用的每个目标文件中的所有函数。换句话说,如果您有一个包含 10 个函数的目标文件,那么即使实际调用了一个 1,所有 10 个函数都包含在您的可执行文件中。

标准库(例如 libc)会将函数拆分为许多单独的目标文件,然后将其归档。然后可执行文件与存档链接。 通过拆分成许多目标文件,链接器能够仅包含实际调用的函数。 (这假设您正在静态链接)

你没有理由不能做同样的把戏。当然,您可以争辩说,如果不调用函数,您可能可以自己删除它们。

如果您要静态链接到其他库,您也可以在它们上运行上面列出的工具,以确保它们遵循类似的规则。

【讨论】:

  • 如果您要研究符号,那么您确实希望在调试选项打开的情况下进行编译,而不是剥离 exe。
  • 我还建议使用 -mthumb,因为 thumb 可执行文件通常比 arm 可执行文件小。
【解决方案2】:

假设您使用的是 GCC,另一个可以节省您工作量的优化是 -ffunction-sections、-Wl、--gc-sections。不过,一个好的工具链不需要被告知。

解释:GNU ld 链接部​​分,除非你另有说明,否则 GCC 会为每个翻译单元发出一个部分。但是在 C++ 中,依赖图中的节点是对象和函数。

【讨论】:

    【解决方案3】:

    在深度嵌入的项目中,我总是尽量避免使用任何标准库函数。即使是像“strtol()”这样的简单函数也会放大二进制文件的大小。如果可能的话,只是简单地避免这些调用。

    在大多数深度嵌入式项目中,您不需要通用的“printf()”或动态内存分配(许多控制器的 RAM 为 32kb 或更少)。

    我使用了一个非常简单的自定义“printf()”,而不是仅仅使用“printf()”,这个函数只能打印十六进制或十进制格式的数字。大多数数据结构都是在编译时预分配的。

    【讨论】:

      【解决方案4】:

      只是为了仔细检查和记录以供将来参考,但是您是否使用 Thumb 指令?它们是普通指令的 16 位版本。有时您可能需要 2 条 16 位指令,因此不会节省 50% 的代码空间。

      一个体面的链接器应该只使用所需的功能。但是,您可能需要编译器和链接设置来为单个链接打包函数。

      【讨论】:

      • 谢谢,没想到拇指的东西。老实说,我不知道它是否被使用,我认为是这样,但会进一步调查。是的,链接器只添加了使用的功能,我很确定。但我就是不知道是什么调用了这个函数?我需要一张谁叫什么的地图?
      【解决方案5】:

      好的,最后我只是将项目简化为最简单的形式,然后慢慢地逐个添加文件,直到我要删除的功能出现在“readelf”文件中。然后,当我得到文件时,我将所有内容都注释掉并慢慢添加内容,直到该功能再次弹出。所以最后我找到了它的名字并删除了所有这些电话......现在它可以按预期工作......甜美!

      但必须是更好的方法。

      【讨论】:

        【解决方案6】:

        Andrew EdgeCombe 有一个很棒的列表,但如果你真的想抓取最后一个字节,sstrip 是一个很好的工具,但列表中缺少它,并且可以减少更多 kB。

        例如,在strip 本身上运行时,it can shave off ~2kB

        来自旧的 README(请参阅 this 间接源文件顶部的 cmets):

        sstrip 是一个小型实用程序,可以删除末尾的内容 不属于程序内存映像的 ELF 文件。

        大多数 ELF 可执行文件都使用程序头表和 节标题表。但是,只有前者是必需的 供操作系统加载、链接和执行程序。脱衣舞尝试 提取 ELF 头、程序头表及其内容, 将其他所有内容留在位桶中。它只能删除部分 最后出现的文件,在要保存的部分之后。然而, 这几乎总是包括节标题表,偶尔也包括 运行程序时不使用的一些随机部分。

        请注意,由于它删除了一些信息,带有一些工具的 sstrip'd 可执行文件是 rumoured to have issues。这在源代码的 cmets 中有更多讨论。

        另外...对于如何制作尽可能小的可执行文件的有趣/疯狂的阅读,this article 值得一读。

        【讨论】:

          【解决方案7】:

          要满足这一特定需求:

          •我想省略那些函数(如果可能的话)但我找不到是什么 打电话给他们!!可以从任意数量的库函数中调用 猜测。

          如果您想分析您的代码库以查看谁调用了什么、由谁调用给定函数等等,SciTools 提供了一个名为“Understand C”的出色工具。

          https://scitools.com/

          过去我经常使用它来执行静态代码分析。它确实可以帮助确定库依赖树。它允许轻松地上下浏览调用树等。

          他们提供限时评估,然后您必须购买许可证。

          【讨论】:

            【解决方案8】:

            您可以查看 executable compression 之类的内容。

            【讨论】:

            • 如果 running 程序的大小不应该超过 32K,那也无济于事。
            • 如果一次只解压 32k 就可以了。
            猜你喜欢
            • 2010-10-01
            • 2022-01-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多