我们目前正在使用他们的ESP-IDF框架基于流行的ESP32芯片开发IoT产品的固件,该框架使用GCC / G ++构建二进制文件 - 名为xtensa
的工具链(https://github.com/icamgo/xtensa-toolchain )。
最近,我注意到二进制大小相当大(只有1 MB)并且决定看一看并尝试减少它。 NDEBUG
已定义,-Os
已启用且输出为strip
ped。
基本上,工具链会生成.elf
文件,因此我查看了其内容:
nm -S -C --size-sort <my-app>.elf
六个最大的函数(大小为6 kB-12 kB)是:
4011b24c 0000187b T __ssvfscanf_r
400f9f38 00001ffa T _svfiprintf_r
400f2aa4 000020fe T _vfiprintf_r
4012005c 000030d2 T _svfwprintf_r
400ef4d8 000030de T _svfprintf_r
400f50dc 000031e6 T _vfprintf_r
因此,我的固件映像中最大的功能是vfprintf和朋友,单独添加大约60 kB的二进制大小。他们为什么这么大?如何减小它们的大小或摆脱它们(我根本不需要vfprintf,因为我在微控制器上没有文件系统)?
还有其他减少二进制大小的技术吗?我将如何处理我的任务?
编辑(澄清优化原因):
ESP32有不同版本,最高 16 MB的闪存。我们使用的那个有4 MB。其中1 MB用于存储固定服务器证书,可信URL配置选项。而且,由于我们需要OTA更新功能,因此我们需要保留与应用程序映像所使用的相同数量的闪存,以用于新版本。这为我们的应用程序映像留下了1.5 MB的闪存,这与我们当前的1 MB相差不远。因此,我认为在问题阻止我们引入新功能之前考虑缩小尺寸是合理的。
我确实意识到60 kB的不需要的vfprintf()函数只占1 MB的一小部分,但我们确实需要很多实际有用的库(用于加密的mbedtls,一个完整的IP堆栈,一个瘦Web服务器,。 ..)。我无法摆脱这些,所以我想通过删除我没有任何用处的函数来尽可能地减小的大小。
答案 0 :(得分:3)
考虑到各个功能的大小并不是一种合理的方法。单个“微小”函数可能在其调用图中具有数百个同样微小的依赖关系,这些依赖关系构成了一个巨大的块。例如,以下内容:
int main()
{
for(;;)
{
do_statemachine() ;
}
return 0 ;
}
main()
会很小,但最终导致所有应用程序的其余部分被链接,因为do_statemachine()
所做的任何事情都可以任何大小。您需要考虑函数的总大小和所有其依赖项。
同样,存储在ROM中的静态或常量数据初始化程序的总大小也需要考虑。
您应该使用链接器生成地图文件和调用图 - 这比在事件发生后使用nm更有用。
关于你问题中的特定符号,你必须问自己你在stdio中调用了什么?例如,printf
需要流访问(对于stdout
),格式说明符解析和可变参数遍历 - 即vfprintf
提供的 all 。如果不是这样,你就会有重复的代码,虽然你可能会链接更少的函数,但它们都非常大并且可能表现出不同的行为。您在链接中具有“文件”导向功能的事实不是特定问题; stdio在流而不是文件上运行 - “文件”是概念性的,而不是物理的。如果您还没有将库连接到文件系统(或者如果工具中没有提供),则不会包含文件系统代码。低级别流访问由低级I / O功能执行,这些功能可能支持也可能不支持文件访问。
另一种可能性是库缺乏粒度 - 如果所有这些函数都在同一个对象模块中定义,则链接器别无选择,只能将它们全部链接,即使它们未被引用。这可以解释为什么链接中有整数,浮点和宽字符版本。
答案 1 :(得分:2)
那些符号出现的地方有更多您不需要的 wchar 相关符号。 您可以通过使用
构建 ESP32 工具链来摆脱这些符号-fdata-sections -ffunction-sections
为 newlib 启用。还要在 libstdc++ 上设置标志 --disable-wchar_t
。然后使用 -Wl,--gc-sections
删除这些部分。
您也可以尝试使用 -flto
它应该为您做同样的事情 - 但我在使用 lto 构建 newlib 和 libstdc++ 时遇到了问题。似乎 libstdc++ 的构建工具在 newlib 档案中存在问题。
尽管如此,lto 是您在可能的情况下应该更喜欢的,因为它还可以很好地检测和 ODR 违规或其他可能隐藏的错误代码。