• Aucun résultat trouvé

Projets multi-fichiers : édition de liens

Dans le document Conservatoire National des Arts et Métiers (Page 194-200)

adresses croissantes

11. Edition de lien

11.4 Projets multi-fichiers : édition de liens

int tab[500000]; /* demande 2 Mo */

printf("coucou\n");

}

Pour corriger ce problème, il faut modifier la taille de la pile avec par exemple sous Linux, la commande « ulimit -s 32768 » pour passer la taille de la pile à 32 Mo (sous Visual C++, c’est une option du projet).

11.4 Projets multi-fichiers : édition de liens

Dès que l’on développe un programme d’une taille un peu importante (ce qui est généralement le cas dans un projet réel), il devient nécessaire d’utiliser plusieurs fichiers source. Il y a au moins deux raisons pour cela :

1. Il est plus facile de chercher des informations et de modifier des fonctions dans plusieurs fichiers source de taille raisonnable (quelques centaines de lignes au maximum) que dans un seul gros fichier.

2. En cas de modification, il est plus rapide de ne compiler qu’un seul petit fichier (celui où a eu lieu la modification) qu’un gros fichier qui contient tout le programme.

Chaque fichier est compilé séparément, puis les fichiers objets obtenus sont réunis pour former un exécutable à l’aide de l’éditeur de liens (linker).

DUTmain.c DUTfct1.c DUTfct2.c

compilateur

DUTmain.o DUTfct1.o DUTfct2.o

linker dut.exe

Pour réaliser ce genre de projet, il faut pouvoir dire au compilateur que certaines variables ou fonctions sont définies dans un autre fichier source. Pour cela, deux méthodes existent :

l’utilisation du mot clé extern ou bien l’utilisation d’un fichier d’entête (.h) général qui contiendra toutes les définitions globales. C’est la deuxième méthode que nous utiliserons.

Notre projet multi-fichiers sera donc constitué :

• d’un fichier source qui ne contiendra que la fonction main.

• d’un fichier source par famille de fonctions.

• d’un fichier d’entête (ou plusieurs si nécessaire) qui contiendra toutes les déclarations globales (structures, prototypes de fonction, macros, ...). Il sera inclus au début de tous les fichiers sources.

DUTmain.c DUTfct1.c DUTfct2.c

compilateur

DUTmain.o DUTfct1.o DUTfct2.o

linker dut.exe DUTstd.h

Les variables globales seront déclarées dans un des fichiers source et déclarées comme extern dans le fichier d’entête.

Penchons-nous maintenant un peu sur le contenu des fichiers objets et exécutables ainsi que sur le travail effectué par l’éditeur de lien. Il existe deux grands formats de fichiers objets (et exécutables) en informatique : le format COFF « Common Object File Format » et le format ELF « Executable and Linking Format ». Le format COFF est le format historique d’Unix (avec le format a.out). Il a été repris par Microsoft et est toujours utilisé sous Windows sous une forme évoluée (format PE « Portable Executable »). Le format ELF a remplacé le format COFF sous Unix et est devenu le format standard sous Linux.

Un fichier objet (extension .o sous Linux, .obj sous Windows) contient les instructions en langage machine correspondant au source et des données destinées à l’éditeur de lien afin que celui-ci puisse créer un exécutable. Chaque fonction ou chaque variable définie dans un fichier objet est référencée par un nom de symbole. Ces symboles peuvent avoir une définition locale (utilisation à l’intérieur du fichier seulement) ou globale (utilisation à

l’extérieur du fichier). Toute référence à un symbole qui n’est pas défini dans le fichier objet (un appel à une fonction définie dans un autre fichier par exemple) est appelée une référence externe. Pour garder la trace des différents symboles internes ou externes, un fichier objet contient une table des symboles. L’éditeur de lien utilise cette table pour relier les références externes avec les définitions globales des symboles. Voici de manière très schématique la structure d’un fichier objet ou exécutable :

entête fichier

On retrouve la section text qui contient le code exécutable de la fonction, la section data qui contient les variables statiques (et les variables globales initialisées) et enfin la section bss qui contient les variables globales non-initialisées. Le but de l’édition de lien va être de fusionner les sections de chaque fichier objet afin de créer un fichier exécutable.

essai

Pour cela, il va falloir relier les références externes avec les définitions globales contenues dans d’autres fichiers objets (c’est la résolution de symbole) et changer les adresses du code et des données (c’est le relogement du code ou code relocation en anglais). Prenons un exemple simple. Le programme essai comprend un fichier source ESSmain.c :

#include <stdio.h>

extern void affiche(char *);

main() {

static char str[] = "coucou\n";

affiche(str);

}

qui appelle une fonction affiche contenue dans un autre fichier source ESSfct.c : void affiche(char *s)

{

printf(s);

}

Sous Linux, la compilation via la commande cc -c *.c donne deux objets ESSmain.o et ESSfct.o. Grâce au programme objdump, nous pouvons visualiser le contenu de certaines parties d’un objet, à savoir les entêtes de section (option h), la table des symboles (option t) et le code machine désassemblé (option d). La commande « objdump -dth ESSmain.o » donne le résultat suivant :

ESSmain.o: file format elf32-i386 Sections:

SYMBOL TABLE:

Disassembly of section .text:

00000000 <main>:

Le fichier objet est au format ELF 32 bits Intel 386, sa section .text comporte 26 octets (taille du code machine) et sa section .data en compte 8 (taille de la chaîne str avec le \0).

On voit dans la table des symboles que str est définie (dans .data), que main est définie (dans .text) mais qu'affiche ne l’est pas. Par ailleurs, les adresses 32 bits dans les entêtes de section et dans la table des symboles sont toutes à 0. Dans le code assembleur de la fonction main, on note que l’appel à la fonction affiche (c’est le call) est non résolu (l’adresse est fausse) et que le push qui précède ce call est à 0. Ce push correspond à la mise sur la pile du paramètre passé à la fonction, c’est-à-dire str dans notre exemple. En général, les paramètres d’une fonction sont toujours passés par la pile. Dans notre exemple, str ne se trouve bien entendu pas à l’adresse 0, mais on ne connaît pas encore son adresse. La commande « objdump -dth ESSfct.o » donne le résultat suivant :

ESSfct.o: file format elf32-i386 Sections:

Idx Name Size VMA LMA File off Algn 0 .text 00000019 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE

1 .data 00000000 00000000 00000000 00000050 2**2

Disassembly of section .text:

00000000 <affiche>:

On retrouve le même format d’objet, la taille du code est de 25 octets mais la section .data est vide. La fonction affiche est définie dans le fichier objet, mais pas la fonction printf. Dans le code assembleur de la fonction, on note l’appel à printf ainsi que le passage de paramètre qui le précède.

L’éditeur de lien doit combiner les deux objets avec une fonction d’initialisation (_start) et les routines de la librairie standard du C pour produire l’exécutable essai (cc *.c -o essai). La commande « objdump -dth essai» donne le résultat suivant :

essai: file format elf32-i386

Disassembly of section .text:

08048320 <_start>:

Dans le document Conservatoire National des Arts et Métiers (Page 194-200)

Documents relatifs