Explication détaillée d'un script de lien ld pour GCC avec exemple

Introduction aux scripts de lien

Les scripts de lien décrivent la disposition d'un progrmame dans l'espace mémoire. Le compilateur GCC les utilise pour générer des fichiers exécutables. Différentes architectures emploient des formats variés : par exemple, ARM Compiler utilise le format scat, tandis que GCC repose sur le format ld, chacun avec sa propre syntaxe.

Lors de la compilation de fichiers C, GCC produit trois parties principales : RO (section en lecture seule, souvent .text pour le code), RW (section en lecture-écriture, .data pour les variables initialisées) et ZI (section non initialisée, .bss pour les variables sans valeur assignée, sans occuper d'espace ROM). Le script de lien agrège ces sections compilées.

Sections d'entrée et de sortie

Les sections de sortie (ou nœuds) définissent les blocs mémoire, comme .text, .data ou .bss. Les sections d'entrée spécifient les éléments à inclure, par exemple des ficheirs objectifs ou des segments spécifiques. Si une section d'entrée n'est pas assignée à une section de sortie, le linker crée automatiquement une section de sortie portant le même nom.

Exemple de structure :

.text /* Section de sortie */
{
    *main.o(segment_code) /* Section d'entrée */
    *(.text)      /* Section d'entrée */
    *(.rodata)   /* Section d'entrée */
} >MEM_PRINCIPAL

L'objectif principal est de mapper les sections .text, .data et .bss aux adresses mémoire souhaitées.

Exemple pratique

Voici un script de lien ld révisé avec des noms et adresses modifiés pour illustration :

ENTRY(debut_code)

MEMORY
{
    MEM_PRINCIPAL (xrw) : ORIGIN = 0x10000000, LENGTH = 4M
    MEM_SECONDAIRE (xrw) : ORIGIN = 0x40000000, LENGTH = 256M
}

SECTIONS
{
    . = ORIGIN(MEM_PRINCIPAL);

    .text
    {
        *init.o(segment_demarrage)
        *(.text)
        *(.rodata)
    } >MEM_PRINCIPAL AT>STOCKAGE_FLASH

    .bss (NOLOAD) : SUBALIGN(0x20)
    {
        PROVIDE(_debut_bss = ABSOLUTE(.));
        *(.bss)
        PROVIDE(_fin_bss = ABSOLUTE(.));
    } >MEM_PRINCIPAL AT>STOCKAGE_FLASH
}

Analyse de l'exemple

ENTRY

ENTRY(debut_code) définit le point d'entrée du programme, ici debut_code. Sans cette directive, le linker utiliserait par défaut _start ou l'adresse de la première instruction. Des priorités existent, comme l'option -e en ligne de commande.

MEMORY

Cette directive déclare des régions mémoire avec des attributs (x pour exécutable, r pour lecture, w pour écriture) et des tailles. Les noms, comme MEM_PRINCIPAL, sont internes au script. On peut ensuite assigner des sections à ces régions avec >region.

SECTIONS

La directive SECTIONS organise les sections de sortie. L'opérateur . représente le compteur d'adresses courant, ici initialisé à ORIGIN(MEM_PRINCIPAL).

  • Nom de la section de sortie : Doit correspondre au format de sortie, comme .text, .data ou .bss.
  • Attributs : NOLOAD indique que la section n'est pas chargée en mémoire à l'exécution. D'autres comme DSECT ou INFO sont rarement utilisés.
  • Adresses : Chaque section a une VMA (adresse virtuelle pour l'exécution) et une LMA (adresse de chargement). Par exemple, avec >MEM_PRINCIPAL pour VMA et AT>STOCKAGE_FLASH pour LMA, le code s'exécute en RAM mais est stocké en Flash.
  • Alignement : ALIGN force l'alignement de sortie, SUBALIGN écrase l'alignement d'entrée.
  • Remplissage : Utilisez FILL pour combler les lacunes, comme FILL(0xAAAAAAAA) pour remplir avec des motifs spécifiques.
  • Sections d'entrée : Décrivent les fichiers et segments à inclure. Par exemple, main.o(.text) ou des listes comme *(.text .data) avec ordre fixe ou variable.
  • Symboles courants : Les symboles COMMON sont souvent placés dans .bss via *(COMMON).
  • PROVIDE : Définit un symbole conditionnel, comme PROVIDE(symbole_temp = .), qui n'est utilisé que si référencé ailleurs. Cela permet une redéfinition en code C sans préfixe.
  • ABSOLUTE : Convertit l'adresse courante en valeur absolue, utile pour capturer des positions, comme _fin_bss = ABSOLUTE(.).
  • KEEP : Prévient la collecte de déchets, par exemple KEEP(*(.vectors)) pour préserver des sections critiques.
  • Autres symboles : LOADADDR obtient la LMA, ADDR obtient la VMA, et LONG stocke des données de 4 octets.

Étiquettes: GCC ld script-de-lien SECTIONS MEMORY

Publié le 14 juillet à 20h18