Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Windows Tipps & SecurityTestMu AI Review: How AI is Solving the Quality Engineering Problem(23.09.2026 um 13:18 Uhr)
Windows Tipps & SecurityAmazon haut den kabellosen Dyson V8 Stabstaubsauger zum Tiefstpreis raus(24.09.2026 um 09:32 Uhr)
Windows Tipps & SecurityUpdates beheben etliche Schwachstellen in Foxit PDF Reader(24.09.2026 um 09:44 Uhr)
Windows Tipps & Security„Vom Experience Center zum monumentalen Signage-Projekt“(24.09.2026 um 10:30 Uhr)
Windows Tipps & SecurityTestMu AI Review: How AI is Solving the Quality Engineering Problem(23.09.2026 um 13:18 Uhr)
Windows Tipps & SecurityAmazon haut den kabellosen Dyson V8 Stabstaubsauger zum Tiefstpreis raus(24.09.2026 um 09:32 Uhr)
Windows Tipps & SecurityUpdates beheben etliche Schwachstellen in Foxit PDF Reader(24.09.2026 um 09:44 Uhr)
Windows Tipps & Security„Vom Experience Center zum monumentalen Signage-Projekt“(24.09.2026 um 10:30 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Linker Scripts Explained: Controlling Memory Layout on Bare Metal

The Anatomy of a Minimal Linker Script ENTRY Directive MEMORY Block SECTIONS Block The Location Counter: . VMA vs LMA: Virtual and Load Memory Addresses Linker-Defined Symbols The Map File Complete Minimal…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!


  • The Anatomy of a Minimal Linker Script


    • ENTRY Directive

    • MEMORY Block

    • SECTIONS Block






  • The Location Counter: .


  • VMA vs LMA: Virtual and Load Memory Addresses


  • Linker-Defined Symbols


  • The Map File


  • Complete Minimal Example




  • Verification: What the Linker Produced


    • Step 1: Examine Disassembly

    • Step 2: Examine Section Headers

    • Step 3: Examine the Map File






  • Verification: Loading in QEMU and Inspecting with GDB


  • Alignment Directives


  • Conclusion







Compilers generate relocatable object code—machine instructions and data whose addresses are not yet fixed. On hosted systems, a loader chooses the runtime addresses of each segment. Bare-metal systems have no loader: the ELF file’s section addresses become the addresses used directly by the CPU.



A linker script provides this mapping. It determines:





  1. What memory regions are available? For example, FLASH at 0x00000000 (64M), RAM at 0x60000000 (128M), etc.


  2. Where should each section go? .text to flash, .bss to RAM, etc.


  3. Exactly where each section begins, with alignment enforced according to architecture requirements.



Without an explicit script, the linker defaults to placing sections at low addresses such as 0x00000000, which rarely matches real hardware memory layouts. Bare-metal firmware requires deterministic placement, so linker scripts are a fundamental tool.



In this post, the linker script targets QEMU’s vexpress-a9, where RAM begins at 0x60000000. QEMU’s -kernel argument loads ELF segments to their VMA addresses, so linking into RAM is sufficient for initial bring-up.








The Anatomy of a Minimal Linker Script



A linker script has two primary blocks: MEMORY and SECTIONS.




ENTRY(_start)

MEMORY
{
RAM (rwx) : ORIGIN = 0x60000000, LENGTH = 128M
}

SECTIONS
{
. = 0x60000000;
.text : {
*(.text)
} > RAM
}






Each component has a specific purpose.








ENTRY Directive



ENTRY(_start) specifies the program's entry point symbol. When the ELF file is loaded, this symbol's address becomes the starting point that debuggers and loaders recognize. In bare metal, _start should match the first instruction executed after the reset vector transfers control.



Note that QEMU does not read an architectural reset vector for vexpress-a9 when using -kernel. Instead, it sets the CPU’s PC to the ELF entry point address.








MEMORY Block



The MEMORY block declares available address regions and their properties. Each region has:





  • Name: RAM, FLASH - labels used in SECTIONS


  • Attributes: r (read), w (write), x (execute)


  • ORIGIN: the starting address of the region


  • LENGTH: the size in bytes



Example:




MEMORY
{
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 64M
RAM (rwx) : ORIGIN = 0x60000000, LENGTH = 128M
}






Attributes are advisory. They let the linker validate that sections are placed in regions matching their needs:





  • FLASH (rx): read and execute only; marking it writable would be nonsensical


  • RAM (rwx): fully flexible for code, initialized data, uninitialized data



The linker issues warnings if a section with write permission is assigned to a read-only region, helping catch configuration mistakes.








SECTIONS Block



The SECTIONS block specifies the output memory layout. Each entry maps input sections (from object files) to output sections (in the final binary) and assigns a memory location.




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

.rodata : {
*(.rodata*)
} > FLASH
}






Breaking this down:





  • .text : { ... } defines an output section named .text


  • *(.text) means "include all .text input sections from all object files"


  • > FLASH assigns this output section to the FLASH memory region



