Scripts de linker, seções e mapas de memória

TL;DR


1. O trabalho do linker (definição prática)

Dado:

O linker produz:


2. Seções que você precisa conhecer

SeçãoSignificadoBytes no arquivo?Bytes na memória?
.textcódigosimsim
.rodataconstantes somente leiturasimsim
.dataglobais inicializadassimsim
.bssglobais zero-inicializadasnãosim

3. VMA vs LMA (por que podem existir dois endereços)

Em firmware bare-metal, um padrão comum é:

Isso implica:

Vamos usar primeiro um layout simples de RAM única e depois explicar a ideia de “copy down”.


4. Na prática: um programa RV32 bare-metal com posicionamento explícito

Crie:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// src/ld_demo.c
#include "types.h"

volatile u32 g_counter = 0u;      // .bss ou .data dependendo da init
volatile u32 g_init    = 0x1234u; // .data

void _start(void) {
  for (;;) {
    g_counter++;
    g_init ^= 0x1111u;
  }
}

Crie um script de linker:

/* src/ld_demo.ld */
OUTPUT_ARCH(riscv)
ENTRY(_start)

MEMORY {
  RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 16M
}

SECTIONS {
  . = ORIGIN(RAM);

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

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

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

  .bss : {
    *(.bss .bss.*)
    *(COMMON)
  } > RAM
}

Compile:

1
2
3
4
5
6
7
riscv64-unknown-elf-gcc -O0 -g -ffreestanding -nostdlib \
  -march=rv32im -mabi=ilp32 \
  -T src/ld_demo.ld \
  -Wl,-Map=build/ld_demo.map \
  -o build/ld_demo.elf src/ld_demo.c

qemu-system-riscv32 -M virt -nographic -bios none -kernel build/ld_demo.elf

5. Leia o map file como um detetive

Abra o map:

1
less build/ld_demo.map

O que procurar:

Um map file é a chave para “por que isso está neste endereço?”.


6. Verifique endereços de seções com readelf

1
2
readelf -S build/ld_demo.elf
readelf -s build/ld_demo.elf | grep -E '_start$|g_counter$|g_init$'

Você deve ver:


7. ELF -> binário bruto e a armadilha de “sem endereços”

Crie um .bin:

1
riscv64-unknown-elf-objcopy -O binary build/ld_demo.elf build/ld_demo.bin

Agora compare tamanhos:

1
ls -l build/ld_demo.elf build/ld_demo.bin

8. Alinhamento e por que importa

Duas realidades comuns de alinhamento:

Se você vir gaps estranhos, geralmente é alinhamento.

Você pode forçar alinhamento explicitamente:

.text : {
  . = ALIGN(16);
  *(.text .text.*)
} > RAM

9. Um preview de startup code (o que estamos pulando por enquanto)

Em sistemas bare-metal reais, normalmente existe uma rotina de startup que:

Estamos mantendo os exemplos iniciais mínimos de propósito, para você enxergar o posicionamento sem maquinário extra.


Exercícios

  1. Mude ORIGIN(RAM) de 0x80000000 para 0x80200000. Verifique que _start se moveu.
  2. Adicione um novo const char msg[] = "hi"; e confirme que aparece em .rodata.
  3. Gere um disassembly e confirme que loads/stores para g_counter usam o endereço absoluto (ou uma sequência que o calcula).
  4. Tente -Wl,--gc-sections junto com -ffunction-sections -fdata-sections e observe o que é removido.

Como testar suas respostas


Resumo

Você aprendeu como scripts de linker definem um mapa de memória, como seções são posicionadas e como validar isso com map files e readelf.

A seguir: ponto flutuante, endianness e bit packing, vamos explicar por que constantes double às vezes aparecem como dois .word e como verificar isso com Python.