Linker

After reset, the micro-controller looks at the vector table, reads the address of the reset handler in the second entry ands calls this handler.

The handler will accomplish the follwing tasks:

  • define the interrupt vector table

  • perform memory initialization for initialized data () and uninitialized data (bss)

Linker Script

Instead of using -Ttext 0x8000000 and -Tdata=0x20000000 with arm-none-eabi-ld, we can use a linker script. We replace the two switches -Ttext 0x8000000 and -Tdata=0x20000000 by the single switch T stm32l476x.ld.

The file named “stm32l476x.ld” is the linker script:

ENTRY(main)

MEMORY
{
 FLASH (rx)   : ORIGIN = 0x08001000, LENGTH = 1024K
 SRAM  (rwx)  : ORIGIN = 0x20000440, LENGTH = 96K
 SRAM2 (rwx)  : ORIGIN = 0x15000000, LENGTH = 32K
}

SECTIONS
{
}

With $ arm-none-eabi-objdump -h gpio.elf, we get:

LOAD directive

The LOAD directive in a linker script is used to specify the memory region where a section should be loaded. This directive is particularly important in embedded systems, where the placement of code and data in different memory regions (such as flash and RAM) must be managed carefully.

The LOAD directive helps to distinguish between where a section is stored (loaded) and where it is executed. This is crucial for handling scenarios where code or data is stored in non-volatile memory (like flash) but needs to be copied to volatile memory (like RAM) for execution or use.

SECTIONS
{
    .section_name LOADADDR(memory_region) :
    {
        /* Section contents */
    } > memory_region
}

An embedded system uses flash memory for storing the program and RAM for executing certain parts of the program.

MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS
{
    .text : {
        KEEP(*(.isr_vector))    /* Keep the interrupt vector table */
        *(.text*)               /* All .text sections */
        *(.rodata*)             /* All read-only data sections */
    } > FLASH

    .data : AT(__data_load_address) {
        *(.data)
        *(.data*)
    } > RAM

    .bss : {
        *(.bss)
        *(.bss*)
    } > RAM

    /* Symbol definitions for data load address */
    __data_load_address = LOADADDR(.data);
}

You can use AT(LOADADDR(.data)) in a linker script to specify that the .data section should be loaded at its load address, which typically corresponds to its location in flash memory, and then executed from RAM.

This approach ensures that the initialized data defined in the .data section is stored in non-volatile memory (flash) and then copied to RAM during the initialization phase of your program.

LOADADDR(section_name)

LOADADDR(section_name) returns the load address of the specified section.

For .data, LOADADDR(.data) would return the address in flash where the .data section is loaded.

Here’s how you can use AT(LOADADDR(.data)) in a linker script:

MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS
{
    .text : {
        *(.text*)
} > FLASH

    .data : AT(LOADADDR(.data)) {
        *(.data)
} > RAM

.bss : {
    *(.bss)
} > RAM

/* Define symbols for use in initialization code */
__data_load_start = LOADADDR(.data);
__data_start = ADDR(.data);
__data_end = ADDR(.data) + SIZEOF(.data);
__bss_start = ADDR(.bss);
__bss_end = ADDR(.bss) + SIZEOF(.bss);

}