Embedded Development Skills: Further Familiarization with MCU Startup Process from STM32’s Startup and Linker Files

Line-by-Line Analysis of the Linker File

In the previous article, we analyzed the startup file, and here we will supplement an important C function within the startup file.

/**
  * @brief  Setup the microcontroller system
  *         Initialize the FPU setting, vector table location and External memory 
  *         configuration.
  * @param  None
  * @retval None
  */
void SystemInit(void)
{
    /* FPU settings ------------------------------------------------------------*/
    #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
        SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */
    #endif

    #if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
        SystemInit_ExtMemCtl(); 
    #endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */

    /* Configure the Vector Table location -------------------------------------*/
    #if defined(USER_VECT_TAB_ADDRESS)
        SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
    #endif /* USER_VECT_TAB_ADDRESS */
}

<span>SystemInit()</span> is the “official” hardware-level initialization entry after powering on the STM32 chip, executed by the startup file before calling <span>main()</span>. The main functions of this function are as follows:

  • • Set FPU, relocate the interrupt vector table, and initialize external SRAM/SDRAM (if enabled).
  • <span>#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)</span> If the compilation options confirm the presence of an FPU and it is to be used, enable it.
  • <span>SystemInit_ExtMemCtl();</span> Call the external memory controller initialization function to configure FSMC/FMC timing, pin multiplexing, and map external SRAM/SDRAM to addresses such as 0x6000 0000/0xC000 0000.
  • <span>#if defined(USER_VECT_TAB_ADDRESS)</span> If the user wants to move the interrupt vector table to internal SRAM, reset the vector table offset register VTOR = new base address + offset, so that the CPU exception/interrupt entry points to the new vector table.

<span>SystemInit()</span> completes three tasks before entering <span>main()</span>:

  1. 1. Enable FPU (if enabled);
  2. 2. Initialize external SRAM/SDRAM (if enabled);
  3. 3. Move the interrupt vector table to the specified location (if enabled).

STM32Fxx_FLASH.ld

Top Comments

**  Abstract    : Linker script for STM32F407VETx series
**                512Kbytes FLASH and 192Kbytes RAM
**
**                Set heap size, stack size and stack location according
**                to application requirements.
**
**                Set memory bank area and size if external memory is used.

This linker script is suitable for the STM32F407VETx series: 512 KB FLASH, 192 KB RAM and can be configured according to application requirements:

  • • Heap size
  • • Stack size and stack location If external memory is used, please set the corresponding memory area and its size.

Program Entry Point

/* Program entry point: The executable file generated by the linker uses Reset_Handler as the first instruction address */
ENTRY(Reset_Handler)

/* User mode main stack (MSP) top address = highest address of RAM area */
_estack = ORIGIN(RAM) + LENGTH(RAM);    /* end of RAM */
/* If the combined heap and stack exceed the actual RAM capacity, the linker will report an error */
_Min_Heap_Size = 0x200;      /* Minimum required heap space of 0x200 bytes */
_Min_Stack_Size = 0x400;     /* Minimum required stack space of 0x400 bytes */

/* Define the available memory areas and their attributes on the chip */
MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 128K
CCMRAM (xrw)      : ORIGIN = 0x10000000, LENGTH = 64K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 512K
}

RAM Area is Divided into Two Parts:

  • • RAM: Starts at 0x2000 0000, total 128 KB, executable, readable, and writable.
  • • CCMRAM: Starts at 0x1000 0000, total 64 KB (CPU zero-wait SRAM, not accessible by DMA), executable, readable, and writable.

Flash Area

  • • FLASH: 0x800 0000, total 512K, readable, executable.

Sections

/* Define output sections */
SECTIONS
{
...
}

Specific Sections

1. isr_vector

.isr_vector :           /* Section name */
{                       /* Content description */
    . = ALIGN(4);       /* 4-byte alignment */
    KEEP(*(.isr_vector))/* Merge all .isr_vector sections from .o files */
    . = ALIGN(4);       /* Align again */
} &gt;FLASH                /* Finally placed in FLASH area, address calculated by the linker incrementally */
  • • Physical location: Starting from FLASH (0x0800 0000).
  • • Function: Holds the interrupt vector table; the CPU reads the stack top from 0x0800 0000 and Reset_Handler from 0x0800 0004 upon power-up.
  • • Key symbol: None (the vector table itself is defined by g_pfnVectors in the C startup file).

2. text

.text : {               /* Actual code section */
    . = ALIGN(4);
    *(.text)            /* All .text input sections */
    *(.text*)           /* Wildcard sections, e.g., .text.myfunc */
    *(.glue_7)          /* ARM↔Thumb glue code */
    *(.glue_7t)
    *(.eh_frame)        /* C++ exception unwind table (GCC) */

    KEEP(*(.init))      /* .init code for C runtime startup */
    KEEP(*(.fini))      /* .fini code for program exit */

    . = ALIGN(4);
    _etext = .;         /* Generate global symbol: end address of code section */
} &gt;FLASH
  • • Physical location: FLASH, immediately following the vector table.
  • • Key symbol: _etext → used by startup code to determine the end position of data in FLASH when moving .data.

