使用 GCC 工具链构建由两部分组成的固件映像
Building a two-part firmware image using GCC toolchain
我有一些使用 GCC 构建的固件,它们在基于 ARM Cortex M0 的微控制器上运行。该构建当前生成单个二进制映像,可以将其写入微控制器的程序内存。
由于现场更新的原因,我需要将这张图片分成两部分,可以分别更新。我将它们称为 Core 和 App.
Core:包含中断向量table、main()
例程,以及各种驱动程序和库例程。它将位于程序存储器的前半部分。
App:包含特定于应用程序的代码。它将位于程序存储器的后半部分。它将有一个位于已知地址的单一入口点,内核会调用该入口点来启动应用程序。它将通过已知地址访问内核中的功能和数据。
这里有一些明显的限制,我很清楚:
构建应用程序时,需要知道核心中符号的地址。因此必须首先构建核心,并且在 link 运行应用程序时必须可用。
应用程序映像将仅与其构建所针对的特定核心映像兼容。
可以在不更新核心的情况下更新应用程序,但反之则不行。
一切正常。
我的问题很简单,如何使用 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".
设置构建
决定如何将闪存和 RAM 分成单独的区域用于内核和应用程序。定义每个区域的起始地址和大小。
为核心创建一个 linker 脚本。这将与平台的标准 linker 脚本相同,只是它必须只使用为核心保留的区域。这可以通过更改 linker 脚本的 MEMORY
部分中闪存和 RAM 条目的 ORIGIN
和 LENGTH
来完成。
创建一个 header 文件来声明应用程序的入口点。这只需要一个原型例如:
void app_init(void);
.
从核心 C 代码中包含此 header 并让核心调用 app_init()
启动应用程序。
创建一个符号文件,声明入口点的地址,这将是应用程序闪存区域的起始地址。我称之为 app.sym
。可以是一行,格式如下:
app_init = 0x00010000;
构建核心,使用核心linker脚本,在linker参数中加入--just-symbols=app.sym
,给出app_init
的地址.保留构建中的 ELF 文件,我称之为 core.elf
.
为应用程序创建一个 linker 脚本。这将再次基于平台的标准 linker 脚本,但闪存和 RAM 内存范围更改为为应用程序保留的范围。此外,它需要一个特殊的部分来确保 app_init
位于应用程序闪存区域的开头,在 .text
部分的其余代码之前:
SECTIONS
{
.text :
{
KEEP(*(.app_init))
*(.text*)
编写app_init
函数。这需要在汇编中,因为它必须在调用应用程序中的任何 C 代码之前执行一些低级工作。它需要用 .section .app_init
标记,以便 linker 将它放在应用程序闪存区域开头的正确位置。 app_init
函数需要:
- 使用来自 Flash 的初始值填充应用的
.data
部分中的变量。
- 将应用的
.bss
部分中的变量设置为零。
- 调用应用程序的 C 入口点,我称之为
app_start()
。
编写启动应用程序的app_start()
函数。
构建应用程序,使用应用程序 linker 脚本。此 link 步骤应传递给 object 文件,其中包含 app_init
、app_start
以及 app_start
调用的任何不在核心中的代码。 linker 参数 --just-symbols=core.elf
应该通过它们的地址传递给核心中的 link 函数。此外,应传递 -nostartfiles
以省略正常的 C 运行时启动代码。
弄清楚这一切花了一段时间,但现在运行良好。
我有一些使用 GCC 构建的固件,它们在基于 ARM Cortex M0 的微控制器上运行。该构建当前生成单个二进制映像,可以将其写入微控制器的程序内存。
由于现场更新的原因,我需要将这张图片分成两部分,可以分别更新。我将它们称为 Core 和 App.
Core:包含中断向量table、
main()
例程,以及各种驱动程序和库例程。它将位于程序存储器的前半部分。App:包含特定于应用程序的代码。它将位于程序存储器的后半部分。它将有一个位于已知地址的单一入口点,内核会调用该入口点来启动应用程序。它将通过已知地址访问内核中的功能和数据。
这里有一些明显的限制,我很清楚:
构建应用程序时,需要知道核心中符号的地址。因此必须首先构建核心,并且在 link 运行应用程序时必须可用。
应用程序映像将仅与其构建所针对的特定核心映像兼容。
可以在不更新核心的情况下更新应用程序,但反之则不行。
一切正常。
我的问题很简单,如何使用 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".
设置构建决定如何将闪存和 RAM 分成单独的区域用于内核和应用程序。定义每个区域的起始地址和大小。
为核心创建一个 linker 脚本。这将与平台的标准 linker 脚本相同,只是它必须只使用为核心保留的区域。这可以通过更改 linker 脚本的
MEMORY
部分中闪存和 RAM 条目的ORIGIN
和LENGTH
来完成。创建一个 header 文件来声明应用程序的入口点。这只需要一个原型例如:
void app_init(void);
.
从核心 C 代码中包含此 header 并让核心调用
app_init()
启动应用程序。创建一个符号文件,声明入口点的地址,这将是应用程序闪存区域的起始地址。我称之为
app.sym
。可以是一行,格式如下:
app_init = 0x00010000;
构建核心,使用核心linker脚本,在linker参数中加入
--just-symbols=app.sym
,给出app_init
的地址.保留构建中的 ELF 文件,我称之为core.elf
.为应用程序创建一个 linker 脚本。这将再次基于平台的标准 linker 脚本,但闪存和 RAM 内存范围更改为为应用程序保留的范围。此外,它需要一个特殊的部分来确保
app_init
位于应用程序闪存区域的开头,在.text
部分的其余代码之前:
SECTIONS { .text : { KEEP(*(.app_init)) *(.text*)
编写
app_init
函数。这需要在汇编中,因为它必须在调用应用程序中的任何 C 代码之前执行一些低级工作。它需要用.section .app_init
标记,以便 linker 将它放在应用程序闪存区域开头的正确位置。app_init
函数需要:- 使用来自 Flash 的初始值填充应用的
.data
部分中的变量。 - 将应用的
.bss
部分中的变量设置为零。 - 调用应用程序的 C 入口点,我称之为
app_start()
。
- 使用来自 Flash 的初始值填充应用的
编写启动应用程序的
app_start()
函数。构建应用程序,使用应用程序 linker 脚本。此 link 步骤应传递给 object 文件,其中包含
app_init
、app_start
以及app_start
调用的任何不在核心中的代码。 linker 参数--just-symbols=core.elf
应该通过它们的地址传递给核心中的 link 函数。此外,应传递-nostartfiles
以省略正常的 C 运行时启动代码。
弄清楚这一切花了一段时间,但现在运行良好。