Search code examples
clinkerarmembeddedbare-metal

ARM bare metal binary linking


I have an ARM board with ROM at 0x80000000 and RAM at 0x20000000. Board starts execution of raw binary code at 0x80000000.

I managed to get a simple ARM assembler program running on it, but I have to use C instead of ASM. I know I will need to use some kind of linker script, and then manually copy .data section to RAM and clear .bss, setup stack etc. but I haven't found a reliable solution how to do this yet, especially the linker script (it's very messy in my opinion).

Also, I can't get the linker to output a raw binary instead of ELF, but it's not a big deal as I can use objcopy later.

Thanks in advance.


Solution

  • This example is for STM32F051 MCU:

    Very simple application as "blinky" (without delays):

    // 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;
    

    Here is also very simple startup file written in C (also will work in C++), which start application mainApp and initialise static variables .data initialised from ROM and .bss only set to zero, there are variables initialised to zero and uninitialised variables.

    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
    };
    

    And finaly linker script which define location and sizes of memories, sections for vector, program (text), data (.data and .bss) a stacktop

    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);
    }
    

    Build this with these command (build and link at once):

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

    In symbol table is possible to see where are stored data:

    $ 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
    

    Also you can look into listing:

    arm-none-eabi-objdump -S main.elf
    

    Raw binary:

    arm-none-eabi-objcopy -O binary main.elf main.bin