Pour comprendre les éditeurs de liens, il est utile de comprendre d'abord ce qui se passe "sous le capot" lorsque vous convertissez un fichier source (tel qu'un fichier C ou C ++) en fichier exécutable (un fichier exécutable est un fichier qui peut être exécuté sur votre machine ou la machine de quelqu'un d'autre exécutant la même architecture de machine).
Sous le capot, lorsqu'un programme est compilé, le compilateur convertit le fichier source en code d'octet objet. Ce code d'octet (parfois appelé code objet) est des instructions mnémotechniques que seule l'architecture de votre ordinateur comprend. Traditionnellement, ces fichiers ont une extension .OBJ.
Une fois le fichier objet créé, l'éditeur de liens entre en jeu. Le plus souvent, un vrai programme qui fait quelque chose d'utile devra référencer d'autres fichiers. En C, par exemple, un programme simple pour imprimer votre nom à l'écran se composerait de:
printf("Hello Kristina!\n");
Lorsque le compilateur a compilé votre programme dans un fichier obj, il met simplement une référence à la printf
fonction. L'éditeur de liens résout cette référence. La plupart des langages de programmation ont une bibliothèque standard de routines pour couvrir les éléments de base attendus de ce langage. L'éditeur de liens relie votre fichier OBJ à cette bibliothèque standard. L'éditeur de liens peut également lier votre fichier OBJ avec d'autres fichiers OBJ. Vous pouvez créer d'autres fichiers OBJ qui ont des fonctions qui peuvent être appelées par un autre fichier OBJ. L'éditeur de liens fonctionne presque comme le copier-coller d'un traitement de texte. Il "copie" toutes les fonctions nécessaires auxquelles votre programme fait référence et crée un seul exécutable. Parfois, d'autres bibliothèques copiées dépendent encore d'autres fichiers OBJ ou de bibliothèque. Parfois, un éditeur de liens doit devenir assez récursif pour faire son travail.
Notez que tous les systèmes d'exploitation ne créent pas un seul exécutable. Windows, par exemple, utilise des DLL qui rassemblent toutes ces fonctions dans un seul fichier. Cela réduit la taille de votre exécutable, mais rend votre exécutable dépendant de ces DLL spécifiques. DOS utilisait des choses appelées Overlays (fichiers .OVL). Cela avait de nombreux objectifs, mais l'un était de conserver les fonctions couramment utilisées ensemble dans un fichier (un autre objectif, au cas où vous vous poseriez la question, était de pouvoir insérer de grands programmes en mémoire. DOS a une limitation de la mémoire et les superpositions pourraient être "déchargé" de la mémoire et d'autres superpositions pourraient être "chargées" au-dessus de cette mémoire, d'où le nom, "superpositions"). Linux a des bibliothèques partagées, ce qui est fondamentalement la même idée que les DLL (les gars de Linux du noyau dur que je connais me diraient qu'il y a BEAUCOUP DE GRANDES différences).
J'espère que cela vous aide à comprendre!
Exemple minimal de relocalisation d'adresse
La délocalisation d'adresses est l'une des fonctions cruciales de la liaison.
Voyons donc comment cela fonctionne avec un exemple minimal.
0) Présentation
Résumé: la relocalisation modifie la
.text
section des fichiers objets à traduire:Cela doit être fait par l'éditeur de liens car le compilateur ne voit qu'un seul fichier d'entrée à la fois, mais nous devons connaître tous les fichiers objets à la fois pour décider comment:
.text
et.data
sections de plusieurs fichiers objetsPrérequis: compréhension minimale de:
La liaison n'a rien à voir avec C ou C ++ spécifiquement: les compilateurs génèrent simplement les fichiers objets. L'éditeur de liens les prend alors comme entrée sans jamais savoir dans quel langage les compilait. Cela pourrait aussi bien être Fortran.
Alors pour réduire la croûte, étudions un NASM x86-64 ELF Linux bonjour le monde:
compilé et assemblé avec:
avec NASM 2.10.09.
1) .text de .o
Nous décompilons d'abord la
.text
section du fichier objet:qui donne:
les lignes cruciales sont:
qui devrait déplacer l'adresse de la chaîne hello world dans le
rsi
registre, qui est passé à l'appel système d'écriture.Mais attendez! Comment le compilateur peut-il savoir où
"Hello world!"
se retrouvera en mémoire lorsque le programme est chargé?Eh bien, ce n'est pas possible, surtout après avoir lié un tas de
.o
fichiers avec plusieurs.data
sections.Seul l'éditeur de liens peut le faire car lui seul aura tous ces fichiers objets.
Donc, le compilateur vient de:
0x0
sur la sortie compiléeCes "informations supplémentaires" sont contenues dans la
.rela.text
section du fichier objet2) .rela.text
.rela.text
signifie "relocalisation de la section .text".Le mot relocation est utilisé car l'éditeur de liens devra déplacer l'adresse de l'objet vers l'exécutable.
Nous pouvons démonter la
.rela.text
section avec:qui contient;
Le format de cette section est corrigé et documenté à l' adresse : http://www.sco.com/developers/gabi/2003-12-17/ch4.reloc.html
Chaque entrée indique à l'éditeur de liens une adresse qui doit être déplacée, ici nous n'en avons qu'une pour la chaîne.
Pour simplifier un peu, pour cette ligne particulière, nous avons les informations suivantes:
Offset = C
: quel est le premier octet de la.text
modification de cette entrée.Si nous regardons en arrière le texte décompilé, il est exactement à l'intérieur du critique
movabs $0x0,%rsi
, et ceux qui connaissent le codage des instructions x86-64 remarqueront que cela encode la partie adresse 64 bits de l'instruction.Name = .data
: l'adresse pointe vers la.data
sectionType = R_X86_64_64
, qui spécifie exactement quel calcul doit être effectué pour traduire l'adresse.Ce champ dépend en fait du processeur et est donc documenté sur l' extension AMD64 System V ABI section 4.4 «Relocation».
Ce document dit que
R_X86_64_64
:Field = word64
: 8 octets, donc l'00 00 00 00 00 00 00 00
adresse at0xC
Calculation = S + A
S
est la valeur à l'adresse à déplacer, donc00 00 00 00 00 00 00 00
A
est l'addend qui est0
ici. Il s'agit d'un champ de l'entrée de réinstallation.Donc
S + A == 0
et nous allons être relocalisés à la toute première adresse de la.data
section.3) .text de .out
Regardons maintenant la zone de texte de l'exécutable
ld
généré pour nous:donne:
Donc, la seule chose qui a changé depuis le fichier objet sont les lignes critiques:
qui pointent maintenant vers l'adresse
0x6000d8
(d8 00 60 00 00 00 00 00
en petit-boutiste) au lieu de0x0
.Est-ce le bon emplacement pour la
hello_world
chaîne?Pour décider, nous devons vérifier les en-têtes du programme, qui indiquent à Linux où charger chaque section.
Nous les démontons avec:
qui donne:
Cela nous indique que la
.data
section, qui est la seconde, commence àVirtAddr
=0x06000d8
.Et la seule chose sur la section de données est notre chaîne hello world.
Niveau bonus
PIE
liaison: Qu'est-ce que l'option -fPIE pour les exécutables indépendants de la position dans gcc et ld?la source
Dans des langages comme `` C '', les modules de code individuels sont traditionnellement compilés séparément en blobs de code objet, qui est prêt à s'exécuter à tous égards autre que toutes les références que le module fait en dehors de lui-même (c'est-à-dire à des bibliothèques ou à d'autres modules) ont pas encore été résolu (c'est-à-dire qu'ils sont vides, en attendant que quelqu'un arrive et fasse toutes les connexions).
Ce que fait l'éditeur de liens est de regarder tous les modules ensemble, de regarder ce dont chaque module a besoin pour se connecter à l'extérieur de lui-même, et de regarder toutes les choses qu'il exporte. Il corrige ensuite tout cela et produit un exécutable final, qui peut ensuite être exécuté.
Là où la liaison dynamique est également en cours, la sortie de l'éditeur de liens n'est toujours pas capable d'être exécutée - il y a encore des références à des bibliothèques externes non encore résolues, et elles sont résolues par le système d'exploitation au moment où il charge l'application (ou peut-être même plus tard pendant la course).
la source
Lorsque le compilateur produit un fichier objet, il inclut des entrées pour les symboles définis dans ce fichier objet et des références à des symboles qui ne sont pas définis dans ce fichier objet. L'éditeur de liens les prend et les assemble afin que (lorsque tout fonctionne correctement) toutes les références externes de chaque fichier soient satisfaites par des symboles définis dans d'autres fichiers objets.
Il combine ensuite tous ces fichiers objets ensemble et attribue des adresses à chacun des symboles, et lorsqu'un fichier objet a une référence externe à un autre fichier objet, il remplit l'adresse de chaque symbole partout où il est utilisé par un autre objet. Dans un cas typique, il construira également une table de toutes les adresses absolues utilisées, de sorte que le chargeur peut / va "réparer" les adresses lorsque le fichier est chargé (c'est-à-dire qu'il ajoutera l'adresse de chargement de base à chacun de ces adresses afin qu'elles renvoient toutes à l'adresse mémoire correcte).
De nombreux éditeurs de liens modernes peuvent également effectuer certains (dans certains cas, beaucoup ) d'autres "trucs", tels que l'optimisation du code d'une manière qui n'est possible qu'une fois que tous les modules sont visibles (par exemple, supprimer des fonctions qui ont été incluses car il était possible qu'un autre module puisse les appeler, mais une fois que tous les modules sont assemblés, il est évident que rien ne les appelle jamais).
la source