固件文件的常见结构是什么?

What are common structures for firmware files?

我在嵌入式编程方面完全是 n00b。假设我正在使用编译器构建固件。此操作的结果是一个文件,该文件将被闪存到(我猜)MCU 的闪存中,例如 ARM 或 AVR。

我的问题是: 这种包含固件的生成文件使用了哪些通用结构(如果有) ?

我来自桌面开发,我知道例如对于 Windows,编译器很可能会生成 PE or PE+, while Unix-like systems I may get a ELF and COFF,但对嵌入式系统一无所知。

我也明白 这在很大程度上取决于许多因素(编译器、ISA、MCU 供应商、OS 等)所以我至少可以接受一个例子。


更新: 我将对所有提供已用结构示例的答案投赞成票,并将 select 我认为最好的调查最先进的答案。

最终,写入 ROM 的数据通常只是组成您的应用程序的代码和常量数据,因此除了可能被分割成代码和数据之外没有任何结构,如果您创建了自定义段,则可能是自定义段他们。这种意义上的结构由用于构建代码的链接描述文件或配置定义。包含此 code/data 的文件可能是原始二进制文件,或者是编码的二进制格式,例如 Intel Hex 或 Motorola S-Record。

通常,您的工具链也会生成一个目标代码文件,其中不仅包含 code/data,还包含供调试器使用的符号和调试信息。在这种情况下,当调试器运行时,它将代码加载到目标(如上面的二进制文件情况)并将 symbol/debug 信息加载到主机以允许源代码级调试。这些文件可能是特定于工具链的专有目标文件格式,但通常是标准 "open" 格式,例如 ELF。然而,严格来说,目标文件的元数据组件不属于 固件 的一部分,因为它们未加载到目标上。

固件文件是 Executable and Linkable File,通常处理为二进制 (.bin) 或文本表示的二进制 (.hex)。

这个二进制文件就是写入嵌入式闪存的确切内存。当您首次为电路板供电时,内部引导加载程序会将执行重定向到您的固件入口点,通常位于地址 0x0。

从那里开始,您的代码就是 运行,这就是为什么您有启动代码(通常是 startup.s 文件)来配置时钟、堆栈指针寄存器、向量 table,将数据部分加载到 RAM(你的初始化变量),清除零初始化部分,也许你会想将你的代码复制到 RAM 并跳转到副本以避免来自 FLASH 的 运行 代码(可以更快在某些平台上),等等。

当 运行 在操作系统上时,所有这些平台选择和资源都不受用户代码控制,您只能 link 到 OS 库并使用提供 API 来执行低级操作。在嵌入式中,它是 100% 的用户代码,您访问硬件并管理其资源。

毫不奇怪,操作系统的启动方式与固件类似,因为两者都与处理器、内存和 I/Os。

综上所述,就是说:固件的结构类似于任何已编译程序的结构。在操作系统加载期间,或者在嵌入式 运行 时,由程序本身组织在内存中的数据部分和代码部分。

一个主要区别是 firwmare 二进制文件中的内存寻址,通常地址是 physical RAM address,因为大多数微控制器上没有内存映射功能。这对用户是透明的,编译器会抽象它。

另一个显着区别是堆栈指针,在 OSs 上,用户代码不会自行为堆栈保留内存,它依赖于 OS。在固件上,出于与以前相同的原因,您必须在用户代码中执行此操作,没有中间人为您管理它。编译器的 linker 脚本将保留相应配置的堆栈和堆内存,并且在您的 .map 文件上会有一个 stack_pointer 符号,让您知道它指向哪里。您不会在 OSs 程序的地图文件中找到它。

大多数工具输出 ELF、COFF 或类似的东西,最终可以归结为 HEX/bin 文件。

但这不一定是您的目标想要看到的。每个供应商都有自己的 "firmware" 文件格式。有时它们是加密和签名的,有时是纯文本。有时有压缩,有时是原始的。它可能是一个简单的文件,或者不仅仅是您的程序的复杂文件。

进行嵌入式工作的一个组成部分是构建流程和系统 startup/booting 过程,以及将您的代码放到该部分上。不要低估努力。

我最近 运行 研究了此处未列出的另一种固件格式。它是一种二进制格式,可能称为“.EEP”,但也可能不是。我认为它被恩智浦使用。我已经看到它用于 ARM THUMB2 和可能是 DSP/BSP.

的神秘东西

以下均为 32 位值,全部以倒序存储 除了 用于 CAFEBABE(所以... BEBAFECA?):

CAFEBABE 
length_in_16_bit_words(yes, 16-bit...?!)
base_addr 
CRC32 
length*2 bytes of data
FFFF (optional filler if the length is an odd number)

如果有更多的数据块:

length 
base
checksum that is not a CRC but something bizarre
data
FFFF (optional filler if the length is an odd number)

...

当没有更多数据块剩余时:

length == 0
base == 0
checksum that is not a CRC but something bizarre

然后为另一个记忆重复所有这些bank/device。