Scripts de linker, seções e mapas de memória
TL;DR
- Você vai aprender o que o linker realmente faz: coloca código/dados em endereços seguindo regras e produz um ELF onde endereços virtuais batem com o mapa de memória do alvo.
- Vai aprender como
.text,.rodata,.data,.bsse o código de startup se relacionam com VMA (Virtual Memory Address) e LMA (Load Memory Address). - Vai escrever um script de linker para um pequeno programa RV32 bare-metal, gerar um link map (
.map) e converter o ELF em um.binbruto.
1. O trabalho do linker (definição prática)
Dado:
- arquivos objeto (
.o) contendo seções e relocações, - bibliotecas (opcional),
- um layout de memória,
O linker produz:
- um ELF executável com endereços finais,
- símbolos resolvidos,
- e (opcionalmente) um arquivo map explicando cada decisão de posicionamento.
2. Seções que você precisa conhecer
| Seção | Significado | Bytes no arquivo? | Bytes na memória? |
|---|---|---|---|
.text | código | sim | sim |
.rodata | constantes somente leitura | sim | sim |
.data | globais inicializadas | sim | sim |
.bss | globais zero-inicializadas | não | sim |
.bss não tem bytes no arquivo porque é representada como “aloque N bytes e preencha com zeros”.3. VMA vs LMA (por que podem existir dois endereços)
- VMA (Virtual Memory Address): onde os bytes vivem quando o programa roda.
- LMA (Load Memory Address): onde os bytes ficam armazenados na imagem que os carrega.
Em firmware bare-metal, um padrão comum é:
- o código roda na RAM,
- mas
.datafica armazenado na flash e é copiado para a RAM no boot.
Isso implica:
- o VMA de
.datafica na RAM, - o LMA de
.datafica na flash.
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:
| |
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:
| |
5. Leia o map file como um detetive
Abra o map:
| |
O que procurar:
- a faixa de endereços atribuída a
.text - onde
.datafoi parar - onde
.bsscomeça - endereços dos símbolos
_start,g_counter,g_init
Um map file é a chave para “por que isso está neste endereço?”.
6. Verifique endereços de seções com readelf
| |
Você deve ver:
_startem ou perto de0x80000000(dependendo do alinhamento)g_initem.datag_counterem.bss
7. ELF -> binário bruto e a armadilha de “sem endereços”
Crie um .bin:
| |
Agora compare tamanhos:
| |
.bin bruto não tem tabela de símbolos, nem headers de seção, nem ideia de onde pertence na memória. Você precisa fornecer o endereço de carga externamente (boot ROM, bootloader, docs do SoC etc.).8. Alinhamento e por que importa
Duas realidades comuns de alinhamento:
- instruções e dados têm alinhamentos naturais (word aligned, etc.)
- linkers alinham seções para que código e dados continuem eficientes
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:
- configura o stack pointer
- copia
.datade ROM/flash para RAM - zera
.bss - e então chama
main
Estamos mantendo os exemplos iniciais mínimos de propósito, para você enxergar o posicionamento sem maquinário extra.
Exercícios
- Mude
ORIGIN(RAM)de0x80000000para0x80200000. Verifique que_startse moveu. - Adicione um novo
const char msg[] = "hi";e confirme que aparece em.rodata. - Gere um disassembly e confirme que loads/stores para
g_counterusam o endereço absoluto (ou uma sequência que o calcula). - Tente
-Wl,--gc-sectionsjunto com-ffunction-sections -fdata-sectionse observe o que é removido.
Como testar suas respostas
- Use
readelf -Sereadelf -spara validar posicionamento. - Use o map file como verdade fundamental.
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.