Course contentsShow
← Rust
Lesson 1491 of 1,91012. Unsafe Rust and Low-Level AccessFree lesson

Low-Level Memory Map Layout

You will define custom linker scripts to map memory sections to physical addresses.

In this lesson, you will learn how to gain absolute control over where your code and data reside in physical memory by defining custom linker scripts.

The Linker’s Responsibility

When you compile a Rust program, the linker aggregates various object files and assigns them addresses. By default, it follows standard platform conventions (like ELF for Linux). However, in embedded or low-level systems, you often need to place specific data (like a bootloader or hardware-mapped registers) at precise physical addresses. This is achieved by creating a linker.ld script.

Defining Sections and Addresses

A linker script acts as a map, telling the linker exactly where to place specific memory sections. Suppose you want to ensure your code begins at 0x08000000 and your volatile data lives in a specific RAM block. You define these regions within the script:

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

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

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

In the example above, the MEMORY block defines the physical boundaries of the device. The SECTIONS block then maps your object sections (the output of rustc) into these regions. Notice the AT > FLASH syntax for .data: this tells the linker that the initial values for your variables reside in non-volatile storage (FLASH), but they must be copied to RAM at runtime before your code executes.

Why this matters

Without this manual intervention, the linker might place critical hardware-interface code in a memory region that the CPU cannot access or that lacks the necessary execution permissions. By crafting your own layout, you enforce the physical invariants required by your hardware architecture, moving beyond the "general-purpose" assumptions of the standard OS-level linker behavior. This is the foundation upon which safe no_std abstractions are built.

Key Takeaway: Custom linker scripts allow you to define physical memory boundaries and map logical code sections to specific hardware addresses, which is essential for low-level control and embedded system stability.