Essential Guide for Embedded Engineers: Comprehensive Analysis of Keil MDK-ARM .sct Files for Memory Layout Control

1. What is a Scatter-Loading File?

In embedded development, after the compiler and assembler generate object files (.o) from the source code, the core task of the linker is to combine all object files and library files into a single executable file (such as .axf or .elf). During this process, the linker needs to know two key things:

  1. Where should the code and data be placed in memory? (For example: the starting address of Flash is 0x08000000, and the starting address of RAM is 0x20000000)

  2. How to combine different code/data sections? (For example: grouping all initialization code together, grouping all constants together)

A Scatter-Loading file (.sct) is a configuration file that tells the linker how to precisely layout memory. It allows you to have complete control over the storage addresses of various parts of the program in the microcontroller’s memory space (Flash, RAM, peripheral storage, etc.).

2. Why is it Needed?

Although MDK automatically generates a simple linker script based on the chip selection by default, you must manually write or modify the .sct file in the following complex scenarios:

  • Complex memory layout: The chip has multiple non-contiguous Flash or RAM blocks.

  • Placing functions/ data at specific addresses: For example, placing a function in RAM for faster execution, or placing an array of variables in specific external storage.

  • Memory-mapped peripherals: Allocating specific sections for peripheral register areas (such as 0x40000000).

  • Creating multiple program: Such as Bootloader and Application located at different positions in Flash.

  • Using non-standard startup methods: Or having special initialization requirements.

3. Basic Syntax Structure

A .sct file consists of several Load Regions and Execution Regions, with a core structure that is hierarchically organized:

