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:
- adresse du fichier objet
- dans l'adresse finale de l'exécutable
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:
- résoudre les symboles non définis comme les fonctions non définies déclarées
- pas de conflit entre plusieurs
.text
et .data
sections de plusieurs fichiers objets
Pré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:
section .data
hello_world db "Hello world!", 10
section .text
global _start
_start:
; sys_write
mov rax, 1
mov rdi, 1
mov rsi, hello_world
mov rdx, 13
syscall
; sys_exit
mov rax, 60
mov rdi, 0
syscall
compilé et assemblé avec:
nasm -o hello_world.o hello_world.asm
ld -o hello_world.out hello_world.o
avec NASM 2.10.09.
1) .text de .o
Nous décompilons d'abord la .text
section du fichier objet:
objdump -d hello_world.o
qui donne:
0000000000000000 <_start>:
0: b8 01 00 00 00 mov $0x1,%eax
5: bf 01 00 00 00 mov $0x1,%edi
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
14: ba 0d 00 00 00 mov $0xd,%edx
19: 0f 05 syscall
1b: b8 3c 00 00 00 mov $0x3c,%eax
20: bf 00 00 00 00 mov $0x0,%edi
25: 0f 05 syscall
les lignes cruciales sont:
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
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:
- met une valeur d'espace réservé
0x0
sur la sortie compilée
- donne des informations supplémentaires à l'éditeur de liens sur la façon de modifier le code compilé avec les bonnes adresses
Ces "informations supplémentaires" sont contenues dans la .rela.text
section du fichier objet
2) .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:
readelf -r hello_world.o
qui contient;
Relocation section '.rela.text' at offset 0x340 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
00000000000c 000200000001 R_X86_64_64 0000000000000000 .data + 0
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
section
Type = 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 est 0
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:
objdump -d hello_world.out
donne:
00000000004000b0 <_start>:
4000b0: b8 01 00 00 00 mov $0x1,%eax
4000b5: bf 01 00 00 00 mov $0x1,%edi
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
4000c1: 00 00 00
4000c4: ba 0d 00 00 00 mov $0xd,%edx
4000c9: 0f 05 syscall
4000cb: b8 3c 00 00 00 mov $0x3c,%eax
4000d0: bf 00 00 00 00 mov $0x0,%edi
4000d5: 0f 05 syscall
Donc, la seule chose qui a changé depuis le fichier objet sont les lignes critiques:
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
4000c1: 00 00 00
qui pointent maintenant vers l'adresse 0x6000d8
( d8 00 60 00 00 00 00 00
en petit-boutiste) au lieu de 0x0
.
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:
readelf -l hello_world.out
qui donne:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000000d7 0x00000000000000d7 R E 200000
LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8
0x000000000000000d 0x000000000000000d RW 200000
Section to Segment mapping:
Segment Sections...
00 .text
01 .data
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