ARM裸机二进制链接

时间:2015-09-06 09:37:54

标签: c linker arm embedded bare-metal

我有一块ARM板,ROM为0x80000000,RAM为0x20000000。 Board开始执行原始二进制代码0x80000000。

我设法在其上运行一个简单的ARM汇编程序,但我必须使用C而不是ASM。我知道我需要使用某种链接器脚本,然后手动将.data部分复制到RAM并清除.bss,设置堆栈等但我还没有找到一个可靠的解决方案如何做到这一点,尤其是链接器脚本(在我看来它非常混乱)。

另外,我无法让链接器输出原始二进制文件而不是ELF,但这不是什么大不了的事,因为我以后可以使用objcopy。

提前致谢。

2 个答案:

答案 0 :(得分:4)

此示例适用于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并初始化从ROM初始化的静态变量.data.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
};

最终链接器脚本,用于定义内存的位置和大小,向量,程序(文本),数据(.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);
}

使用这些命令(一次构建和链接)构建它:

$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -nostartfiles main.c startup.c -T stm32f051x8.ld -o main.elf

在符号表中可以查看存储数据的位置:

$ 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

答案 1 :(得分:2)

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十六进制或s记录,然后再次检查它以查看它是否都在同一地址空间中,然后再尝试二进制文件。

问题的第一个关键是AT

MEMORY
{
    bob : ORIGIN = 0x8000, LENGTH = 0x1000
    ted : ORIGIN = 0xA000, LENGTH = 0x1000
}

SECTIONS
{
   .text : { *(.text*) } > bob
   .data : { *(.data*) } > ted AT > bob
   .bss  : { *(.bss*) } > bob
}

它说我想在地址空间中使用.data,但是将它放在bob空间中的二进制文件中。它为ted地址空间编译,但是这些位是从bob空间加载的。正是你想要的。除了你不知道从rom复制到ram有多少.data。你必须非常小心将变量放在更复杂的变量中,否则就无法正常工作。

相关问题