; Load Region: The area where the program is initially burned (usually Flash)Load_Region_Name (Start Address | +Offset) [Attributes] [Max Size]{    ; Execution Region: The area where the program runs (can be in Flash/RAM)    Execution_Region_Name (Start Address | +Offset) [Attributes] [Max Size]    {        ; Input Section: Specifies which sections from the object files go here        Object_File (Section_Name)    }    ; Can contain multiple execution regions    Execution_Region_Name2 ... { ... }
  • Load_Region_Name: The name of the Load Region. It defines the physical memory area where the program is initially burned (usually Flash). A program must have at least one Load Region.

  • Execution_Region_Name: The name of the Execution Region. It defines the physical memory area where the program’s code and data reside during execution. A Load Region can contain multiple Execution Regions. For example, initialized data is copied from Flash (Load Region) to RAM (Execution Region) for execution.

  • Base_Address: The starting address of the region (e.g., 0x08000000).

  • +Offset: Indicates the position immediately following the previous region. For example, +0 means starting right after the end of the previous region.

  • AttributeList: An optional attribute list that defines the memory characteristics of the region.

    • PI (Position Independent): Position independent.

    • RELOC (Relocatable): Relocatable.

    • ABSOLUTE: Absolute address (default).

    • FIXED: Fixed address.

    • UNINIT: Uninitialized data.

  • Max_Size: The maximum size of the region, optional. Used by the linker to check for overflow.

  • Input_Section_Description: This is the most critical part, as it tells the linker which sections from the object files (.o) to place in the current execution region.

4. Input_Section_Description

This is key to matching various sections in the object files, with the syntax as follows:

object_file (input_section_selector)
  • object_file: Specifies the object file name (e.g., main.o, startup_stm32f10x.o), and wildcards * can be used to match all files.

  • input_section_selector: Specifies the section name in the object file. Section names are usually prefixed with a dot .. Wildcards * can also be used.

Common sections and their meanings:

Section Name Meaning
.o file itself, such as main.o Matches all sections in main.o
*(.text) or * (.text) Code sections (i.e., functions) from all files
*(.data) All initialized and non-zero global/static variables
*(.bss) All uninitialized or zero-initialized global/static variables
*(.rodata) or *(.constdata) Read-only data (constants, strings, etc.)
*(.arm.extab) *(.arm.exidx) Sections related to C++ exception handling
*(.init_array) C++ global constructor list
*(.fini_array) C++ global destructor list
*(.stack) Stack section (usually defined in the startup file)
*(.heap) Heap section (usually defined in the startup file)

Special symbols:

  • * or *(+): Matches all sections that have not been explicitly allocated by any previous execution region. This is usually placed in the last execution region as a “catch-all”.

5. Predefined Symbols and Examples

The linker automatically defines some symbols that you can use in your program (especially in the startup file startup_*.s) to obtain the start and end addresses of regions for data copying (from Flash to RAM) and zeroing the .bss section.

  • Load$$LR_name$$Base: The starting address of the Load Region LR_name.

  • Image$$ER_name$$Base: The starting address of the Execution Region ER_name.

  • Image$$ER_name$$Limit: The end address of the Execution Region ER_name (the position after the last byte).

  • Image$$ER_name$$ZI$$Base: The starting address of the ZI (Zero Initialized, i.e., .bss) section in the Execution Region ER_name.

  • Image$$ER_name$$ZI$$Limit: The end address of the ZI section in the Execution Region ER_name.

6. Practical Example Analysis

This is a .sct file for a typical Cortex-M chip (such as STM32):

; Define the first Load Region LR_IROM1, starting address 0x08000000, size 128KB, attribute ABSOLUTE LR_IROM1 0x08000000 0x00020000  {    ; First Execution Region ER_IROM1: stores all code and read-only data, also in Flash    ER_IROM1 0x08000000 0x00020000  {        *.o (RESET, +First)        ; Place the RESET section (interrupt vector table) from the startup file first        * (InRoot$$Sections)       ; All special sections related to libraries, provided by the compiler, must be placed in the root area        .ANY (+RO)                 ; Place all read-only sections (RO, ReadOnly) from all files here                                   ; (.ANY is similar to *, but can avoid overly large regions)    }    ; Second Execution Region RW_IRAM1: stores read-write data (RW, ReadWrite), in RAM    RW_IRAM1 0x20000000 0x00005000  {        .ANY (+RW +ZI)             ; Place all read-write sections (RW) and zero-initialized sections (ZI) from all files here    }

Workflow:

  1. After power-up, all content (code .text, constants .rodata, initial values .data) is stored in LR_IROM1/ER_IROM1 (Flash).

  2. The startup code (in startup_*.s) will use the aforementioned Image$$ symbols to find the source address of the .data section in Flash (the LoadAddr) and its target address in RAM (the ExecutionAddr), then copy the initial values from Flash to RAM.

  3. The startup code will also find the address of the .bss section in RAM and zero out that area.

  4. Only after that does the program officially jump to the main function for execution. At this point, all global variables are ready in RAM.

7. More Complex Example: Executing Functions in RAM

Suppose there is a function __attribute__((section(".RAMcode"))) void Fast_Function() {...}, and we want it to be burned into Flash but copied to RAM for execution after power-up.

LR_IROM1 0x08000000 0x00020000  {    ; Flash Execution Region (regular code)    ER_IROM1 0x08000000 0x0001F000 {        *.o (RESET, +First)        * (InRoot$$Sections)        .ANY (+RO)                 ; Note: .RAMcode section is no longer included here    }    ; Define a Load Region in Flash, but the Execution Region in RAM for storing Fast_Function    ; This region is first treated as a Load Region, part of Flash    RW_IRAM2 0x0801F000 0x00001000 {        .ANY (.RAMcode)            ; Allocate space for .RAMcode section in Flash    }    ; RAM Execution Region (regular data)    RW_IRAM1 0x20000000 0x00004000  {        .ANY (+RW +ZI)    }    ; RAM Execution Region (for executing Fast_Function)    ER_IRAM2 0x20004000 0x00001000  {        .ANY (.RAMcode)            ; Allocate execution space for .RAMcode section in RAM    }

Workflow:

  1. The code for Fast_Function is compiled into the .RAMcode section.

  2. The linker places its initial content in the RW_IRAM2 execution region under the LR_IROM1 load region (which is actually at Flash address 0x0801F000).

  3. At the same time, the linker allocates space for its runtime in the ER_IRAM2 execution region (RAM address 0x20004000).

  4. The startup code not only needs to copy the .data section but also needs to copy the .RAMcode section from Flash’s 0x0801F000 to RAM’s 0x20004000. This usually requires you to modify the startup file or manually write copy code.

8. Summary and Key Points

  • Hierarchical relationship: Load Region (LR) -> Execution Region (ER) -> Input Sections.

  • Core task: Assign specific sections from object files to specific memory addresses using *(section_name).

  • Address specification: Can use absolute addresses (0x...) or relative offsets (+0).

  • Wildcards: * matches all, .ANY is a smarter wildcard that can balance multiple regions.

  • Special sections: RESET (interrupt vector table) and InRoot$$Sections must be handled properly, usually placed in the first execution region.

  • Startup code coordination: The Scatter-Loading file defines the layout, while the data copying and zeroing tasks need the startup code to utilize symbols like Image$$XX$$Base/Limit to complete.

By skillfully using the Scatter-Loading file, you can greatly enhance your control over memory management in embedded systems to meet various advanced and optimized requirements. In MDK, you can uncheck Use Memory Layout from Target Dialog in Options for Target -> Linker to use your own .sct file.

Leave a Comment