
The linker script is the single source of truth for your firmware’s memory layout — yet it’s the most misunderstood file in embedded projects. Engineers searching for “linker script .data not copied to RAM”, “firmware crashes after adding global variable”, or “how to place variable in specific memory region” hit a wall: the linker accepts invalid layouts silently, startup code assumes correct LMA/VMA mapping, and debuggers show symbols at wrong addresses.
A single misplaced > or missing AT> can cause: variables in wrong memory regions, .data not initialized at startup, stack/heap collision, or ISR vectors at wrong address. This article provides a complete reference for writing, debugging, and verifying linker scripts — with patterns that work across Cortex-M, RISC-V, and multi-core SoCs.
Every embedded engineer eventually faces a linker script issue. The symptoms are maddening: a global variable reads back garbage, an ISR never fires, the stack overflows into the heap, or the firmware simply won’t boot. The root cause is almost always a mismatch between what the linker thinks the memory layout is and what the hardware actually provides.
+===============================================================================+| TYPICAL LINKER SCRIPT FAILURE CHAIN |+===============================================================================+| || WRONG LINKER SCRIPT || | || v || +-------------+ +-------------+ +-------------+ +-------------+ || | .data LMA |--->| Startup |--->| .data in |--->| Global vars | || | != Flash | | copies from | | RAM is | | contain | || | address | | wrong flash | | garbage | | garbage | || +-------------+ +-------------+ +-------------+ +-------------+ || || OR: || +-------------+ +-------------+ +-------------+ +-------------+ || | Stack/heap |--->| No ASSERT |--->| Stack grows |--->| Corrupt | || | overlap | | in script | | into heap | | heap vars | || +-------------+ +-------------+ +-------------+ +-------------+ || || OR: || +-------------+ +-------------+ +-------------+ +-------------+ || | .isr_vector |--->| Vector at |--->| CPU jumps |--->| Hard fault | || | not 1st | | wrong addr | | to garbage | | on reset | || +-------------+ +-------------+ +-------------+ +-------------+ || || ALL PREVENTABLE with proper linker script + link-time verification |+===============================================================================+
Every embedded firmware operates in three address spaces simultaneously:
+=======================================================================+| THREE ADDRESS SPACES IN EMBEDDED |+=======================================================================+| || BUILD TIME (Linker View) RUNTIME (CPU View) || +---------------------+ +---------------------+ || | VMA (Virtual | | Physical Addresses | || | Memory Address) | --MAP--> | (Flash @ 0x08... | || | Where code THINKS | | RAM @ 0x20...) | || | it lives | | | || +---------------------+ +---------------------+ || ^ ^ || | LOAD TIME | || +--------------------------------------+ || LMA (Load Memory Address) || Where data is STORED in Flash || || KEY INSIGHT: .text/.rodata: LMA == VMA (Execute-in-Place from Flash) || .data: LMA != VMA (Copied Flash -> RAM at boot) || .bss: No LMA (Zero-initialized in RAM at boot) || |+=======================================================================+
Critical distinction: The linker resolves symbols at VMA. The flash programmer writes at LMA. Startup code copies LMA -> VMA for .data. If these diverge -> silent corruption.
/* STM32F407 example: 1 MB Flash, 192 KB RAM (+ 64 KB CCM RAM) */MEMORY{FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024KRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192KCCM_RAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64KBKPSRAM (xrw) : ORIGIN = 0x40024000, LENGTH = 4K}/* Stack/heap symbols for startup code */_stack_size = 0x2000;_heap_size = 0x1000;
Rule: Define ALL memory regions upfront. Every output section must map to a region with >REGION. The linker will error if sections overflow.
The attribute string in MEMORY definitions controls section permissions in the generated ELF:
| Attribute | Meaning | Typical Use |
|---|---|---|
r | Readable | Flash, ROM, .text, .rodata |
w | Writable | RAM, .data, .bss |
x | Executable | Flash, .text |
a | Allocatable | All loadable sections |
i / l | Initialized | .data (has LMA content) |
! | Invert | !w = read-only |
For Cortex-M, the standard combination is FLASH (rx) and RAM (xrw). The x on RAM enables execute-from-RAM (needed for bootloaders, OTA staging).
The interaction between Flash (non-volatile storage) and RAM (runtime execution space) in an ARM Cortex-M system is visualized below:
+===============================================================================+| FLASH MEMORY (ROM) RAM (SRAM) || Non-Volatile Storage (LMA) Runtime Execution (VMA) |+===============================================================================+| || 0x08000000 -> +---------------------+ || | .isr_vector (VTOR) | || +---------------------+ || | .text (Executable) | || +---------------------+ || | .rodata (Constants) | || +---------------------+ 0x20000000 -> +---------------------+ || _sidata ----> | .data image (LMA) | --- Copy ---> | .data (Read/Write) | || | (Initial values) | at startup | (_sdata to _edata) | || +---------------------+ +---------------------+ || | | | .bss (Zero-filled) | || | (Unused Flash) | | (_sbss to _ebss) | || | | +---------------------+ || | | | .heap (malloc) | || | | | (_heap_start -> up) | || | | +---------------------+ || | | | | || | | | ^ Stack/Heap Gap | || | | | v (Safety Margin) | || | | | | || | | +---------------------+ || | | | .stack (MSP limit) | || | | | (_stack_bottom) | || | | | | | || | | | v | || 0x08100000 -> +---------------------+ 0x20030000 -> +---------------------+ || (End of 1MB Flash) | _stack_top (End RAM)| || +---------------------+ |+===============================================================================+
SECTIONS{/* 1. Vector table -- MUST BE FIRST, at FLASH origin with power-of-two alignment */.isr_vector :{. = ALIGN(128); /* Cortex-M VTOR alignment requirement */KEEP(*(.isr_vector)) /* Prevent linker GC (--gc-sections) removal */. = ALIGN(4);} >FLASH/* 2. Code and read-only data (execute-in-place) */.text :{. = ALIGN(4);*(.text) /* Standard .text input sections */*(.text*) /* Subsections generated by -ffunction-sections */*(.rodata) /* Read-only constants */*(.rodata*) /* Subsections generated by -fdata-sections */*(.glue_7) *(.glue_7t) /* ARM/Thumb interworking veneers (if applicable) */*(.eh_frame*) /* Exception unwinding frames */KEEP(*(.init))KEEP(*(.fini)). = ALIGN(4);_etext = .; /* End of code symbol */} >FLASH/* 3. Initialized data -- LMA in Flash, VMA in RAM */_sidata = LOADADDR(.data); /* Export absolute Flash address for C startup copy */.data :{. = ALIGN(4);_sdata = .; /* VMA start in RAM */*(.data)*(.data*). = ALIGN(4);_edata = .; /* VMA end in RAM */} >RAM AT>FLASH /* VMA=RAM, LMA=FLASH *//* 4. Zero-initialized data -- VMA only in RAM */.bss :{. = ALIGN(4);_sbss = .; /* VMA start in RAM */*(.bss)*(.bss*)*(COMMON) /* Uninitialized global variables */. = ALIGN(4);_ebss = .; /* VMA end in RAM */} >RAM/* 5. Dynamic heap -- allocated after .bss, grows upwards (newlib _sbrk) */.heap (NOLOAD) :{. = ALIGN(8);PROVIDE(_heap_start = .);PROVIDE(end = .); /* Required by standard C libraries (newlib) */PROVIDE(_end = .);. = . + _heap_size;. = ALIGN(8);PROVIDE(_heap_end = .);} >RAM/* 6. User Stack reservation -- allocated at the top of RAM (grows downwards) */.stack (NOLOAD) :{. = ALIGN(8);_stack_bottom = .; /* Lowest valid stack address (stack guard) */. = . + _stack_size;. = ALIGN(8);_stack_top = .; /* Top of stack (initial MSP loaded on reset) */} >RAM/* 7. Discard debugging and ABI sections from release image *//DISCARD/ :{*(.note.gnu.build-id)*(.note.ABI-tag)*(.comment)}}
.data)In C, symbols defined in a linker script do not allocate storage variables; their addresses represent the location counter values established by the linker. Always use the address-of operator (&_sdata) or declare them as array types:
/* startup.c -- called during Reset_Handler before main() */#include <stdint.h>void CopyDataInit(void){/* Linker script symbols */extern uint32_t _sidata; /* LMA of .data in Flash */extern uint32_t _sdata; /* VMA start of .data in RAM */extern uint32_t _edata; /* VMA end of .data in RAM */uint32_t *p_src = &_sidata;uint32_t *p_dst = &_sdata;while (p_dst < &_edata) {*p_dst++ = *p_src++;}}void ZeroBSS(void){extern uint32_t _sbss; /* VMA start of .bss in RAM */extern uint32_t _ebss; /* VMA end of .bss in RAM */uint32_t *p_dst = &_sbss;while (p_dst < &_ebss) {*p_dst++ = 0U;}}
[!IMPORTANT] GNU ld Does Not Automatically Generate
_sidataA common misconception is that the GNU linker automatically synthesizes_sidata. It does not. The symbol MUST be explicitly exported in your linker script using_sidata = LOADADDR(.data);. Without this definition, the linker emits anundefined reference to '_sidata'error when compiling startup code.
Specialized embedded microcontrollers feature distinct memory regions with unique physical interfaces, such as Core-Coupled Memory (CCM) or battery-backed Backup SRAM:
/* STM32F407: Flash, main SRAM, CCM RAM, and Backup SRAM */MEMORY{FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024KRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192KCCM_RAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K /* Core-Coupled Memory */BKPSRAM (xrw) : ORIGIN = 0x40024000, LENGTH = 4K /* Backup SRAM (VBAT) */}SECTIONS{/* ... standard .text, .rodata, .data, .bss ... *//* Initialized CCM data (copied from Flash at boot) */_siccm_data = LOADADDR(.ccm_data);.ccm_data :{. = ALIGN(4);_sccm_data = .;*(.ccm_data*). = ALIGN(4);_eccm_data = .;} >CCM_RAM AT>FLASH/* Uninitialized CCM BSS (zero-filled at boot) */.ccm_bss (NOLOAD) :{. = ALIGN(4);_sccm_bss = .;*(.ccm_bss*). = ALIGN(4);_eccm_bss = .;} >CCM_RAM/* Initialized Backup SRAM data (retained during VBAT mode) */_sibkpsram_data = LOADADDR(.bkpsram_data);.bkpsram_data :{. = ALIGN(4);_sbkpsram_data = .;*(.bkpsram_data*). = ALIGN(4);_ebkpsram_data = .;} >BKPSRAM AT>FLASH.bkpsram_bss (NOLOAD) :{. = ALIGN(4);_sbkpsram_bss = .;*(.bkpsram_bss*). = ALIGN(4);_ebkpsram_bss = .;} >BKPSRAM}
[!WARNING] Hardware Architecture Pitfall: CCM RAM and DMA on STM32F4 On STM32F4 devices, CCM RAM (
0x10000000) is wired exclusively to the Cortex-M4 D-Code and I-Code buses. It is completely isolated from the multi-AHB bus matrix. Because DMA controllers reside on the AHB matrix, DMA controllers cannot access CCM RAM. Attempting to execute DMA transfers to or from CCM RAM will trigger a bus transfer error (TEIFflag) and fail silently. Reserve CCM RAM strictly for CPU-intensive DSP math, FIR/IIR filters, fast control loops, or dedicated ISR stacks. Allocate all DMA buffers in general SRAM (0x20000000).
Usage in C:
/* High-throughput FFT workspace in CCM RAM (0-wait-state CPU access up to 168 MHz) */__attribute__((section(".ccm_bss"), aligned(4)))uint32_t fft_workspace[2048];/* Time-critical PID control loop states in CCM RAM */__attribute__((section(".ccm_data"), aligned(4)))volatile struct pid_controller_t fast_loop = { .kp = 1.25f, .ki = 0.05f, .kd = 0.01f };/* Persistent calibration parameters retained across system resets in Backup SRAM */__attribute__((section(".bkpsram_data"), aligned(4)))struct sensor_calibration_t saved_cal = { .magic = 0xDEADBEEF, .version = 1 };/* Startup copy routine for custom initialized regions */void CopyCustomSections(void){extern uint32_t _siccm_data, _sccm_data, _eccm_data, _sccm_bss, _eccm_bss;uint32_t *p_src = &_siccm_data;uint32_t *p_dst = &_sccm_data;while (p_dst < &_eccm_data) {*p_dst++ = *p_src++;}p_dst = &_sccm_bss;while (p_dst < &_eccm_bss) {*p_dst++ = 0U;}}
In applications where multiple large algorithmic routines exceed available RAM execution space, GNU ld’s OVERLAY mechanism allows multiple sections to share the same runtime VMA in RAM while being stored contiguously at different LMAs in Flash:
/* Overlay: multiple code banks share execution VMA in RAM, loaded sequentially in FLASH */OVERLAY 0x20010000 : NOCROSSREFS AT (LOADADDR(.data) + SIZEOF(.data)){.ovly_bank0 { *(.ovly_bank0*) }.ovly_bank1 { *(.ovly_bank1*) }.ovly_bank2 { *(.ovly_bank2) }} >RAM AT>FLASH
GNU ld automatically defines load symbols for each overlay bank:
__load_start_ovly_bank0 and __load_stop_ovly_bank0__load_start_ovly_bank1 and __load_stop_ovly_bank1#include <string.h>extern uint8_t __load_start_ovly_bank0[], __load_stop_ovly_bank0[];extern uint8_t __load_start_ovly_bank1[], __load_stop_ovly_bank1[];#define OVERLAY_RAM_EXEC_ADDR ((void *)0x20010000)void load_overlay(uint8_t bank_id){uint8_t *load_start;size_t size;if (bank_id == 0) {load_start = __load_start_ovly_bank0;size = (size_t)(__load_stop_ovly_bank0 - __load_start_ovly_bank0);} else {load_start = __load_start_ovly_bank1;size = (size_t)(__load_stop_ovly_bank1 - __load_start_ovly_bank1);}memcpy(OVERLAY_RAM_EXEC_ADDR, load_start, size);__asm__ volatile ("dsb 0xF" ::: "memory");__asm__ volatile ("isb 0xF" ::: "memory"); /* Pipeline flush for newly staged code */}
In heterogeneous dual-core microcontrollers (e.g., STM32H7 with Cortex-M7 and Cortex-M4), shared RAM regions require strict cache coherency policies:
/* Dual-core STM32H7 (CM7 @ 480 MHz, CM4 @ 240 MHz) */MEMORY{FLASH_CM7 (rx) : ORIGIN = 0x08000000, LENGTH = 1024KFLASH_CM4 (rx) : ORIGIN = 0x08100000, LENGTH = 1024KRAM_CM7 (xrw) : ORIGIN = 0x20000000, LENGTH = 128K /* DTCM */RAM_CM4 (xrw) : ORIGIN = 0x10000000, LENGTH = 128K /* SRAM1/2 */SHARED_RAM (rw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 (Non-cacheable) */}SECTIONS{.shared_data (NOLOAD) :{. = ALIGN(32);*(.shared_data*). = ALIGN(32);} >SHARED_RAM}
[!NOTE] Cache Coherency Notice: Because the Cortex-M7 features L1 data caching, any access to
SHARED_RAMmust be marked as Non-Cacheable using the Cortex-M MPU (Memory Protection Unit) or explicitly maintained using CMSIS functionsSCB_CleanDCache_by_Addr()andSCB_InvalidateDCache_by_Addr()before and after inter-core IPC transactions.
When designing firmware with an in-field bootloader and an application payload, Flash must be partitioned into two distinct images:
/* Application Linker Script (Offset past 32KB Bootloader) */MEMORY{FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 992K /* Application Flash */RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K}SECTIONS{/* Application vector table placed at offset 0x08008000 */.isr_vector :{. = ALIGN(128);KEEP(*(.isr_vector)). = ALIGN(4);} >FLASH.text :{*(.text*)*(.rodata*). = ALIGN(4);} >FLASH/* Standard .data, .bss, .heap, and .stack follow */}
Key Bootloader Runtime Rules:
SystemInit(), update the Vector Table Offset Register: SCB->VTOR = 0x08008000;.NVIC->ICER[x]), clear pending interrupts (NVIC->ICPR[x]), and reset the Main Stack Pointer (__set_MSP(*(uint32_t *)0x08008000)).(NOLOAD) communication mailbox section at a fixed RAM origin that neither runtime’s .bss clears./* Provide default values if not defined in object files */PROVIDE(g_heap_guard = 0xDEADBEEF);PROVIDE_HIDDEN(_system_clock_hz = 168000000);
| Symptom | Likely Cause | Fix |
|---|---|---|
| Crash on startup | .isr_vector not at ORIGIN(FLASH) | Make .isr_vector first section, >FLASH |
| Globals uninitialized | .data missing AT>FLASH | Add >RAM AT>FLASH |
| Variables at wrong address | LMA/VMA mismatch | Check objdump -h vs linker map |
| Stack overflow | _stack_size too small / heap collision | Increase stack, add ASSERT |
| Linker “region overflow” | Section too large for MEMORY | Reduce section or increase region |
| ISR handler missing | KEEP() missing on .isr_vector | Add KEEP(*(.isr_vector)) |
+===============================================================================+| DEBUGGING: GLOBAL VARIABLE AT WRONG ADDRESS |+===============================================================================+| || SYMPTOM: || struct config_t cfg = { .baud = 115200 }; // In .data || printf("%d", cfg.baud); // Prints 0 or garbage || || STEP 1: Check .data LMA vs VMA || $ arm-none-eabi-objdump -h firmware.elf | grep '\.data' || Idx Name Size VMA LMA File off Algn || 8 .data 0080 20000100 0800C000 0000D000 2**2 || [PASS] LMA (0x0800C000) != VMA (0x20000100) -- copy required || || STEP 2: Verify startup code uses _sidata || $ arm-none-eabi-nm firmware.elf | grep _sidata || 0800C000 D _sidata || [PASS] Symbol exists at correct Flash LMA || || STEP 3: Check CopyDataInit() implementation || Found: src = &_sidata; dst = &_sdata; // CORRECT || || STEP 4: Check .data section alignment || Linker script: .data : { . = ALIGN(4); ... } >RAM AT>FLASH || [PASS] 4-byte word aligned || || STEP 5: Check for overlapping sections || $ arm-none-eabi-objdump -h firmware.elf || .text 08000000 - 0800BFFF || .data 0800C000 - 0800C07F (LMA) || .rodata 0800C080 - 0800D120 || [PASS] No overlapping address space || || STEP 6: Hardware check -- is flash actually programmed? || $ openocd -c "program firmware.elf verify" || FAIL: verification mismatch at 0x0800C000 || || ROOT CAUSE: Flash sector not erased before write! || The .data LMA fell in a sector that was dirty. || || FIX: Ensure flash programmer performs sector or mass erase on upload || |+===============================================================================+
# 1. Check section addresses and sizesarm-none-eabi-objdump -h firmware.elf | grep -E '\.text|\.data|\.bss|\.isr_vector'# 2. Verify LMA vs VMA for .dataarm-none-eabi-objdump -h firmware.elf | grep '\.data'# Should show: VMA=0x200xxxxx LMA=0x080xxxxx (different!)# 3. Full linker map (search for region overflows)arm-none-eabi-ld -Map=firmware.map ...grep -i overflow firmware.map# 4. Check symbol placement for C runtime startuparm-none-eabi-nm firmware.elf | grep -E '_sdata|_edata|_sbss|_ebss|_sidata'# 5. Verify vector table at Flash origin (0x08000000)arm-none-eabi-objdump -s -j .isr_vector firmware.elf# 6. Check discarded unused functions and sections (dead code elimination)# Pass -Wl,--print-gc-sections during build to inspect stripped sections:arm-none-eabi-gcc ... -Wl,--gc-sections -Wl,--print-gc-sections 2>&1 | grep "removing unused section"# 7. Check section alignments and sizes across all segmentsarm-none-eabi-objdump -h firmware.elf
/* Prevent stack/heap collision in RAM */ASSERT(_heap_end <= _stack_bottom,"HEAP/STACK COLLISION: Heap overlaps with reserved user stack in RAM")/* Ensure total static RAM allocations fit within physical RAM boundaries */ASSERT(_stack_top <= ORIGIN(RAM) + LENGTH(RAM),"RAM OVERFLOW: Combined .data + .bss + heap + stack exceed RAM length")/* Ensure vector table resides precisely at Flash origin */ASSERT(ADDR(.isr_vector) == ORIGIN(FLASH),"VECTOR TABLE NOT AT FLASH ORIGIN: Cortex-M boot will fail")/* Verify custom memory region capacity */ASSERT(SIZEOF(.ccm_data) + SIZEOF(.ccm_bss) <= LENGTH(CCM_RAM),"CCM RAM OVERFLOW: Custom data exceeds 64KB boundary")/* Flash usage sanity check (warn if firmware exceeds 90% budget) */ASSERT((_etext - ORIGIN(FLASH)) + SIZEOF(.data) <= (LENGTH(FLASH) * 9) / 10,"FLASH USAGE WARNING: Firmware image exceeds 90% of total Flash")
--gc-sections together with -ffunction-sections -fdata-sections for maximum dead code stripping.ENTRY(Reset_Handler) must match your startup code entry symbol.--print-memory-usage displays a console overview of region capacity and percentage utilized.-Wl,--cref appends a cross-reference table to the generated map file./* IAR equivalent -- .icf file */define memory mem with size = 1M;define region FLASH = mem:[from 0x08000000 size 1M];define region RAM = mem:[from 0x20000000 size 192K];initialize by copy { section .data };do not initialize { section .bss };place in FLASH { readonly section .isr_vector, section .text, section .rodata };place in RAM { section .data, section .bss };
clang --target=arm-none-eabi -fuse-ld=lld -T script.ld.MEMORY, SECTIONS, AT>, LOADADDR(), and PROVIDE().--icf=safe enables conservative Identical Code Folding, collapsing duplicated read-only code while guaranteeing distinct function pointer addresses.VERSION {} blocks; symbol versioning must be passed via the --version-script=<file> command-line flag.; Keil scatter file equivalentLR_FLASH 0x08000000 0x00100000 {ER_IROM 0x08000000 0x00100000 {*.o (RESET, +First)*(InRoot$$Sections).ANY (+RO)}RW_IRAM 0x20000000 0x00030000 {.ANY (+RW +ZI)}}
| GNU ld Feature | LLD Equivalent | Notes |
|---|---|---|
ASSERT(expr, msg) | Same | Fully compatible |
PROVIDE(sym = val) | Same | Fully compatible |
KEEP(*(.sec)) | Same | Fully compatible |
>REGION AT>REGION | Same | Fully compatible |
OVERLAY | Partial | Prefer explicit section definitions in LLD |
VERSION {} | --version-script | LLD requires dedicated version file |
INPUT(file.a) | Same | Fully compatible |
GROUP(file1, file2) | Same | Fully compatible |
ASSERT failures during compilation.region overflowed errors.objdump -j .isr_vector confirms base address 0x08000000..data LMA != VMA — confirms Flash storage address differs from runtime RAM address._stack_top at RAM top, _stack_bottom >= _heap_end.arm-none-eabi-nm..bss.The linker script is the memory contract between your build system, startup code, and hardware. Master these patterns:
>RAM AT>FLASH for .data and export _sidata = LOADADDR(.data);.KEEP() on vectors — protect hardware exception tables from linker garbage collection._sdata, _edata, _sbss, _ebss, _sidata, and _stack_top.| Task | Linker Script Syntax |
|---|---|
| Define memory region | MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M } |
| Section in RAM, LMA in Flash | .data : { *(.data*) } >RAM AT>FLASH |
| Export section Flash LMA | _sidata = LOADADDR(.data); |
| Section in RAM only | .bss : { *(.bss*) } >RAM |
| Keep section from GC | KEEP(*(.isr_vector)) |
| Provide symbol | PROVIDE(_stack_top = ORIGIN(RAM) + LENGTH(RAM)); |
| Assert condition | ASSERT(_heap_end <= _stack_bottom, "Stack/heap collision"); |
| Align address | . = ALIGN(4); |
| Discard sections | /DISCARD/ : { *(.note*) } |
When reviewing linker script changes, verify:
MEMORY regions match datasheet (ORIGIN, LENGTH, attributes).isr_vector is first section, mapped to ORIGIN(FLASH) with power-of-two alignment.data has AT>FLASH and exports _sidata = LOADADDR(.data);.bss has no LMA (NOLOAD implied)KEEP() on .isr_vector and hardware-referenced tablesPROVIDE or direct assignment)ASSERT statements for stack/heap collision, RAM overflow, and Flash capacityORIGIN()/LENGTH()objdump -h confirms LMA/VMA as expected| Anti-Pattern | Why It’s Wrong | Correct Approach |
|---|---|---|
Hardcoded _sidata = 0x0800C000 | Breaks silently when code grows or sections shift | Export _sidata = LOADADDR(.data); in linker script |
.data >RAM (no AT>) | LMA = VMA — Flash image contains no initialization copy | .data : { ... } >RAM AT>FLASH |
.isr_vector not first | Vector table misaligned; CPU jumps to arbitrary memory on reset | Place .isr_vector first in SECTIONS, mapped to >FLASH |
No KEEP() on vectors | --gc-sections strips interrupt vector table as unused code | Decorate section with KEEP(*(.isr_vector)) |
| No stack/heap ASSERT | Silent heap and stack memory corruption at runtime | Add ASSERT(_heap_end <= _stack_bottom, ...) |
| Placing DMA buffers in CCM RAM | CCM RAM does not connect to AHB bus matrix on STM32F4; DMA fails | Place DMA buffers in general SRAM (0x20000000) |
Single MEMORY region for all | Prevents selective placement and MPU permission enforcement | Define separate regions for Flash, SRAM, CCM, and backup RAM |
Quick Links
Legal Stuff