3. rodata

.rodata : {
    . = ALIGN(4);
    *(.rodata)          /* const strings, global const arrays, etc. */
    *(.rodata*)
    . = ALIGN(4);
} &gt;FLASH
  • • Physical location: FLASH, placed after .text.
  • • Function: Read-only constants; do not need to be moved at runtime, can be addressed directly.

4. ARM.extab

.ARM.extab () : {
    . = ALIGN(4);
    *(.ARM.extab* .gnu.linkonce.armextab.*)
    . = ALIGN(4);
} &gt;FLASH
  • • Physical location: FLASH, read-only.
  • • Function: ARM exception unwind auxiliary table (for exceptions/stack backtrace).

5. ARM

.ARM : {
    . = ALIGN(4);
    __exidx_start = .;
    *(.ARM.exidx*)      /* C++ exception index table */
    __exidx_end   = .;
    . = ALIGN(4);
} &gt;FLASH
  • • Physical location: FLASH
  • • Key symbols: __exidx_start / __exidx_end → used by C++/exception libraries to locate the table at runtime.

6. preinit_array

.preinit_array () : {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP(*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
    . = ALIGN(4);
} &gt;FLASH
  • • Physical location: FLASH
  • • Function: Early C runtime hook array (rarely used).
  • • Key symbols: __preinit_array_start / end → called by __libc_init_array.

7. init_array

.init_array () : {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP(*(SORT(.init_array.*)))   /* C++ constructor array with priority */
    KEEP(*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
    . = ALIGN(4);
} &gt;FLASH
  • • Physical location: FLASH
  • • Key symbols: __init_array_start / end → __libc_init_array executes global C++ constructors in order.

8. fini_array

.fini_array () : {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP(*(SORT(.fini_array.*)))
    KEEP(*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
    . = ALIGN(4);
} &gt;FLASH
  • • Physical location: FLASH
  • • Function: Destructor table called upon program exit (bare metal rarely returns to main, generally not used).

9. _sidata

_sidata = LOADADDR(.data);
  • • Not a section, but a linker variable: Records the “LMA (Load Memory Address) of .data in FLASH” → used as the source address when the startup code moves .data.

10. data

.data : {
    . = ALIGN(4);
    _sdata = .;          /* Starting address of .data in RAM (VMA) */
    *(.data) *(.data*)
    *(.RamFunc) *(.RamFunc*)  /* Optional: place functions here when moved to RAM */
    . = ALIGN(4);
    _edata = .;          /* Ending address of .data in RAM */
} &gt;RAM AT&gt; FLASH
  • • VMA is in RAM (the actual read/write location at runtime).
  • • LMA is in FLASH (AT> FLASH) → during startup, moved from _sidata to _sdata.._edata.

11. _siccmram

_siccmram = LOADADDR(.ccmram);
  • • Similar to _sidata, records the LMA of .ccmram in FLASH for the startup code to use during movement.

12. ccmram

.ccmram : {
    . = ALIGN(4);
    _sccmram = .;
    *(.ccmram) *(.ccmram*)
    . = ALIGN(4);
    _eccmram = .;
} &gt;CCMRAM AT&gt; FLASH
  • • VMA is at 0x1000 0000 (CCMRAM).
  • • LMA is in FLASH.
  • • Important: The startup code does not move by default; if initial value variables are placed here, please add a memcpy segment in Reset_Handler.

13. bss

.bss : {
    . = ALIGN(4);
    _sbss = .;          /* Starting address of .bss in RAM */
    __bss_start__ = _sbss;
    *(.bss) *(.bss*) *(COMMON)
    . = ALIGN(4);
    _ebss = .;
    __bss_end__ = _ebss;
} &gt;RAM
  • • Runtime address in RAM, LMA does not exist (since all initial values are 0).
  • • The startup code uses _sbss/_ebss to clear it.

14. _user_heap_stack

._user_heap_stack : {
    . = ALIGN(8);
    PROVIDE(end = .);
    PROVIDE(_end = .);      /* Traditional Unix symbol, malloc starting point */
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
} &gt;RAM
  • • Purely a placeholder section, allowing the linker to calculate: Remaining RAM = 128 KB – (.data + .bss + this section) If it cannot accommodate _Min_Heap_Size + _Min_Stack_Size, the linker will report an error.
  • • Runtime library/bare metal malloc uses end/_end as the heap start.

15. DISCARD

/DISCARD/ : {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
} 
  • • Discard certain unused sections from the standard library directly, reducing image size (actual discarding depends on –gc-sections and reference conditions).

Each logical section tells the linker: “These input sections (.text/.data/.bss…) → merge → place in which physical memory → generate which symbols → align how many bytes.” The startup code then uses these symbols to complete the movement, clearing, and setting stack boundaries, among other initialization tasks.

Leave a Comment