ARM 裸机二进制链接
ARM bare metal binary linking
我有一块 ROM 位于 0x80000000 和 RAM 位于 0x20000000 的 ARM 板。开发板在 0x80000000 处开始执行原始二进制代码。
我设法得到一个简单的 ARM 汇编程序 运行,但我必须使用 C 而不是 ASM。我知道我需要使用某种链接器脚本,然后手动将 .data 部分复制到 RAM 并清除 .bss、设置堆栈等,但我还没有找到可靠的解决方案,尤其是链接器脚本(我觉得很乱)
此外,我无法让链接器输出原始二进制文件而不是 ELF,但这没什么大不了的,因为我稍后可以使用 objcopy。
提前致谢。
MEMORY
{
bob : ORIGIN = 0x8000, LENGTH = 0x1000
ted : ORIGIN = 0xA000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
__data_rom_start__ = .;
.data : {
__data_start__ = .;
*(.data*)
} > ted AT > bob
__data_end__ = .;
__data_size__ = __data_end__ - __data_start__;
.bss : {
__bss_start__ = .;
*(.bss*)
} > ted
__bss_end__ = .;
__bss_size__ = __bss_end__ - __bss_start__;
}
当然可以根据需要更改地址。很明显各段的名称没有任何意义,如果对您有帮助,您可以尝试rom和ram。
如果你要这样做
MEMORY
{
rom : ORIGIN = 0x80000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.bss : { *(.bss*) } > ram
.rodata : { *(.rodata*) } > rom
.data : { *(.data*) } > ram
}
然后对其进行 objcopy 然后您将得到一个巨大的文件,即使您只有一个 .text 指令和一个字节的数据。因为二进制格式必须覆盖内存中的所有内容,所以它会生成一个 0x80000000+sizeof(text)-0x20000000 的文件。如果您在 ram 中只有一条指令和一个字节的数据,那么该文件将为 0x60000004 字节或 1.6 gig。将其保留为小精灵并使用 objdump -D 直到您完成链接描述文件,然后如果您确实需要一个 .bin 则创建一个 .bin。或制作一个 intel hex 或 s 记录,然后您可以再次检查它以查看它是否都在同一地址 space,然后再尝试二进制文件。
你的问题的第一个关键是 AT
MEMORY
{
bob : ORIGIN = 0x8000, LENGTH = 0x1000
ted : ORIGIN = 0xA000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.data : { *(.data*) } > ted AT > bob
.bss : { *(.bss*) } > bob
}
它说我想要地址 space ted 中的 .data,但将它放在 bob space 的二进制文件中。它针对 ted 地址 space 进行编译,但这些位是从 bob space 加载的。正是你想要的。除了您不知道要从 ROM 复制多少 .data 到 RAM。在将变量放在更复杂的变量中时,您必须格外小心,否则它将无法正常工作。
此示例适用于 STM32F051 MCU:
非常简单的应用程序 "blinky"(没有延迟):
// define used registers
#define RCC_AHB1 *(volatile unsigned int *)(0x40021014)
#define GPIOC_MODER *(volatile unsigned int *)(0x48000800)
#define GPIOC_BSRR *(volatile unsigned int *)(0x48000818)
// main program
void mainApp() {
RCC_AHB1 = 1 << 19; // enable clock for GPIOC
GPIOC_MODER = 1 << (9 * 2); // set output on GPIOC.P9
while (1) {
GPIOC_BSRR = 1 << 9; // set output on GPIOC.P9
GPIOC_BSRR = 1 << (9 + 16); // clear output on GPIOC.P9
}
}
// variables for testing memory initialisation
int x = 10;
int y = 0;
int z;
这也是用 C 编写的非常简单的启动文件(也可以在 C++ 中运行),它启动应用程序 mainApp
并初始化静态变量 .data
从 ROM 和 .bss
初始化设置为零,有初始化为零的变量和未初始化的变量。
extern void mainApp();
// external variables defined in linker script
// address in FLASH where are stored initial data for .data section
extern unsigned int _data_load;
// defines start and end of .data section in RAM
extern unsigned int _data_start;
extern unsigned int _data_end;
// defines start and end of .bss section in RAM
extern unsigned int _bss_start;
extern unsigned int _bss_end;
void resetHandler() {
unsigned int *src, *dst;
// copy .data area
src = &_data_load;
dst = &_data_start;
while (dst < &_data_end) {
*dst++ = *src++;
}
// clear .bss area
dst = &_bss_start;
while (dst < &_bss_end) {
*dst++ = 0;
}
mainApp();
while(1);
}
// _stacktop is defined in linker script
extern unsigned int _stacktop;
// vector table, will be placed on begin of FLASH memory, is defined in linker script
// only reset vector defined, need add other used vectors (especially NMI)
__attribute__((section(".vectors"), used)) void *isr_vectors[] = {
&_stacktop, // first vector is not vector but initial stack position
(void *)resetHandler, // vector which is called after MCU start
};
最后 linker 脚本定义了存储器的位置和大小、矢量部分、程序(文本)、数据(.data 和 .bss)和堆栈
MEMORY {
FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 64K
SRAM(rwx) : ORIGIN = 0x20000000, LENGTH = 8K
}
SECTIONS {
. = ORIGIN(FLASH);
.text : {
*(.vectors)
*(.text)
} >FLASH
. = ORIGIN(SRAM);
.data ALIGN(4) : {
_data_start = .;
*(.data)
. = ALIGN(4);
_data_end = .;
} >SRAM AT >FLASH
.bss ALIGN(4) (NOLOAD) : {
_bss_start = .;
*(.bss)
. = ALIGN(4);
_bss_end = .;
} >SRAM
_stacktop = ORIGIN(SRAM) + LENGTH(SRAM);
_data_load = LOADADDR(.data);
}
使用这些命令构建它(一次构建和 link):
$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -nostartfiles main.c startup.c -T stm32f051x8.ld -o main.elf
在符号 table 中可以看到存储数据的位置:
$ arm-none-eabi-nm -C -l -n -S main.elf
08000000 00000008 T isr_vectors
08000008 00000034 T mainApp
0800003c 0000005c T resetHandler
08000098 A _data_load
20000000 D _data_start
20000000 00000004 D x
20000004 D _data_end
20000004 B _bss_start
20000004 00000004 B y
20000008 B _bss_end
20000008 00000004 B z
20002000 A _stacktop
您也可以查看列表:
arm-none-eabi-objdump -S main.elf
原始二进制文件:
arm-none-eabi-objcopy -O binary main.elf main.bin
我有一块 ROM 位于 0x80000000 和 RAM 位于 0x20000000 的 ARM 板。开发板在 0x80000000 处开始执行原始二进制代码。
我设法得到一个简单的 ARM 汇编程序 运行,但我必须使用 C 而不是 ASM。我知道我需要使用某种链接器脚本,然后手动将 .data 部分复制到 RAM 并清除 .bss、设置堆栈等,但我还没有找到可靠的解决方案,尤其是链接器脚本(我觉得很乱)
此外,我无法让链接器输出原始二进制文件而不是 ELF,但这没什么大不了的,因为我稍后可以使用 objcopy。
提前致谢。
MEMORY
{
bob : ORIGIN = 0x8000, LENGTH = 0x1000
ted : ORIGIN = 0xA000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
__data_rom_start__ = .;
.data : {
__data_start__ = .;
*(.data*)
} > ted AT > bob
__data_end__ = .;
__data_size__ = __data_end__ - __data_start__;
.bss : {
__bss_start__ = .;
*(.bss*)
} > ted
__bss_end__ = .;
__bss_size__ = __bss_end__ - __bss_start__;
}
当然可以根据需要更改地址。很明显各段的名称没有任何意义,如果对您有帮助,您可以尝试rom和ram。
如果你要这样做
MEMORY
{
rom : ORIGIN = 0x80000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.bss : { *(.bss*) } > ram
.rodata : { *(.rodata*) } > rom
.data : { *(.data*) } > ram
}
然后对其进行 objcopy 然后您将得到一个巨大的文件,即使您只有一个 .text 指令和一个字节的数据。因为二进制格式必须覆盖内存中的所有内容,所以它会生成一个 0x80000000+sizeof(text)-0x20000000 的文件。如果您在 ram 中只有一条指令和一个字节的数据,那么该文件将为 0x60000004 字节或 1.6 gig。将其保留为小精灵并使用 objdump -D 直到您完成链接描述文件,然后如果您确实需要一个 .bin 则创建一个 .bin。或制作一个 intel hex 或 s 记录,然后您可以再次检查它以查看它是否都在同一地址 space,然后再尝试二进制文件。
你的问题的第一个关键是 AT
MEMORY
{
bob : ORIGIN = 0x8000, LENGTH = 0x1000
ted : ORIGIN = 0xA000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.data : { *(.data*) } > ted AT > bob
.bss : { *(.bss*) } > bob
}
它说我想要地址 space ted 中的 .data,但将它放在 bob space 的二进制文件中。它针对 ted 地址 space 进行编译,但这些位是从 bob space 加载的。正是你想要的。除了您不知道要从 ROM 复制多少 .data 到 RAM。在将变量放在更复杂的变量中时,您必须格外小心,否则它将无法正常工作。
此示例适用于 STM32F051 MCU:
非常简单的应用程序 "blinky"(没有延迟):
// define used registers
#define RCC_AHB1 *(volatile unsigned int *)(0x40021014)
#define GPIOC_MODER *(volatile unsigned int *)(0x48000800)
#define GPIOC_BSRR *(volatile unsigned int *)(0x48000818)
// main program
void mainApp() {
RCC_AHB1 = 1 << 19; // enable clock for GPIOC
GPIOC_MODER = 1 << (9 * 2); // set output on GPIOC.P9
while (1) {
GPIOC_BSRR = 1 << 9; // set output on GPIOC.P9
GPIOC_BSRR = 1 << (9 + 16); // clear output on GPIOC.P9
}
}
// variables for testing memory initialisation
int x = 10;
int y = 0;
int z;
这也是用 C 编写的非常简单的启动文件(也可以在 C++ 中运行),它启动应用程序 mainApp
并初始化静态变量 .data
从 ROM 和 .bss
初始化设置为零,有初始化为零的变量和未初始化的变量。
extern void mainApp();
// external variables defined in linker script
// address in FLASH where are stored initial data for .data section
extern unsigned int _data_load;
// defines start and end of .data section in RAM
extern unsigned int _data_start;
extern unsigned int _data_end;
// defines start and end of .bss section in RAM
extern unsigned int _bss_start;
extern unsigned int _bss_end;
void resetHandler() {
unsigned int *src, *dst;
// copy .data area
src = &_data_load;
dst = &_data_start;
while (dst < &_data_end) {
*dst++ = *src++;
}
// clear .bss area
dst = &_bss_start;
while (dst < &_bss_end) {
*dst++ = 0;
}
mainApp();
while(1);
}
// _stacktop is defined in linker script
extern unsigned int _stacktop;
// vector table, will be placed on begin of FLASH memory, is defined in linker script
// only reset vector defined, need add other used vectors (especially NMI)
__attribute__((section(".vectors"), used)) void *isr_vectors[] = {
&_stacktop, // first vector is not vector but initial stack position
(void *)resetHandler, // vector which is called after MCU start
};
最后 linker 脚本定义了存储器的位置和大小、矢量部分、程序(文本)、数据(.data 和 .bss)和堆栈
MEMORY {
FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 64K
SRAM(rwx) : ORIGIN = 0x20000000, LENGTH = 8K
}
SECTIONS {
. = ORIGIN(FLASH);
.text : {
*(.vectors)
*(.text)
} >FLASH
. = ORIGIN(SRAM);
.data ALIGN(4) : {
_data_start = .;
*(.data)
. = ALIGN(4);
_data_end = .;
} >SRAM AT >FLASH
.bss ALIGN(4) (NOLOAD) : {
_bss_start = .;
*(.bss)
. = ALIGN(4);
_bss_end = .;
} >SRAM
_stacktop = ORIGIN(SRAM) + LENGTH(SRAM);
_data_load = LOADADDR(.data);
}
使用这些命令构建它(一次构建和 link):
$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -nostartfiles main.c startup.c -T stm32f051x8.ld -o main.elf
在符号 table 中可以看到存储数据的位置:
$ arm-none-eabi-nm -C -l -n -S main.elf
08000000 00000008 T isr_vectors
08000008 00000034 T mainApp
0800003c 0000005c T resetHandler
08000098 A _data_load
20000000 D _data_start
20000000 00000004 D x
20000004 D _data_end
20000004 B _bss_start
20000004 00000004 B y
20000008 B _bss_end
20000008 00000004 B z
20002000 A _stacktop
您也可以查看列表:
arm-none-eabi-objdump -S main.elf
原始二进制文件:
arm-none-eabi-objcopy -O binary main.elf main.bin