The * is a wildcard matching all input files. More specific patterns are possible (e.g., startup.o(.text) to include only startup's .text section), but wildcards are typical for bare-metal work.








The Location Counter: .



The linker maintains an implicit variable called the location counter, written as a dot: .. The location counter tracks the current position within memory and automatically increments as sections are laid out.




SECTIONS
{
. = 0x60000000; /* Set location counter to 0x60000000 */

.text : {
*(.text) /* Place .text at current location */
} > RAM
/* After .text, . is automatically incremented by .text's size */

.rodata : {
*(.rodata*) /* Place .rodata immediately after .text */
} > RAM
}






Explicit assignment of . ensures predictable placement. Without . = 0x60000000;, the linker might place .text at 0x0 by default, ignoring the intended RAM region.



The location counter can also be used to create symbols:




_text_start = .;            /* Symbol marks current position */
.text : { *(.text) } > RAM
_text_end = .; /* Symbol marks end position */






These symbols have no storage; they are merely addresses that can be referenced from assembly or C.








VMA vs LMA: Virtual and Load Memory Addresses



Every output section has two associated addresses:





  • VMA (Virtual Memory Address): where the section resides during execution (runtime)


  • LMA (Load Memory Address): where the section is stored initially (typically in non-volatile flash)



In simple cases—executable code stored and executed from the same location—VMA and LMA are identical. Both the > RAM clause and implicit location counter assignment govern VMA.



The .data section stores its initial contents in flash (LMA) but executes from RAM (VMA). Startup code copies the data from flash to RAM before execution.



Linker script syntax to specify both addresses:




.data : {
*(.data)
} > RAM AT > FLASH /* VMA in RAM, LMA in FLASH */






The > RAM specifies VMA. The AT > FLASH specifies LMA. The linker stores the section in FLASH (LMA) but generates symbols and relocation information assuming runtime execution from RAM (VMA).








Linker-Defined Symbols



The linker can create symbols by assigning the location counter or other expressions to a name. These symbols exist only as addresses; they occupy no storage.




SECTIONS
{
. = 0x60000000;

_text_start = .; /* Symbol: address where .text begins */

.text : {
*(.text)
} > RAM

_text_end = .; /* Symbol: address where .text ends */

_stack_top = ORIGIN(RAM) + LENGTH(RAM); /* Custom symbol */
}






In assembly, these symbols are referenced using the = pseudo-op:




ldr r0, =_text_start        @ Load symbol address into r0
ldr sp, =_stack_top @ Load stack top address into sp






The = pseudo-op generates an LDR instruction with a literal pool reference. The linker resolves the symbol address and embeds it in the instruction encoding. When the CPU executes the LDR, the symbol's address is loaded into the register.








The Map File



The linker can generate a map file showing all sections, symbols, and their addresses. Generate it by adding -Map=output.map to the linker command:




arm-none-eabi-ld -T linker.ld startup.o -o boot.elf -Map=output.map











Complete Minimal Example



startup.s:




.global _start

.section .vectors, "ax"
_vectors:
b _start @ branch to start_

.section .text
_start:
ldr r0, =0xDEADBEEF @ Load test value
b . @ Infinite loop






Note that QEMU does not use the vectors section above as a real reset vector when booting with -kernel.



linker.ld:




ENTRY(_start)

MEMORY
{
RAM (rwx) : ORIGIN = 0x60000000, LENGTH = 128M
}

SECTIONS
{
. = 0x60000000;

.vectors : {
*(.vectors)
} > RAM

.text : {
*(.text)
} > RAM
}






Build Commands




# Assemble the startup code
arm-none-eabi-as -mcpu=cortex-a9 -g startup.s -o startup.o

# Link with the linker script
arm-none-eabi-ld -T linker.ld startup.o -o boot.elf

# Generate a map file
arm-none-eabi-ld -T linker.ld startup.o -o boot.elf -Map=output.map

# Create raw binary (optional, for certain QEMU loading modes)
arm-none-eabi-objcopy -O binary boot.elf boot.bin











Verification: What the Linker Produced








Step 1: Examine Disassembly






arm-none-eabi-objdump -d boot.elf






Excerpt:




Disassembly of section .vectors:

60000000 <_vectors>:
60000000: eaffffff b 60000004 <_start>

Disassembly of section .text:

60000004 <_start>:
60000004: e51f0000 ldr r0, [pc, #-0] @ 6000000c <_start+0x8>
60000008: eafffffe b 60000008 <_start+0x4>
6000000c: deadbeef .word 0xdeadbeef






Key observations:




  • Addresses in disassembly match the VMA from objdump -h


  • .vectors at 0x60000000 contains a branch to _start at 0x60000004


  • ldr r0, =0xDEADBEEF -> ldr r0, [pc, #-0] is located at 0x60000004 and it depends on the literal stored at 0x6000000c.

  • The infinite loop b . branches to itself at 0x60000008

  • Literal deadbeef is stored at 0x6000000c








Step 2: Examine Section Headers






arm-none-eabi-objdump -h boot.elf






Excerpt:




Sections:
Idx Name Size VMA LMA File off Algn
0 .vectors 00000004 60000000 60000000 00001000 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .text 0000000c 60000004 60000004 00001004 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE






Key observations:




  • VMA shows where code resides at runtime: 0x60000000 for .vectors, 0x60000004 for .text

  • LMA matches VMA in this simple case (both in RAM)

  • Section sizes: 4 bytes for the branch instruction in .vectors, 12 bytes for the load and branch in .text








Step 3: Examine the Map File






cat output.map | head -30






Excerpt:




Memory Configuration

Name Origin Length Attributes
RAM 0x60000000 0x08000000 xrw
*default* 0x00000000 0xffffffff

Linker script and memory map

0x60000000 . = 0x60000000

.vectors 0x60000000 0x4
*(.vectors)
.vectors 0x60000000 0x4 startup.o

.text 0x60000004 0xc
*(.text)
.text 0x60000004 0xc startup.o
0x60000004 _start











Verification: Loading in QEMU and Inspecting with GDB



Launch QEMU with GDB Server:




qemu-system-arm -M vexpress-a9 -cpu cortex-a9 -m 128M \
-kernel boot.elf \
-nographic \
-S -gdb tcp::1234






Flags:





  • -kernel boot.elf: load ELF file (QEMU parses program headers and loads sections to their VMA)


  • -S: start halted, waiting for debugger connection


  • -gdb tcp::1234: open GDB server on port 1234



Connect GDB and Inspect:




gdb-multiarch boot.elf
(gdb) target remote :1234
Remote debugging using :1234
_start () at startup.s:9
9 ldr r0, =0xDEADBEEF @ Load test value
(gdb) info registers pc
pc 0x60000004 0x60000004 <_start>






The program counter starts at 0x60000004, points to the next instruction.



Disassemble in GDB:




(gdb) disassemble _start
Dump of assembler code for function _start:
=> 0x60000004 <+0>: ldr r0, [pc, #-0] @ 0x6000000c <_start+8>
0x60000008 <+4>: b 0x60000008 <_start+4>
0x6000000c <+8>: cdple 14, 10, cr11, cr13, cr15, {7}
End of assembler dump.






Instructions are at exact addresses from objdump, confirming the linker placed code at the intended locations.



The literal 0xDEADBEEF appears as a coprocessor instruction in disassembly because objdump interprets raw data as instructions when listing code; this is expected.



Step Through Instructions:




(gdb) info registers r0
r0 0x0 0
(gdb) break _start
Breakpoint 1 at 0x60000004: file startup.s, line 9.
(gdb) stepi
10 b . @ Infinite loop
(gdb) info registers r0
r0 0xdeadbeef -559038737






After the instruction 0x60000004, r0 contains the test value, proving the instruction executed and the linker's address assignment was correct.



Inspect Memory:




(gdb) x/4i 0x60000000
0x60000000 <_vectors>: b 0x60000004 <_start>
0x60000004 <_start>: ldr r0, [pc, #-0] @ 0x6000000c <_start+8>
=> 0x60000008 <_start+4>: b 0x60000008 <_start+4>
0x6000000c <_start+8>: cdple 14, 10, cr11, cr13, cr15, {7}






Memory contains the expected branch and ldr instructions at exact addresses, confirming the linker-assigned layout matches actual memory.








Alignment Directives



ARMv7 uses 4-byte instructions and requires 4-byte alignment. Explicit alignment ensures that sections begin at valid boundaries.




SECTIONS
{
. = 0x60000000;
. = ALIGN(4); /* Ensure 4-byte alignment */

.vectors : {
*(.vectors)
} > RAM

. = ALIGN(4); /* Ensure next section is aligned */

.text : {
*(.text)
} > RAM
}






The ALIGN(n) function rounds the location counter up to the next multiple of n bytes. If already aligned, it is a no-op. If misaligned, it advances the counter and introduces padding.








Conclusion



A linker script describes the mapping from ELF sections to concrete memory addresses and serves as the bridge between compiler output and hardware layout. By defining memory regions, assigning sections, managing alignment, and generating linker-defined symbols, the script establishes the program’s static memory structure. Verifying these decisions with map files and objdump ensures the final image matches the intended layout.



With this foundation established, the next step is to examine how execution begins—specifically, the reset mechanism and the first instruction fetched by the CPU. Understanding reset behavior complements the static layout described here and completes the initial stage of bare-metal bring-up.

SOC Incident Playbook: Remote Code Execution (RCE) Defense
title: Detect Exploitation - Linker Scripts Explained: Controlling Memory Layout on Bare Metal
id: 2bf70252-5287-4222-b875-081e3a31b5b1
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-24
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-24"
        description = "YARA Signature for "
    strings:
        $str = "Linker Scripts Explained: Cont" ascii wide
    condition:
        any of them
}
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich Linker Scripts Explained: Controlling Me.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Linker Scripts Explained: Controlling Memory Layout on Bare Metal

Thematisch verwandte Begriffe: Linker, Scripts, Explained, Controlling · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-97056 | SigNoz versions from v0.98.0 up to (but not including) v0.143.0, when co…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel TTP ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick