【问题标题】:ARM bare metal binary linkingARM 裸机二进制链接
【发布时间】:2015-09-06 09:37:53
【问题描述】:

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

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

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

提前致谢。

【问题讨论】:

  • 您使用的是什么工具链(我猜是 GNU?)。您使用的是什么 ARM 部件(或者可能指定板)? RAM 是片上的还是外部的?代码会从 ROM 还是 RAM 执行?这些问题的答案会给你一个更好的答案。但至少我现在可以告诉你,objcopy 正是你生成原始二进制文件(或 hex 文件)的方式;通常作为 makefile 中的规则,或 IDE 中的构建后步骤。
  • GNU 工具链,片上 RAM,代码将从 ROM 执行。
  • 对于blinky,如果你不使用静态变量(只有堆栈),那么你不需要从FLASH复制数据到SRAM。
  • @m_chojnacki :您应该通过编辑问题而不是添加评论来添加这些详细信息。但你仍然没有回答所有问题。

标签: c linker arm embedded bare-metal


【解决方案1】:

此示例适用于 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
};

最后是链接器脚本,它定义了内存的位置和大小、向量部分、程序(文本)、数据(.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

【讨论】:

    【解决方案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 如果您真的需要一个。或者制作一个 intel hex 或 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
    }
    

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

    【讨论】:

    • 是的,链接描述文件很乱,尤其是 gnu ld。如果您只是看的话,那里有大量示例,包括将 bss 归零和复制 .data 的引导代码(基本上所有这些示例)。链接器脚本也非常特定于工具链,因此如果您从 gnu 转移到其他东西,那么您将重新开始。或者你可以编写你的代码,使其没有任何 .data 并且不依赖 .bss 被归零,然后你的链接器脚本(如果有)(有时可以只使用命令行,尽管在 gnu 的情况下它会产生代价高昂的差距)。
    • objdump 是您的朋友,如果不是经常使用,请经常使用它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-31
    • 1970-01-01
    • 2013-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多