Rust嵌入式二进制文件大小

8

我是 Rust 的新手,在与编译器和借用检查器进行了多次战斗后,我终于快要完成我的第一个项目。但现在我遇到的问题是二进制文件太大了,无法适应微控制器的闪存。

我正在使用带有 64K 闪存的 BluePill 上的 STM32F103C8。起初,我的代码可以适应微控制器,渐渐地我不得不启用优化等功能。现在我使用以下方式进行编译:

[profile.dev]
codegen-units = 1
debug = 0
lto = true
opt-level = "z"

我能够适应二进制文件,但是opt-level = "s"会生成一个过大的二进制文件。我遇到的错误是:rust-lld: error: section '.rodata' will not fit in region 'FLASH': overflowed by 606 bytes

由于我的代码不到1000行,而且依赖关系也不算特别复杂,所以这看起来有些奇怪。

有一些网站类似于这样的,介绍了一些最小化二进制文件的方法。由于这些方法并不是为嵌入式设备设计的,因此大多数最小化方法已经被采用了。

我该如何最小化二进制文件大小,同时仍然能够进行调试?

我使用的依赖项是:

[dependencies]
cortex-m = "*"
panic-halt = "*"
embedded-hal = "*"

[dependencies.cortex-m-rtfm]
version = "0.4.3"
features = ["timer-queue"]

[dependencies.stm32f1]
version = "*"
features = ["stm32f103", "rt"]

[dependencies.stm32f1xx-hal]
version = "0.4.0"
features = ["stm32f103", "rt"]

我注意到一个问题,可能存在的问题是会以不同版本多次编译某些子依赖项。

memory.x文件内部:

MEMORY
{
  FLASH : ORIGIN = 0x08000000, LENGTH = 64K
  RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

Rustc版本rustc 1.37.0 (eae3437df 2019-08-13)

编辑

Rust的panic行为是abort。

该代码可在以下网址查看:https://github.com/DarkPhoeniz/rc-switcher-rust


.rodata 表示您有太多/太大的常量,不一定是代码本身太大。但似乎 .rodata.text 共享相同的段 FLASH,因此两者都可能是罪魁祸首。 - Lundin
如果1000行Rust代码会生成64kib的代码,那么显而易见的解决方案就是不使用Rust... - Lundin
我目前只创建了5个大小为27的u8数组。虽然我不知道依赖项中使用了哪些常量... arm-none-eabi-size 的结果是 text data bss dec hex 7424 332 112 7868 1ebc - l4z6_z
我对Rust的了解非常有限,但你应该尝试让链接器生成一个“映射”文件。它将包含所有代码和变量在内存中的位置列表。从那里,你也许能够找出是否有什么异常之处。也许你链接了大量的软件浮点库之类的东西。如果我没记错,STM32F103是Cortex M3,所以它没有FPU。 - Lundin
为什么要使用宏来生成访问3个不同串口的近乎相同的3个模块?这将会生成三倍的代码。 - harmic
因为我对Rust还很陌生,无法使用泛型来复制所需的包装器。此外,我在我的LOC信息中三次计算了宏。 - l4z6_z
2个回答

7

我遇到了类似的问题,可能能帮你解决输出二进制文件大小的问题。

你已经发现了其中之一:opt-level = "z"sz之间的区别在于代码内联约束-即编译器认为不值得进行内联的函数大小。 z将其设置为25,s为75。根据您正在构建的内容,这可能或可能不是二进制文件大小的显著减小(主要影响.rodata.text)。

另一个可以调整的内容是有关您的代码中panic的行为。如果我记得正确,stm32目标支持dev配置文件中启用unwindabort,如您所理解的那样,在堆栈展开时需要大量代码大小成本。因此,在您的cargo文件中设置panic="abort"可以进一步减少二进制文件大小。

除此之外,需要手动调整,并且像cargo-binutils这样的工具可能极为有用。 根据您的使用情况,可能会有仅偶尔需要的遗留Debug实现,这绝对是您可以处理的内容。


恐慌行为被设置为中止。 当我没有进行优化编译(并将FLASH设置得更大,以便链接器成功)时,“cargo size”会输出一个“0x13d2e”的“.text”大小。由于我只有“0x10000”,所以即使在任何“.debug*”部分开始之前,这已经太大了。但是,如果没有调试,则“.text”的大小仅为“0x10c3e”。 如果我分析链接器的nm和map文件的输出,我不会发现我不希望的函数调用,尽管使用的HAL存在一些大的调用。也许HAL的依赖关系比预期的要大得多... - l4z6_z
我有疑问。你能把整个项目放到某个地方让我好好看看吗?听起来你在做一些奇怪的事情,但仅凭你提供的信息我无法确定。 - Sébastien Renauld

6

以下是缩小二进制文件的几个通用技巧:

首先,使用cargo-bloat工具可以帮助你确定二进制文件中占用空间的部分,然后你可以根据结果做出决策来修改代码以缩小二进制文件大小。

其次,我曾经成功地通过配置编译器优化所有依赖项,但保留顶层包以便更容易进行调试。你可以在Cargo.toml中添加以下内容来实现:

# Optimize all dependencies
[profile.dev.package."*"]
opt-level = "z"

如果您想调试特定的依赖项(例如:cortex-m-rt),您可以按如下方式使其未优化:
# Don't optimize the `cortex-m-rt` crate
[profile.dev.package.cortex-m-rt]
opt-level = 0

# Optimize all the other dependencies
[profile.dev.package."*"]
opt-level = "z"

我克隆了你的代码库,通过在依赖项上使用opt-level="z",我成功地让二进制文件适应了空间。运行"cargo bloat"显示你有数百个大小为20-100字节的小方法,不确定是什么生成了这些方法,但这似乎是缩小二进制文件大小的关键所在。 - effect
谢谢你的回答!这是我第一个使用Rust的项目。在我无法解决这个问题后,我将其保留,并使用它进行了非硬件项目。所以现在有了更多的经验,我正在重新编写整个项目。我们将看到是否会再次遇到相同的问题。但无论如何,这些提示很不错! - l4z6_z

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接