5.5.17 · D4Embedded Systems & Real-Time Software

Exercises — Linker scripts — memory regions, sections (.text, .data, .bss)

3,267 words15 min readBack to topic

The chip used throughout (unless a problem overrides it):

where means ORIGIN (the first byte's address) and means LENGTH (how many bytes the region spans). bytes.


Vocabulary you need before problem 1

Two terms appear in nearly every solution. Meet them once, with a picture, so no symbol is used before it's earned.

Figure 1 shows the two memory regions and how the cursor . walks down each one, placing sections.

Figure — Linker scripts — memory regions, sections (.text, .data, .bss)

Read Figure 1 like this: the left column is Flash (non-volatile), the right is RAM (wiped on power-off). The cyan cursor arrow labelled . starts at each region's ORIGIN at the top and drops downward as sections are placed. Notice .data appears twice — an amber block in Flash (its stored initial values, the LMA) and an amber block in RAM (where it runs, the VMA) — joined by the amber "copy at boot" arrow. .bss (cyan, RAM only) has no Flash twin; it is simply zeroed. This single picture is the whole page in miniature; every exercise below is a slice of it.


Level 1 — Recognition

Exercise 1.1 (L1)

For each declaration, name the section it lands in: .text, .rodata, .data, or .bss.

  • (a) int counter = 42; (global)
  • (b) int total; (global)
  • (c) const char msg[] = "hi"; (global)
  • (d) void loop(void) { ... }
  • (e) static int cache = 0; (global)
Recall Solution 1.1
  • (a) .data — initialized to a non-zero value, so the value must be stored (in Flash) and copied to RAM.
  • (b) .bss — a global with no initializer is implicitly zero, so it goes to .bss (only its size is recorded).
  • (c) .rodataconst data is read-only, kept in Flash.
  • (d) .text — machine code always lives in Flash and executes there.
  • (e) .bss — this is the trap. It's initialized to 0, and zero-initialized data is .bss, not .data (storing zeros in Flash would be wasteful). See mistake below.

Exercise 1.2 (L1)

Match each linker-script keyword to what it returns: ADDR(.data), LOADADDR(.data), SIZEOF(.data).

Recall Solution 1.2
  • ADDR(.data) → the VMA (run address, in RAM).
  • LOADADDR(.data) → the LMA (load address, in Flash, where the init values physically sit).
  • SIZEOF(.data) → the number of bytes in .data.

Memory hook from the parent: Data Doubles — two addresses. On Figure 1 these are the two amber blocks.


Level 2 — Application

Exercise 2.1 (L2)

A program has: .text bytes, .rodata bytes, .data bytes, .bss bytes.

  • (a) How many bytes does the Flash .bin image occupy?
  • (b) How many bytes of RAM does static data use (ignore stack/heap)?
Recall Solution 2.1

(a) Flash image. The image must physically contain everything that survives power-off: code, read-only constants, and the initial values of .data (its LMA copy — the amber Flash block in Figure 1). .bss contributes nothing to Flash. (b) RAM. Static RAM holds .data (once copied) plus .bss: Note .data is counted in both budgets — it has two homes, exactly the LMA/VMA idea.

Exercise 2.2 (L2)

Given the layout .text then .data placed in Flash (both AT> FLASH), with .text = 0x2FEC bytes starting at :

  • (a) What is LOADADDR(.data)?
  • (b) If .data is 0x200 bytes, what Flash address is the first free byte after .data?
Recall Solution 2.2

Recall the location counter . (defined at the top): it is the cursor that starts at a region's ORIGIN and advances by each section's size. In Flash it starts at . (a) After .text (0x2FEC bytes) is placed, the cursor has advanced by 0x2FEC, so .data loads there: (b) After .data (0x200 bytes) the cursor advances again:


Level 3 — Analysis

Exercise 3.1 (L3)

The startup copy loop from the parent note:

for (uint32_t *p=&_sidata, *q=&_sdata; q < &_edata; ) *q++ = *p++;

The symbols are: _sidata = LMA of .data, _sdata = VMA start, _edata = VMA end. Suppose _sdata = 0x20000000, _edata = 0x20000010, _sidata = 0x08002FEC.

  • (a) How many 32-bit words does this loop copy?
  • (b) Which source (Flash) addresses are read?

Figure 2 draws this loop as a mirror: the Flash source block on the left, the RAM destination on the right, and one arrow per word.

Figure — Linker scripts — memory regions, sections (.text, .data, .bss)
Recall Solution 3.1

The pointers are uint32_t*, so q++ advances by 4 bytes each iteration (one word per arrow in Figure 2). (a) Byte span in RAM bytes. Words: (b) The Flash pointer p starts at 0x08002FEC and also steps by 4: (four reads, matching four writes to 0x20000000, 04, 08, 0C — the four arrows in Figure 2).

Exercise 3.2 (L3)

A student removes KEEP(...) around .isr_vector and enables linker garbage collection (--gc-sections). The board hard-faults on reset. Explain the chain of cause and effect. (Recall .isr_vector was defined at the top of this page; deep dive Vector table & interrupt handling and Startup code & Reset_Handler.)

Recall Solution 3.2
  1. Garbage collection deletes any section not referenced by a symbol to save Flash.
  2. The .isr_vector section (the vector table) is never called by name in C — the CPU reads it by hardware convention from the base of Flash. The linker sees no reference.
  3. So GC drops .isr_vector. Now the first bytes of Flash are whatever section landed there instead (or padding).
  4. On reset the CPU loads the initial stack pointer and Reset_Handler address from the (now missing) vector table → garbage addresses → immediate hard fault. KEEP() tells the linker "pretend this is referenced; never collect it."

Level 4 — Synthesis

A word on ALIGN before you build

Exercise 4.1 (L4)

Write a complete, minimal linker script for the chip at the top of this page that:

  • puts the vector table first, then .text, then .rodata as separately named output sections in Flash;
  • places .data running in RAM but loaded from Flash;
  • places .bss in RAM;
  • keeps all section starts/ends 4-byte aligned;
  • exports _sidata, _sdata, _edata, _sbss, _ebss for the startup code.
Recall Solution 4.1

To honour "vector table, then .text, then .rodata" as distinct, ordered sections, we give each its own output section rather than folding .rodata into .text. Their order in the file is the order they are placed in Flash.

MEMORY
{
  FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 256K
  RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}
 
SECTIONS
{
  .isr_vector : {
    . = ALIGN(4);
    KEEP(*(.isr_vector))   /* vector table FIRST; KEEP survives GC */
    . = ALIGN(4);
  } > FLASH
 
  .text : {
    . = ALIGN(4);
    *(.text*)              /* machine code */
    . = ALIGN(4);
  } > FLASH
 
  .rodata : {
    . = ALIGN(4);
    *(.rodata*)            /* read-only constants, kept separate */
    . = ALIGN(4);
  } > FLASH
 
  _sidata = LOADADDR(.data);   /* Flash source of init values (LMA) */
  .data : {
    . = ALIGN(4);
    _sdata = .;                /* RAM run start (VMA) */
    *(.data*)
    . = ALIGN(4);
    _edata = .;                /* RAM run end */
  } > RAM AT> FLASH            /* VMA in RAM, LMA in Flash */
 
  .bss : {
    . = ALIGN(4);
    _sbss = .;
    *(.bss*)
    *(COMMON)
    . = ALIGN(4);
    _ebss = .;
  } > RAM                      /* no AT>: nothing to load, just zeroed */
}

Every choice justified: .isr_vector, .text, .rodata are three separate output sections in the requested order (matching the prompt exactly); KEEP protects the table (Ex 3.2); ALIGN(4) keeps every boundary word-aligned so the copy loop is legal; AT> FLASH gives .data two addresses (Ex 2.2); .bss has no AT> because it's zeroed at boot, not copied.

Exercise 4.2 (L4)

Using the script above and assuming .isr_vector+.text+.rodata total bytes (already 4-byte aligned), and .data bytes:

  • (a) Compute _sidata, _sdata, _edata.
  • (b) Confirm the Flash image fits in 256K.
Recall Solution 4.2

(a)

  • _sidata = LOADADDR(.data) = the Flash cursor after the three code sections = .
  • _sdata = ADDR(.data) = the RAM cursor at the start of .data. .data is first in RAM, so it equals .
  • _edata: why _edata = _sdata + 0x40? In the script _sdata is stamped from . before *(.data*) and _edata is stamped after, so _edata - _sdata is exactly the bytes the cursor advanced — that is SIZEOF(.data) = 0x40. Hence:

(b) Flash image bytes. Region size bytes. Since , it fits comfortably.


Level 5 — Mastery

Exercise 5.1 (L5)

A design needs, in RAM: .data = 8K, .bss = 20K, plus a stack of 8K and a heap of 16K. RAM is 64K. There is also a requirement to reserve a 4K DMA buffer in RAM.

  • (a) Does everything fit? Show the arithmetic.
  • (b) How many bytes are left over?
  • (c) The engineer wants to double the heap to 32K. Now what happens? Suggest the cheapest fix that keeps all functionality. (See Stack vs Heap in embedded systems and Flash vs RAM tradeoffs.)
Recall Solution 5.1

(a) Total RAM demand: , so it fits. (b) Leftover: (c) Doubling the heap adds , giving overflow by 8K. The linker would emit a "region RAM overflowed" error. Cheapest fixes that keep functionality:

  • Move truly constant data out of RAM into .rodata (Flash) if any was mistakenly in RAM — frees RAM at no cost.
  • Or, if the 32K heap is genuinely required, no software trick creates RAM: you must pick a chip with more RAM. But the cheapest software-only move is to reclaim the 8K slack + shrink an over-provisioned stack/DMA buffer if measurements allow. There is no free lunch — RAM is a hard physical limit ().

Exercise 5.2 (L5)

Prove, in words backed by the address arithmetic, why the following invariant must hold for a correct boot, and give the exact word count the copy loop performs in terms of the exported symbols:

Recall Solution 5.2

Why the invariant holds. .data occupies one contiguous block. In RAM it spans _sdata_edata, so its size is . In Flash the same bytes are mirrored starting at _sidata (the left–right amber twins in Figure 1, the mirror in Figure 2). Since the copy is byte-for-byte, the Flash mirror must span an identical number of bytes, i.e. from _sidata to _sidata + (\_edata - \_sdata). If these two sizes disagreed, either some init values would never be copied or the loop would read past the mirror. The linker guarantees equality because both are computed from the same SIZEOF(.data).

Word count. With uint32_t (4-byte) pointers: assuming .data is 4-byte aligned and a multiple of 4 in size (guaranteed by the ALIGN(4) directives in Ex 4.1). For the numbers of Ex 4.2, words.


Recall One-line self-test before you leave

Flash image counts .text + .rodata + .data; RAM counts .data + .bss + stack + heap. .data appears in both because it has two addresses (LMA in Flash, VMA in RAM).

Flashcards

Does static int cache = 0; go in .data or .bss?
.bss — a zero initializer is the same as none; only non-zero initializers force .data.
Is .data counted in the Flash image size?
Yes — its initial values are mirrored in Flash at the LMA; that copy is real bytes in the .bin.
A uint32_t* copy loop over a 16-byte .data region runs how many iterations?
4 — each q++ advances 4 bytes, so 16/4 = 4 words.
Why does removing KEEP around .isr_vector cause a hard fault?
GC drops the unreferenced table; the CPU reads garbage for the stack pointer and Reset_Handler at reset.
What does the linker location counter . do?
It is a cursor starting at a region's ORIGIN that advances by each placed section's size; stamping it into a symbol records the current address.
What does ALIGN(4) do and why is it needed for .data?
Bumps . up to the next multiple of 4 so the word-wise copy loop reads/writes legally aligned addresses.
If RAM demand exceeds LENGTH, what is the only real fix (no free lunch)?
Use less RAM (move const to Flash, shrink buffers) or choose a chip with more RAM — you cannot conjure RAM.