Linker scripts: controlando o layout de memória
1. Introdução ao Linker e ao Linker Script
O processo de compilação de um programa em C envolve quatro etapas principais: pré-processador, compilador, montador (assembler) e linker. Enquanto o compilador gera código objeto com endereços relativos, o linker é responsável por resolver símbolos e gerar o executável final com endereços absolutos. O linker script é o arquivo de configuração que controla exatamente como essa resolução ocorre.
Para desenvolvedores de sistemas embarcados, bootloaders ou kernels, o linker script é essencial. Ele permite definir onde cada segmento do programa será carregado na memória física, otimizar o uso de recursos limitados e criar segmentos personalizados para requisitos específicos de hardware.
2. Estrutura Básica de um Linker Script
Um linker script do GNU ld possui comandos globais e seções principais. Os comandos mais importantes são:
ENTRY: define o ponto de entrada do programaOUTPUT_FORMAT: especifica o formato do arquivo de saída (ELF, binary, etc.)OUTPUT_ARCH: define a arquitetura alvoMEMORY: declara as regiões de memória disponíveisSECTIONS: mapeia as seções de entrada para as regiões de memória
Exemplo mínimo:
ENTRY(_start)
OUTPUT_FORMAT(elf32-littlearm)
OUTPUT_ARCH(arm)
SECTIONS
{
. = 0x00000000;
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
}
3. A Seção MEMORY: Definindo Regiões de Memória
A seção MEMORY declara regiões físicas disponíveis no hardware. Cada região possui nome, endereço inicial, tamanho e atributos de acesso.
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
Atributos de acesso:
- r (read) - região legível
- w (write) - região gravável
- x (execute) - região executável
- a (allocatable) - região alocável
Para um microcontrolador típico, a Flash armazena o código e constantes, enquanto a RAM contém dados variáveis e pilha.
4. A Seção SECTIONS: Controlando o Posicionamento dos Segmentos
A seção SECTIONS define como as seções de entrada (.text, .data, .bss) são organizadas nas regiões de memória. Comandos importantes incluem ALIGN, AT (para carregamento em endereço diferente) e KEEP (para evitar que o linker remova seções).
Exemplo prático posicionando .text na Flash e .data + .bss na RAM:
SECTIONS
{
. = ORIGIN(FLASH);
.text : {
*(.text)
*(.text.*)
*(.rodata)
*(.rodata.*)
. = ALIGN(4);
_etext = .;
} > FLASH
. = ORIGIN(RAM);
.data : AT(LOADADDR(.text) + SIZEOF(.text)) {
_sdata = .;
*(.data)
*(.data.*)
. = ALIGN(4);
_edata = .;
} > RAM
.bss : {
_sbss = .;
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} > RAM
. = ALIGN(4);
_end = .;
}
Note o uso de AT() para indicar que .data é carregado na Flash mas executado na RAM. O símbolo _etext marca o fim do código, e _sdata/_edata delimitam a seção de dados inicializados.
5. Inicialização de Dados e Seções Especiais
No código de inicialização (crt0), é necessário copiar .data da ROM para a RAM e zerar .bss. O linker script fornece os símbolos necessários:
extern uint32_t _sdata, _edata, _sbss, _ebss;
extern uint32_t _etext;
void _start(void) {
// Copiar .data da Flash para RAM
uint32_t *src = &_etext;
uint32_t *dst = &_sdata;
while (dst < &_edata) *dst++ = *src++;
// Zerar .bss
for (dst = &_sbss; dst < &_ebss; *dst++ = 0);
// Chamar main()
main();
}
Seções customizadas como .noinit (dados que persistem após reset) podem ser adicionadas:
.noinit (NOLOAD) : {
*(.noinit)
*(.noinit.*)
} > RAM
A diretiva NOLOAD impede que o linker tente inicializar essa seção.
6. Símbolos e Expressões no Linker Script
Símbolos podem ser declarados com PROVIDE (define se não existir) ou HIDDEN (não exportado). Expressões aritméticas permitem cálculos dinâmicos:
PROVIDE(__stack_top = ORIGIN(RAM) + LENGTH(RAM));
PROVIDE(__heap_start = _end);
PROVIDE(__heap_size = __stack_top - __heap_start);
ASSERT(__heap_size > 0, "Heap size must be positive");
Funções especiais incluem:
- ADDR(section): endereço virtual da seção
- LOADADDR(section): endereço de carga da seção
- SIZEOF(section): tamanho da seção
- ABSOLUTE(expr): força expressão a ser absoluta
Exemplo para cálculo de heap:
__heap_start = _end;
__heap_end = __stack_top - 1024; /* reservar 1KB para pilha */
__heap_size = __heap_end - __heap_start;
7. Técnicas Avançadas e Casos de Uso
Overlays: permitem que múltiplas seções compartilhem o mesmo espaço de endereço, carregadas sob demanda:
OVERLAY : {
.overlay1 { *(.overlay1) }
.overlay2 { *(.overlay2) }
} > RAM
Múltiplas memórias: sistemas com TCM (Tightly Coupled Memory) e SDRAM:
MEMORY
{
ITCM (rx) : ORIGIN = 0x00000000, LENGTH = 16K
DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K
SDRAM (rwx): ORIGIN = 0x70000000, LENGTH = 64M
}
SECTIONS
{
.text : { *(.text) } > ITCM
.data : { *(.data) } > DTCM
.heap : { *(.heap) } > SDRAM
}
Debugging: para inspecionar o layout final, use:
arm-none-eabi-objdump -h programa.elf
arm-none-eabi-readelf -S programa.elf
O mapa de símbolos gerado pelo linker (-Map=programa.map) mostra endereços e tamanhos detalhados.
Armadilhas comuns:
- Alinhamento incorreto: usar ALIGN(4) para alinhamento word
- Seções órfãs: verificar se todas as seções de entrada têm mapeamento
- Conflitos de símbolos: usar PROVIDE com cuidado
8. Conclusão e Boas Práticas
Os comandos essenciais do linker script incluem MEMORY, SECTIONS, ENTRY, PROVIDE e ASSERT. Para criar um linker script funcional, siga este checklist:
- Defina regiões de memória com
MEMORY - Mapeie
.texte.rodatapara memória executável - Configure
.datacomAT()para cópia ROM→RAM - Defina
.bsssem inicialização - Declare símbolos para o código de inicialização
- Inclua seções customizadas conforme necessário
- Use
ASSERTpara validar tamanhos - Gere mapa de símbolos para verificação
A integração com ferramentas como Makefile ou CMake é direta:
# Makefile
LDFLAGS = -T linker.ld -Map=output.map
Dominar linker scripts é fundamental para projetos embarcados profissionais, permitindo controle preciso sobre o uso de memória e a inicialização do sistema.
Referências
- GNU LD Documentation - Linker Scripts — Documentação oficial completa sobre linker scripts do GNU ld
- Barr Group - Linker Scripts for Embedded Systems — Tutorial prático sobre linker scripts em sistemas embarcados
- Embedded Artistry - Linker Scripts Guide — Guia abrangente com exemplos para ARM Cortex-M
- OSDev - Linker Scripts — Referência técnica para desenvolvimento de sistemas operacionais
- Microchip - Linker Script Tutorial — Tutorial com exemplos para microcontroladores PIC e ARM
- IAR Systems - Linker Script Guide — Guia sobre linker scripts no contexto da ferramenta IAR Embedded Workbench