我正在生成一个在 ARM 处理器上运行的十六进制文件,我希望将其保持在 32K 以下。 它目前比这个大得多,我想知道是否有人可以提供一些建议,以了解缩小它的最佳方法是什么?
这是我到目前为止所做的
- 所以我对其运行“size”以确定十六进制文件有多大。
- 然后再次“大小”以查看链接以创建十六进制文件的每个目标文件有多大。 看来大部分大小都来自外部库。
- 然后我使用“readelf”来查看哪些函数占用了最多的内存。
- 我搜索了代码,看看是否可以消除对这些函数的调用。
这就是我陷入困境的地方,有一些函数我不直接调用(例如 _vfprintf),并且我找不到调用它的函数,因此我可以删除调用(因为我认为我不需要它)。
那么接下来的步骤是什么?
对答案的回应:
- 正如我所看到的,正在调用的函数占用了大量内存。 然而我找不到它的名字。
- 我想省略这些函数(如果可能的话),但我找不到调用它们的函数! 我猜可以从任意数量的库函数中调用。
- 我认为链接器正在按预期工作,它只包含相关的库文件。 您如何知道是否仅包含相关功能? 你可以为此设置一个标志或其他东西吗?
- 我正在使用海湾合作委员会
I'm producing a hex file to run on an ARM processor which I want to keep below 32K. It's currently a lot larger than that and I wondered if someone might have some advice on what's the best approach to slim it down?
Here's what I've done so far
- So I've run 'size' on it to determine how big the hex file is.
- Then 'size' again to see how big each of the object files are that link to create the hex files. It seems the majority of the size comes from external libraries.
- Then I used 'readelf' to see which functions take up the most memory.
- I searched through the code to see if I could eliminate calls to those functions.
Here's where I get stuck, there's some functions which I don't call directly (e.g. _vfprintf) and I can't find what calls it so I can remove the call (as I think I don't need it).
So what are the next steps?
Response to answers:
- As I can see there are functions being called which take up a lot of memory. I cannot however find what is calling it.
- I want to omit those functions (if possible) but I can't find what's calling them! Could be called from any number of library functions I guess.
- The linker is working as desired, I think, it only includes the relevant library files. How do you know if only the relevant functions are being included? Can you set a flag or something for that?
- I'm using GCC
发布评论
评论(8)
您可以查看类似可执行压缩之类的内容。
You could look at something like executable compression.
常规列表:
strip
-M
),也可以在最终的可执行文件上使用 objdump(请注意,这仅适用于未剥离的可执行文件!)实际上并不能解决问题,但它会让您知道最严重的问题。最初的问题是一个关于仅包含相关功能的子问题。
gcc
将包含所使用的每个目标文件中的所有函数。 换句话说,如果您有一个包含 10 个函数的目标文件,则所有 10 个函数都将包含在您的可执行文件中,即使实际调用了其中一个 1。标准库(例如 libc)会将函数分割成许多单独的目标文件,然后将其存档。 然后将可执行文件链接到存档。
通过分成许多目标文件,链接器能够仅包含实际调用的函数。 (这假设您是静态链接)
您没有理由不能执行相同的操作。 当然,您可能会争辩说,如果不调用这些函数,您可能可以自己删除它们。
如果您静态链接其他库,您也可以在它们上运行上面列出的工具,以确保它们遵循类似的规则。
General list:
strip
on the executable-M
when using ld), or you can use objdump on the final executable (note that this will only work on an unstripped executable!) This won't actually fix the problem, but it will let you know of the worst offenders.nm
to investigate the symbols that are called from each of your object files. This should help in finding who's calling functions that you don't want called.In the original question was a sub-question about including only relevant functions.
gcc
will include all functions within every object file that is used. To put that another way, if you have an object file that contains 10 functions, all 10 functions are included in your executable even if one 1 is actually called.The standard libraries (eg. libc) will split functions into many separate object files, which are then archived. The executable is then linked against the archive.
By splitting into many object files the linker is able to include only the functions that are actually called. (this assumes that you're statically linking)
There is no reason why you can't do the same trick. Of course, you could argue that if the functions aren't called the you can probably remove them yourself.
If you're statically linking against other libraries you can run the tools listed above over them too to make sure that they're following similar rules.
另一个可能节省您工作量的优化是 -ffunction-sections、-Wl、--gc-sections(假设您使用的是 GCC)。 不过,一个好的工具链不需要被告知这一点。
说明:GNU ld 链接节,并且 GCC 为每个翻译单元发出一个节,除非您另有说明。 但在 C++ 中,依赖图中的节点是对象和函数。
Another optimization that might save you work is -ffunction-sections, -Wl,--gc-sections, assuming you're using GCC. A good toolchain will not need to be told that, though.
Explanation: GNU ld links sections, and GCC emits one section per translation unit unless you tell it otherwise. But in C++, the nodes in the dependecy graph are objects and functions.
在深度嵌入的项目中,我总是尽量避免使用任何标准库函数。 即使是像“strtol()”这样的简单函数也会放大二进制文件的大小。 如果可能的话,只需避免这些电话即可。
在大多数深度嵌入式项目中,您不需要通用的“printf()”或动态内存分配(许多控制器具有 32kb 或更少的 RAM)。
我不只是使用“printf()”,而是使用一个非常简单的自定义“printf()”,该函数只能以十六进制或十进制格式打印数字,而不能打印更多。 大多数数据结构是在编译时预先分配的。
On deeply embedded projects I always try to avoid using any standard library functions. Even simple functions like "strtol()" blow up the binary size. If possible just simply avoid those calls.
In most deeply embedded projects you don't need a versatile "printf()" or dynamic memory allocation (many controllers have 32kb or less RAM).
Instead of just using "printf()" I use a very simple custom "printf()", this function can only print numbers in hexadecimal or decimal format not more. Most data structures are preallocated at compile time.
Andrew EdgeCombe 有一个很棒的列表,但如果你真的想抓取最后一个字节,sstrip< /a> 是一个很好的工具,列表中缺少它,并且可以减少更多的 kB 大小。
例如,当在
strip
本身上运行时,它可以剃须约 2kB。来自旧的自述文件(请参阅此 间接源文件):
请注意,由于它删除了一些信息,sstrip 的可执行文件是 据传某些工具存在问题。 这在来源的评论中进行了更多讨论。
另外...对于如何制作尽可能最小的可执行文件的有趣/疯狂阅读,这篇文章值得一读。
Andrew EdgeCombe has a great list, but if you really want to scrape every last byte, sstrip is a good tool that is missing from the list and and can shave off a few more kB.
For example, when run on
strip
itself, it can shave off ~2kB.From an old README (see the comments at the top of this indirect source file):
Note that due to some of the information that it removes, a sstrip'd executable is rumoured to have issues with some tools. This is discussed more in the comments of the source.
Also... for an entertaining/crazy read on how to make the smallest possible executable, this article is worth a read.
只是为了仔细检查并记录以供将来参考,但是您使用 Thumb 指令吗? 它们是普通指令的 16 位版本。 有时您可能需要 2 条 16 位指令,因此它不会节省 50% 的代码空间。
一个好的链接器应该只具有所需的功能。 但是,您可能需要编译器和 linke 设置为单独链接的封装函数。
Just to double-check and document for future reference, but do you use Thumb instructions? They're 16 bit versions of the normal instructions. Sometimes you might need 2 16 bit instructions, so it won't save 50% in code space.
A decent linker should take just the functions needed. However, you might need compiler & linke settings to package functions for individual linking.
好吧,最后我只是将项目简化为最简单的形式,然后慢慢地逐个添加文件,直到我想要删除的函数出现在“readelf”文件中。 然后,当我获得该文件时,我注释掉了所有内容,然后慢慢将内容添加回来,直到该函数再次弹出。 所以最后我找到了它的名字并删除了所有这些调用......现在它按预期工作......甜蜜!
但一定是更好的方法。
Ok so in the end I just reduced the project to it's simplest form, then slowly added files one by one until the function that I wanted to remove appeared in the 'readelf' file. Then when I had the file I commented everything out and slowly add things back in until the function popped up again. So in the end I found out what called it and removed all those calls...Now it works as desired...sweet!
Must be a better way to do it though.
为了满足这一特定需求:
如果您想分析您的代码库以了解谁调用了什么、谁正在调用给定函数等等,可以使用 SciTools 提供的一个很棒的工具,名为“Understand C”。
https://scitools.com/
我过去经常使用它来执行静态代码分析。 它确实可以帮助确定库依赖关系树。 它允许轻松地上下浏览调用树等。
他们提供有限时间的评估,然后您必须购买许可证。
To answer this specific need:
If you want to analyze your code base to see who calls what, by whom a given function is being called and things like that, there is a great tool out there called "Understand C" provided by SciTools.
https://scitools.com/
I have used it very often in the past to perform static code analysis. It can really help to determine library dependency tree. It allows to easily browse up and down the calling tree among other things.
They provide a limited time evaluation, then you must purchase a license.