使用 GCC 工具链构建由两部分组成的固件映像

Building a two-part firmware image using GCC toolchain

我有一些使用 GCC 构建的固件,它们在基于 ARM Cortex M0 的微控制器上运行。该构建当前生成单个二进制映像,可以将其写入微控制器的程序内存。

由于现场更新的原因,我需要将这张图片分成两部分,可以分别更新。我将它们称为 CoreApp.

这里有一些明显的限制,我很清楚:

一切正常。

我的问题很简单,如何使用 GCC 和 GNU binutils 构建这些映像?

基本上我想像普通固件映像一样构建核心,然后构建应用程序映像,应用程序将核心视为库。但是共享 linking(需要动态 linking 机制)或静态 linking(将使用的核心功能复制到应用程序二进制文件中)均不适用于此处。我想做的实际上要简单得多:link 使用已知的固定地址对现有二进制文件进行攻击。我只是不清楚如何使用这些工具来做到这一点。

首先...如果这只是为了字段更新,你不需要依赖核心中的中断向量table space 用于 应用程序 。我认为 ARM M0 部件总是能够移动它。我知道它可以在某些(全部?)STM32Fx 的东西上完成,但我相信这是 ARM M-x 的东西,而不是 ST 的东西。在做出让您的应用程序 ISR 全部成为从核心调用的挂钩的决定之前,请先研究一下。

如果您计划与核心进行大量交互(顺便说一句,我总是将 self-updating 在 MCU 上做 "bootloader" 的部分称为 "bootloader"),这里有一个替代建议:

Core 将指针传递给描述其功能的结构/table 函数到 App 条目中点?

除了共享 header(假设您的 ABI 不变)之外,这将允许应用代码与核心代码完全分离,并防止名称冲突。

它还提供了一种合理的方法来防止 GCC 优化您可能仅从 App 调用的任何函数,而不会弄乱您的优化设置或乱用编译指示。

core.h:

struct core_functions
{
    int (*pcore_func1)(int a, int b);
    void (*pcore_func2)(void);
};

core.c:

int core_func1(int a, int b){ return a + b; }
void core_func2(void){ // do something here }

static const struct core_functions cfuncs= 
{
    core_func1,
    core_func2
};

void core_main()
{
   // do setup here
   void (app_entry*)(const struct core_functions *) = ENTRY_POINT;
   app_entry( &cfuncs );
}

app.c

void app_main(const struct core_functions * core)
{
   int res;
   res = core->pcore_func1(20, 30);
}

缺点/成本是轻微的运行时和内存开销以及更多代码。

我们现在有这个工作,所以我要回答我自己的问题。这是执行此操作所必需的,从正常的单个图像构建开始,将其转换为 "core",然后为 "app".

设置构建
  1. 决定如何将闪存和 RAM 分成单独的区域用于内核和应用程序。定义每个区域的起始地址和大小。

  2. 为核心创建一个 linker 脚本。这将与平台的标准 linker 脚本相同,只是它必须只使用为核心保留的区域。这可以通过更改 linker 脚本的 MEMORY 部分中闪存和 RAM 条目的 ORIGINLENGTH 来完成。

  3. 创建一个 header 文件来声明应用程序的入口点。这只需要一个原型例如:

void app_init(void);.

  1. 从核心 C 代码中包含此 header 并让核心调用 app_init() 启动应用程序。

  2. 创建一个符号文件,声明入口点的地址,这将是应用程序闪存区域的起始地址。我称之为 app.sym。可以是一行,格式如下:

app_init = 0x00010000;

  1. 构建核心,使用核心linker脚本,在linker参数中加入--just-symbols=app.sym,给出app_init的地址.保留构建中的 ELF 文件,我称之为 core.elf.

  2. 为应用程序创建一个 linker 脚本。这将再次基于平台的标准 linker 脚本,但闪存和 RAM 内存范围更改为为应用程序保留的范围。此外,它需要一个特殊的部分来确保 app_init 位于应用程序闪存区域的开头,在 .text 部分的其余代码之前:

SECTIONS
{
    .text :
    {
        KEEP(*(.app_init))
        *(.text*)
  1. 编写app_init函数。这需要在汇编中,因为它必须在调用应用程序中的任何 C 代码之前执行一些低级工作。它需要用 .section .app_init 标记,以便 linker 将它放在应用程序闪存区域开头的正确位置。 app_init 函数需要:

    1. 使用来自 Flash 的初始值填充应用的 .data 部分中的变量。
    2. 将应用的 .bss 部分中的变量设置为零。
    3. 调用应用程序的 C 入口点,我称之为 app_start()
  2. 编写启动应用程序的app_start()函数。

  3. 构建应用程序,使用应用程序 linker 脚本。此 link 步骤应传递给 object 文件,其中包含 app_initapp_start 以及 app_start 调用的任何不在核心中的代码。 linker 参数 --just-symbols=core.elf 应该通过它们的地址传递给核心中的 link 函数。此外,应传递 -nostartfiles 以省略正常的 C 运行时启动代码。

弄清楚这一切花了一段时间,但现在运行良好。