Projet Logiciel en C - Ensiwiki

publicité
Ensimag — Printemps 2013
—
Projet Logiciel en C
—
Sujet :
Assembleur pour microprocesseur MIPS
Auteurs : des enseignants actuels et antérieurs du projet C.
2
Table des matières
1
Présentation générale du projet
2
Description du microprocesseur MIPS
2.1 Définitions et notations . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3 La mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.4 Les registres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.4.1 Les registres d’usage général . . . . . . . . . . . . . . . . . .
2.4.2 Les registres spécialisés . . . . . . . . . . . . . . . . . . . .
2.5 Les modes d’adressage . . . . . . . . . . . . . . . . . . . . . . . . .
2.5.1 Adressage registre direct . . . . . . . . . . . . . . . . . . . .
2.5.2 Adressage immédiat . . . . . . . . . . . . . . . . . . . . . .
2.5.3 Adressage indirect avec base et déplacement : offset(base)
2.5.4 Adressage relatif . . . . . . . . . . . . . . . . . . . . . . . .
2.5.5 Adressage absolu aligné dans une région de 256Mo . . . . . .
2.6 Les entrées/sorties . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.7 Gestion des exceptions . . . . . . . . . . . . . . . . . . . . . . . . .
2.8 Exécution et delay slot . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
9
9
10
10
12
12
13
13
13
13
14
14
14
15
15
16
Le langage d’assemblage du MIPS
3.1 Les commentaires . . . . . . . . . . . . . . . . . . . . . .
3.2 Les étiquettes . . . . . . . . . . . . . . . . . . . . . . . .
3.3 Les nombres littéraux . . . . . . . . . . . . . . . . . . . .
3.4 Les instructions machine . . . . . . . . . . . . . . . . . .
3.5 Les directives . . . . . . . . . . . . . . . . . . . . . . . .
3.5.1 Directives de sectionnement .text, .data et .bss
3.5.2 Les directives de définition de données . . . . . .
3.5.3 Directive d’alignement .align . . . . . . . . . .
3.5.4 Directive d’exportation des noms . . . . . . . . .
3.5.5 Directive de non ré-ordonnancement . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
19
19
19
20
20
20
21
21
22
23
24
Les instructions du MIPS
4.1 Catégories d’instructions . . . . .
4.1.1 Les instructions de type R
4.1.2 Les instructions de type I
4.1.3 Les instructions de type J
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
25
25
25
26
26
3
4
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
TABLE DES MATIÈRES
4.2
4.3
4.4
5
6
7
8
Instructions étudiées dans le projet . . . . . . . . . . . . . . . . . . . . . . .
4.2.1 Instructions arithmétiques . . . . . . . . . . . . . . . . . . . . . . .
4.2.2 Les instructions logiques . . . . . . . . . . . . . . . . . . . . . . . .
4.2.3 Les instructions de décalage et set . . . . . . . . . . . . . . . . . . .
4.2.4 Les instructions de lecture/écriture mémoire . . . . . . . . . . . . . .
4.2.5 Les instructions de branchement et de saut, et de contrôle . . . . . . .
Les pseudo-instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Codage binaire des instructions . . . . . . . . . . . . . . . . . . . . . . . . .
4.4.1 Exemple : l’instruction ADD . . . . . . . . . . . . . . . . . . . . . .
4.4.2 Champs spécifiques de certaines instructions (JR, SRL, SYSCALL, . . .)
Relocation
5.1 Cycle de vie d’un programme . . . . . .
5.2 Les symboles . . . . . . . . . . . . . .
5.3 Principe de la relocation . . . . . . . .
5.3.1 Définition . . . . . . . . . . . .
5.3.2 Relocation en pratique . . . . .
5.4 Relocation au format ELF pour le MIPS
5.4.1 La table des symboles . . . . .
5.4.2 Table de relocation . . . . . . .
5.4.3 Champ addend . . . . . . . . .
5.4.4 Modes de relocation du MIPS .
5.4.5 . . . . . . . . . . . . . . . . . . .
Comment programmer un assembleur ?
6.1 Analyse lexicale . . . . . . . . . . .
6.2 Analyse syntaxique . . . . . . . . .
6.3 Génération du code binaire . . . . .
6.4 Écriture du fichier objet . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
27
27
27
27
28
28
29
30
30
31
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33
33
34
35
35
35
37
37
38
38
39
40
.
.
.
.
41
41
41
42
43
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Spécifications, travail à réaliser
7.1 Spécifications de l’assembleur . . . . . . . . . .
7.2 Code et modules fournis . . . . . . . . . . . . .
7.2.1 Squelette de l’assembleur . . . . . . . .
7.2.2 Module d’écriture de fichiers ELF . . . .
7.3 Travail à réaliser . . . . . . . . . . . . . . . . . .
7.3.1 Assembleur . . . . . . . . . . . . . . . .
7.3.2 Validation . . . . . . . . . . . . . . . . .
7.3.3 Extension : réécriture de elf_writer.o
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
45
45
46
46
48
48
48
48
48
Généralités et organisation du projet
8.1 Objectifs du projet . . . . . . . . . . . . . .
8.2 Déroulement du projet . . . . . . . . . . . .
8.2.1 Organisation du libre-service encadré
8.2.2 Cas de fraudes . . . . . . . . . . . .
8.2.3 Conseils et consignes . . . . . . . . .
8.2.4 Styles de codage . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
49
49
49
49
50
50
51
.
.
.
.
.
.
.
.
.
.
.
.
TABLE DES MATIÈRES
8.3
5
8.2.5 Outils . . . . . . . . . . . . . . .
8.2.6 Aspects matériel . . . . . . . . .
Evaluation du projet . . . . . . . . . . . .
8.3.1 Rendu des fichiers de votre projet
8.3.2 Soutenance . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Bibliographie
52
53
53
53
54
55
A Suite d’outils spécifiques au MIPS
A.1 Génération de fichiers ELF . . . . . .
A.1.1 L’assembleur mips-as . . . .
A.1.2 L’éditeur de liens mips-ld . .
A.2 Étude du contenu de fichiers ELF . . .
A.3 Exécution de programmes pour MIPS
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
B Exemple complet avec relocation
B.1 Programme exempleElf.s . . . . . . . . . . . .
B.2 Désassemblage, tables de relocation et de symboles
B.3 Calculs de relocation . . . . . . . . . . . . . . . .
B.3.1 JAL write . . . . . . . . . . . . . . . . .
B.3.2 SW $3, Y . . . . . . . . . . . . . . . . . .
B.3.3 Étiquettes Y et Z . . . . . . . . . . . . . .
B.3.4 Et moi dans tout ça ? . . . . . . . . . . . .
B.4 Cas d’un fichier exécutable . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
57
57
57
57
58
58
.
.
.
.
.
.
.
.
59
59
60
61
61
62
63
63
63
C Exemple avec relocation sur plusieurs fichiers
C.1 Fichiers sources . . . . . . . . . . . . . . .
C.2 Listings générés . . . . . . . . . . . . . . .
C.3 Tables de relocation . . . . . . . . . . . . .
C.4 Table de symboles . . . . . . . . . . . . . .
C.5 Édition de liens . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
65
65
65
66
67
68
D Exemple avec relocation R_MIPS_PC16
D.1 Fichiers sources . . . . . . . . . .
D.2 Listings générés . . . . . . . . . .
D.3 Tables de relocation . . . . . . . .
D.4 Édition de liens . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
70
70
70
71
71
E Structure de fichiers ELF et module fourni elf_writer
E.1 Tables . . . . . . . . . . . . . . . . . . . . . . . . . .
E.2 Module elf_writer et structure des principales tables
E.2.1 Structures de données . . . . . . . . . . . . .
E.2.2 Utilisation générale . . . . . . . . . . . . . . .
E.2.3 Ecriture des sections .text, .data et .bss . .
E.2.4 Ecriture de la table des chaînes . . . . . . . . .
E.2.5 Ecriture de la table des symboles . . . . . . . .
E.2.6 Entrées de relocation . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
73
73
74
76
76
76
76
77
78
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
F Spécifications des instructions
TABLE DES MATIÈRES
81
Chapitre 1
Présentation générale du projet
Le rôle d’un assembleur est de transformer un programme écrit dans un langage informatique accessible à l’homme, le langage assembleur, en un programme décrivant la même série d’instructions mais
cette fois en langage machine, binaire. L’objectif de ce projet informatique est d’implanter en langage
C, sous Linux, un logiciel assembleur pour microcontrôleur MIPS.
Pour des fichiers à assembler corrects, l’assembleur générera deux sorties :
1. un fichier objet binaire source_file.o contenant la traduction en langage machine du programme assemblé. Ce fichier, au format relogeable ELF, sera directement utilisable par un éditeur
de liens générant des programmes pour une machine ou un simulateur MIPS.
2. sur option (-l), une liste d’assemblage, compte-rendu du déroulement de l’assemblage.
Si ce programme n’est pas correct (symboles inconnus, opérandes invalides, etc.), l’assembleur s’arrêtera en affichant des messages d’erreur appropriés et le numéro des lignes correspondantes dans le
fichier source. Pour ce projet nous considèrerons en fait un microprocesseur simplifié et n’acceptant
qu’un jeu réduit d’instructions.
Les chapitres 2 à 4 décrivent la structure du MIPS et de ses instructions ainsi que la syntaxe du
langage assembleur utilisé. Le chapitre 5 introduit des notions avancées de fichier objet relogeable.
Le chapitre 6 présente quelques principes sur la manière de programmer un assembleur. Le chapitre
7 fournit les spécifications précises du travail demandé (une première partie et une extension) et la
structure d’un squelette de code fourni par les enseignants. Enfin, le chapitre 8 présente des modalités
d’organisation et de rendu et quelques conseils généraux pour le bon déroulement de votre projet.
Des informations supplémentaires sur les outils spécifiques au projet, des exemples d’assemblage,
les mécanismes de relocation, le code fourni et les spécifications des instructions sont données en annexe.
Sous le terme de “Projet logiciel en C” sont en fait mises en œuvres plusieurs compétences variées
et complémentaires : compréhension d’un problème donné, découverte de nouveaux aspects techniques,
compréhension de code ou de formats à partir de leur spécification, conception (modules, structures de
données, algorithmes) puis implantation et validation de votre propre code, travail en groupe, etc. Beau
programme, à vous d’en profiter !
Bon travail à tous !
7
8
CHAPITRE 1. PRÉSENTATION GÉNÉRALE DU PROJET
Chapitre 2
Description du microprocesseur MIPS
Cette section contient une description de la machine MIPS : mémoires, registres et modes d’adressage et fonctionnements des entrées/sorties et exceptions.
2.1
Définitions et notations
Commençons par rappeler quelques définitions et notations utiles. Une excellente référence sur
l’arithmétique binaire et sur le MIPS peut être trouvée ici [2].
Octet/Mot Un octet (byte en anglais) est une suite de 8 bits qui constitue la plus petite entité que l’on
peut adresser sur la machine. La concaténation de deux octets forme un demi-mot (half-word) de 16
bits, et la concaténation de quatre octets, ou de deux demi-mots, forme un mot (word) de 32 bits. Les
bits sont numérotés de la droite (poids faible) vers la gauche (poids fort) de 0 à 7 pour l’octet, de 0 à 15
pour un demi-mot et de 0 à 31 pour un mot.
0
1
1
0
1
1
1
0
Figure 2.1 – Octet représentant la valeur décimale 110, ou 0x6E en notation hexadécimale.
Représentation hexadécimale On représente par 0xi j la valeur d’un octet dont les 4 bits de poids fort
valent i et les 4 bits de poids faible j (avec i, j ∈ ([0 . . . 9, A . . . F]). Par exemple, la valeur de l’octet de
la figure 2.1 s’écrit 0x6E en hexadécimal. Pour un demi-mot ou un mot, on aura respectivement 4 ou 8
chiffres hexadécimaux, chacun représentant 4 bits.
Codage binaire d’un entier non signé Quand on parle d’un entier non signé codé sur n bits ou plus
simplement d’un entier codé sur n bits, il s’agit de sa représentation en base 2 sur n bits, donc d’une
valeur entière comprise entre 0 et 2n − 1. Un entier codé sur un octet a donc une valeur comprise entre 0
et 255, 0x00 à 0xFF en notation hexadécimale. Un entier codé sur un demi-mot a une valeur comprise
entre 0 et 65535, 0x0000 à 0xFFFF en notation hexadécimale. Enfin, un entier codé sur un mot a une
valeur comprise entre 0 et 4 294 967 295, 0x00000000 à 0xFFFFFFFF en notation hexadécimale.
Les adresses d’un processeur MIPS sont des entiers non signés sur 32 bits.
9
10
CHAPITRE 2. DESCRIPTION DU MICROPROCESSEUR MIPS
Codage binaire d’un entier signé Les entiers signés sont représentés en complément à 2. Le codage
sur n bits du nombre i est la représentation en base 2 sur n bits de 2n + i, si −2n−1 ≤ i ≤ −1, et de i,
si 0 ≤ i ≤ 2n−1 − 1. Un entier signé codé sur un octet est compris entre -128 à 127, 0x80 à 0x7F en
notation hexadécimale. Un entier signé sur un demi-mot est compris entre -32768 et 32767, 0x8000 à
0x7FFF en notation hexadécimale. Enfin, un entier signé sur un mot est compris entre -2 147 483 648 et
2 147 483 647, 0x80000000 à 0x7FFFFFFF en notation hexadécimale.
Il faut remarquer que le bit de plus fort poids d’un octet/demi-mot/mot représentant un entier négatif
est toujours égal à 1 alors qu’il vaut 0 pour un nombre positif ou nul (c’est le bit de signe). On peut aussi
dire que tout se passe comme si on travaillait modulo 8/16/32 bits respectivement.
Extension d’une valeur entière L’extension d’une valeur sur un octet (ou un demi-mot) consiste à
coder cette valeur sur un mot entier. Un entier non signé est simplement étendu en ajoutant des bits nuls
à gauche. Par exemple la valeur 0xF2 est étendue sur un mot par 0x000000F2. La valeur décimale est
toujours +242.
Cette règle ne peut par contre pas être appliquée aux valeurs signées. Ainsi 0xF2 sur un octet signé
représente la valeur décimale −14, alors que 0x000000F2 sur un mot signé représente la valeur décimale
+242 ! L’extension des valeurs signées est donc réalisée en complétant à gauche avec le bit de signe (de
poids fort). 0xF2 sur un mot est donc 0xFFFFFFF2.
En anglais, on parle d’une valeur extended (complétion avec des zéros) ou bien sign-extended
(complétion avec le bit de poids fort).
2.2
Généralités
Un MIPS, pour Microprocessor without Interlocked Pipeline Stages, est un processeur RISC 32 bits.
RISC signifie qu’il possède un jeu d’instructions réduit (Reduced Instruction Set Computer). En contrepartie, il est capable de terminer l’exécution d’une instruction à chaque cycle d’horloge. Pour obtenir
ce type de performances, il est nécessaire d’avoir des instructions de taille constante et de construire
un pipeline. Cette structure permet d’exécuter chaque instruction en plusieurs cycles, mais de terminer
l’exécution d’une instruction à chaque cycle. En plus d’un banc de registres, le MIPS possède des unités
séparées pour l’extraction des instructions, le décodage des instructions, l’exécution et les accès mémoire
(figure 2.2 ).
Les processeurs MIPS sont notamment utilisés dans des stations de travail (Silicon Graphics, DEC. . .),
de nombreux systèmes embarqués (Palm, modems, imprimantes, . . .), et également des consoles de jeux
(Nintendo 64, Sony PlayStation 2, etc.).
2.3
La mémoire
La mémoire d’un ordinateur contient l’ensemble des instructions et données des programmes, toujours codées en binaire. Elle est généralement découpée en différents secteurs, ce qui permet d’accélérer
les accès aux données et de leurs affecter des droits de lecture/écriture/exécution spécifiques. Si un système d’exploitation est installé, c’est lui qui a pour rôle de gérer cette mémoire, en utilisant le plus
souvent une unité matérielle spécialisée, la MMU (Memory Management Unit).
Avant l’exécution, les programmes binaires générés par un assembleur sont d’abord recopiés en mémoire (phase de chargement). Pendant l’exécution le microprocesseur vient y chercher les instructions
et données en se repérant notamment grâce au compteur programme PC qui contiendra successivement
2.3. LA MÉMOIRE
11
Figure 2.2 – Architecture interne du microprocesseur MIPS
toutes les adresses des instructions à exécuter.
La mémoire peut être vue comme un tableau d’octets (il n’est pas possible d’adresser une donnée
plus petite, comme un unique bit par exemple). Une adresse mémoire correspond alors au rang de l’octet
désigné dans ce tableau. Pour stocker en mémoire des valeurs sur plusieurs octets, par exemple un mot
de 32 bits, deux solutions existent (figure 2.3) :
– les systèmes de type gros-boutiste ou grand indien (big endian en Anglais) écrivent toujours
l’octet de poids le plus fort à l’adresse la plus basse.
– à l’inverse, les systèmes de type petit-boutiste ou petit indien (little endian) écrivent l’octet de
poids le plus faible à l’adresse la plus basse.
Le MIPS est un processeur big-endian, tout comme les COLDFIRE, DEC ou SUN. Les processeurs ATHLON ou PENTIUM notamment sont little endian.
0x00000004
↓
0x00000007
big endian
...
0x01
0x23
0x45
0x67
...
little endian
...
0x67
0x45
0x23
0x01
...
Figure 2.3 – Mode d’écriture en mémoire, à l’adresse 0x4, de la valeur hexadécimale 0x01234567 pour
un système big endian ou little endian.
Les adresses du MIPS sont codées sur 32 bits. L’espace d’adressage potentiel est donc de 4 Go, soit
des adresses comprises entre 0 et 232 − 1. Cependant, les mémoires physiques effectivement présentes
12
CHAPITRE 2. DESCRIPTION DU MICROPROCESSEUR MIPS
dans les microcontrôleurs sont généralement bien plus petites, en particulier sur des systèmes embarqués !
La mémoire du MIPS est dite alignée. Ceci signifie en particulier que les accès mémoire à des instructions ou données ne peuvent se faire qu’à des adresses “compatibles” avec le type d’information
accédée. Par exemple : les instructions étant codées sur 4 octets, le processeur ne pourra lire une instruction qu’à une adresse multiple de 4. Pour les données, les mots (respectivement les demi-mots) devront
toujours être placés à des adresses multiples de 4 octets (respectivement 2). Un octet seul pourra par
contre être lu n’importe où en mémoire.
Le respect des contraintes d’alignement n’incombe pas à l’assembleur, 1 mais au programmeur !
Utilisée à bon escient, la directive .align décrite section 3.5.3 permet d’assurer l’alignement correct
des données et instructions.
2.4
Les registres
Les registres sont des emplacements mémoire spécialisés utilisés par le processeur pour stocker les
opérandes et résultats des opérations. Ils sont directement câblés au processeur et ont donc un temps
d’accès très rapide.
2.4.1
Les registres d’usage général
Le MIPS dispose de 32 registres d’usage général (General Purpose Registers, GPR) de 32 bits chacun, dénotés de $0 à $31. Ils peuvent également être identifiés par un mnémonique indiquant leur usage
conventionnel (arguments d’une fonction, valeurs temporaires, etc.) ou imposé par l’architecture. Par exemple, le registre $29 est également noté $sp (pour Stack Pointer), car il est couramment utilisé comme
le pointeur de sommet de pile.
Registre
$0
$1
$2, $3
$4-$7
$8-$15
$16-$23
$24, $25
$26, $27
$28
$29
$30
$31
Mnémonique
$zero
$at
$v0, $v1
$a0-$a3
$t0-$t7
$s0-$s7
$t8, $t9
$k0, $k1
$gp
$sp
$fp
$ra
Usage
Registre toujours nul (relié à la masse)
Assembler temporary : réservé à l’assembleur
Valeurs retournées par une sous-routine
Arguments d’une sous-routine
Temporaires non préservés par les sous-routines (caller-save)
Temporaires préservés par les sous-routines (callee-save)
Deux temporaires de plus, non préservés.
kernel : réservés pour l’OS
Global pointer
Stack pointer : pointeur de pile
Frame pointer
Return address : utilisé par certaines instructions (p. ex. JAL)
pour sauvegarder l’adresse de retour d’une sous-routine
Ces conventions devraient être utilisées dans tout programme assembleur. 2 Dans le cadre de ce
projet, les restrictions imposées porteront sur $zero (toujours nul, ne peut pas être modifié), $at (utilisé
1. La seule contrainte d’alignement imposée par l’assembleur et l’éditeur de liens concerne l’alignement des adresses de
section.
2. Sauf à aimer les comportements non attendus lors de l’exécution. . .
2.5. LES MODES D’ADRESSAGE
13
par l’assembleur, notamment pour les pseudo-instructions), $sp et $ra (pour le retour après exécution
d’une sous-routine).
2.4.2
Les registres spécialisés
En plus des registres généraux, trois registres spécialisés existent :
– Le compteur programme 32 bits PC (Program Counter), qui contient l’adresse mémoire de la
prochaine instruction à exécuter. Il est automatiquement incrémenté d’un mot (donc de 32 bits)
après la lecture en mémoire de chaque instruction. Les sauts et branchements seront parfois
amenés à le modifier directement. Attention, PC a déjà été incrémenté au moment où une instruction est réellement exécutée !
– Deux registres 32 bits HI et LO utilisés pour stocker le résultat de la multiplication ou de la division
de deux données de 32 bits.
D’autres registres existent encore, mais qui ne seront pas utilisés dans ce projet (registres de valeurs
flottantes, . . .).
2.5
Les modes d’adressage
Les instructions utilisent toujours zéro, un, deux ou trois opérandes. Le mode d’adressage est la
méthode utilisée par le processeur pour localiser un opérande, soit dans un registre soit à une adresse
donnée de la mémoire. Tous les modes ne sont pas utilisables pour tous les opérandes d’une opération.
Le MIPS compte au total 5 modes d’adressage.
2.5.1
Adressage registre direct
L’opérande est une valeur sur 32 bits contenue dans un registre.
Exemple :
ADD $2, $3, $4
2.5.2
# Les valeurs des opérandes sont dans les registres 3 et 4
# Le résultat (somme) est placé dans le registre 2
Adressage immédiat
La valeur numérique de l’opérande est directement encodée dans l’instruction. Les valeurs attendues
sont généralement de 16 bits (voir les spécifications des instructions), signées ou non.
Exemple :
ADDI
ADDI
ADDI
ADDI
$2,
$2,
$2,
$2,
$3,
$3,
$3,
$3,
200
0xC8
-200
0xFFFFFF38
#
#
#
#
valeur immédiate décimale
la même en hexadécimal
valeur immédiate décimale négative
en hexa négatif, toujours écrire sur 32 bits!
14
CHAPITRE 2. DESCRIPTION DU MICROPROCESSEUR MIPS
2.5.3
Adressage indirect avec base et déplacement : offset(base)
L’adresse en mémoire de l’opérande est la somme de la valeur contenue dans le registre base (interprétée comme une adresse 32 bits) et du déplacement offset, entier signé sur 16 bits.
Exemple :
LW $2, 200($3)
# La valeur à l’adresse GPR[3]+200 est placée dans GPR[2]
D’autres syntaxes alternatives sont reconnues par mips-as, par exemple ($4) ou 0x100 interprétées
respectivement comme 0($4) ou 0x100($0). Elles peuvent être acceptées par votre assembleur, mais
ce n’est pas obligatoire.
A noter que mips-as reconnaît aussi la syntaxe etiquette($rt), mais qui est plus ambigüe (déplacer de la valeur d’une étiquette ?) et n’a pas à être gérée dans ce projet.
2.5.4
Adressage relatif
Ce mode d’adressage est utilisé par les instructions de branchement. L’opérande est un offset signé
sur 16 bits.
L’adresse de branchement est déterminée en décalant offset de 2 bits à gauche puis en ajoutant cette
valeur sur 18 bits à l’adresse courante du compteur programme. Attention, PC a déjà été incrémenté au
moment du calcul, et contient l’adresse mémoire du mot suivant l’instruction de branchement !
Exemple :
B 0xF2
2.5.5
# si cette instruction est à l’adresse 0x20001234 en mémoire,
# PC vaut 0x20001238 au moment du calcul, et l’adresse de
# branchement sera: 0xF2<<2 + 0x20001238 = 0x20001600
Adressage absolu aligné dans une région de 256Mo
Une adresse 32 bits est calculée à partir des quatre bits de poids forts du compteur programme PC et
de l’opérande, un décalage non signé sur 26 bits. Ce mode est utilisé par les instructions de saut (J, JAL,
. . .) pour calculer l’adresse de la prochaine instruction à exécuter.
L’adresse de saut est calculée en décalant l’opérande de 2 bits vers la gauche (car les instructions sont
toujours alignées sur 4 octets). Les 4 bits de poids forts manquants sont ceux du compteur programme.
Comme pour les branchements, PC contient déjà l’adresse de l’instruction de saut + 4 au moment du
calcul.
Exemple :
J 0xABC3
# Si cette instruction est à l’adresse 0x20001234 en mémoire,
# PC vaut 0x20001238 au moment du calcul, et l’adresse de
# saut sera: 0xABC3<<2 + 0x20000000 = 0x2002AF0C
2.6. LES ENTRÉES/SORTIES
2.6
15
Les entrées/sorties
Le mécanisme d’entrée/sortie de données, par exemple dans la console ou sur un périphérique externe, consiste à interrompre l’exécution du programme pour faire exécuter un service par le système.
Le principe général est le suivant :
1. Placer un code de service dans le registre $v0 ;
2. Appeler l’instruction syscall : l’exécution du programme est interrompue et le système réalise
la tâche dont le code est présent dans $v0. Une fois le service effectué, l’exécution reprend.
Les paramètres des services sont lus dans les registres $a0 à $a3 (par exemple les valeurs à afficher),
et les résultats des services sont placés dans $v0 et $v1 à la fin de l’exécution (par exemple les valeurs
lues). Les services les plus courants, notamment d’entrée/sortie, sont les suivants :
Service
Affiche un entier sur la sortie standard
Affiche une chaîne de caractères sur
la sortie standard
Lit un entier sur l’entrée standard
Lit une chaîne de caractères sur
l’entrée standard
Code
1
Exit (termine l’exécution)
Arguments
$a0 : l’entier à afficher
Sorties
—
4
$a0 : adresse de la chaîne
—
5
8
—
$a0 : adresse du buffer où
écrire la chaîne
$a1 : taille du buffer
—
$v0 : l’entier lu
—
10
—
L’exemple suivant affiche une chaîne de caractères sur la sortie standard :
Exemple :
.data
chaine: .asciiz "Hello World!\n"
.text
LI $v0, 4
LA $a0, chaine
SYSCALL
2.7
# la chaine à afficher
# met le code de service dans le registre $v0
# copie l’adresse de la chaine à afficher dans $a0
# appel système, qui effectuera l’affichage
Gestion des exceptions
Plusieurs exceptions peuvent survenir lors de l’exécution d’un programme : débordements, mémoire
non allouée, etc. Par exemple la somme de deux valeur de 32 bits ne tient pas forcément sur 32 bits. La
valeur contenue dans le registre de destination n’est pas alors pas correcte :
Exemple :
LI $8, 0xA1234567
LI $9, 0xA1234567
ADD $10, $8, $9
# 0xA1234567 + 0xA1234567 = 0x142468ACE
# mais $10 ne peut contenir que 0x42468ACE (32 bits)
# débordement => la valeur du registre est fausse!
16
CHAPITRE 2. DESCRIPTION DU MICROPROCESSEUR MIPS
Certains processeurs possèdent un registre d’état, dont la valeur est mise à jour après l’exécution de
chaque instruction, par exemple pour indiquer un fonctionnement normal ou un débordement. Le MIPS
ne possède pas de registre d’état, mais utilise un système d’exceptions qui, selon les cas, peuvent amener
à une interruption de l’exécution du programme.
2.8
Exécution et delay slot
Sans rentrer dans les détails de l’architecture de type pipeline du processeur MIPS, il est important
de mentionner un point concernant l’ordre d’exécution des instructions.
Les instructions de branchement ou de saut (BEQ, J. . .) nécessitent des cycles supplémentaires avant
de sortir du pipeline d’exécution. De ce fait, l’instruction qui suit immédiatement le branchement (dite
dans son delay slot) aura déjà été entrée dans le pipeline et partiellement exécutée, avant même que le
branchement soit lui-même exécuté. En particulier :
– l’instruction dans le delay slot sera au final toujours exécutée, quelle que soit l’évaluation de la
condition de branchement ;
– le compteur programme PC aura déjà été incrémenté lorsque l’adresse de branchement est calculée
(PC contient donc alors l’adresse du delay slot, i.e. celle du branchement + 4) ;
– le comportement du programme sera (très) incertain si l’instruction de branchement et celle dans
le delay slot utilisent les mêmes registres, si plusieurs branchements se suivent, si une interruption
est déclenchée, etc.
Exemple :
debut:
ADDI $10, $0, 0xAB
ADD $11, $0, $10
ADDI $8, $0, 17
BEQ $10, $11, ici
ADDI $8, $8, 1
ici:
ADDI $8, $8, 1
#
#
#
#
#
#
Met la valeur 0xAB dans $10
Copie cette valeur dans $11
Met la valeur 17 dans $8
Branchement si le contenu des registres est identique
*DELAY SLOT* du BEQ: La valeur dans $8 est incrémentée,
que le branchement précédent ait lieu ou non!!
# Incrémente (encore) la valeur dans $8
Dans ce programme, la valeur du registre $8 aura été incrémentée avant que le branchement ait lieu !
Donc la valeur finale dans $8, après exécution de l’addition en ici, sera 19 et non 18. . .
Une première solution pour éviter ce problème à l’exécution est de toujours placer une instruction
NOP (qui ne fait rien) après un saut ou un branchement. Il s’agit d’une “bulle” dans le pipeline. Un cycle
est perdu dans le temps d’exécution, mais le fonctionnement est correct :
BEQ $10, $11, ici
NOP
ADDI $8, $8, 1
# Branchement si le contenu des registres est identique
# un innofensif NOP dans le delay slot...
# Désormais, ce ADDI n’est pas exécuté si branchement
Une optimisation, réalisée par l’assembleur, est d’inverser l’ordre de l’instruction et de celle qui la
précède (lorsque les registres concernés sont indépendants) :
BEQ $10, $11, ici
ADDI $8, $0, 17
ADDI $8, $8, 1
# Branchement si le contenu des registres est identique
# Ce ADDI sera en fait exécuté AVANT le branchement!
# Ici ADDI n’est pas exécuté si branchement
2.8. EXÉCUTION ET DELAY SLOT
17
Dans le cadre de ce projet, votre assembleur n’a pas à optimiser le code généré en réordonnant
l’ordre des instructions.
Dans la phase de tests, vous serez amené à comparer vos fichiers objet avec ceux générés par
mips-as (voir annexe A). Pour forcer mips-as à conserver l’ordre des instructions du fichier source,
toujours ajouter la directive .set noreorder dans vos fichiers test. L’assembleur ajoutera des NOP
si nécessaire, mais n’inversera pas d’instructions.
18
CHAPITRE 2. DESCRIPTION DU MICROPROCESSEUR MIPS
Chapitre 3
Le langage d’assemblage du MIPS
La syntaxe présentée ici est issue de l’assembleur GNU as [9] (notamment utilisé par le compilateur
gcc), avec certaines options dédiées aux processeurs MIPS. Plusieurs restrictions de syntaxe ont été
apportées afin de simplifier et d’éviter de commettre des erreurs difficiles à détecter.
Si l’on ignore les portions de texte en commentaire, un programme se présente comme une liste de
lignes séparées par des caractères de fin de ligne. Chaque ligne peut contenir une définition d’étiquette,
et éventuellement soit une instruction avec ses paramètres, soit une directive avec son paramètre. Une
ligne peut aussi être vide (recommandé pour aérer le texte). Les différentes unités lexicales peuvent être
séparées par des combinaisons d’espaces et/ou de tabulations.
3.1
Les commentaires
Il y a deux types de commentaires 1 : un commentaire commence par le caractère # et se termine à
la fin de la ligne, ou commence par /* et se termine au premier */. Par exemple :
# ceci est une ligne entièrement commentée
B 0xF2 # ici le commentaire commence après l’instruction
/* Encore un commentaire, mais sur
plusieurs lignes cette fois */
3.2
Les étiquettes
Une étiquette permet de nommer une adresse mémoire, et peut servir d’opérande à une instruction
ou à une directive.
– Syntaxiquement, une étiquette est une suite de caractères alphanumériques 2 ou ’_’, et ne commençant pas par un chiffre
– Une étiquette est toujours définie en début de ligne, puis suivie du caractère ’:’ (qui ne fait pas
partie de son nom).
– Plusieurs étiquettes peuvent être associées à la même opération ou directive.
– Une étiquette ne peut être définie qu’une seule fois dans un programme.
1. On ne le répétera jamais assez : commentez ! Aussi bien dans vos programmes en assembleur que dans les sources C de
ce projet d’ailleurs. . .
2. Un caractère alphanumérique est une lettre majuscule ou minuscule, ou un chiffre.
19
20
CHAPITRE 3. LE LANGAGE D’ASSEMBLAGE DU MIPS
– L’adresse désignée par une étiquette est celle du prochain octet codé lors de l’assemblage. Sa
valeur lors de l’exécution est égale à son adresse d’implantation dans la mémoire après le chargement du programme. Elle dépend donc de la section dans laquelle elle est définie et de sa position
dans cette section (voir les sections 3.5.1 et 5).
3.3
Les nombres littéraux
Un nombre littéral est une suite de chiffres hexadécimaux précédée de ’0x’, ou une suite de chiffres
décimaux.
0xFdCba987
123456789
# nombre hexadécimal
# nombre décimal
En général, les littéraux hexadécimaux sont interprétés comme des nombres non-signés, et les littéraux décimaux comme des nombres signés en complément à 2 (voir chapitre 2.1). Cette interprétation
est en particulier utilisée par l’assembleur pour vérifier le non-débordement des littéraux dans leur contexte d’utilisation. Par exemple, les littéraux signés sur 32 bits doivent avoir des valeurs comprises entre
-2147483648 et 2147483647, alors que les littéraux non signés doivent avoir des valeurs entre 0 et
4294967295. La seule exception à cette règle porte sur les littéraux utilisés dans le mode d’adressage
avec base et déplacement où le déplacement est toujours considéré comme un signé sur 32 bits, même
si le littéral du déplacement est hexadécimal. 3
Il est possible d’appliquer l’opérateur unaire ‘-’ pour désigner l’opposé d’un littéral signé ou nonsigné. Dans le cas non-signé, le nombre alors désigné est toujours considéré comme non-signé et correspond au calcul de l’opposé sur 32 bits.
3.4
Les instructions machine
Une instruction comporte un champ operation, suivi éventuellement par un, deux ou trois champs
opérandes séparés par des virgules (’,’).
operation [op [,op [,op]]]
Le champ operation désigne un des mnémoniques d’instruction du MIPS. Pour simplifier, ils sont
toujours écrits en majuscules (ADD, et pas add ni AdD).
Les opérandes suivent la syntaxe des modes d’adressage présentés section 2.5. Les registres seront
toujours écrits sous la forme $1 ou via leur mnémonique $at. Les opérandes source sont le plus souvent
des registres ou des valeurs immédiates quand la destination est un registre ou une donnée en mémoire.
Lorsqu’il y a plusieurs opérandes, l’opérande destination précède généralement le ou les opérandes
source.
3.5
Les directives
Une directive commence toujours par un point (’.’). Deux types de directives seront principalement
utilisés : les directives de sectionnement du programme et les directives de définition de données.
3. Cette interprétation n’est pas celle de l’assembleur GNU qui interprète les littéraux suivant leur représentation en complément à 2 sur 32 bits. Il ne vérifie donc pas vraiment les débordements de littéraux.
3.5. LES DIRECTIVES
3.5.1
21
Directives de sectionnement .text, .data et .bss
La mémoire est généralement divisée en blocs physiques de 4 kilo-octets qui peuvent être protégés en
lecture, écriture ou exécution, et éventuellement partagés par plusieurs processus ou par des dispositifs
matériels du microprocesseur. L’intérêt est de pouvoir placer différemment en mémoire le code et les
données d’un programme, en fonction du niveau de protection désiré. Les instructions d’un programme
seront par exemple placées dans des blocs protégés en écriture, de même que certaines données dont on
veut interdire la modification en cours d’exécution. Les autres données seront chargées dans une zone
en lecture et écriture permise et exécution interdite.
Pour pouvoir profiter de cette possibilité offerte par le matériel, l’assembleur permet de définir
plusieurs sections, qui indiquent à l’assembleur d’assembler les lignes suivantes du programme dans
la section correspondante :
.text la section d’instruction, qui sera toujours chargée dans une zone de la mémoire en exécution
et lecture permise, mais en écriture interdite ;
.data la section de données initialisées, qui sera créée et chargée dans une zone de la mémoire en
lecture et écriture permise, mais en exécution interdite.
.bss la section de données non initialisées. Similaire à la section précédente, mais n’est définie que
par sa taille (son contenu n’est pas explicitement initialisé).
En général, les instructions ne sont définies que dans la section .text, mais on peut en fait tout à
fait les définir dans la section .data (dans ce cas, on ne pourra probablement pas les exécuter. . .). Les
données initialisées peuvent être définies dans les deux sections .text ou .data.
Si aucune directive de sectionnement n’est présente dans un programme, tout est assemblé dans la
section .text.
3.5.2
Les directives de définition de données
On distingue les définitions de données initialisées, qui ont lieu dans les zones .text ou .data, des
déclarations de données non initialisées qui se font généralement dans la zone .bss.
Déclaration de données initialisées
.byte valeur Cette directive réserve un octet en mémoire et lui affecte une valeur initiale donnée
par un entier sur 8 bits, en décimal ou hexadécimal. Par exemple, les lignes suivantes permettent de
réserver deux octets attachés à l’étiquette tab, aux adresses tab et tab+1 :
tab:
.byte 2
.byte 0xFF
L’interprétation de ces valeurs comme signées ou non dépend du contexte d’utilisation, et non de leur
définition. Ainsi la valeur 0xFF en tab+1 peut être vue comme 255 ou -1, selon l’instruction qui y
accède.
.word valeur Cette directive réserve un mot en mémoire et lui affecte une valeur initiale donnée par
un entier sur 32 bits, en décimal ou hexadécimal.
22
CHAPITRE 3. LE LANGAGE D’ASSEMBLAGE DU MIPS
Directive avec étiquette La directive précédente peut être utilisée avec pour paramètre valeur une
étiquette. Dans ce cas, la valeur assemblée est en fait une référence à l’étiquette.
mot1: .word mot3
mot2: .word 0xABC
mot3: .word -14
# mot1 désigne un mot contenant l’adresse de mot3
A noter que dans ces cas l’assembleur doit toujours générer des données de relocation, les adresses des
étiquettes n’étant connues que par rapport au début de leur section et non de manière absolue. Tout ceci
sera détaillé dans la section 5.
Directives de déclaration de chaînes de caractères Il est aussi possible de définir des chaînes de
caractères, à l’aide des directives .string, .ascii et .asciiz. A vous de trouver comment les utiliser !
Déclaration de données avec .skip taille
La directive .skip permet de réserver un nombre d’octets égal à taille à l’adresse étiquette.
toto: .skip 13
Dans la section .bss, ces octets sont simplement réservés mais pas initialisés. Dans les deux autres
sections, la zone mémoire correspondant à une directive .skip est en fait initialisée avec des zéros.
3.5.3
Directive d’alignement .align
Les contraintes d’alignement ont été présentées section 2.3. Leur non respect peut entraîner des
erreurs à l’exécution (plantage ou comportement aberrant). Exemple :
.text
ici:
ADD $5, $6, $7
.byte 0xAB
SUB $5, $6, $8
mot: .word 0x1234567
#
#
#
#
une instruction
un octet réservé (pas de sens ici, mais autorisé)
une autre instruction
un mot de 32 bits
Les codages hexadécimaux des instructions ADD et SUB étant respectivement 00c72820 et 00c82822, le
contenu de la mémoire à partir de l’adresse ici sera : [00c72820AB00c8282212345678].
En supposant que l’adresse ici est alignée (multiple de 4) la première instruction ADD pourra être
lue correctement.
Par contre, l’instruction SUB ne pourra jamais l’être, puisque le compteur programme PC ne peut contenir que des valeurs multiples de 4. On pourra lire le code AB00c828 en ici+4 ou encore 22123456 en
ici+8, mais on ne retrouvera jamais l’instruction SUB ! De même le mot 0x12345678 ne pourra jamais
être lu par une instruction de lecture de mots.
La directive .align puiss indique à l’assembleur d’incrémenter l’adresse du prochain élément à
coder sur la prochaine adresse multiple de 2 puiss . Si des octets doivent être sautés, ils sont codés à 0.
.text
ici:
ADD $5, $6, $7
# une instruction
3.5. LES DIRECTIVES
.byte 0xAB
.align 2
SUB $5, $6, $8
mot: .word 0x1234567
23
# un octet réservé (pas trop de sens ici, mais autorisé)
#
==> Pour aligner le SUB!
# une autre instruction
# un mot de 32 bits
En hexadécimal, l’exemple ci-dessus est codé : [00c72820AB00000000c8282212345678] Les instructions et le mot sont correctement alignés, et pourront être bien récupérés à l’exécution.
“Politique d’alignement” de votre assembleur par rapport à mips-as
Une précision liée au comportement de l’assembleur GNU mips-as (annexe A), illustrée sur l’exemple suivant et le code généré par cet assembleur :
.data
.word 0x12345678
.byte 0xab
.word 0x12345678
00000000 <.data>:
0: 12345678
4: ab000000
8: 12345678
On voit que le code a été “zero padded” : 3 octets nuls ont été ajoutés après celui réservé par la directive .byte, aux adresses 0x5, 0x6 et 0x7. Ceci est fait pour que les données réservées par le deuxième
.word soient alignées, i.e. sur une adresse multiple de 4. En quelque sorte mips-as fait de “l’autoalignement”, puisqu’il ajoute de lui même des zéros de sorte que tous les mots définis pas des directives
.word soient sur des adresses alignées.
C’est très sympa, sauf que ce n’est pas ce qui est attendu ! Normalement, un assembleur doit faire
ce qu’on lui dit de faire, et rien d’autre. Si le programmeur décide de définir un mot sur une adresse
non alignée, il en va de sa responsabilité. Et s’il voulait aligner mais a oublié le .align 2 avant son
deuxième .word, tant pis pour lui !
De la même manière, il se peut que mips-as “auto-aligne” des instructions si vous mettez des .byte
dans une section .text. Ceci peut être intéressant car une instruction n’a de sens que si elle est sur une
adresse alignée, mais encore une fois c’est plutôt l’utilisateur qui devrait gérer ça correctement (il a
peut-être une raison d’agir comme il le fait).
Les consignes pour votre assembleur sont très claires :
– faites ce qu’on vous dit de faire, pas “d’auto-alignement”. L’exemple ci-dessus doit être codé
12345678 ab123456 78, rien de plus.
– à la limite, si vous estimez que le programmeur peut avoir omis un .align (pour une instruction
ou un .word sur une adresse non alignée), vous pouvez afficher un warning. Mais c’est tout 4 .
3.5.4
Directive d’exportation des noms
Par défaut, les étiquettes définies dans un fichier source sont considérées comme locales et ne seront
pas visibles par d’autres unités de compilation lors de l’édition de liens.
La directive .global <etiq> indique que l’étiquette <etiq> doit être exportée. On parle alors de
symbole global, qui pourra être référencé depuis d’autres unités de compilation. Par exemple, le point
d’entrée d’un programme s’appelle généralement _start et doit être exporté :
4. pas de politique “à la Windows” : “je fais des choses sans te le dire car je pense que c’est mieux pour toi”. Dans certains
contextes pourquoi pas, mais pas pour un assembleur !
24
CHAPITRE 3. LE LANGAGE D’ASSEMBLAGE DU MIPS
.global _start
# ...
_start: # ...
Si une étiquette est externe, c’est-à-dire utilisée dans un fichier source mais non définie localement
dans ce fichier, elle est supposé globale (exportée par une autre source). Si ce n’était pas le cas, il y aurait
une erreur à l’édition de liens (étiquette utilisée mais non définie).
3.5.5
Directive de non ré-ordonnancement
Pour éviter que l’assembleur n’optimise le code binaire en réordonnant des instructions (voir la
section 2.8 sur le delay slot), il est nécessaire de toujours ajouter la directive .set noreorder dans vos
fichiers de tests en langage assembleur.
Chapitre 4
Les instructions du MIPS
Ce chapitre présente le sous-ensemble des instructions et pseudo-instructions du MIPS retenu pour
le projet. Les spécifications détaillées des instructions, issues de la documentation de référence de MIPS
Technologies [1], sont fournies dans l’annexe F. Elles indiquent notamment le processus de codage permettant de traduire les instructions du programme source en binaire, et le comportement des instructions
à l’exécution.
4.1
Catégories d’instructions
L’architecture MIPS est de type load/store architecture : la mémoire n’est accédée que par les instructions de lecture/écriture. Toutes les opérations autres sont effectuées à partir de valeurs contenues
dans des registres, ou immédiates (encodées dans l’instruction). Contrairement à bien des processeurs,
chaque opérande d’une instruction n’admet ainsi qu’un seul mode d’adressage, et il n’y en a du reste
que 5 possibles (décrits section 2.5).
Les processeurs MIPS possèdent un jeu d’instruction réduit (processeur RISC) et au codage très
régulier. Le codage binaire d’une instruction est toujours sur 32 bits 1 , alignés en mémoire. Ceci permet
d’augmenter la vitesse de transfert des données et facilite l’extraction et le décodage des instructions.
Chaque étape est généralement réalisée en un seul cycle d’horloge dans le pipeline.
Il existe seulement trois formats d’instructions : R-type, I-type et J-type (pour register, immediate et jump), dont la syntaxe générale en langage assembleur est la suivante :
R-instruction rd, rs, rt
I-instruction rt, rs, immediate
J-instruction instr_index
Certaines instructions ont moins d’opérandes (par exemple B offset, qui est en fait de type I)
auquel cas le codage des opérandes "manquants" est une valeur constante, généralement zéro.
4.1.1
Les instructions de type R
Le codage binaire des instructions R-type, pour Register-type, suit le format suivant :
1. 64 bits pour des architectures plus récentes, non étudiées ici
25
26
CHAPITRE 4. LES INSTRUCTIONS DU MIPS
31
26 25
opcode
6
21 20
rs
5
16 15
rt
5
11 10
rd
5
6 5
sa
5
0
function
6
avec les champs suivants :
– opcode : (operation code) identifie la famille d’instructions (ce champ est commun à tous les
types d’instruction) ;
– rd : identifie le registre destination (valeur sur 5 bits, donc entre 0 et 31, codant le numéro du
registre) ;
– rs : identifie le registre contenant le premier opérande source ;
– rt : identifie le registre contenant le second opérande source (aussi appelé target) ;
– sa : (shift amount) est le nombre de bits de décalage, pour les instructions SLL, SRL, . . .
– function : 6 bits additionnels pour différencier les instructions R-type.
Pour les instructions de type R, c’est l’ensemble des champs opcode et function qui spécifie
l’instruction. Ceci permet de coder plus que 64 instructions (6 bits de opcode), ce qui serait peu même
pour un processeur RISC.
4.1.2
Les instructions de type I
Le codage binaire des instructions I-type, pour Immediate-type, suit le format suivant :
31
26 25
opcode
6
21 20
rs
5
16 15
rt
5
0
immediate
16
opcode est le code opération, rt le registre cible, rs le registre source et immediate une valeur
immédiate signée codée sur 16 bits.
4.1.3
Les instructions de type J
Le codage binaire des instructions J-type, pour Jump-type, suit le format suivant :
31
26 25
opcode
6
0
instr_index
26
opcode est le code opération et instr_index une valeur de saut non signée sur 26 bits.
4.2. INSTRUCTIONS ÉTUDIÉES DANS LE PROJET
4.2
4.2.1
27
Instructions étudiées dans le projet
Instructions arithmétiques
Mnémonique
ADD
ADDU
ADDI
ADDIU
SUB
MULT
DIV
Opérandes
rd, rs, rt
rd, rs, rt
rt, rs, immediate
rt, rs, immediate
rd, rs, rt
rs, rt
rs, rt
Opération
GPR[rd] ← GPR[rs] + GPR[rt]
GPR[rd] ← GPR[rs] + GPR[rt]
GPR[rt] ← GPR[rs] + immediate
GPR[rt] ← GPR[rs] + immediate
GPR[rd] ← GPR[rs] - GPR[rt]
(HI, LO) ← GPR[rs] * GPR[rt]
LO ← GPR[rs] div GPR[rt]
HI ← GPR[rs] mod GPR[rt]
– La plupart des instructions arithmétiques ont plusieurs “versions” : signée, non signée (U), immédiate (I), etc. Seules celles de l’addition sont présentées ici : ADD, ADDU, ADDI et ADDIU. Il existe
d’autres formats pour les autres opérations.
Le suffixe ’U’ est un faux ami : ADDU est exactement équivalent à ADD sauf qu’il n’y a pas d’exception levée en cas de débordement (voir la documentation).
– Pour MULT, les valeurs des deux registres rs et rt sont multipliées, ce qui donne un résultat sur 64
bits. Les 32 bits de poids fort de ce résultat sont placés dans le registre HI, et les 32 bits de poids
faible dans le registre LO. Les valeurs de ces registres sont accessibles à l’aide des instructions
MFHI et MFLO définies section 4.2.4.
– Pour DIV, le quotient de rs divisé par rt est placée dans le registre LO, et le reste de la division
entière dans le registre HI.
4.2.2
Les instructions logiques
Mnémonique
AND
OR
ORI
XOR
Opérandes
rd, rs, rt
rd, rs, rt
rd, rs, immediate
rd, rs, rt
Opération
GPR[rd] ←
GPR[rd] ←
GPR[rd] ←
GPR[rd] ←
GPR[rs]
GPR[rs]
GPR[rs]
GPR[rs]
AND GPR[rt]
OR GPR[rt]
OR immediate
XOR GPR[rt]
– Les deux registres de 32 bits rs et rt sont combinés bit à bit selon l’opération logique effectuée.
Le résultat est placé dans rd.
– Il existe aussi des opérations immédiates (ici, ORI) : immediate (16 bits) est étendue à gauche
avec des 0, puis combinée avec le contenu de rs.
4.2.3
Les instructions de décalage et set
Mnémonique
SLL
SRL
LUI
SLT
Opérandes
rd, rt, sa
rd, rt, sa
rt, immediate
rd, rs, rt
Opération
GPR[rd] ← GPR[rt] sa
GPR[rd] ← GPR[rt] sa
GPR[rt] ← immediate 16
GPR[rd] = (GPR[rs] < GPR[rt]) ? 1 : 0
– Le contenu du registre 32 bits rt est décalé de sa bits, à gauche pour SLL (shift left) et à droite
pour SRL (shift right, en insérant des zéros sur les bits de poids fort). sa est une valeur immédiate
sur 5 bits, donc entre 0 et 31. Le résultat est placé dans le registre rd.
28
CHAPITRE 4. LES INSTRUCTIONS DU MIPS
– LUI (Load Upper Immediate) charge une valeur immediate sur 16 bits dans les 2 octets de poids
fort du registre rt. Les deux octets de poids faible sont mis à zéro.
– SLT (Set on Less Than) Met la valeur 1 dans rd si le contenu de rs est plus petit que celui de rt,
0 sinon. Les valeurs dans rs et rt sont interprétées comme des entiers 32 bits signés.
4.2.4
Les instructions de lecture/écriture mémoire
Mnémonique
LW
SW
LB
LBU
SB
MFHI
MFLO
rt,
rt,
rt,
rt,
rt,
Opérandes
offset(base)
offset(base)
offset(base)
offset(base)
offset(base)
rd
rd
Opération
GPR[rt] ← memory[GPR[base]+offset]
memory[GPR[base]+offset] ← GPR[rt]
GPR[rt] ← memory[GPR[base]+offset]
GPR[rt] ← memory[GPR[base]+offset]
memory[GPR[base]+offset] ← GPR[rt]
GPR[rd] ← HI
GPR[rd] ← LO
– LW (Load Word) place le contenu du mot de 32 bits à l’adresse mémoire GPR[base] + offset
dans le registre rt. offset est une valeur immédiate signée sur 16 bits.
– SW (Store Word) place le contenu du registre rt dans le mot de 32 bits à l’adresse mémoire
GPR[base] + offset.
– LB (Load Byte) charge un octet en mémoire, l’étend à gauche (sign-extention), et place le
résultat dans rt. Idem pour LBU, mais la valeur est extended (complétion avec des zéros).
– SB (Store Byte) place l’octet de poids faible du registre rt à l’adresse mémoire GPR[base] +
offset.
– MFHI (Move from HI) : copie le contenu du registre HI dans le registre rd.
– MFLO (Move from LO) : copie le contenu du registre LO dans rd.
4.2.5
Les instructions de branchement et de saut, et de contrôle
Mnémonique
B
BEQ
BNE
BGTZ
BLEZ
J
JAL
JR
SYSCALL
Opérandes
offset
rs, rt, offset
rs, rt, offset
rs, offset
rs, offset
instr_index
instr_index
rs
Opération
branchement: PC ← PC + offset 2
Si (GPR[rs] = GPR[rt]) alors branchement
Si (GPR[rs] != GPR[rt]) alors branchement
Si (GPR[rs] > 0) alors branchement
Si (GPR[rs] <= 0) alors branchement
PC ← PC[31:28] instr_index 2
GPR[31] ← PC, PC ← PC[31:28] instr_index 2
PC ← GPR[rs].
Interrompt l’exécution pour un service
– B effectue un branchement après l’instruction. Le décalage signé de 18 bits (offset de 16 bits
décalés de 2) est ajouté à l’adresse de l’instruction de branchement + 4 pour déterminer l’adresse
effective du saut (PC a déjà été incrémenté au moment du calcul du décalage).
– Les autres instructions de branchement B[xx] ne branchent que si la condition sur les registres
rs et rt est vérifiée.
– J effectue un branchement aligné à 256 Mo dans la région mémoire du PC. Les 28 bits de poids
faible de l’adresse du saut correspondent au champ instr_index décalé de 2. Les 4 bits de poids
forts restant correspondent aux 4 bits de poids fort du compteur PC (voir section 2.5).
Attention, l’instruction suivant immédiatement le J, dans son delay slot, sera exécutée avant le
saut lui-même (voir la section 2.8) ! On y place généralement un NOP.
4.3. LES PSEUDO-INSTRUCTIONS
29
– JAL fonctionne exactement comme J, mais place en plus l’adresse de retour dans le registre $31
($ra, return address). Il s’agit de l’adresse de la deuxième instruction suivant le JAL et où l’exécution pourra reprendre après le traitement de la routine (voir l’exemple annexe B). C’est l’instruction utilisée en particulier pour faire des appels de fonctions.
De même que pour J, l’instruction dans le delay slot sera exécutée avant le saut lui-même.
– JR effectue un saut à l’adresse spécifiée dans rs. Son usage le plus fréquent est JR $ra, en fin
d’une routine ayant été appelée par un JAL.
4.3
Les pseudo-instructions
Les pseudo-instructions ne sont pas définies parmi les instructions du MIPS, mais uniquement au
niveau de l’assembleur. Lors de la compilation, l’assembleur les remplace automatiquement par un
équivalent (pas forcément unique) composé d’une ou plusieurs instructions en langage machine.
Certaines pseudo-instructions traduisent simplement des opérations courantes :
Mnémonique
NOP
MOVE
LI
LI
LI
LA
Opérandes
rt, rs
rt, immediate15
rt, immediate16
rt, immediate32
rt, label
Opération équivalente
SLL $zero, $zero, $zero
ADDU rt, rs, $zero
ADDIU rt, $zero, immediate15
ORI rt, $zero, immediate16
LUI rt, hi(immediate32)
ORI rt, rt, lo(immediate32)
LUI rt, addendHI16
ADDIU rt, rt, addendLO16
+ relocation
+ relocation
– NOP (Not an Operation) n’effectue aucun traitement, seul le compteur programme est incrémenté
(lors du fetch, lecture de l’instruction).
– MOVE copie le contenu du registre rs dans le registre destination rt.
– LI (Load Immediate) place la valeur immédiate dans le registre destination rt. Selon la valeur
de immediate, sur 15 bits (≤ 0x7FFF), sur 16 bits (avec bit 15 == 1) ou sur 32 bits, le code
correspondant est différent.
– LA (Load Address) charge l’adresse d’un symbole dans rt. Cette pseudo-instruction est obligatoirement accompagnée d’informations de relocation liées au symbole.
Les autres pseudo-instructions (très nombreuses, en réalité) reprennent des instructions existantes,
mais avec des paramètres différents (nombre, mode d’adressage, etc.). Nous ne gardons ici que les cas
où une étiquette est utilisée en paramètre pour un saut, un branchement ou un accès mémoire.
Mnémonique
B[xx]
J/JAL
LW/SW
Opérandes
[rs, rt,] label
label
rt, label
Opération équivalente
offset = label - (adresse instruction + 4)
B[xx] [rs, rt,] offset
J addend26
+ relocation
LUI $1, addendHI16
+ relocation
LW/SW rt, addendLO16($1)
+ relocation
– Tous les branchements sur une étiquette fonctionnent sur le même principe : le décalage offset
est calculé par différence entre l’adresse mémoire nommée par l’étiquette et celle de l’instruction
suivant le branchement (car à l’exécution du branchement, PC aura déjà été incrémenté).
30
CHAPITRE 4. LES INSTRUCTIONS DU MIPS
Les pseudo-instructions de saut et mémoire avec étiquettes ont besoin du mécanisme de relocation,
partie “avancée” de ce projet. Il est entièrement décrit au chapitre 5 et nécessite sa lecture avant de
comprendre comment fonctionnent ces pseudo-instructions. Un exemple complet utilisant étiquettes et
relocation est fourni en annexe B.
– Un saut sur une étiquette est obligatoirement accompagné d’informations de relocation. La valeur
codée dans le champ instr_index est l’addend de 26 bits nécessaire au calcul de relocation.
– L’adresse mémoire accédée par les instructions LW et SW suit la syntaxe offset(base), où
offset est codé sur 16 bits. Mais puisqu’une étiquette désigne une adresse mémoire, donc 32
bits, il est nécessaire d’utiliser deux instructions LUI puis LW/SW pour accéder correctement à
cette adresse. LUI charge la valeur de 16 bits addendHI16 dans les 2 octets de poids fort du
registre temporaire $1, et LW/SW rajoute un offset addendLO16 à cette adresse de base. Ces deux
valeurs addend ne sont pas absolues, et sont toujours accompagnées d’informations de relocation.
Une précision sur le registre temporaire des pseudos LW/SW. Ces pseudo-instructions, utilisées avec
un symbole, sont remplacées par une instruction LUI suivie d’une instruction de Load/Store (LW, SW,
mais aussi LB . . .).
Pour les instructions Store, c’est bien $1 qui est utilisé comme registre temporaire. Par contre
pour les instruction Load mips-as utilise le registre de destination $rt comme temporaire, et non
$1. Par exemple, le code équivalent à LW $rt, label est LUI $rt, addendHI16 suivi de LW $rt,
addendLO16($rt). En fait, mips-as cherche à minimiser le nombre de registres utilisés par le programme, donc utilise $rt (qui sera de toutes façons écrasé).
Notez qu’à l’exécution, il n’y a aucune différence ! Par conséquent les deux codages seront acceptés,
avec $at ou $rt.
4.4
Codage binaire des instructions
Les spécifications des instructions étudiées dans ce projet sont données dans l’annexe F. Elles sont
directement issues de la documentation fournie par le Software User’s Manual de Architecture For Programmers Volume II de MIPS Technologies [1].
4.4.1
Exemple : l’instruction ADD
Nous donnons ici un exemple pour expliciter la spécification d’une instruction, l’opération ADD. Les
autres instructions ne seront pas détaillées, seules les spécifications du manuel seront données.
Pour chaque instruction, la documentation fournit le format de codage binaire, une description de
l’opération effectuée et éventuellement des remarques ou restrictions d’usage.
31
26
SPECIAL
000000
6
25
21
20
16
15
11
rs
rt
rd
5
5
5
Format: ADD rd, rs, rt
10
6
5
0
0
ADD
00000
5
100000
6
4.4. CODAGE BINAIRE DES INSTRUCTIONS
31
Purpose: To add 32-bit integers. If an overflow occurs, then trap.
Additionne deux nombres entiers sur 32-bits, si il y a un débordement, l’opération n’est pas effectuée.
Description: GPR[rd] ← GPR[rs] + GPR[rt]
The 32-bit word value in GPR rt is added to the 32-bit value in
GPR rs to produce a 32-bit result.
. If the addition results in 32-bit 2’s complement arithmetic
overflow, the destination register is not modified and an
Integer Overflow exception occurs.
. If the addition does not overflow, the 32-bit result is placed
into GPR rd.
No comment, juste un petit exercice pratique d’anglais. . . Les descriptions données dans le manuel
sont généralement très claires.
Restrictions: None
Operation:
temp <- (GPR[rs]31||GPR[rs]31..0) + (GPR[rt]31||GPR[rt]31..0)
if temp32 != temp31 then
SignalException(IntegerOverflow)
else
GPR[rd] <- temp
endif
Exceptions: Integer Overflow
Programming Notes:
ADDU performs the same arithmetic operation but does not trap on overflow.
Exemple de codage pour les instructions ADD et ADDI :
ADD $2, $3, $4
ADDI $2, $3, 200
00641020
206200C8
A vous de retrouver ceci ! Un bon petit exercice pour la compréhension. . .
4.4.2
Champs spécifiques de certaines instructions (JR, SRL, SYSCALL, . . .)
Dans la documentation, les champs de certaines instructions peuvent avoir plusieurs valeurs, qui
dépendent généralement de la version de l’architecture ou de l’utilisation de ces instructions. Dans le
cadre de ce projet, nous travaillons sur une version 32 bits R3000 du MIPS, version 1 de l’architecture.
Avec ceci :
– codage de l’instruction JR : le champ hint vaut toujours 0.
– codage de SRL : le bit 21 (noté R0) vaut toujours 0.
– codage de SYSCALL : le champ code (bits 6 à 25) vaut toujours 0.
32
CHAPITRE 4. LES INSTRUCTIONS DU MIPS
Chapitre 5
Relocation
5.1
Cycle de vie d’un programme
Un programme exécutable est obtenu en plusieurs étapes à partir de un ou plusieurs fichiers sources.
Chacun de ces fichiers sources décrit l’un des modules du programme dans un langage de programmation
donné : langage de haut niveau (Ada, C, etc.) ou langage d’assemblage.
Les étapes successives permettant d’obtenir un programme exécutable sont les suivantes :
1. Analyse et traduction de chaque fichier source : cette étape est réalisée pour chaque fichier par
le compilateur ou l’assembleur du langage utilisé. Le résultat est un fichier binaire relogeable
(on dit aussi translatable). Ce fichier contient des instructions qui font référence à des adresses
incomplètes ou inconnues. En effet :
– Le module peut utiliser des données ou des sous-programmes définis dans d’autres modules
que celui en cours de compilation ou d’assemblage.
– L’adresse de chargement en mémoire du programme exécutable n’est pas connue (et pour cause,
puisque ce dernier n’existe pas encore). Le compilateur ou l’assembleur place donc à l’adresse
0 chaque section du module, qu’il s’agisse des données ou des instructions.
2. Fusion des différents fichiers binaires relogeables : il s’agit ici de réunir les différents modules du
programme. Il faut en particulier « résoudre » les adresses inconnues : si un module A utilise un
sous-programme P défini dans un module B, il devient dans cette étape possible de compléter A
avec l’adresse de P. Le résultat est un fichier binaire relogeable unique, ne contenant en principe
plus aucune adresse inconnue. 1
Cette étape est réalisée par l’éditeur de liens.
3. Implantation du programme en mémoire (ou relocation) : il s’agit de la dernière étape permettant d’obtenir un programme exécutable. Toutes les adresses utilisées par le programme sont
relogées (ou translatées) de façon à tenir compte des adresses auxquelles il est prévu de charger
en mémoire les différentes sections du programme (instructions, données modifiables, données
non modifiables, etc.). Ces adresses de chargement ont une valeur par défaut, mais peuvent aussi
être spécifiées par le programmeur.
L’étape de relocation est en général confiée, elle aussi, à l’éditeur de liens : le résultat est un
fichier binaire exécutable qui ne contient que des adresses définies et absolues et donc définitives.
1. Ceci n’est vrai que dans le cas où le système d’exploitation sous-jacent n’utilise pas les bibliothèques partagées et
l’édition de liens dynamique.
33
34
CHAPITRE 5. RELOCATION
code binaire
relogeable
(adresses non résolues)
code
source
prog1.c
compilation
code binaire
relogeable
(adresses résolues)
prog1.o
fusion
prog2.s
assemblage
code binaire
exécutable
(adresses relogées)
prog.o
implantation
prog
prog2.o
éditeur de liens
chargement
en memoire
traducteurs
chargeur
image
mémoire
résultat
exécution
données
processeur
Figure 5.1 – Cycle de vie d’un programme
La relocation est effectuée une fois pour toutes et le chargement ultérieur en mémoire d’un tel
fichier est trivial.
5.2
Les symboles
Un programme en langage d’assemblage (et à plus forte raison un programme en langage de haut
niveau) ne manipule jamais ou très rarement des adresses directement. Ces dernières sont représentées
sous forme symbolique au moyen de noms. Ce sont les noms de variables ou de sous-programmes
dans un programme en langage de haut niveau, et ce sont les étiquettes dans un programme en langage
d’assemblage. Chacun de ces noms est associé à une adresse, et on appelle symbole cette association.
Chaque symbole est caractérisé par plusieurs propriétés essentielles :
– nom : c’est la partie “visible” du symbole, c’est à dire ce qui permet au programmeur de déclarer
puis d’utiliser le symbole. Il s’agit bien sûr d’une chaîne de caractères.
– valeur : la valeur d’un symbole est l’adresse associée au nom du symbole. Cette valeur peut être
indéfinie (inconnue), relative au début d’une section (d’une partie) du programme, ou bien absolue
(relative au début de la mémoire).
– portée : un symbole peut être local ou global :
– un symbole local est défini dans un fichier source et n’est visible, c’est à dire utilisable, que
dans ce fichier source (on pourrait dire aussi que ce symbole est privé). La valeur d’un symbole
local est toujours définie.
– un symbole global défini est un symbole qui est défini dans le fichier source considéré et est
exporté : ce symbole est visible dans tous les fichiers sources qui constituent le programme (on
pourrait dire aussi que ce symbole est public).
– un symbole global indéfini est un symbole qui est utilisé dans le fichier source considéré mais
n’est pas défini dans ce fichier : ce symbole est importé par le fichier. La valeur d’un tel symbole
est indéfinie.
– type : on distingue essentiellement deux types de symboles :
5.3. PRINCIPE DE LA RELOCATION
35
– les symboles de programme sont définis et utilisés explicitement par le programmeur. Ce sont
principalement les étiquettes (ou les noms de variables et de sous-programmes).
– les symboles de section sont associés chacun à une section. La valeur d’un symbole de section
est l’adresse en mémoire de la section associée à ce symbole.
– section de définition : un symbole défini (local ou global) est défini dans une section, laquelle est
donc sa section de définition. Un symbole indéfini ne possède pas bien sûr de section de définition.
5.3
5.3.1
Principe de la relocation
Définition
L’opération dite de relocation, ou de relogement ou d’implantation, intervient lors de la fusion de
fichiers objet et à la transformation d’un fichier binaire relogeable en un fichier binaire exécutable. En
d’autres termes, il s’agit de convertir les adresses incomplètes dans les instructions ou les réservations de
données en adresses définitives (définies et absolues) en respectant des adresses d’implantation finales
des différentes sections, qui sont soit fournies soit fixées par convention.
Le principe des codes relogeables repose sur le fait que si les adresses effectives des instructions et
étiquettes ne sont pas connues au moment de l’assemblage, leur position relative au début de la section
où elles se trouvent l’est ! Lors de l’assemblage, il suffit donc de générer une version «relative» du
code binaire et d’inclure dans le fichier objet toutes les informations qui permettront d’adapter ce code
lorsque les adresses seront connues de manière absolues.
Un fichier relogeable contient donc (au moins) :
– le code des sections .text et éventuellement .data, assemblé avec des informations liées aux
positions relatives des instructions et données par rapport au début de chaque section.
– une table de relocation pour chaque section, indiquant pour chaque adresse relative à mettre à jour
au moment de l’implantation : la position dans la section de l’emplacement à mettre à jour, le mode
de relocation (définissant les calculs à effectuer pour déterminer les bonnes adresses effectives),
ainsi que le numéro dans la table des symboles du symbole correspondant à l’opérande relogé.
– une table des symboles contenant pour chaque étiquette son adresse relative au début de la section
dans laquelle elle est définie (cas des symboles locaux ou globaux définis dans le fichier considéré), ou bien 0 lorsque l’étiquette n’est pas définie dans ce fichier (cas des symboles globaux
indéfinis).
5.3.2
Relocation en pratique
Les exemples suivants illustrent les problèmes rencontrés nécessitant de la relocation :
Exemple 1
. text
ADDI $3 , $0 , 12345
SW $3 , X
# Met l a v a l e u r 12345 d a n s l e r e g i s t r e 3
# E c r i t l e c o n t e n u du r e g i s t r e 3 à l ’ a d r e s s e X
. data
X : . word 0 x0
# D é c l a r a t i o n d ’ un mot de 32 b i t s
initialisé à 0
Pas de problème pour l’instruction ADDI, toujours codée de la même manière.
36
CHAPITRE 5. RELOCATION
En revanche le codage du SW 2 dépend de l’adresse à laquelle correspond l’étiquette "X" ! En effet
il est nécessaire de calculer le décalage entre l’adresse du SW et celle de la donnée X. Ceci n’est
possible qu’à partir du moment où les adresses d’implantation des sections .text, .data et .bss
sont connues. Or, ce n’est pas le cas au moment de l’assemblage puisque ces adresses ne seront
déterminées que lors de la phase d’implantation, c’est à dire lors de l’édition de liens finale, ou
bien lors du chargement du programme en mémoire avant son exécution.
Comment alors coder la pseudo-instruction SW si le décalage entre son adresse et celle de la donnée
X ne peut pas être calculé ?
Exemple 2
. data
X : . b y t e 0xAB
Y : . word Z
Z : . word 0 x0
# D é c l a r a t i o n d ’ un o c t e t i n i t i a l i s é à 0xAB
# Mot de 32 b i t s Y, en f a i t une r é f é r e n c e à l ’ a d r e s s e Z
# D é c l a r a t i o n d ’ un mot de 32 b i t s i n i t i a l i s é à 0
Dans cet exemple la valeur déclarée derrière l’étiquette Y correspond en fait à l’adresse associée à
l’étiquette Z. Comme dans le cas précédent, il est nécessaire de connaître l’adresse d’implantation
de la section .data pour déterminer celles des données X, Y et Z.
Exemple 3
JAL f o n c t i o n
NOP
B end
NOP
#
#
#
#
Appel à l a p r o c é d u r e " f o n c t i o n " ( met PC à l a bonne a d r e s s e )
On ne f a i t r i e n i c i ( delay slot — section 2.8 )
R e t o u r i c i a p r è s e x é c u t i o n de " f o n c t i o n " ; b r a n c h e m e n t s u r end
Delay s l o t
fonction :
NOP
JR $31
# On ne f a i t . . . r i e n !
# F i n de l a p r o c é d u r e " f o n c t i o n " , r e t o u r à l ’ a p p e l a n t
end :
# The End !
Ici deux étiquettes sont utilisées par des instructions de branchement (B) et de saut (JAL). Ces
deux cas ne sont cependant pas équivalents :
– Le codage du branchement nécessite de calculer le décalage entre l’instruction B et l’étiquette
end. Mais puisque cette étiquette est dans la même section .text que le branchement luimême, le décalage peut être calculé directement lors de l’assemblage !
Mais ce n’est pas toujours le cas : dans un programme multi-fichiers, si l’étiquette désignant la
destination du saut est un symbole externe non résolu (situé dans la section .text d’un autre
fichier objet assemblé séparément), alors ce décalage n’est pas calculable.
– Le problème est différent pour l’instruction de saut vers l’adresse désignée par l’étiquette
fonction. D’après les spécifications de l’instruction JAL, lors de son exécution, l’adresse de
destination du saut à effectuer est calculée à partir de l’opérande (ce que l’on cherche justement
à remplir) ET de la valeur courante de PC, soit l’adresse de l’instruction JAL + 4 (Souvenez
vous que lors de l’exécution effective d’une instruction, la valeur de PC a déjà été incrémentée
de la longueur d’un mot). En effet, les 4 bits de poids forts du PC déterminent une zone mémoire (un segment) de 256 Mo, dont l’adresse est un multiple de 228 ; le saut est effectué dans
les limites de ce segment.
2. Il s’agit ici de la pseudo-instruction SW, dont le deuxième opérande est une adresse (32 bits), et non de l’instruction qui
attend un offset sur 16 bits ! Elle sera remplacée par l’assembleur par les instructions LUI et SW, voir section 4.3.
5.4. RELOCATION AU FORMAT ELF POUR LE MIPS
37
Donc, même si la position de l’étiquette fonction est ici connue par rapport à la section où se
trouve l’instruction JAL (la section .text), il manque la position effective en mémoire de cette
section pour pouvoir déterminer le codage complet de l’opérande.
En résumé, le codage d’un programme n’est déterminé de manière absolue qu’à partir du moment où les adresses mémoires auxquelles seront implantées les différentes sections .text, .data
et .bss (donc instructions et données) sont connues.
5.4
Relocation au format ELF pour le MIPS
Plusieurs formats relogeables existent. L’un des premiers mis au point fut le format COFF (Common
Object File Format), initialement développé sous Unix et encore utilisé sur les systèmes Windows. Mais
le format le plus répandu à ce jour est ELF (Executable and Linkable Format [5], [7]), utilisé par de
nombreux systèmes d’exploitation dont Linux, MacOs ou SunOs. Il est conçu pour assurer une certaine
portabilité entre différentes plateformes. Il s’agit d’un format standard pouvant supporter l’évolution des
architectures et des systèmes d’exploitation.
La suite de cette section présente les informations nécessaires à la relocation au sein du format ELF.
Des informations techniques supplémentaires sont décrites dans l’annexe E.
5.4.1
La table des symboles
Tous les symboles définis et/ou utilisés dans un fichier source sont regroupés au sein d’une table de
symboles dans le fichier binaire issu de la compilation ou de l’assemblage de ce fichier source. La table
de symboles d’un fichier binaire est l’élément central de l’architecture interne de ce fichier. En effet,
c’est dans cette table qu’on retrouve les adresses associées aux noms définis par le programmeur, mais
aussi les adresses où sont implantées les différentes sections du programme en vue de son exécution.
Le contenu d’une table de symboles d’un fichier binaire au format ELF peut être affiché sous une
forme lisible au moyen de l’outil readelf, ou plus précisément de l’outil mips-readelf dans le cas
du processeur MIPS (annexe A). Par exemple (voir en fait l’annexe B.2) :
Symbol
Num :
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
t a b l e ’ . symtab ’
Value S i z e
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000014
0
00000024
0
00000008
0
00000000
0
00000004
0
c o n t a i n s 12 e n t r i e s :
Type
Bind
Vis
NOTYPE LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
OBJECT GLOBAL DEFAULT
NOTYPE LOCAL DEFAULT
NOTYPE LOCAL DEFAULT
NOTYPE LOCAL DEFAULT
NOTYPE LOCAL DEFAULT
NOTYPE LOCAL DEFAULT
Ndx
UND
1
3
5
6
7
1
1
1
3
3
3
Name
. text
. data
. bss
. reginfo
. pdr
_start
write
end
Z
X
Y
On remarque dans cet exemple que tous les symboles sont locaux, sauf un défini localement mais
exporté (directive .global). Certains sont des symboles de section (.text, .data, etc.). Les autres
symboles sont des symboles de programme : leur type est noté NOTYPE. La section de définition de
chaque symbole figure dans la colonne Ndx sous la forme d’un numéro de section : il s’agit d’un index
38
CHAPITRE 5. RELOCATION
dans la table des sections, sorte de table des matières du contenu du fichier.
Pour plus de détails, voir l’annexe E.2.5, page 77 et les exemples.
5.4.2
Table de relocation
Les informations nécessaires à la relocation sont définies dans des tables de relocation, qui seront
incluses par l’assembleur dans le fichier objet binaire relogeable. Une table est associée à chaque section
contenant des symboles à reloger. La table associée à la section .text (respectivement .data) est
nommée .rel.text (respectivement .rel.data).
Chaque entrée (ou ligne) d’une table de relocation est définie par les informations suivantes :
– offset : la position de l’entrée à modifier (une instruction ou une donnée), en nombre d’octets
par rapport au début de la section à laquelle la table est associée.
– type : le mode de relocation (voir plus loin, section 5.4.4).
– symbol : l’instruction ou la donnée à reloger fait référence à un symbole s d’adresse inconnue.
S’il est local, la valeur symbol est l’index (le numéro), dans la table des symboles, du symbole de
section correspondant à la section où s est défini. Si s est un symbole global (défini ou indéfini),
symbol est l’index, dans la table des symboles, du symbole en question.
Dans l’exemple 2, l’entrée à reloger est un mot situé 4 octets après le début de la section .data (à
l’adresse de l’étiquette Y). Le symbole Z à reloger est local et est défini dans la section .data. L’entrée
correspondante dans la table de relocation .rel.data est :
$ mips-readelf -r exemple.o
Relocation section ’.rel.data’ at offset 0x358 contains 1 entries:
Offset
Info
Type
Sym.Value Sym. Name
00000004 00000202 R_MIPS_32
00000000
.data
On constate que le symbole utilisé dans cette entrée (colonne Sym. Name) est bien le symbole de section
de la section de définition du symbole Z (section .data).
Dans l’exemple 3, l’entrée à reloger est l’instruction JAL, la première de la section .text, et le
symbole recherché est l’étiquette fonction. Ce symbole est local, aussi défini dans la section .text.
La table .rel.text contient donc :
Relocation section ’.rel.text’ at offset 0x350 contains 1 entries:
Offset
Info
Type
Sym.Value Sym. Name
00000000 00000104 R_MIPS_26
00000000
.text
Le symbole utilisé dans cette entrée est là encore le symbole de section de la section de définition de
fonction.
5.4.3
Champ addend
Une dernière donnée pour la relocation d’une entrée est le addend, c’est-à-dire la valeur binaire
présente dans l’emplacement qui va être modifié par la relocation. Cette valeur, contenue dans le fichier
binaire relogeable, sera récupérée par l’éditeur de liens ou le chargeur, et utilisée avec les informations
de la table de relocation concernée pour déterminer le codage définitif.
Selon le mode de relocation, le champ addend correspond aux 32/26/16 bits de poids faible de
l’emplacement à reloger (toujours sur 32 bits).
Dans le cas des symboles définis, mais dont on ne connaît pas encore l’adresse définitive, l’addend
est en général l’adresse relative du symbole par rapport au début de sa section de définition. Dans le cas
5.4. RELOCATION AU FORMAT ELF POUR LE MIPS
39
des symboles indéfinis (référence à un symbole défini dans un autre fichier), l’assembleur n’a même pas
d’adresse relative, et ne peut que laisser une valeur arbitraire pour l’addend, en général 0.
5.4.4
Modes de relocation du MIPS
31
WORD32
0
31
TARG26
0
31
15
LO16
0
31
15
HI16
0
31
15
PC16
0
Figure 5.2 – Selon le mode de relocation 32, 26 ou 16 bits de poids faible sont mis à jour. Les bits de
poids fort (codage de l’instruction) restent inchangés.
Le mode de relocation détermine le calcul spécifique à effectuer pour reloger une entrée (instruction
ou donnée). Une quinzaine de modes existent (définis dans [8]), mais seul ceux nécessaires à notre projet
sont ici décrits (avec quelques restrictions).
R_MIPS_32 Ce mode est utilisé pour reloger une donnée en section .data (exemple 2). Les 32 bits de
la donnée sont modifiés.
R_MIPS_26 Ce mode correspond à la relocation des instructions de saut de type J (exemple 3). Les 6
bits de poids fort de l’instruction (opcode) ne seront jamais changés par la relocation.
R_MIPS_HI16 & R_MIPS_LO16 Ces deux modes fournissent les informations pour la relocation
d’instructions successives de type I. Dans notre cadre, ils seront utilisés pour les instructions
d’accès mémoire (LW, SW, LUI ; exemple 1). Seuls les 16 bits de poids faible de chaque entrée
sont mis à jour. Des entrées de ces types apparaissent toujours par couple dans une table de
relocation : chaque relocation R_MIPS_HI16 et immédiatement suivie d’une relocation de type
R_MIPS_LO16 3 .
R_MIPS_PC16 Ce mode est sans doute le plus difficile à comprendre, on pourra l’ignorer dans un premier temps et revenir dessus par la suite. Il permet d’assembler un branchement relatif (instruction
B par exemple). Dans le cas des branchements relatifs internes à une section, il n’y a pas besoin
de relocation, puisque le déplacement est connu à l’assemblage (c’est en fait le gros avantage des
branchements relatifs !). Pour les branchements relatifs inter-section, par contre, on a besoin de
ce mode particulier : à l’assemblage, on assemblera un branchement sur l’instruction elle-même
(donc, un saut en arrière de 4 octets puisque le branchement s’effectue depuis l’instruction suivant
l’instruction B). L’éditeur de liens va soustraire l’adresse finale de l’instruction relogée, et ajouter
l’adresse effective de destination du branchement.
Les notations utilisées pour la description des calculs de relocation sont les suivantes :
3. En réalité non, une entrée HI16 est suivie par une ou plusieurs entrées LO16.
40
CHAPITRE 5. RELOCATION
– P désigne l’adresse de l’emplacement (instruction ou réservation de donnée) à reloger, c’est-àdire l’adresse d’implantation de la section + l’adresse relative de l’emplacement dans la section
(offset).
– A désigne la valeur de l’addend : il s’agit de la valeur présente provisoirement dans l’emplacement
à reloger, plus précisément dans les 32/26/16 bits de poids faible de l’emplacement, selon le mode
de relocation. Cette valeur peut dans certains cas être négative (bit de poids le plus fort à 1), il faut
donc dans les calculs utiliser l’extension signée à 32 bits de cette valeur, si nécessaire.
– S désigne l’adresse (c’est à dire la valeur) du symbole référencé par l’entrée de relocation. On
rappelle que ce symbole peut être un symbole de section (cas des symboles locaux) ou bien un
symbole de programme (cas des symboles globaux).
– AHL est une valeur 32 bits calculée à partir des addend de deux entrées de relocation R_MIPS_HI16
et R_MIPS_LO16 consécutives. Si AHI et ALO sont les addends d’une paire d’entrées HI16 et
LO16, de 16 bits chacun, AHL est alors calculé par :
AHL = (AHI 16) + (short)ALO.
Par exemple, si AHI = 0xABCD et ALO = 0x12, alors AHL = 0xABCD0012.
De même, si AHI = 0xABCD et ALO = 0xFFF0, alors AHL = 0xABCCFFF0.
Avec ceci, les calculs de relocation sont les suivants :
R_MIPS_32
R_MIPS_26
Adresse du 1er
bit à modifier
P
P + 6 bits
Nb de bits à
modifier
32
26
R_MIPS_HI16
P + 16 bits
16
R_MIPS_LO16
P + 16 bits
16
R_MIPS_PC16 4
P + 16 bits
16
Mode
5.4.5
Valeur à écrire
S+A
([(A 2) | (P & 0xF0000000)] + S) 2
ou bien :
(((A 2) + S) | (P & 0xF0000000)) 2
((AHL + S) - (short)(AHL + S)) 16
ou bien : ((AHL + S) 16) & 0x0000FFFF
AHL + S
ou bien : (AHL + S) & 0x0000FFFF
A + (S 2) - (P 2)
...
Pour passer le mal de tête, rien de tel qu’un exemple complet et commenté, non ?
Allez donc vous coucher, puis faites demain un tour en annexe B et tout ira mieux !
56
4. La norme prétend que la formule est A + S - P, mais ne la croyez pas !
5. Vous pouvez reprendre le sujet en relevant (par exemple au surligneur) tout ce qui parle de relocation. Finir de tout lire,
puis faire les applications des annexes B à D avec vos trinômes d’équipe.
6. Que celui ou celle qui a compris l’ensemble de ce chapitre à la première lecture se dénonce, svp.
Chapitre 6
Comment programmer un assembleur ?
Ce chapitre introduit les principes généralement retenus pour écrire un assembleur, structurés selon
trois étapes successives :
– l’analyse lexicale, qui découpe le fichier texte d’entrée en une suite de lexèmes, mots reconnus par
le langage assembleur ;
– l’analyse syntaxique ou grammaticale, qui vérifie que cette suite de lexèmes est conforme à la
grammaire du langage et aux spécifications des instructions ;
– la phase de génération de code, qui traduit le programme correct au format binaire du MIPS et
génère le fichier objet correspondant.
6.1
Analyse lexicale
Un analyseur lexical commence par décomposer le fichier d’entrée en une suite d’unités formées de
caractères contigus, elles-mêmes séparées par des espaces ou délimitées sans ambiguïté par la nature des
caractères qui les composent. Cette phase d’analyse produit une suite de lexèmes, mots autorisés par le
langage assembleur : mnémoniques des instructions et directives, noms de registres, valeurs numériques,
étiquettes définies par le programmeur, etc. Les commentaires sont ignorés. La figure 6.1 fourni un
exemple de programme et le découpage en lexèmes correspondant.
L’analyse lexicale est généralement réalisée à l’aide de règles définissant la grammaire du langage,
à laquelle est associé un automate à états finis. un dictionnaire est souvent utilisé pour la reconnaissance
des identificateurs (mnémoniques de directives, d’instructions, etc.).
Cette phase de l’analyse ne fait pas partie de votre travail, elle est gérée par le module de code fourni.
6.2
Analyse syntaxique
L’analyse syntaxique sert à vérifier que les lexèmes extraits lors de l’analyse lexicale sont employés
dans un ordre correct. L’analyseur transforme le flot de léxèmes en une suite de lignes logiques, chacune
contenant la définition d’un symbole local, une directive avec éventuellement son paramètre ou bien une
instruction avec ses paramètres (figure 6.1). L’analyse syntaxique vérifie l’intégrité des lignes logiques
composant le programme.
L’analyse syntaxique est souvent décomposée en deux étapes 1 :
1. Ces notions et les techniques d’implantation de l’analyse syntaxique seront étudiées dans les cours de compilation et
lors du projet Génie Logiciel de deuxième année. Pour ce projet. . .à vous de voir !
41
42
CHAPITRE 6. COMMENT PROGRAMMER UN ASSEMBLEUR ?
.text
# Un commentaire...
etiq: .byte -4
ADD $2, $3, $4
J 0xABCD
[1][
LEX_DIR] .text
[1][
LEX_NL]
[2][
LEX_NL]
[3][
LEX_SYMB] etiq
[3][ LEX_DEUX_PTS] :
[3][
LEX_DIR] .byte
[3][
LEX_MOINS] [3][
LEX_NOMBRE] 4
[3][
LEX_NL]
[4][
LEX_INST] ADD
[4][
LEX_REG] $2
[4][ LEX_VIRGULE] ,
[4][
LEX_REG] $3
[4][ LEX_VIRGULE] ,
[4][
LEX_REG] $4
[4][
LEX_NL]
[5][
LEX_INST] J
[5][
LEX_NOMBRE] 0xabcd
[5][
LEX_NL]
Ligne log 1 (directive)
[ 1][
LEX_DIR] .text
[ 1][
LEX_NL]
Ligne log 2 (def symbole)
[ 3][
LEX_SYMB] etiq
[ 3][ LEX_DEUX_PTS] :
Ligne log 3 (directive)
[ 3][
LEX_DIR] .byte
[ 3][
LEX_MOINS] [ 3][
LEX_NOMBRE] 4
[ 3][
LEX_NL]
Ligne log 4
(instruction)
[ 4][
LEX_INST] ADD
[ 4][
LEX_REG] $2
[ 4][ LEX_VIRGULE] ,
[ 4][
LEX_REG] $3
[ 4][ LEX_VIRGULE] ,
[ 4][
LEX_REG] $4
[ 4][
LEX_NL]
Ligne log 5
(instruction)
[ 5][
LEX_INST] J
[ 5][
LEX_NOMBRE] 0xabcd
[ 5][
LEX_NL]
Figure 6.1 – Découpage en lexèmes puis en lignes logiques d’un programme assembleur. Les commentaires ont été supprimés. etiq est noté comme LEX_SYMB car ne fait pas partie des identificateurs du
langage. Seule l’analyse syntaxique, contextuelle, confirmera s’il s’agit bien d’un “vrai” symbole ou
non.
Analyse non contextuelle La syntaxe d’un langage de programmation peut être entièrement explicitée
par une grammaire hors-contexte. Elle décrit une décomposition hiérarchique de l’objet global
qu’est le programme vers les objets les plus élémentaires possibles que sont les lexèmes, en passant par toutes les relations régissant les objets intermédiaires.
La grammaire hors-contexte de ce projet (celle de as simplifiée) est donnée figure 6.2.
Vérification contextuelle Appelée aussi analyse sémantique, cette étape consiste à vérifier l’intégrité
des lignes logiques dans leur contexte.
Par exemple, la ligne logique ADD 0x17, $3, $2 est correcte du point de vue de la grammaire
hors contexte : il s’agit bien d’une ligne logique composée d’un mnémonique d’instruction suivi
d’opérandes séparés par une virgule. Il y a par contre une erreur syntaxique contextuelle, puisque
le mode d’adressage du premier opérande (valeur immédiate) n’est pas valide pour l’instruction
ADD (la destination doit être un registre).
Si la grammaire hors-contexte est liée au langage d’assemblage, la vérification contextuelle est
elle intrinsèquement dépendante du processeur.
6.3
Génération du code binaire
Suite aux phases précédentes, le programme a été décomposé en une suite de lignes logiques valides.
La dernière étape est donc de générer le code binaire correspondant.
6.4. ÉCRITURE DU FICHIER OBJET
43
Chaque ligne logique d’instruction ou de directive de déclaration de donnée est traduite en binaire,
suivant les spécifications du manuel. Le code binaire de chaque section est ensuite la concaténation du
codage de l’ensemble des lignes logiques de la section.
Il n’est pas toujours possible après le premier parcours de la structure des lignes logiques de déterminer entièrement le codage binaire du programme, par exemple si une étiquette est utilisée comme
opérande mais est définie plus loin dans le programme (son adresse n’est pas encore connue). Il faut
alors procéder à une deuxième lcture de la structure de données afin de compléter les instructions non
codées. On parle généralement d’assemblage en deux passes.
A la fin de cette étape, tous les symboles (définis localement ou externes) ont été entrés dans la table
de symboles. Les symboles locaux ont de plus été résolus (leur adresse dans la section est connue).
Si des informations de relocation sont nécessaires, elles sont également entièrement déterminées en
sortie de cette étape.
6.4
Écriture du fichier objet
La dernière étape consiste simplement à écrire les informations (codage binaire de chaque section,
tables des symboles, tables de relocation) dans un fichier objet. Le format ELF sera utilisé dans ce projet
(voir section 5.4 et annexe E).
44
CHAPITRE 6. COMMENT PROGRAMMER UN ASSEMBLEUR ?
Programme
−→
Liste_NL Liste_Lignes
Suite (év. vide) de NL puis suite lignes
Liste_NL
Liste_NL_nv
−→
−→
| Liste_NL_nv
LEX_NL Liste_NL
Suite de NL év. vide
Suite de NL non vide
Liste_Lignes
−→
|
Ligne Liste_NL_nv Liste_Lignes
Suite non vide de lignes
Ligne
−→
|
|
|
|
LEX_DIRSECTION
LEX_DIR Param_Dir
LEX_INST Liste_Params
Etiq Ligne
Etiq
Directive de sectionnement (nom de zone)
Autre directive puis param. de dir
Instruction puis liste de param.
Etiquette puis Ligne
Etiquette seule
Etiq
−→
LEX_SYMB LEX_DEUX_PTS
Nom du symbole puis deux-points
Param_Dir
−→
| Nombre | LEX_CHAINE | LEX_SYMB
Liste_Params
−→
|
|
|
Liste de paramètres,
Param_Inst
vide ou avec 1, 2 ou 3 paramètres d’instruction
Param_Inst LEX_VIRGULE Param_Inst
Param_Inst LEX_VIRGULE Param_Inst LEX_VIRGULE Param_Inst
Param_Inst
−→
|
|
|
LEX_REG
Nombre
LEX_SYMB
Nombre LEX_PAR_OUVR LEX_REG LEX_PAR_FERM
Nombre
−→
LEX_INTEGER | LEX_MOINS Nombre
Figure 6.2 – Grammaire hors-contexte du langage assembleur de ce projet.
Chapitre 7
Spécifications, travail à réaliser
7.1
Spécifications de l’assembleur
L’objectif du projet est d’implanter en C l’assembleur asmips pour processeur MIPS. Le langage
d’assemblage du fichier source est présenté en particulier au chapitre 3, avec sa grammaire formelle figure 6.2). Seul un sous-ensemble des instructions est à gérer dans ce projet, celles décrites en section 4.2.
La documentation des instructions (syntaxe et codage), similaire à l’exemple de ADD donné section 4.4,
est fournie dans le document [1] disponible sur le kiosk.
Utilisation
Votre assembleur sera appelé depuis une console en tapant :
asmips source_file_name.s
où source_file_name.s est le nom du fichier source du programme à assembler.
Assemblage d’un programme correct
Si source_file_name.s est un fichier en langage assembleur MIPS syntaxiquement correct,
l’assembleur doit produire un fichier objet binaire “source_file_name.o” au format ELF relogeable.
Gestion des erreurs
Si le fichier source_file_name.s n’est pas correct, aucun fichier objet ne doit être généré.
L’assembleur doit rejeter les programmes comportant des erreurs lexicales ou syntaxiques (double
définition de symbole, mode d’adressages invalides, etc.) avec des messages d’erreurs appropriés
et en indiquant le numéro de ligne dans le texte source (voir par exemple en section 7.2.1, et en
testant le code distribué).
Option -s : stripping
L’option “-s” indique que le fichier binaire généré ne doit pas contenir les symboles locaux (excepté les symboles de section), ni dans la table des symboles, ni dans la table des chaînes. Le
binaire ainsi produit est plus petit, mais l’édition de lien doit toujours fonctionner.
Cette option est équivalente à l’option --strip-local-absolute de l’assembleur GNU as.
Option -l : Liste d’assemblage Cette option permet d’afficher sur la sortie standard une liste d’assemblage, qui résume les informations sur le fichier assemblé. Il est conseillé d’implémenter cette
option rapidement, pour faciliter la validation du fonctionnement de l’assembleur (avant même de
générer le fichier objet proprement dit).
45
46
CHAPITRE 7. SPÉCIFICATIONS, TRAVAIL À RÉALISER
Son format n’est pas formellement imposé, mais vous pouvez vous inspirer des exemples disponibles
en annexes B et C, de la sortie de GNU as avec l’option -a ou des formats utilisés par les outils
mips-objdump ou mips-readelf décrits annexe A.
La liste d’assemblage est composée de plusieurs parties portant sur les sections, les symboles et
la relocation :
Sections d’instructions et de données Pour les trois sections .text, .data et .bss, chaque
ligne de la liste d’assemblage correspond à une ligne logique du programme avec :
– le numéro en décimal de la ligne du fichier source ;
– l’adresse en hexadécimal de l’instruction ou de la donnée ;
– le codage en hexadécimal de l’instruction ou de la donnée ;
– le contenu de la ligne correspondante du fichier source (déduit des informations contenues
dans les lignes logiques).
Pour les lignes sans instruction ni déclaration de donnée, seul le numéro et le contenu
(éventuellement vide) seront présents. Pour les pseudo-instructions se décomposant en plusieurs
instructions (par exemple LW avec un symbole), l’adresse et le codage de chacune de ces instructions seront affichés à la suite (voir les exemples en annexe).
Table des symboles Après le programme, la liste de tous les symboles définis dans le programme
est affichée, avec pour chacun d’eux :
– le numéro de la ligne où il est défini ;
– la section à laquelle il appartient (.text, .data, .bss) ;
– son déplacement, en nombre d’octets par rapport au début de sa section ;
– sa portée, c’est-à-dire s’il est local ou global, et s’il est défini ou non.
– son nom.
Table(s) de relocation À chaque section contenant des instructions ou données à reloger est associée une table de relocation, dont le format est introduit en section 5.4.2.
Le nom de la table (.rel.text ou .rel.data) indique à quelle section elle se rattache.
Pour chaque entrée de relocation seront affichés : la valeur du champ offset, le mode de
relocation et la section contenant le symbole référencé.
7.2
Code et modules fournis
Plusieurs éléments sont distribués :
– un squelette de code permettant l’analyse lexicale et une partie de l’analyse syntaxique ;
– un module d’écriture de fichier au format ELF.
7.2.1
Squelette de l’assembleur
Le squelette d’assembleur réalise :
1. l’analyse lexicale d’un fichier source, c’est-à-dire la génération de la suite de lexèmes correspondants au programme ;
2. le début de l’analyse syntaxique. Les lexèmes sont regroupés en lignes logiques, et la vérification
contextuelles des lignes contenant une directive est effectuée. Les autres types de lignes, instruction ou définition de symbole, ne sont pas analysées. Elles ne contiennent que la liste des lexèmes
qui les composent.
7.2. CODE ET MODULES FOURNIS
. set noreorder
. text
ADD $8 , $9 , $10
/∗
/∗
/∗
47
1 ∗/
2 ∗/
3 ∗/
. data
i c i : . b y t e 0xFA
. text
LW $ t 9 , l a b a s
/∗
/∗
/∗
/∗
/∗
5
6
6
8
9
[
[
[
[
[
[
. set noreorder
. text
LIGNE NON ANALYSEE
3][
LEX_INST ] ADD
3][
LEX_REG ] $8
3 ] [ LEX_VIRGULE ] ,
3][
LEX_REG ] $9
3 ] [ LEX_VIRGULE ] ,
3][
LEX_REG ] $10
[
[
[
[
. data
ici :
. byte 0 xfa
. text
LIGNE NON ANALYSEE
9][
LEX_INST ] LW
9][
LEX_REG ] $25
9 ] [ LEX_VIRGULE ] ,
9][
LEX_SYMB] l a b a s
∗/
∗/
∗/
∗/
∗/
Figure 7.1 – Exemple de trace obtenable avec le squelette de code fourni. Les lignes logiques de directives ont été analysées. Pour les autres, seule une suite des lexème est donnée.
Si des erreurs lexicales ou syntaxiques sont détectées, un message approprié est affiché pour chaque
ligne concernée. L’analyse se poursuit ensuite jusqu’à la fin du programme. Par exemple .byte 0xFAB
dans l’exemple de la figure 7.1 entraînerait un message du style :
ERREUR ligne 6:
debordement arithmetique: valeur sur 1 octet attendue
Le squelette distribué contient :
– quelques éléments décrivant la syntaxe du langage d’assemblage as et des structures représentant
les résutats de l’analyse effectuée : lignes logiques, lexèmes, directives. . .
– une fonction qui effectue l’analyse lexicale et le début de l’analyse syntaxique ;
– un “dictionnaire” des instructions MIPS reconnues, sous format texte.
Ces structures devront être utilisées pour accéder au résultats de l’analyse faites par le squelette de
code. Si nécessaire, vous pourrez également les enrichir pour mener à bien la suite de votre projet.
Ce squelette est distribué sous forme de plusieurs modules : fichiers .h et parfois .c, et un unique
fichier objet noyau.o déjà compilé qui devra être utilisé pour l’édition de liens de votre programme.
L’API (Application Programming Interface), c’est-à-dire les spécifications des structures de données et
fonctions fournies, est disponible sur le kiosk. Ces spécifications sont en fait directement inclues entre
des balises spécifiques dans les fichiers d’en-tête .h. La documentation est ensuite générée à l’aide d’un
analyseur (ici doxygen 1 ) qui extrait les informations contenues dans le code source pour créer une
documentation formatée.
Une des premières étapes de votre travail consistera à étudier et comprendre le code distribué, et
à réaliser un premier programme parcourant la liste des lexèmes des lignes logiques, pour afficher le
contenu d’un fichier source sous une forme similaire à la trace donnée figure 7.1.
1. www.doxygen.org
48
7.2.2
CHAPITRE 7. SPÉCIFICATIONS, TRAVAIL À RÉALISER
Module d’écriture de fichiers ELF
Le module elf_writer est une interface de haut-niveau permettant de générer facilement des
fichiers objet relogeables au format ELF (avec quelques limitations liées au cadre de ce projet). Il fournit
un ensemble de procédures pour spécifier les informations à inclure dans le fichier objet (code binaire
des différentes sections, table des symboles, table(s) de relocation, . . .). Ce module est détaillé dans l’annexe E. Il est distribué sous forme d’un fichier d’interface elf_writer.h et d’un fichier objet compilé
elf_writer.o.
Dans un premier temps, vous devrez simplement utiliser ce module pour générer le fichier de sortie
de votre assembleur. La partie “extension” de ce projet consistera ensuite à réimplémenter vous-même
ce module, pour remplacer le fichier elf_writer.o fourni par les enseignants.
7.3
7.3.1
Travail à réaliser
Assembleur
Le cahier des charges minimal de ce projet est naturellement, en partant du squelette de code distribué, de terminer l’implémentation de l’assembleur asmips pour répondre en totalité aux spécifications
vues en section 7.1.
7.3.2
Validation
Il est conseillé de travailler de manière incrémentale, en validant différents aspects (vérification
syntaxique, gestion des erreurs, génération du code binaire, écriture ELF, etc.) sur des programmes de
difficulté croissante (codes simples, puis avec symboles, puis relocation, etc.).
Un autre point important sera de vous constituer une base de fichiers tests écrits en assembleur.
L’utilisation des outils pour le MIPS décrits en annexe A (mips-as, mips-objdump, mips-readelf,
. . .) sera particulièrement importante tout d’abord pour bien comprendre l’assemblage et les informations
contenues dans les fichiers ELF, puis pour comparer les résultats obtenus avec votre assembleur et avec
ces outils.
7.3.3
Extension : réécriture de elf_writer.o
À condition d’avoir répondu entièrement au cahier des charges minimal, une extension du projet 2
est de réécrire votre propre module d’écriture de fichiers objets au format ELF, pour remplacer le fichier
elf_writer.o fourni par les enseignants. L’interface de votre module devra suivre celle de celui distribué.
Ce travail sera réalisé à partir de l’étude de la spécification détaillée du format ELF. Le meilleur document de référence, clair et complet, sur ce sujet est [7]. La première partie “Object files” est nécessaire
et suffisante. Se référer également à l’annexe E.
Remarque : il existe une librairie libelf fournissant une interface de plus bas niveau que elf_writer
pour la lecture et l’écriture de fichiers ELF. Cependant cette librairie est difficile d’utilisation et très peu
(voire mal) documentée. Dans ce projet, il est explicitement demandé de ne pas utiliser libelf 3 .
2. Une autre extension possible serait de réécrire l’analyse lexicale et l’analyse syntaxique complète (avec arbre syntaxique
décoré, . . .). Si vous êtes intéressé il vous suffit. . . de passer en 2A et d’attendre patiemment le Projet Génie Logiciel du mois
de Janvier !
3. Et en toute honnêté, il est plus intéressant mais aussi plus simple (si si !) de ne pas utiliser libelf.
Chapitre 8
Généralités et organisation du projet
8.1
Objectifs du projet
Tout informaticien doit connaître le langage C. C’est une espèce d’espéranto de l’informatique. Les
autres langages fournissent en effet souvent une interface avec C (ce qui leur permet en particulier de
s’interfacer plus facilement avec le système d’exploitation) ou sont eux-mêmes écrits en C. D’autre part
c’est le langage de base pour programmer les couches basses des systèmes informatiques. Par exemple,
on écrit rarement un pilote de périphérique en Ada ou Java. Le langage C est un excellent langage
pour les programmes dont les performances sont critiques, en permettant des optimisations fines, à la
main, des structures de données ou des algorithmes (typiquement, les systèmes de gestions de base de
données et d’une manière générale les logiciels serveurs sont majoritairement écrits en C). Finalement,
en compilation, C est souvent choisi comme cible de langages de plus haut niveau.
Cependant, beaucoup d’entre vous ne seront que rarement, voire jamais, confrontés à de gros développements logiciels entièrement en C dans leur vie professionnelle. L’objectif pédagogique du projet logiciel
en C est donc surtout de montrer comment C peut servir d’interface entre les langages de haut niveau et
les couches basses de la machine. Plus précisément, les objectifs du projet logiciel en C sont :
– Apprentissage de C (en soi, et pour la démarche qui consiste à apprendre un nouveau langage).
– Lien du logiciel avec les couches basses de l’informatique, ici logiciel de base et architecture.
– Le premier projet logiciel un peu conséquent, à développer dans les règles de l’art (mise en œuvre
de tests, documentation, démonstration du logiciel, partage du travail, ...)
8.2
8.2.1
Déroulement du projet
Organisation du libre-service encadré
Pendant tout le libre-service encadré, il faut consulter régulièrement la page d’EnsiWiki du projet 1 .
En effet cette page contient les informations de dernière minute sur le déroulement et l’organisation du
projet. En particulier, il est impératif de consulter régulièrement la page spécifique au sujet MIPS.
Pour réaliser le projet, les étudiants bénéficient d’un encadrement du travail en libre-service (voir
la page EnsiWiki pour les détails des horaires). Pendant ces heures de libre-service encadré, des salles
machines de l’Ensimag ont été réservées pour les étudiants participant au projet. De plus, les enseignants
assurent une permanence pour aider les étudiants sur :
– la programmation en langage C.
1. http://ensiwiki.ensimag.fr/index.php/Projet_C
49
50
CHAPITRE 8. GÉNÉRALITÉS ET ORGANISATION DU PROJET
– l’environnement de développement (make, autres outils gnu, etc) et les programmes fournis.
– la conception du programme.
– l’organisation du projet.
– la compréhension générale des sujets donnés aux étudiants.
Les enseignants ne sont pas là pour corriger les bugs, pour programmer ou concevoir le programme
à la place des étudiants. Si les enseignants l’estiment nécessaire, ils peuvent débloquer les groupes en
difficulté. Les questions posées par les étudiants doivent être précises et réfléchies.
8.2.2
Cas de fraudes
Il est interdit de copier ou de s’inspirer, même partiellement, de fichiers concernant le projet C, en
dehors des fichiers donnés explicitement par les enseignants et des fichiers écrits par des membres de
son trinôme. Il est aussi interdit de communiquer des fichiers du projet C à d’autres étudiants que des
membres de son trinôme.
Les sanctions encourues par les étudiants pris en flagrant délit de fraude sont le zéro au projet (sans
possibilité de rattrapage en deuxième session), plus les sanctions prévues dans le règlement de la scolarité en cas de fraude aux examens. Dans ce cadre, il est en particulier interdit :
– d’échanger (par mail, internet, etc) des fichiers avec d’autres étudiants que les membre de son
trinôme.
– de lire ou copier des fichiers du projet C dans des répertoires n’appartenant pas à un membre de
son trinôme.
– de posséder sur son répertoire des fichiers de projets des années précédentes ou appartenant à
d’autres trinômes.
– de laisser ses fichiers du projet C accessibles à d’autres étudiants que les membres du trinôme.
Cela signifie en particulier que les répertoires contenant des fichiers du projet C doivent être des
répertoires privés, avec autorisation en lecture, écriture ou exécution uniquement pour le propriétaire, et que les autres membres du trinôme ne peuvent y accéder que par ssh (échange de clef
publique) ou un contrôle de droit avec les ACLs (dans les deux cas, des documentations se trouvent sur la page d’EnsiWiki pour vous aider). Pendant la durée du projet, seul les membres du
trinôme doivent pouvoir accéder au compte.
– De récupérer du code sur Internet ou toute autre source (sur ce dernier point, contactez les responsables du projet si vous avez de bonnes raisons de vouloir une exception).
Les fichiers concernés par ces interdictions sont tous les fichiers écrits dans le cadre du projet : fichiers
C, fichiers assembleurs, scripts de tests, etc.
Dans le cadre du projet C, la fraude est donc un gros risque pour une faible espérance de gain, car
étant donné le mode d’évaluation du projet (voir section 8.3), la note que vous aurez dépend davantage
de la compréhension du sujet et de la connaissance de l’implantation que vous manifestez plutôt que
de la qualité “brute” de cette implantation. Notez également que des outils automatisés de détection de
fraude seront utilisés dans le cadre de ce projet.
8.2.3
Conseils et consignes
Le projet ne consiste pas seulement à programmer pendant 2 semaines. Il est conseillé de réfléchir
avant de taper : un temps important passé à la conception du programme réduit le temps de développement/débogage du programme, et permet d’éviter les impasses.
8.2. DÉROULEMENT DU PROJET
51
Pour attaquer le projet, il est bien sûr nécessaire de lire attentivement le sujet et de refaire les calculs
sur les points délicats (codage des instructions, relocation, . . .). Il aussi fondamental pour bien comprendre de “jouer” avec les outils décrits en section A, pour assembler des fichiers de tests en langage
assembleur et étudier le contenu des fichiers objets générés.
Après ces expériences, vous pouvez vous lancer dans la conception du programme : choix des structures de données intermédiaires, découpage modulaire du code (fonctions, fichiers), etc. Il faut prévoir
une décomposition du développement de manière à pouvoir tester et corriger le programme au fur et à
mesure que vous l’écrivez. Sinon, vous risquez d’avoir un programme très difficile à corriger, ou vous
risquez de devoir réécrire de grandes portions de code. De manière générale, on code d’abord les cas les
plus généraux et/ou les plus simples, avant de coder les cas particuliers et/ou compliqués. La programmation modulaire permet en outre d’avoir un code plus concis et donc plus facile à déboguer. Programmez
de manière défensive : pour les cas que votre programme ne devrait jamais rencontrer si vous avez
programmé correctement, mettez un message compréhensible du type Erreur interne, fonction
bidule et arrêtez proprement le programme, afin de pouvoir déboguer plus facilement. Placez des traces
d’exécutions dans vos programmes de manière à pouvoir suivre le déroulement du programme. Utilisez
les macros C pour faire afficher ou supprimer ces traces facilement (cf. cours de C au début du projet).
Vous allez aussi travailler à trois (toujours instructif !) : gérez bien les interfaces entre vos tâches
et réfléchissez à comment tester votre propre travail même si le module du trinôme n’est pas encore fini
(ceci est valable pour les trois membres du trinôme !).
Enfin, définissez vos priorités ! Est-ce la peine de s’attaquer à une extension alors qu’un module de
la spécification minimale n’a pas été implanté ? Gardez aussi comme ligne directrice d’avoir le plus tôt
possible un programme qui fonctionne, même s’il ne gère pas tout. Ensuite, améliorez-le au fur et à
mesure.
8.2.4
Styles de codage
Indépendamment de la correction des algorithmes, un code de bonne qualité est aussi un code facile,
et agréable à lire. Dans un texte en langue naturelle, le choix des mots justes, la longueur des phrases,
l’organisation en chapitres et en paragraphes peuvent rendre la lecture fluide, ou bien au contraire très
laborieuse.
Pour du code source, c’est la même chose : le choix des noms de variables, l’organisation du code
en fonctions, et la disposition (indentation, longueur des lignes, ...) sont très importants pour rendre un
code clair. La plupart des projets logiciels se fixent un certain nombre de règles à suivre pour écrire et
présenter le code, et s’y tiennent rigoureusement. Ces règles (Coding Style en anglais) permettent non
seulement de se forcer à écrire du code de bonne qualité, mais aussi d’écrire du code homogène. Par
exemple, si on décide d’indenter le code avec des tabulations, on le décide une bonne fois pour toutes et
on s’y tiens, pour éviter d’écrire du code dans un style incohérent comme :
if (a == b) {
printf("a == b\n");
} else
{
printf ( "a et b sont différents\n");
}
Pour le projet C, les règles que nous vous imposons sont celles utilisées par le noyau Linux. Pour
vous donner une idée du résultat, vous pouvez regarder un fichier source de noyau au hasard (a priori,
52
CHAPITRE 8. GÉNÉRALITÉS ET ORGANISATION DU PROJET
sans comprendre le fond). Vous trouverez un lien vers le document complet sur EnsiWiki, lisez-le.
Certains chapitres sont plus ou moins spécifiques au noyau Linux, vous pouvez donc vous contenter
des Chapitres 1 à 9.
Nous rappelons ici le document dans les grandes lignes :
– Règles de présentation du code (indentation par tabulations de taille 8 caractères, pas de lignes de
plus de 80 caractères, placements des espaces et des accolades, ...)
– Règles et conseils pour le nommage des fonctions (trouver des noms courts et expressifs à la fois).
– Règles de découpage du code en fonction : faire des fonctions courtes, qui font une chose et qui
le font bien.
– Règles d’utilisations des commentaires : en bref, expliquez pourquoi votre code est comme il est,
et non comment. Si le code a besoin de beaucoup de commentaire pour expliquer comment il
fonctionne, c’est qu’il est trop complexe et qu’il devrait être simplifié.
Certaines de ces règles (en particulier l’indentation) peuvent être appliquées plus ou moins automatiquement. Le chapitre 9 vous présente quelques outils pour vous épargner les tâches les plus ingrates :
GNU Emacs et la commande indent (qui fait en fait un peu plus que ce que son nom semble suggérer).
Pour le projet C, nous vous laissons le choix des outils, mais nous exigeons un code conforme à
toutes ces directives.
8.2.5
Outils
Les outils pour développer et bien développer en langage C sont nombreux. Nous en présentons ici
quelques-uns, mais vous en trouverez plus sur EnsiWiki (les liens appropriés sont sur la page du projet),
et bien sur, un peu partout sur Internet !
– Emacs ou Vim sont d’excellents éditeurs de texte, qui facilite la vie du développeur en C (et pas
seulement) : indentation automatique, coloration syntaxique, navigation de code . . .
– hexdump, qui permet de dumper 2 sur le terminal sous différents formats le contenu d’un fichier.
On pourra apprécier l’option -C, qui non contente de rappeler la forme classique des années 80,
permet de voir les caractères binaires et leur interprétation ASCII.
Pour les amateurs, od permet des affichages du même ordre et est en 2 lettres au lieu de 7.
– xxd, en trois lettre cette fois, est une sorte de hexdump -C mais qui permet aussi de régénérer un
fichier binaire à partir de sa représentation hexadécimale avec l’option -r.
– gdb le debugger ou son interface graphique ddd permettent de tracer l’exécution. Son utilisation
est très intéressante, mais il ne faut pas espérer réussir à converger vers un programme correct par
approximations successives à l’aide de cet outil, ...
– valgrind sera votre compagnon tout au long de ce projet. Il vous permet de vérifier à l’exécution
les accès mémoires faits par vos programmes. Ceci permet de détecter des erreurs qui seraient
passées inaperçues autrement, ou bien d’avoir un diagnostic pour comprendre pourquoi un programme ne marche pas. Il peut également servir à identifier les fuites mémoires (i.e. vérifier que
les zones mémoires allouées sont bien désallouées). Pour l’utiliser :
valgrind [options] <executable> <paramètres de l’exécutable>
– Deux programmes peuvent vous servir lors de vos tests, ou pour l’optimisation de votre code.
gprof est un outil de profiling du code, qui permet d’étudier les performances de chaque morceau
de votre code. gcov permet de tester la couverture de votre code lors de vos tests. L’utilisation
des deux programmes en parallèle permet d’optimiser de manière efficace votre code, en ne vous
2. Néologisme pas si récent, c.f. http://www.infoclick.fr/dico/D/dump.html, de l’homo-informaticus signifiant
« copier le contenu d’une mémoire vers un autre support sans aucune transformation de son contenu ».
8.3. EVALUATION DU PROJET
53
concentrant que sur les points qui apporteront une réelle amélioration à l’ensemble. Pour savoir
comment les utiliser, lisez le manuel.
– Pour vous aider à travailler à plusieurs en même temps, des outils peuvent vous aider. Les gestionnaires de versions permettent d’avoir plusieurs personnes travaillant sur le même code en parallèle, et de fusionner automatiquement les changements. Votre formation Git du premier semestre
devrait vous servir.
– Finalement, pour tout problème avec les outils logiciels utilisés, ou avec certaines fonctions
classiques du C, les outils indispensables restent l’option --help des programmes, le manuel
( man <commande>), et en dernier recours, Google ! 3
8.2.6
Aspects matériel
Tout le travail de programmation s’effectuera sur des machines avec un système d’exploitation
Linux. Tous les outils nécessaire sont installés à l’école.
Vous pouvez naturellement travailler sur votre ordinateur personnel si vous en possédez un, mais attention le projet final doit fonctionner correctement à l’école, sous Linux, le jour de la soutenance !.
Soyez prudent si vous développez sous Windows, le portage est loin d’être toujours automatique...
8.3
Evaluation du projet
L’évaluation de votre projet se fera lors d’une soutenance à partir de votre rendu.
8.3.1
Rendu des fichiers de votre projet
L’archive de votre projet devra être déposée sur Teide. Elle contiendra :
– tous les sources et fichiers entêtes (y compris les en-têtes qui vous sont fournis),
– les fichiers de tests créés
– un fichier README.txt décrivant le contenu du rendu, son organisation, son utilisation et l’intérêt
des tests fournis,
– un fichier Makefile avec à minima une cible (clean).
– ne pas inclure par contre les fichiers objets de votre projet, qui sera de toutes façons recompilé
lors de la soutenance.
Le code rendu doit :
– respecter les directives de codage imposées
– compiler avec la commande make
– et sans warning(s) problématiques (l’option -Werror de GCC peut vous aider)
– marcher sur telesun
– et s’y exécuter correctement (là, c’est valgrind qui vous aidera)
Attention, les projets qui ne compilent pas sur Telesun le jour de la soutenance ou qui terminent de
manière quasi-systématique par un Segmentation Fault auront une note inférieure à 8 ! Un moyen
simple d’éviter ce type de problème est de compiler et de déboguer le programme au fur et à mesure.
Réaliser parfaitement ce projet pendant le temps imparti est difficile pour un trinôme standard d’étudiants en première année. Il n’est donc pas nécessaire d’avoir tout fait pour avoir une bonne note. Par
contre, il est nécessaire que ce qui a été fait, ait été bien fait. Pour cela, il faut bien tester au fur et à
mesure du développement, en laissant les aspects les plus avancés du projet pour plus tard.
3. 7g de CO2 par requête
54
8.3.2
CHAPITRE 8. GÉNÉRALITÉS ET ORGANISATION DU PROJET
Soutenance
Les modalités exactes de la soutenance seront données lors de la présentation générale du projet en
amphithéatre. De manière générale, les points suivants seront abordés :
– Le trinôme expose brièvement un bilan du projet et présente une démonstration du fonctionnement
du programme. Le bilan doit préciser :
– l’état du programme vis-à-vis du cahier des charges : ce qui a été réalisé, ce qui n’a pas été
réalisé, les bugs non corrigées, etc.
– les principaux choix de conception du programme : structures de données choisies, architecture
du programme, etc.
– les facilités/difficultés rencontrées, les bonnes et mauvaises idées,
– l’organisation du projet dans le trinôme (répartition des tâches, synchronisation, etc.).
La démonstration illustre le fonctionnement du programme sur quelques exemples, afin de montrer son adéquation vis-à-vis des spécifications. Il est conseillé d’utiliser plusieurs exemples courts
et pertinents pour illustrer les différents points de la spécification. La démonstration pourra contenir 1 ou 2 exemples plus longs pour montrer “le passage à l’échelle”.
– Il pourra vous être demandé de modifier votre programme, par exemple pour rajouter une fonctionnalité
– Ensuite l’enseignant teste vos programmes et vous interroge sur le projet. Les questions peuvent
porter sur tous les aspects du projet, mais plus particulièrement sur des détails de votre implémentation, comment vous procéderiez pour terminer les fonctionnalités manquantes, et comment
vous procéderiez pour ajouter une nouvelle fonctionnalité.
Vous aurez au moins une demi-journée entre la date de rendu des fichiers et votre soutenance.
Préparez la sérieusement ! Il serait dommage d’avoir fait du bon travail sur le projet, mais de perdre
des points à cause d’une soutenance mal préparée. Il n’est pas demandé de transparents, mais répétez
plusieurs fois la soutenance pour vous assurer de répondre aux points abordés ci-dessus, dans le temps
imparti et en assurant une bonne répartition du temps de parole entre les membres du trinôme.
Bibliographie
[1] MIPS Technologies, MIPS32 Architecture For Programmers Volume II : The MIPS32 Instruction
Set, Rev 2.50, July 1, 2005. http://www.mips.com/ 4, 4.4, 7.1, F
[2] Bradley Kjell, Programmed Introduction to MIPS Assembly langage.
http://chortle.ccsu.edu/AssemblyTutorial/TutorialContents.html 2.1
[3] Emulators - LinuxMIPS. http://www.linux-mips.org/wiki/Emulators
[4] Wikipedia : MIPS architecture. http://en.wikipedia.org/wiki/MIPS_architecture
[5] MIPS Assembly Language Programmer’s Guide.
http://www.cs.unibo.it/~solmi/teaching/arch_2002-2003/
AssemblyLanguageProgDoc.pdf 5.4
[6] Charles Lin, Computer Organization, University of Maryland.
http://www.cs.umd.edu/class/spring2003/cmsc311/Notes
[7] Tools Interface Standard (TIS), Executable and Linkable Format (ELF), Portable Formats Specification, Ver 1.2.
http://refspecs.linuxfoundation.org/ 5.4, 7.3.3, E.1
[8] SYSTEM V APPLICATION BINARY INTERFACE, MIPS RISC Processor Supplement. http://
refspecs.linuxfoundation.org 5.4.4
[9] Free Software Foundation & TIGCC Team, The GNU Assembler.
http://tigcc.ticalc.org/doc/gnuasm.html 3
[10] Club d’entraide des développeurs francophones.
http://www.developpez.com
55
56
BIBLIOGRAPHIE
Annexe A
Suite d’outils spécifiques au MIPS
Pour assembler des fichiers de tests en langage assembleur et étudier leur contenu, les outils de la
suite binutils ont été cross-compilés pour une architecture MIPS.
Ces programmes sont disponibles sur les machines de l’école. Pour les installer sur une machine
personelle, une aide sera mise à disposition sur la page web du projet.
A.1
Génération de fichiers ELF
A.1.1
L’assembleur mips-as
mips-as test.s -o test.o permet de créer le fichier objet binaire test.o au format ELF à
partir du fichier texte test.s contenant le source assembleur.
L’utilisation de mips-as sera fondamentale pour compréhension du codage et la validation de votre
propre assembleur (par comparaison des fichiers assemblés).
A.1.2
L’éditeur de liens mips-ld
Il permet de réaliser les deux phases décrites figure 5.1, page 34 : la fusion de fichiers objets (avec
relocation des symboles externes) puis l’implantation des trois sections .text, .data et .bss à des
adresses mémoires déterminées.
Ainsi mips-ld test.o -o test génère un programme exécutable. Les adresses des sections dans
test sont déterminées, de même que le point d’entrée, et il n’y a plus d’informations de relocation. 1 Dans le cas multi-fichiers, mips-ld toto1.o toto2.o toto3.o -o totoexe fusionne les
trois fichiers objets puis crée un exécutable.
Avec l’option -r, il est possible de ne réaliser que la phase de “fusion” de plusieurs fichiers objet, mais sans la phase d’implantation. Par exemple, mips-ld -r toto1.o toto2.o toto3.o -o
totos.o génère un unique fichier objet mais qui n’est pas un exécutable. Seule la relocation des symboles externes à un fichier et définis dans un des deux autres a été faite, et certaines autres informations
de relocation remises à jour (les adresses relatives ont pu changer du fait de la fusion des sections).
totos.s ne contient plus de symbole externe, mais reste un fichier relogeable.
L’option -e entry spécifie le symbole définissant le point d’entrée du programme exécutable. Si
cette option n’est pas présente, l’éditeur de lien défini par défaut le point d’entrée comme l’adresse du
1. Sauf en cas de liaison dynamique de librairies, mais ceci sort du cadre de ce projet.
57
58
ANNEXE A. SUITE D’OUTILS SPÉCIFIQUES AU MIPS
symbole global _start, s’il existe. S’il n’existe pas, le point d’entrée sera placé au début de la section
.text (et vous aurez gagné un warning).
L’éditeur de liens vous permettra de créer des exécutables à partir de fichiers objet assemblés avec
votre propre assembleur. Un moyen de vérifier la validité de ces fichiers objet et de tester l’édition de
liens, et éventuellement la bonne exécution du programme.
A.2
Étude du contenu de fichiers ELF
Plusieurs outils permettent d’afficher les informations contenues dans un fichier ELF :
– mips-objdump test.o permet d’étudier le contenu d’un fichier binaire au format ELF. Plusieurs
options sont possibles : -d pour désassembler la section .text, -r pour voir la table de relocation,
etc.
– mips-readelf test.o permet également de lire les infos sur toutes les sections d’un fichier
ELF. Présentation et informations parfois différentes par rapport à mips-objdump (souvent plus
clair, mais pas de désassemblage).
– od -t xC address-radix=x test.o, ou hexdump -C test.o pour ceux qui aiment lire un
fichier ELF binaire directement en hexadécimal. . .(rare, mais ponctuellement utile).
Ces outils vous serons très utiles, d’abord pour mieux comprendre la structure d’un fichier ELF puis
pour valider les sorties de votre propre assembleur !
A.3
Exécution de programmes pour MIPS
À moins de travailler sur une architecture MIPS (ce qui n’est pas le cas à l’Ensimag, par exemple) il
n’est pas possible d’éxécuter directement un programme que vous aurez assemblé.
Une solution (parmi d’autres) est de compiler gdb pour une architecture MIPS, et d’exécuter les
programmes à l’aide ce ce débogueur. Toutes les informations pour compiler et installer gdb, ainsi que
l’utilisation éventuelle de librairies lors de l’édition de liens, seront disponibles sur la page web du projet.
Annexe B
Exemple complet avec relocation
B.1
Programme exempleElf.s
Soit le programme assembleur complet suivant, qui reprend la plupart des types de relocation abordés
dans les exemples du chapitre 5.
# Pour c o m p i l e r l e f i c h i e r o b j e t :
#
mips − a s e x e m p l e E l f . s −o e x e m p l e E l f . o
# P o u r a v o i r un e x e :
#
mips − l d e x e m p l e E l f . o −o e x e m p l e E l f
. set noreorder
. global _start
# E x p o r t a t i o n du p o i n t d ’ e n t r e e ( s y m b o l e g l o b a l )
. text
_start :
ADDI $3 , $0 , 12345
JAL w r i t e
NOP
B end
NOP
#
#
#
#
#
write :
SW $3 , Z
JR $31
NOP
# E c r i t l e c o n t e n u du r e g i s t r e 3 a l ’ a d r e s s e Z
# f i n de l a p r o c e d u r e " w r i t e " , r e t o u r a l ’ a p p e l a n t
end :
# The End !
. data
X : . b y t e 0xAB
Y : . word Z
Z : . word 0 x0
# D e c l a r a t i o n d ’ un o c t e t i n i t i a l i s e a v e c l a v a l e u r 0xAB
# Mot de 32 b i t s , en f a i t une r e f e r e n c e a l ’ a d r e s s e Z
# D e c l a r a t i o n d ’ un mot de 32 b i t s i n i t i a l i s e a 0
. bss
. skip 9
# R e s e r v a t i o n de 9 o c t e t s , non i n i t i a l i s e s
P o i n t d ’ e n t r e e ( d e b u t de l ’ e x e c u t i o n )
Met l a v a l e u r 12345 d a n s l e r e g i s t r e 3
Appel a l a p r o c e d u r e " w r i t e " ( met PC a l a bonne a d r e s s e )
branch delay s l o t
On r e v i e n t i c i a p r e s l e " w r i t e " ; b r a n c h e m e n t s u r l a f i n
59
60
ANNEXE B. EXEMPLE COMPLET AVEC RELOCATION
B.2
Désassemblage, tables de relocation et de symboles
Le fichier peut être désassemblé à l’aide de la commande mips-objdump exempleElf.o -D :
f i l e format elf32 −bigmips
exempleElf . o :
Disassembly of s e c t i o n . t e x t :
00000000 < _ s t a r t >:
0:
20033039
4:
0 c000005
8:
00000000
c:
10000005
10:
00000000
addi
jal
nop
b
nop
00000014 < w r i t e >:
14:
3 c010000
lui
18:
ac230008
sw
1c :
03 e00008
jr
20:
00000000
nop
Disassembly of s e c t i o n . data :
v1 , z e r o , 1 2 3 4 5
14 < w r i t e >
24 <end >
a t , 0 x0
v1 , 8 ( a t )
ra
00000000 <X>:
0:
ab000000
swl
zero , 0 ( t8 )
00000004 <Y>:
4:
00000008
jr
zero
00000008 <Z>:
8:
00000000
nop
Disassembly of s e c t i o n . bss :
00000000 <. b s s >:
...
Disassembly of s e c t i o n . r e g i n f o :
00000000 <. r e g i n f o >:
0:
9000000 a
...
lbu
zero , 1 0 ( zero )
La commande mips-readelf -r exempleElf.o fournit les informations de relocation :
Relocation section
Offset
Info
00000004 00000104
00000014 00000205
00000018 00000206
’ . r e l . t e x t ’ a t o f f s e t 0 x370 c o n t a i n s 3 e n t r i e s :
Type
Sym . V a l u e Sym . Name
R_MIPS_26
00000000
. text
R_MIPS_HI16
00000000
. data
R_MIPS_LO16
00000000
. data
R e l o c a t i o n s e c t i o n ’ . r e l . d a t a ’ a t o f f s e t 0 x388 c o n t a i n s 1 e n t r i e s :
Offset
Info
Type
Sym . V a l u e Sym . Name
00000004 00000202 R_MIPS_32
00000000
. data
Et mips-readelf -s exempleElf.o la table des symboles :
Symbol t a b l e
’ . symtab ’ c o n t a i n s 12 e n t r i e s :
B.3. CALCULS DE RELOCATION
Num :
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
Value
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000014
00000024
00000008
00000000
00000004
Size
0
0
0
0
0
0
0
0
0
0
0
0
Type
NOTYPE
SECTION
SECTION
SECTION
SECTION
SECTION
OBJECT
NOTYPE
NOTYPE
NOTYPE
NOTYPE
NOTYPE
61
Bind
LOCAL
LOCAL
LOCAL
LOCAL
LOCAL
LOCAL
GLOBAL
LOCAL
LOCAL
LOCAL
LOCAL
LOCAL
Vis
DEFAULT
DEFAULT
DEFAULT
DEFAULT
DEFAULT
DEFAULT
DEFAULT
DEFAULT
DEFAULT
DEFAULT
DEFAULT
DEFAULT
Ndx
UND
1
3
5
6
7
1
1
1
3
3
3
Name
. text
. data
. bss
. reginfo
. pdr
_start
write
end
Z
X
Y
Plusieurs choses à remarquer :
1. La pseudo-instruction SW $3, Y a en fait été remplacée par les deux instructions
LUI $1, 0x0
SW $3, 8($1)
2. Toutes les adresses (instructions, étiquettes, données) sont toujours définies par rapport au début
de la section à laquelle il appartient. Les adresses absolues ne seront fixées qu’à l’édition de liens
ou au chargement du programme.
3. Toutes les entrées nécessitant une relocation sont quand même codées ! La partie de ce code qui
sera modifiée au chargement (les 32, 26 ou 16 bits de poids faible) contient le addend nécessaire
au calcul de relocation (section 5.4.3).
4. D’autres encore mais à vous de fouiller un peu. . .C’est le meilleur moyen de comprendre !
B.3
Calculs de relocation
Détaillons maintenant le fonctionnement de la relocation qui est effectuée à l’édition des liens. Pour
cet exemple, les adresses fournies par l’éditeur de liens sont les suivantes :
– 0xAA1B008 pour la section .text
– 0x26004 pour la section .data
B.3.1 JAL write
Relocation Les variables de calcul introduites section 5.4.4 ont les valeurs suivantes :
Variable
offset
Type
Value
P
A
S
Valeur
0x4
R_MIPS_26
.text
0x0AA1B00C
0x00000005
0x0AA1B008
Signification
Décalage de l’entrée par rapport au début de sa section
Mode de relocation
Section contenant l’étiquette write
Adresse de l’entrée : 0xAA1B008 + offset
addend : 0x0C000005 & 0x03FFFFFF (26 bits de poids faible)
Adresse de la section contenant le symbole
La valeur du champ instr_index du JAL est donc :
62
ANNEXE B. EXEMPLE COMPLET AVEC RELOCATION
(P & 0xF0000000)
(A 2) | (P & 0xF0000000)
[(A 2) | (P & 0xF0000000)] + S
([(A 2) | (P & 0xF0000000)] + S) 2
= 0x0
= 0x14
= 0xAA1B01C
= 0x2A86C07
Finalement, le code écrit en mémoire à l’adresse 0xAA1B00C est 0x0EA86C07, en lieu et place du
précédent (0x0C000005 ; seuls les 6 bits de poids fort sont conservés).
Exécution Pour vérifier ce calcul, il suffit de simuler l’exécution de l’instruction JAL située à l’adresse
0xAA1B00C :
D’après les spécifications de l’instruction JAL, l’adresse destination du saut est obtenue en décalant
de 2 bits vers la gauche l’offset de 26 bits, ce qui donne une adresse sur 28 bits. L’adresse finale est
obtenue en complétant cette adresse avec les 4 bits de poids fort de l’adresse contenue dans PC, c’est à
dire avec les 4 bits de poids fort de l’adresse de l’instruction suivante (située en 0xAA1B010).
0x2A86C07 2
= 0xAA1B01C
PC & 0xF0000000 = 0x00000000
adresse de saut
= 0x0AA1B018
Est-ce bien l’instruction référencée par l’étiquette write ? Oui, puisqu’elle se trouve 0x14 octets après
le début de la section .text, donc bien 0xAA1B008 + 0x14 = 0xAA1B018 ! 1
B.3.2 SW $3, Y
La pseudo-instruction SW a été décomposée en deux instructions successives LUI et SW :
0x00000014
0x00000018
3C010000
AC230008
LUI $1, 0x0
SW $3, 8($1)
chacune associée à une entrée de relocation. Les adresses de chargement des sections sont les mêmes
que précédemment (0xAA1B008 et 0x26004).
Chargement Les variables de calcul sont les suivantes :
Variable
offset
Type
Value
P
A
AHL
S
Valeur pour LUI
0x14
R_MIPS_HI16
.data
0x0AA1B01C
0x0000
0x00000008
0x00026004
Valeur pour SW
0x18
R_MIPS_LO16
.data
0x0AA1B020
0x0008
0x00000008
0x00026004
Le résultat est donc :
Entrée
Calcul
LUI
SW
((AHL + S) - (short)(AHL + S)) 16
(AHL + S) & 0x0000FFFF
Après relocation, le code correspondant est :
1. Eh eh eh eh, c’est pas beautiful ça, hein ?
Adresse
Codage
mémoire
fichier
0x0AA1B01C 3C010000
0x0AA1B020 AC230008
Codage
mémoire
3C010002
AC23600C
B.4. CAS D’UN FICHIER EXÉCUTABLE
0x0AA1B01C
0x0AA1B020
3C010002
AC23600C
63
LUI $1, 0x2
SW $3, 0x600C($1)
Exécution A nouveau une petite vérification :
– l’exécution de LUI $1, 0x2 affecte la valeur 0x20000 au registre $1 2 ;
– l’exécution de SW $3, 0x600C($1) écrit la valeur contenue dans $3 à l’adresse mémoire 0x20000
+ 0x600C = 0x2600C, qui est bien l’adresse de Z.
B.3.3
Étiquettes Y et Z
A vous de jouer et de vérifier, c’est très simple si vous avez compris ce qui précède.
B.3.4
Et moi dans tout ça ?
Lorsque c’est nécessaire, votre travail consistera à récupérer les informations de relocation et à
reloger correctement le code binaire lu en fonction des adresses de sections.
Le plus dur/long est de comprendre, ensuite c’est très simple !
B.4
Cas d’un fichier exécutable
Créez un fichier exécutable : mips-ld exempleElf.o -o exempleElf.
Étudiez ensuite son contenu par rapport à celui du fichier objet (désassemblage, symboles, tables de
relocation, entête de fichiers, etc.).
2. Au passage, c’est une des raisons pour laquelle le registre $1 ne devrait pas être utilisé dans les programmes mais
réservé à l’assembleur. Si un programme l’utilise mais que l’assembleur s’en sert aussi pour stocker des données temporaires,
l’exécution a très peu de chances de se dérouler normalement. . .
64
ANNEXE B. EXEMPLE COMPLET AVEC RELOCATION
Annexe C
Exemple avec relocation sur plusieurs
fichiers
Voici un exemple minimaliste de programme multi-fichiers. fichier1.s fait référence au symbole
write de fichier2.s, et réciproquement, le second fichier fait référence au symbole Z défini dans le
premier fichier. Il permet de comprendre l’intérêt et l’utilisation des informations que vous devez générer
dans les fichiers ELF.
C.1
Fichiers sources
. set noreorder
. global _start
. text
_start :
ADDI $3 , $0 , 12345
JAL w r i t e
. data
X : . b y t e 0xAB
. global Z
Z : . word 0 x0
# Met l a v a l e u r 12345 d a n s l e r e g i s t r e 3
# Appel à l a p r o c é d u r e " w r i t e " ( e x t e r n e )
# D é c l a r a t i o n d ’ un o c t e t
i n i t i a l i s é a v e c l a v a l e u r 0xAB
# D é c l a r a t i o n d ’ un mot de 32 b i t s
initialisé à 0
. global write
. text
write :
SW $3 , Z
JR $31
# E c r i t l e c o n t e n u du r e g i s t r e 3 à l ’ a d r e s s e Z ( e x t e r n e )
# F i n de l a p r o c é d u r e " w r i t e " , r e t o u r à l ’ a p p e l a n t
. data
Y : . word Z
# Mot de 32 b i t s Y, en f a i t une r é f é r e n c e à l ’ a d r e s s e Z
C.2
Listings générés
À l’assemblage, on peut remarquer que les valeurs addend assemblées pour ces références indéfinies
sont nulles.
65
66
ANNEXE C. EXEMPLE AVEC RELOCATION SUR PLUSIEURS FICHIERS
1
2
3
4
5 0000 20033039
6 0004 0 C000000
7
8
9 0000 AB
10
11 0001 00000000
11
000000
DEFINED SYMBOLS
f i c h i e r 1 . s :4
f i c h i e r 1 . s :9
f i c h i e r 1 . s :11
UNDEFINED SYMBOLS
write
1
2
3
4 0000 3 C010000
4
AC230000
5 0008 03 E00008
5
00000000
6
7
8 0000 00000000
DEFINED SYMBOLS
f i c h i e r 2 . s :3
f i c h i e r 2 . s :8
UNDEFINED SYMBOLS
Z
C.3
. set noreorder
. global _start
. text
_start :
ADDI $3 , $0 , 12345
JAL w r i t e
. data
X: . b y t e 0xAB
. global Z
Z : . word 0 x0
. t e x t :0000000000000000 _ s t a r t
. d a t a :0000000000000000 X
. d a t a :0000000000000004 Z
. global write
. text
write :
SW $3 , Z
JR $31
. data
Y: . word Z
. t e x t :0000000000000000 w r i t e
. d a t a :0000000000000000 Y
Tables de relocation
Par contre, la commande mips-readelf -r nous permet de voir les tables de relocation, qui donnent toutes les informations pour construire les valeurs finales.
$ mips − r e a d e l f − r f i c h i e r 1 . o
R e l o c a t i o n s e c t i o n ’ . r e l . t e x t ’ a t o f f s e t 0 x300 c o n t a i n s 1 e n t r i e s :
Offset
Info
Type
Sym . V a l u e Sym . Name
00000004 00000704 R_MIPS_26
00000000
write
Cette unique entrée correspond effectivement à l’assemblage de l’instruction JAL write, à l’offset
(adresse relative) 4 de la section .text de fichier1.
$ mips − r e a d e l f − r f i c h i e r 2 . o
R e l o c a t i o n s e c t i o n ’ . r e l . t e x t ’ a t o f f s e t 0 x318 c o n t a i n s 2 e n t r i e s :
Offset
Info
Type
Sym . V a l u e Sym . Name
00000000 00000705 R_MIPS_HI16
00000000
Z
C.4. TABLE DE SYMBOLES
00000004
00000706 R_MIPS_LO16
67
00000000
Z
R e l o c a t i o n s e c t i o n ’ . r e l . d a t a ’ a t o f f s e t 0 x328 c o n t a i n s 1 e n t r i e s :
Offset
Info
Type
Sym . V a l u e Sym . Name
00000000 00000702 R_MIPS_32
00000000
Z
Ici, on a une paire d’entrée pour l’assemblage de la macro-instruction SW $3, Z dans la section
.text et une pour la déclaration de donnée .word Z (le fait que l’étiquette Y pointe dessus n’a pas
d’importance).
Dans les deux cas, le symbole qui apparaît dans la dernière colonne est le symbole auquel on fait
référence, alors que dans le cas des programmes sur un seul fichier, c’est le nom de la section contenant
le symbole qui apparaissait ici. En fait, dans les deux cas, la valeur addend assemblée dans le binaire est
l’adresse relative au symbole qui apparaît dans la table de relocation : une référence au symbole indéfini
Z a comme adresse relative 0 par rapport au symbole Z lui-même !
C.4
Table de symboles
Les tables de symboles contenues dans les fichiers sont les suivantes :
$ mips − r e a d e l f −s f i c h i e r 1 . o
Symbol
Num :
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
t a b l e ’ . symtab ’
Value S i z e
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000004
0
c o n t a i n s 10 e n t r i e s :
Type
Bind
Vis
NOTYPE LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
OBJECT GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE LOCAL DEFAULT
OBJECT GLOBAL DEFAULT
Ndx
UND
1
3
4
5
6
1
UND
3
3
Name
. text
. data
. bss
. reginfo
. pdr
_start
write
X
Z
$ mips − r e a d e l f −s f i c h i e r 2 . o
Symbol
Num :
0:
1:
2:
3:
4:
5:
6:
7:
8:
t a b l e ’ . symtab ’
Value S i z e
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
contains 9 entries :
Type
Bind
Vis
NOTYPE LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
OBJECT GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE LOCAL DEFAULT
Ndx
UND
1
3
5
6
7
1
UND
3
Name
. text
. data
. bss
. reginfo
. pdr
write
Z
Y
On retrouve la liste qui apparaissait en fin de listing d’assemblage, plus les symboles qui correspondent aux noms de sections. Pour chaque symbole, on définit son adresse relative, et la section dans
laquelle il est défini. Il n’y a par construction pas de section 0, et par convention, un symbole non défini
est marqué comme étant défini dans la section 0. Sans grande surprise, on constate que ces symboles ont
une adresse relative 0 (le symbole .data a une adresse relative de 0 dans la section .data).
68
ANNEXE C. EXEMPLE AVEC RELOCATION SUR PLUSIEURS FICHIERS
C.5
Édition de liens
Pour bien comprendre ce que représentent ces tables de symboles et tables de relocation, on peut
continuer la chaîne de compilation avec l’édition de liens. Vous n’aurez pas à implémenter l’édition de
liens, mais les fichiers que vous produisez doivent pouvoir être liés.
En réalité, l’édition de liens fait plusieurs choses :
1. Combiner plusieurs fichiers objets (*.o) relogeables,
2. Rendre le résultat directement exécutable. En général, le point d’entrée de l’exécutable final est
une fonction _start qui fait quelques initialisations avant d’appeler la fonction main.
3. Décide de la future adresse de chargement.
La commande mips-ld fichier1.o fichier2.o -o exec fait tout cela (pour pouvoir exécuter
correctement le binaire dans gdb, il aurait fallu ajouter quelques options, ce que fait pour vous le script
mips-easy-ld). Les nouvelles adresses des symboles peuvent être observées avec readelf :
$ mips − r e a d e l f −s e x e c
Symbol
Num :
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
t a b l e ’ . symtab ’
Value S i z e
00000000
0
00400000
0
00400018
0
00401030
0
00401030
0
00401038
0
00401030
0
00409030
0
00400020
0
00400018
0
00400018
0
0040103 c
0
00401034
0
0040103 c
0
0040103 c
0
0040103 c
0
c o n t a i n s 16 e n t r i e s :
Type
Bind
Vis
NOTYPE LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
NOTYPE LOCAL DEFAULT
NOTYPE LOCAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
OBJECT GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
OBJECT GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
OBJECT GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
Ndx
UND
1
2
3
3
3
3
ABS
2
2
2
ABS
3
ABS
ABS
ABS
Name
X
Y
_fdata
_gp
write
_ftext
_start
__bss_start
Z
_edata
_end
_fbss
Dans le contenu des sections, on peut vérifier que les références à des symboles (Z, write) sont
correctes (désassemblé avec mips-objdump -D exec) :
$ mips −objdump −D e x e c
exec :
f i l e format elf32 −bigmips
Disassembly of s e c t i o n . r e g i n f o :
00400000 <. r e g i n f o >:
400000:
80000008
...
400014:
00409030
Disassembly of s e c t i o n . t e x t :
00400018 < _ f t e x t >:
lb
0 x409030
zero , 8 ( zero )
C.5. ÉDITION DE LIENS
400018:
40001 c :
addi
jal
v1 , z e r o , 1 2 3 4 5
400020 < w r i t e >
lui
sw
jr
nop
a t , 0 x40
v1 , 4 1 4 8 ( a t )
ra
00401030 < _ f d a t a >:
401030:
ab000000
swl
zero , 0 ( t8 )
00401034 <Z>:
401034:
00000000
nop
00401038 <Y>:
401038:
00401034
0 x401034
00400020 < w r i t e
400020:
400024:
400028:
40002 c :
20033039
0 c100008
69
>:
3 c010040
ac231034
03 e00008
00000000
Disassembly of s e c t i o n . data :
On peut reprendre les formules de calcul pour chaque mode de relocation. Ce sont les mêmes que
pour les cas d’un seul fichier, ici, S est le symbole pour lequel on fait la relocation (la dernière colonne
de la table de relocation). Pour le codage de SW $3, Z, on a S = adresse de write = 00400020, et AHL =
0. On vérifie bien :
Entrée Calcul
Adresse mé- Avant reloca- Après relocamoire
tion
tion
LUI
((AHL + S) - short(AHL + S)) >> 16 0x400020
3C010000
3C010040
SW
AHL + S
0x400024
AC230000
ac231034
(les calculs explicites pour les autres références sont laissés en exercice)
Annexe D
Exemple avec relocation R_MIPS_PC16
Cet exemple est similaire au précédant, mais utilise l’instruction B, donc un branchement relatif. Les
sauts inter-sections seront assemblés avec le mode R_MIPS_PC16.
D.1
Fichiers sources
. set noreorder
. global _start
. text
_start :
nop
B ici
nop
ici :
B ailleurs
# j u s t e p o u r p r e n d r e un peu de p l a c e .
# branchement r e l a t i f l o c a l .
# b r a n c h e m e n t r e l a t i f v e r s un s y m b o l e i n d é f i n i .
. global a i l l e u r s
. text
NOP
NOP
ailleurs :
AND $2 , $3 , $4
D.2
# e n c o r e deux i n s t r u c t i o n s , j u s t e
# p o u r p r e n d r e de l a p l a c e .
# une i n s t r u c t i o n , q u i n ’ a
# pas d ’ importance i c i .
Listings générés
À l’assemblage, les valeurs addend sont 0xFFFF, donc -1 en valeur signée, ce qui correspond à
un saut en arrière d’une instruction (les adresses des instructions étant alignées, on peut compter leurs
adresses sans préciser les deux derniers bits). Sans relocation, ce serait donc un branchement sur l’instruction elle-même.
1
2
3
4
5 0000 00000000
6 0004 10000001
. set noreorder
. global _start
. text
_start :
nop
B ici
70
D.3. TABLES DE RELOCATION
7 0008 00000000
8
9 000 c 1000 FFFF
10
DEFINED SYMBOLS
f i c h i e r 1 . s :4
f i c h i e r 1 . s :8
UNDEFINED SYMBOLS
ailleurs
nop
ici :
B ailleurs
. t e x t :0000000000000000 _ s t a r t
. t e x t :000000000000000 c i c i
1
2
3 0000 00000000
4 0004 00000000
5
6 0008 00641024
7
DEFINED SYMBOLS
f i c h i e r 2 . s :5
NO UNDEFINED SYMBOLS
D.3
71
. global a i l l e u r s
. text
NOP
NOP
ailleurs :
AND $2 , $3 , $4
. t e x t :0000000000000008 a i l l e u r s
Tables de relocation
$ mips − r e a d e l f − r f i c h i e r 1 . o
R e l o c a t i o n s e c t i o n ’ . r e l . t e x t ’ a t o f f s e t 0 x2f0 c o n t a i n s 1 e n t r i e s :
Offset
Info
Type
Sym . V a l u e Sym . Name
0000000 c 0000080 a R_MIPS_PC16
00000000
ailleurs
Cette unique entrée correspond effectivement à l’assemblage de l’instruction B ailleurs, à l’offset
(adresse relative) 0xC de la section .text de fichier1.
D.4
Édition de liens
Comme dans l’exemple précédant, on fait l’édition de liens avec mips-ld fichier1.o fichier2.o
-o exec puis on observe sa table de symboles :
$ mips − r e a d e l f −s e x e c
Symbol
Num :
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
t a b l e ’ . symtab ’
Value S i z e
00000000
0
00400000
0
00400018
0
00400024
0
00401034
0
00409030
0
00400018
0
00400018
0
00401034
0
00400030
0
00401034
0
00401034
0
c o n t a i n s 13 e n t r i e s :
Type
Bind
Vis
NOTYPE LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
NOTYPE LOCAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
OBJECT GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
OBJECT GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
NOTYPE GLOBAL DEFAULT
Ndx
UND
1
2
2
2
ABS
2
2
ABS
2
ABS
ABS
Name
ici
_fdata
_gp
_ftext
_start
__bss_start
ailleurs
_edata
_end
ANNEXE D. EXEMPLE AVEC RELOCATION R_MIPS_PC16
72
1 2 : 00401034
0 NOTYPE
GLOBAL DEFAULT
ABS _ f b s s
et le code relogé :
$ mips −objdump −D e x e c
exec :
f i l e format elf32 −bigmips
Disassembly of s e c t i o n . r e g i n f o :
00400000 <. r e g i n f o >:
...
400014:
00409030
0 x409030
Disassembly of s e c t i o n . t e x t :
00400018 < _ f t e x t
400018:
40001 c :
400020:
>:
00000000
10000001
00000000
nop
b
nop
00400024 < i c i >:
400024:
10000002
400028:
00000000
40002 c :
00000000
b
nop
nop
400030 < a i l l e u r s >
00400030 < a i l l e u r s >:
400030:
00641024
and
v0 , v1 , a0
400024 < i c i >
On a bien A + S - P = -1 + (0x00400030 >> 2) - (0x00400024 >> 2) = 2.
Annexe E
Structure de fichiers ELF et module fourni
elf_writer
Un fichier ELF (Executable and Linkable Format) est un fichier binaire contenant toutes les informations relatives à un fichier objet ou un programme assemblé : son codage binaire, la liste des symboles
définis, l’ensemble des données de relocation, et d’autres informations encore. Toutes ces informations
seront utilisées pour l’édition de liens (fusion entre plusieurs fichiers objets, création d’un fichier exécutable), puis par le chargeur du système d’exploitation avant l’exécution d’un programme.
Pour avoir un exemple de fichier, c’est très facile : créez un petit programme et assemblez-le avec
l’assembleur mips-as fourni, as cross-compilé pour une cible MIPS. Ensuite, étudiez son contenu à
l’aide des commandes mips-objdump ou mips-readelf ! 1
L’objectif de cette annexe est de décrire les principales notions sur la structure d’un fichier ELF puis
le module de haut-niveau elf_writer distribué par les enseignants. La première section sera surtout
utile pour comprendre la structure ELF et réécrire vous-même votre propre module elf_writer. La
dernière section (en fait les spécifications des procédures, et les types de données du format ELF) est
elle suffisante pour utiliser ce module avec votre assembleur.
E.1
Tables
Un fichier ELF est un fichier binaire structuré sous forme de “tables” ou “sections”.
Le contenu de ces tables est accessible via les programmes mips-objdump ou mips-readelf (voir
section A), avec les options adaptées : -s pour afficher la tables des symboles, -r pour les tables de
relocation, etc. Des exemples sont disponibles dans les annexes.
Entête Le fichier commence toujours par un entête donnant les caractéristiques générales du fichier
(processeur cible, version, etc.) puis les caractéristiques de la table des entêtes de sections : sa
taille (nombre de sections) et sa position (décalage, en nombre d’octets par rapport au début du
fichier).
Table des entêtes de sections C’est en fait un tableau d’entêtes, chacun indiquant notamment :
– le nom de la section (en fait un index dans la table des noms de sections)
1. Vous pouvez aussi faire ceci avec le code de votre propre assembleur, quelque que soit l’architecture sur laquelle vous
exécutez (Pentium, Athlon, . . .). Si asmips est le nom de l’exécutable, tapez objdump asmips -d, et vous comprendez
pourquoi vous avez écrit votre projet en C et pas en langage assembleur. . .
73
ANNEXE E. STRUCTURE DE FICHIERS ELF ET MODULE FOURNI ELF_WRITER
74
– sa taille (en octets) et sa position dans le fichier (décalage, toujours en nombre d’octets par
rapport au début).
– son type, lié à la nature des informations de la section (données du programme, symboles,
chaînes, . . .)
– l’adresse mémoire de la section, lorsqu’elle est déterminée (cas d’un exécutable), et une contrainte d’alignement de la section (dont l’adresse doit être un multiple).
Un ficher objet ELF contient généralement : une section par zone (.text, .data ou .bss) et sa
section de relocation associée (.rel.text ou .rel.data), une table des symboles, une table des
chaînes et une table des noms de sections. Toutes les sections ne sont pas forcément présentes,
selon le contenu du code assemblé (par exemple il peut ne pas y avoir de zone .data, ni de
symboles à reloger, aucune donnée n’est associée à .bss, etc.).
La première section (indice 0) est toujours vide, c’est une sentinelle.
Table des noms de sections C’est une table de chaînes de caractères contenant les noms des sections :
“.text”, “.data”, “.symtab”, etc. La première chaîne est toujours la chaîne vide.
Table des chaînes Encore une table de chaînes de caractères, contenant cette fois les noms des symboles de sections et de tous les symboles définis ou utilisés dans le fichier source.
Table des symboles C’est la table des symboles du code, caractérisés notamment par :
– son nom, en fait son index dans la table des chaînes.
– l’index (dans la table des entêtes de sections) de la zone dans laquelle il est défini. Pour un
symbole externe, cet index vaut 0 (la sentinelle dans la table des entêtes de sections).
– sa valeur, i.e. le déplacement en nb d’octets par rapport au début de sa zone de définition (0
pour les symboles externes).
Les sections de données .text et .data, qui contiennent le code assemblé, binaire, du programme (il
faut bien qu’il soit quelque part !).
Les sections de relocation La section .rel.text contient par exemple les informations de relocation
des entrées situées dans la section .text. Le contenu d’une entrée de relocation a en fait déjà été
expliqué section 5.4.2 : la position du code à reloger (par rapport au début de la zone), le mode de
relocation, et la section dans laquelle chercher le symbole référencé (indice de zone ou 0 pour un
symbole externe).
Autres tables D’autres tables peuvent encore être présentes, notamment le Program Header, section
qui contient les informations de pagination d’un fichier exécutable, ou des sections d’informations
spécifiques à une architecture donnée. Elles ne seront pas traitées dans ce projet.
Cette description a juste pour objectif de vous présenter rapidement le contenu et la structure d’un
fichier ELF, en partie shématisée figure E.1.
Une description complète est surtout disponible dans [7] (ne regardez que la première partie, sur les
fichiers objet). Il s’agit DU document de référence, clair et complet, indispensable pour réécrire
vous-même un générateur de fichiers ELF !
E.2
Module elf_writer et structure des principales tables
Cette section décrit de manière détaillée :
– certaines des structures de données du format ELF, utilisées par les principales tables. Seules
celles directement utiles pour votre projet sont décrites.
– le principe de l’utilisation du module elf_writer pour écrire ces tables.
E.2. MODULE ELF_WRITER ET STRUCTURE DES PRINCIPALES TABLES
75
Figure E.1 – Exemple de relations entre les “tables” d’un fichier ELF (qui sont en fait stockées séquentiellement dans le fichier).
ANNEXE E. STRUCTURE DE FICHIERS ELF ET MODULE FOURNI ELF_WRITER
76
E.2.1
Structures de données
Le format ELF définit (via le fichier elf.h installé sur les systèmes d’exploitation) des structures
de données pour tous les types d’informations manipulés : Elf (pointeur sur un fichier ELF), Elf_Scn
(section), Elf_Data (données), Elf32_Sym (symbole), Elf32_Rel (entrée de relocation), . . .
E.2.2
Utilisation générale
Le principe général d’utilisation d’elf_writer est
– de débuter la création d’un fichier à l’aide de la fonction create_elf
– de définir les informations à écrire à l’aide des fonctions add_XXX (chacune étant appelée au plus
une fois)
– de terminer avec close_elf. Le fichier est alors effectivement généré.
Les données passées en argument des fonctions add_XXX devront être générées par votre assembleur.
En cas d’allocation dynamique, elles ne devront pas être libérées avant l’appel à close_elf.
E.2.3
Ecriture des sections .text, .data et .bss
Les procédures d’écriture des sections d’instructions et de données sont très simples :
void a d d _ t e x t _ s e c t i o n ( u i n t 8 _ t ∗ data , s i z e _ t s i z e ) ;
void add_data_section ( u i n t 8 _ t ∗ data , s i z e _ t s i z e ) ;
void add_bss_section ( s i z e _ t s i z e ) ;
Les paramètres sont le nombre d’octets de la section et un tableau contenant ces octets. Avec l’exemple de l’annexe B, le tableau de la section text devra contenir les 36 octets [200330390C00000500...
AC23000803E0000800000000].
Pour la section .bss, seule sa taille est spécifiée.
E.2.4
Ecriture de la table des chaînes
Cette table contient les noms de tous les symboles du fichier (voir section 5.4.1), d’abord les symboles de section puis ceux du programme.
La fonction add_string_table permet de spécifier cette table et sa taille (nombre d’octets) :
void a d d _ s t r i n g _ t a b l e ( char ∗ s t r i n g _ t a b , s i z e _ t s i z e ) ;
La table des chaînes ELF est formatée comme un unique tableau de caractères contenant une concaténation de toutes les chaînes, séparées par des caractères nuls (’\0’). Par exemple, si un fichier ELF ne
contient qu’une section .text avec deux symboles “toto” et “X”, la table des chaînes sera constituée de :
\0
.
t
e
x
t
\0
t
o
t
o
\0
X
\0
– Le premier caractère est une sentinelle, toujours égale à ’\0’.
– La chaîne “.text” (un nom de section est aussi un symbole !) aura l’indice 1, la chaîne “toto”
l’indice 7 et la chaîne “X” l’indice 12.
– La taille totale de la table est de 14 octets.
E.2. MODULE ELF_WRITER ET STRUCTURE DES PRINCIPALES TABLES
E.2.5
77
Ecriture de la table des symboles
La table des symboles d’un fichier ELF n’est jamais vide, elle contient toujours au moins 4 symboles,
d’indice 0 à 3 : une sentinelle en 0 puis les symboles de noms de zones (.text, .data et .bss). Ensuite,
la table contient les symboles locaux ou externes du fichier source assemblé.
La table des symboles d’un fichier objet et sa taille (nombre de symboles) sont spécifiés via la
fonction :
v o i d a d d _ s y m b o l _ t a b l e ( Elf32_Sym ∗ symb_tab , s i z e _ t nb_symb ) ;
Le type de données Elf32_Sym représentant un symbole est défini dans elf.h :
/* Symbol table entry. */
typedef struct {
Elf32_Word
st_name;
Elf32_Addr
st_value;
Elf32_Word
st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Section st_shndx;
} Elf32_Sym;
/*
/*
/*
/*
/*
/*
Symbol name (string table index) */
Symbol value */
Symbol size */
Symbol type and binding */
Symbol visibility */
Section index */
Plusieurs macros C sont associées, pour coder/décoder le champ st_info :
#define ELF32_ST_BIND(i)
((i)>>4)
#define ELF32_ST_TYPE(i)
((i)&0xf)
#define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))
/* de info vers bind */
/* de info vers type */
/* de (bind,type) vers info */
Les différents champ de la structure Elf32_Sym sont les suivants :
– st_name : index du symbole dans la table des chaînes.
– st_value : pour un symbole défini localement, il s’agit du déplacement (en octets) par rapport
au début de la zone de définition. Il vaut 0 pour un symbole non défini localement.
Pour un symbole de section, il vaut l’adresse absolue de la section (cas d’un exécutable) ou 0 (cas
d’un fichier relogeable).
– st_size et st_other : non utilisés dans le projet, ces champs seront toujours nuls (st_size sert
à associer une taille de donnée au symbole).
– st_info : cette valeur codée sur un octet sert en fait à coder 2 champs : le champ “bind” et le
champ “type”, décrits ci-dessous.
– st_shndx : indique l’index (dans la table des en-têtes de sections) de la section où le symbole est
défini. Si le symbole est de type STT_SECTION, cet index est directement l’index de la section. Si
le symbole n’est défini dans aucune section (symbole externe), cet index vaut 0.
Le champ “bind” indique la portée du symbole. Dans ce projet, seuls les 2 cas suivants seront considérés :
– STB_LOCAL (constante 0) indique que le symbole est défini localement et non exporté.
– STB_GLOBAL (constante 1) indique que le symbole est soit défini et exporté, soit non défini et
importé. Il faut noter qu’à l’édition de lien, il est interdit à 2 fichiers distincts de définir deux
symboles de même nom ayant la portée STB_GLOBAL.
Dans le cadre du projet, on ne considère que les cas suivants pour le champ “type” :
ANNEXE E. STRUCTURE DE FICHIERS ELF ET MODULE FOURNI ELF_WRITER
78
– STT_SECTION (constante 3) indiquant que le symbole désigne en fait une section.
– STT_OBJECT (constante 1) indiquant que le symbole est associé à un “objet” (dans un langage de
haut-niveau il s’agirait d’une variable ou d’un tableau ; dans notre cas as utilise ce type pour les
symboles externes ou exportés par la directive .global).
– STT_NOTYPE (constante 0), dans les autres cas.
Le champ st_info est généré à l’aide de la macro définie précédemment, par exemple pour un
symbole local non exporté :
sym−> s t _ i n f o = ELF32_ST_INFO ( STB_LOCAL , STT_NOTYPE ) ;
Pour un exemple, on peut reprendre la table des symboles de l’annexe B.2 :
Symbol
Num :
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
t a b l e ’ . symtab ’
Value S i z e
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000000
0
00000014
0
00000024
0
00000008
0
00000000
0
00000004
0
c o n t a i n s 12 e n t r i e s :
Type
Bind
Vis
NOTYPE LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
SECTION LOCAL DEFAULT
OBJECT GLOBAL DEFAULT
NOTYPE LOCAL DEFAULT
NOTYPE LOCAL DEFAULT
NOTYPE LOCAL DEFAULT
NOTYPE LOCAL DEFAULT
NOTYPE LOCAL DEFAULT
E.2.6
Entrées de relocation
Ndx
UND
1
3
5
6
7
1
1
1
3
3
3
Name
. text
. data
. bss
. reginfo
. pdr
_start
write
end
Z
X
Y
Si nécessaire, les tables de relocation .rel.text et .rel.data) (respectivement associées aux
sections .text et .data) peuvent être spécifiées via les fonctions :
void a d d _ t e x t _ r e l o c _ s e c t i o n ( Elf32_Rel ∗ r e l _ t a b , s i z e _ t n b _ r e l ) ;
void a d d _ d a t a _ r e l o c _ s e c t i o n ( Elf32_Rel ∗ r e l _ t a b , s i z e _ t n b _ r e l ) ;
Ces deux fonctions prennent en paramètre un tableau des entrées de relocation de chaque zone
du fichier, et sa taille (nombre d’entrées). Chaque entrée de relocation est de type Elf32_Rel, défini
comme :
/* Relocation table entry without addend (in section of type SHT_REL). */
typedef struct {
Elf32_Addr r_offset;
/* Address */
Elf32_Word r_info;
/* Relocation type and symbol index */
} Elf32_Rel;
Les champs de cette table se réfèrent directement aux explications de la section 5.4.2 :
– r_offset : contient le décalage en octets de l’entrée de relocation par rapport au début de la zone
considérée (.text pour une entrée de la table .rel.text, etc.)
– r_info : champ composé des deux informations “type” et “sym”
– “type” : le mode de relocation, codé sur 1 octets
E.2. MODULE ELF_WRITER ET STRUCTURE DES PRINCIPALES TABLES
79
– “sym” : codé sur 3 octets, il permet de localiser le symbole à reloger. Si le symbole est défini
localement et non exporté, “sym” est l’index dans la table des symboles de la section contenant
le symbole (1 pour un symbole en zone .text, 2 en zone .data, 3 en zone .bss). Si le symbole
est externe ou défini localement et exporté, “sym“ est directement l’index du symbole dans la
table des symboles.
Cette différence de traitement entre les symboles locaux et les symboles globaux permet en fait
d’omettre tous les symboles locaux (excepté les symboles de section) de la table des symboles
et de la table des chaînes : on réduit ainsi la taille du fichier généré. 2 Toutefois, il peut être
commode de garder les symboles locaux lorsqu’on utilise un débogueur.
Les macros de codage/décodage du champ r_info sont :
/* Macros for accessing the fields of r_info. */
#define ELF32_R_SYM(info)
((info) >> 8)
#define ELF32_R_TYPE(info)
((unsigned char)(info))
/* Macro for constructing r_info from field values. */
#define ELF32_R_INFO(sym, type) (((sym) << 8) + (unsigned char)(type))
Les modes de relocation sont définis, dans elf.h par des constantes entières. Ceux utilisés dans ce
projet sont :
#define
#define
#define
#define
#define
R_MIPS_32
R_MIPS_26
R_MIPS_HI16
R_MIPS_LO16
R_MIPS_PC16
2
4
5
6
10
/*
/*
/*
/*
/*
Direct 32 bit */
Direct 26 bit shifted */
High 16 bit */
Low 16 bit */
PC relative 16 bit */
Pour un exemple, on peut reprendre la table de relocation de l’annexe B.2 :
Relocation section
Offset
Info
00000004 00000104
00000014 00000205
00000018 00000206
’ . r e l . t e x t ’ a t o f f s e t 0 x370 c o n t a i n s 3 e n t r i e s :
Type
Sym . V a l u e Sym . Name
R_MIPS_26
00000000
. text
R_MIPS_HI16
00000000
. data
R_MIPS_LO16
00000000
. data
R e l o c a t i o n s e c t i o n ’ . r e l . d a t a ’ a t o f f s e t 0 x388 c o n t a i n s 1 e n t r i e s :
Offset
Info
Type
Sym . V a l u e Sym . Name
00000004 00000202 R_MIPS_32
00000000
. data
La première entrée de relocation concerne l’instruction située 4 octets après le début de la section
.text (JAL write). Le type de relocation est 4, soit R_MIPS_26. La valeur du symbole est 1, qui est
ici l’index de la section .text dans la table des entêtes de section (c’est la valeur de Ndx associée au
symbole de section .text).
2. Pour omettre les symboles locaux avec l’assembleur du projet mips-as, il faut l’invoquer avec l’option -s.
80
ANNEXE E. STRUCTURE DE FICHIERS ELF ET MODULE FOURNI ELF_WRITER
Annexe F
Spécifications des instructions
Cette annexe contient les spécifications des instructions étudiées dans ce projet. Elles sont directement issues de la documentation de MIPS Technologies : MIPS32 Architecture For Programmers Volume
II : The MIPS32 Instruction Set [1].
81
rs
21 20
rt
16 15
rd
11 10
0
6
5
ADD
34
Operation:
Operation:
Programming Notes:
ADDIU performs the same arithmetic operation but does not trap on overflow.
Programming Notes:
ADDU performs the same arithmetic operation but does not trap on overflow.
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
Integer Overflow
Integer Overflow
MIPS32® Architecture For Programmers Volume II, Revision 2.50
Exceptions:
Exceptions:
temp ← (GPR[rs]31||GPR[rs]31..0) + sign_extend(immediate)
if temp32 ≠ temp31 then
SignalException(IntegerOverflow)
else
GPR[rt] ← temp
endif
None
temp ← (GPR[rs]31||GPR[rs]31..0) + (GPR[rt]31||GPR[rt]31..0)
if temp32 ≠ temp31 then
SignalException(IntegerOverflow)
else
GPR[rd] ← temp
endif
Restrictions:
None
• If the addition does not overflow, the 32-bit result is placed into GPR rt.
• If the addition does not overflow, the 32-bit result is placed into GPR rd.
Restrictions:
• If the addition results in 32-bit 2’s complement arithmetic overflow, the destination register is not modified and
an Integer Overflow exception occurs.
The 16-bit signed immediate is added to the 32-bit value in GPR rs to produce a 32-bit result.
MIPS32
0
ADDI
• If the addition results in 32-bit 2’s complement arithmetic overflow, the destination register is not modified and
an Integer Overflow exception occurs.
Description: GPR[rt] ¨ GPR[rs] + immediate
The 32-bit word value in GPR rt is added to the 32-bit value in GPR rs to produce a 32-bit result.
16
immediate
To add a constant to a 32-bit integer. If overflow occurs, then trap.
5
rt
16 15
Description: GPR[rd] ← GPR[rs] + GPR[rt]
36
5
rs
21 20
Format: ADDI rt, rs, immediate
26 25
Purpose:
MIPS32
6
001000
ADDI
To add 32-bit integers. If an overflow occurs, then trap.
6
31
Add Immediate Word
ADDI
Purpose:
Format: ADD rd, rs, rt
5
5
5
6
5
00000
000000
100000
0
26 25
31
SPECIAL
ADD
Add Word
ADD
MIPS32
38
SPECIAL
5
rs
Operation:
Operation:
Programming Notes:
The term “unsigned” in the instruction name is a misnomer; this operation is 32-bit modulo arithmetic that does not
trap on overflow. This instruction is appropriate for unsigned arithmetic, such as address arithmetic, or integer arithmetic environments that ignore overflow, such as C language arithmetic.
Programming Notes:
The term “unsigned” in the instruction name is a misnomer; this operation is 32-bit modulo arithmetic that does not
trap on overflow. This instruction is appropriate for unsigned arithmetic, such as address arithmetic, or integer arithmetic environments that ignore overflow, such as C language arithmetic.
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
None
None
37
Exceptions:
Exceptions:
temp ← GPR[rs] + GPR[rt]
GPR[rd] ← temp
None
temp ← GPR[rs] + sign_extend(immediate)
GPR[rt]← temp
Restrictions:
MIPS32
0
None
6
100001
ADDU
Restrictions:
5
No Integer Overflow exception occurs under any circumstances.
6
No Integer Overflow exception occurs under any circumstances.
5
00000
0
The 32-bit word value in GPR rt is added to the 32-bit value in GPR rs and the 32-bit arithmetic result is placed into
GPR rd.
5
rd
11 10
Description: GPR[rd] ← GPR[rs] + GPR[rt]
5
rt
16 15
Description: GPR[rt] ← GPR[rs] + immediate
To add 32-bit integers
21 20
ADDU
The 16-bit signed immediate is added to the 32-bit value in GPR rs and the 32-bit arithmetic result is placed into
GPR rt.
Purpose:
Format: ADDU rd, rs, rt
26 25
To add a constant to a 32-bit integer
16
31
Purpose:
Format: ADDIU rt, rs, immediate
5
immediate
0
6
5
rt
16 15
6
rs
21 20
Add Unsigned Word
ADDU
000000
26 25
ADDIU
001001
ADDIU
MIPS32® Architecture For Programmers Volume II, Revision 2.50
31
Add Immediate Unsigned Word
ADDIU
42
31
And
AND
6
0
MIPS32
100100
AND
44
31
26 25
Format: B offset
6
000100
BEQ
5
00000
0
None
Exceptions:
MIPS32® Architecture For Programmers Volume II, Revision 2.50
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
GPR[rd] ← GPR[rs] and GPR[rt]
Operation:
None
target_offset ← sign_extend(offset || 02)
PC ← PC + target_offset
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
With the 18-bit signed instruction offset, the conditional branch range is ± 128 Kbytes. Use jump (J) or jump register
(JR) instructions to branch to addresses outside this range.
Programming Notes:
None
Exceptions:
I:
I+1:
Operation:
Processor operation is UNPREDICTABLE if a branch, jump, ERET, DERET, or WAIT instruction is placed in the
delay slot of a branch or jump.
Restrictions:
An 18-bit signed offset (the 16-bit offset field shifted left 2 bits) is added to the address of the instruction following
the branch (not the branch itself), in the branch delay slot, to form a PC-relative effective target address.
Assembly Idiom
Restrictions:
16
offset
0
B offset is the assembly idiom used to denote an unconditional branch. The actual instruction is interpreted by the
hardware as BEQ r0, r0, offset.
16 15
Description: branch
5
00000
0
Description: GPR[rd] ← GPR[rs] AND GPR[rt]
21 20
B
The contents of GPR rs are combined with the contents of GPR rt in a bitwise logical AND operation. The result is
placed into GPR rd.
To do an unconditional branch
5
Purpose:
6
Unconditional Branch
To do a bitwise logical AND
AND rd, rs, rt
5
0
B
Purpose:
Format:
5
rd
11 10
5
5
rt
16 15
6
rs
21 20
00000
26 25
000000
SPECIAL
AND
60
31
MIPS32
BGTZ
5
rs
Operation:
Operation:
With the 18-bit signed instruction offset, the conditional branch range is ± 128 KBytes. Use jump (J) or jump register
(JR) instructions to branch to addresses outside this range.
With the 18-bit signed instruction offset, the conditional branch range is ± 128 Kbytes. Use jump (J) or jump register
(JR) instructions to branch to addresses outside this range.
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
69
Programming Notes:
Programming Notes:
MIPS32® Architecture For Programmers Volume II, Revision 2.50
None
BEQ r0, r0 offset, expressed as B offset, is the assembly idiom used to denote an unconditional branch.
Exceptions:
None
I+1:
Exceptions:
I+1:
target_offset ← sign_extend(offset || 02)
condition ← GPR[rs] > 0GPRLEN
if condition then
PC ← PC + target_offset
endif
Processor operation is UNPREDICTABLE if a branch, jump, ERET, DERET, or WAIT instruction is placed in the
delay slot of a branch or jump.
Processor operation is UNPREDICTABLE if a branch, jump, ERET, DERET, or WAIT instruction is placed in the
delay slot of a branch or jump.
I:
Restrictions:
Restrictions:
target_offset ← sign_extend(offset || 02)
condition ← (GPR[rs] = GPR[rt])
if condition then
PC ← PC + target_offset
endif
If the contents of GPR rs are greater than zero (sign bit is 0 but value not zero), branch to the effective target address
after the instruction in the delay slot is executed.
If the contents of GPR rs and GPR rt are equal, branch to the effective target address after the instruction in the delay
slot is executed.
I:
An 18-bit signed offset (the 16-bit offset field shifted left 2 bits) is added to the address of the instruction following
the branch (not the branch itself), in the branch delay slot, to form a PC-relative effective target address.
MIPS32
Description: if GPR[rs] > 0 then branch
16
offset
0
An 18-bit signed offset (the 16-bit offset field shifted left 2 bits) is added to the address of the instruction following
the branch (not the branch itself), in the branch delay slot, to form a PC-relative effective target address.
16 15
Description: if GPR[rs] = GPR[rt] then branch
5
00000
0
To test a GPR then do a PC-relative conditional branch
21 20
Purpose:
Format: BGTZ rs, offset
26 25
To compare GPRs then do a PC-relative conditional branch
16
31
BGTZ
Purpose:
Format: BEQ rs, rt, offset
5
offset
0
6
5
rt
16 15
6
rs
21 20
Branch on Greater Than Zero
BGTZ
000111
26 25
BEQ
000100
BEQ
Branch on Equal
BEQ
72
31
26 25
5
rs
21 20
Format: BNE rs, rt, offset
6
000101
BNE
5
rt
16 15
Operation:
Operation:
With the 18-bit signed instruction offset, the conditional branch range is ± 128 KBytes. Use jump (J) or jump register
(JR) instructions to branch to addresses outside this range.
With the 18-bit signed instruction offset, the conditional branch range is ± 128 KBytes. Use jump (J) or jump register
(JR) instructions to branch to addresses outside this range.
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
81
Programming Notes:
Programming Notes:
MIPS32® Architecture For Programmers Volume II, Revision 2.50
None
MIPS32® Architecture For Programmers Volume II, Revision 2.50
Exceptions:
None
I+1:
Exceptions:
I+1:
target_offset ← sign_extend(offset || 02)
condition ← (GPR[rs] ≠ GPR[rt])
if condition then
PC ← PC + target_offset
endif
Processor operation is UNPREDICTABLE if a branch, jump, ERET, DERET, or WAIT instruction is placed in the
delay slot of a branch or jump.
Processor operation is UNPREDICTABLE if a branch, jump, ERET, DERET, or WAIT instruction is placed in the
delay slot of a branch or jump.
I:
Restrictions:
Restrictions:
target_offset ← sign_extend(offset || 02)
condition ← GPR[rs] ≤ 0GPRLEN
if condition then
PC ← PC + target_offset
endif
If the contents of GPR rs and GPR rt are not equal, branch to the effective target address after the instruction in the
delay slot is executed.
If the contents of GPR rs are less than or equal to zero (sign bit is 1 or value is zero), branch to the effective target
address after the instruction in the delay slot is executed.
I:
Description: if GPR[rs] ≠ GPR[rt] then branch
An 18-bit signed offset (the 16-bit offset field shifted left 2 bits) is added to the address of the instruction following
the branch (not the branch itself), in the branch delay slot, to form a PC-relative effective target address.
MIPS32
Description: if GPR[rs] ≤ 0 then branch
16
offset
0
BNE
An 18-bit signed offset (the 16-bit offset field shifted left 2 bits) is added to the address of the instruction following
the branch (not the branch itself), in the branch delay slot, to form a PC-relative effective target address.
To compare GPRs then do a PC-relative conditional branch
MIPS32
31
Purpose:
16
offset
0
Branch on Not Equal
BNE
To test a GPR then do a PC-relative conditional branch
16 15
BLEZ
Purpose:
Format: BLEZ rs, offset
5
5
0
6
rs
21 20
00000
26 25
000110
BLEZ
Branch on Less Than or Equal to Zero
BLEZ
31
MIPS32
←
←
←
←
None
Exceptions:
q
LO
r
HI
Operation:
MIPS32® Architecture For Programmers Volume II, Revision 2.50
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
GPR[rs]31..0 div GPR[rt]31..0
q
GPR[rs]31..0 mod GPR[rt]31..0
r
If the divisor in GPR rt is zero, the arithmetic result value is UNPREDICTABLE.
Restrictions:
No arithmetic exception occurs under any circumstances.
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
This definition creates the following boundary case: When the jump instruction is in the last word of a 256 MB
region, it can branch only to the following 256 MB region containing the branch delay slot.
Forming the branch target address by catenating PC and index bits rather than adding a signed offset to the PC is an
advantage if all program code addresses fit into a 256 MB region aligned on a 256 MB boundary. It allows a branch
from anywhere in the region to anywhere in the region, an action not allowed by a signed relative offset.
Programming Notes:
None
Exceptions:
I:
I+1:PC ← PCGPRLEN-1..28 || instr_index || 02
Operation:
Processor operation is UNPREDICTABLE if a branch, jump, ERET, DERET, or WAIT instruction is placed in the
delay slot of a branch or jump.
Restrictions:
Jump to the effective target address. Execute the instruction that follows the jump, in the branch delay slot, before
executing the jump itself.
This is a PC-region branch (not PC-relative); the effective target address is in the “current” 256 MB-aligned region.
The low 28 bits of the target address is the instr_index field shifted left 2 bits. The remaining upper bits are the corresponding bits of the address of the instruction in the delay slot (not the branch itself).
26
instr_index
0
Description:
138
26 25
Format: J target
6
000010
J
The 32-bit word value in GPR rs is divided by the 32-bit value in GPR rt, treating both operands as signed values.
The 32-bit quotient is placed into special register LO and the 32-bit remainder isplaced into special register HI.
31
Description: (HI, LO) ← GPR[rs] / GPR[rt]
MIPS32
0
J
To branch within the current 256 MB-aligned region
6
011010
DIV
Jump
Purpose:
5
J
To divide a 32-bit signed integers
6
DIV
Purpose:
Format: DIV rs, rt
5
0
10
5
rt
16 15
6
rs
21 20
00 0000 0000
26 25
000000
SPECIAL
Divide Word
122
DIV
31
JR rs
0
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
139
This definition creates the following boundary case: When the branch instruction is in the last word of a 256 MB
region, it can branch only to the following 256 MB region containing the branch delay slot.
Forming the branch target address by catenating PC and index bits rather than adding a signed offset to the PC is an
advantage if all program code addresses fit into a 256 MB region aligned on a 256 MB boundary. It allows a branch
from anywhere in the region to anywhere in the region, an action not allowed by a signed relative offset.
Programming Notes:
None
Exceptions:
I: GPR[31]← PC + 8
I+1:PC
← PCGPRLEN-1..28 || instr_index || 02
Operation:
Processor operation is UNPREDICTABLE if a branch, jump, ERET, DERET, or WAIT instruction is placed in the
delay slot of a branch or jump.
Restrictions:
Jump to the effective target address. Execute the instruction that follows the jump, in the branch delay slot, before
executing the jump itself.
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
None
Exceptions:
I: temp ← GPR[rs]
I+1:if Config1CA = 0 then
PC ← temp
else
PC ← tempGPRLEN-1..1 || 0
ISAMode ← temp0
endif
Operation:
145
Processor operation is UNPREDICTABLE if a branch, jump, ERET, DERET, or WAIT instruction is placed in the
delay slot of a branch or jump.
In release 1 of the architecture, the only defined hint field value is 0, which sets default handling of JR. In Release 2
of the architecture, bit 10 of the hint field is used to encode an instruction hazard barrier. See the JR.HB instruction
description for additional information.
The effective target address in GPR rs must be naturally-aligned. For processors that do not implement the MIPS16e
ASE, if either of the two least-significant bits are not zero, an Address Error exception occurs when the branch target
is subsequently fetched as an instruction. For processors that do implement the MIPS16e ASE, if bit 0 is zero and bit
1 is one, an Address Error exception occurs when the jump target is subsequently fetched as an instruction.
Restrictions:
For processors that implement the MIPS16e ASE, set the ISA Mode bit to the value in GPR rs bit 0. Bit 0 of the target
address is always zero so that no Address Exceptions occur when bit 0 of the source register is one
MIPS32
0
This is a PC-region branch (not PC-relative); the effective target address is in the “current” 256 MB-aligned region.
The low 28 bits of the target address is the instr_index field shifted left 2 bits. The remaining upper bits are the corresponding bits of the address of the instruction in the delay slot (not the branch itself).
6
001000
JR
Description: PC ← GPR[rs]
5
Jump to the effective target address in GPR rs. Execute the instruction following the jump, in the branch delay slot,
before jumping.
5
hint
6
Place the return address link in GPR 31. The return link is the address of the second instruction following the branch,
at which location execution continues after a procedure call.
To execute a branch to an instruction address in a register
11 10
JR
Description:
Purpose:
To execute a procedure call within the current 256 MB-aligned region
Format:
Purpose:
Format: JAL target
26
5
rs
21 20
10
MIPS32
26 25
6
SPECIAL
6
31
Jump Register
00 0000 0000
instr_index
0
JR
000000
26 25
JAL
000011
JAL
Jump and Link
MIPS32® Architecture For Programmers Volume II, Revision 2.50
JAL
31
MIPS32
31
LBU
5
base
21 20
Operation:
Operation:
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
TLB Refill, TLB Invalid, Address Error, Watch
TLB Refill, TLB Invalid, Address Error, Watch
MIPS32® Architecture For Programmers Volume II, Revision 2.50
Exceptions:
Exceptions:
151
None
None
vAddr ← sign_extend(offset) + GPR[base]
(pAddr, CCA)← AddressTranslation (vAddr, DATA, LOAD)
pAddr ← pAddrPSIZE-1..2 || (pAddr1..0 xor ReverseEndian2)
memword← LoadMemory (CCA, BYTE, pAddr, vAddr, DATA)
byte
← vAddr1..0 xor BigEndianCPU2
GPR[rt]← zero_extend(memword7+8*byte..8*byte)
Restrictions:
Restrictions:
vAddr ← sign_extend(offset) + GPR[base]
(pAddr, CCA)← AddressTranslation (vAddr, DATA, LOAD)
pAddr ← pAddrPSIZE-1..2 || (pAddr1..0 xor ReverseEndian2)
memword← LoadMemory (CCA, BYTE, pAddr, vAddr, DATA)
byte
← vAddr1..0 xor BigEndianCPU2
GPR[rt]← sign_extend(memword7+8*byte..8*byte)
The contents of the 8-bit byte at the memory location specified by the effective address are fetched, zero-extended,
and placed in GPR rt. The 16-bit signed offset is added to the contents of GPR base to form the effective address.
MIPS32
Description: GPR[rt] ← memory[GPR[base] + offset]
16
offset
0
Description: GPR[rt] ← memory[GPR[base] + offset]
16 15
LBU
The contents of the 8-bit byte at the memory location specified by the effective address are fetched, sign-extended,
and placed in GPR rt. The 16-bit signed offset is added to the contents of GPR base to form the effective address.
To load a byte from memory as an unsigned value
5
rt
Purpose:
Format: LBU rt, offset(base)
26 25
To load a byte from memory as a signed value
LB rt, offset(base)
16
offset
0
Purpose:
Format:
5
rt
16 15
6
5
base
21 20
6
26 25
Load Byte Unsigned
LBU
100100
LB
LB
100000
Load Byte
150
LB
31
00000
5
001111
6
21 20
26 25
5
base
21 20
Format: LW rt, offset(base)
6
100011
LW
5
rt
None
Exceptions:
GPR[rt] ← immediate || 016
Operation:
None
Restrictions:
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
159
The 16-bit immediate is shifted left 16 bits and concatenated with 16 bits of low-order zeros. The 32-bit result is
placed into GPR rt.
Description: GPR[rt] ← immediate || 0
16
16 15
16
offset
MIPS32
0
LW
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
TLB Refill, TLB Invalid, Bus Error, Address Error, Watch
Exceptions:
vAddr ← sign_extend(offset) + GPR[base]
if vAddr1..0 ≠ 02 then
SignalException(AddressError)
endif
(pAddr, CCA)← AddressTranslation (vAddr, DATA, LOAD)
memword← LoadMemory (CCA, WORD, pAddr, vAddr, DATA)
GPR[rt]← memword
Operation:
161
The effective address must be naturally-aligned. If either of the 2 least-significant bits of the address is non-zero, an
Address Error exception occurs.
Restrictions:
The contents of the 32-bit word at the memory location specified by the aligned effective address are fetched,
sign-extended to the GPR register length if necessary, and placed in GPR rt. The 16-bit signed offset is added to the
contents of GPR base to form the effective address.
Description: GPR[rt] ← memory[GPR[base] + offset]
To load a word from memory as a signed value
MIPS32
31
Load Word
Purpose:
16
immediate
0
LW
To load a constant into the upper half of a word
5
rt
16 15
LUI
Purpose:
Format: LUI rt, immediate
0
LUI
26 25
Load Upper Immediate
MIPS32® Architecture For Programmers Volume II, Revision 2.50
LUI
00 0000 0000
10
000000
6
6
5
6
0
MIPS32
010000
MFHI
182
31
26 25
Format: MFLO
6
000000
SPECIAL
rd
10
00 0000 0000
0
MIPS32
0
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
181
In the MIPS I, II, and III architectures, the two instructions which follow the MFHI must not moodify the HI register.
If this restriction is violated, the result of the MFHI is UNPREDICTABLE. This restriction was removed in MIPS
IV and MIPS32, and all subsequent levels of the architecture.
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
In the MIPS I, II, and III architectures, the two instructions which follow the MFHI must not moodify the HI register.
If this restriction is violated, the result of the MFHI is UNPREDICTABLE. This restriction was removed in MIPS
IV and MIPS32, and all subsequent levels of the architecture.
6
010010
MFLO
Historical Information:
5
Historical Information:
6
None
5
00000
0
None
Exceptions:
GPR[rd] ← LO
5
rd
11 10
MFLO
Exceptions:
GPR[rd] ← HI
Operation:
Operation:
Restrictions: None
Restrictions:
None
Description: GPR[rd] ← LO
The contents of special register LO are loaded into GPR rd.
Description: GPR[rd] ← HI
16 15
The contents of special register HI are loaded into GPR rd.
To copy the special purpose LO register to a GPR
5
00000
0
Purpose:
5
rd
11 10
Move From LO Register
MFLO
To copy the special purpose HI register to a GPR
16 15
MFHI
Purpose:
Format: MFHI rd
0
26 25
SPECIAL
MIPS32® Architecture For Programmers Volume II, Revision 2.50
31
Move From HI Register
MFHI
MIPS32
0
218
31
Format: OR rd, rs, rt
0
MIPS32
0
← GPR[rs]31..0 × GPR[rt]31..0
← prod31..0
← prod63..32
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
209
Where the size of the operands are known, software should place the shorter operand in GPR rt. This may reduce the
latency of the instruction on those processors which implement data-dependent instruction latencies.
Programs that require overflow detection must check for it explicitly.
In some processors the integer multiply operation may proceed asynchronously and allow other CPU instructions to
execute before it is complete. An attempt to read LO or HI before the results are written interlocks until the results are
ready. Asynchronous execution does not affect the program result, but offers an opportunity for performance
improvement by scheduling the multiply so that other instructions can execute in parallel.
Programming Notes:
None
Exceptions:
prod
LO
HI
Operation:
None
Restrictions:
No arithmetic exception occurs under any circumstances.
None
Exceptions:
MIPS32® Architecture For Programmers Volume II, Revision 2.50
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
GPR[rd] ← GPR[rs] or GPR[rt]
Operation:
None
Restrictions:
The contents of GPR rs are combined with the contents of GPR rt in a bitwise logical OR operation. The result is
placed into GPR rd.
6
100101
OR
Description: GPR[rd] ← GPR[rs] or GPR[rt]
5
Description: (HI, LO) ← GPR[rs] × GPR[rt]
6
OR
The 32-bit word value in GPR rt is multiplied by the 32-bit value in GPR rs, treating both operands as signed values,
to produce a 64-bit result. The low-order 32-bit word of the result is placed into special register LO, and the
high-order 32-bit word is splaced into special register HI.
5
rd
11 10
5
5
rt
16 15
00000
5
rs
21 20
6
26 25
000000
SPECIAL
To do a bitwise logical OR
6
011000
MULT
Or
Purpose:
5
OR
To multiply 32-bit signed integers
6
MULT
Purpose:
Format: MULT rs, rt
5
0
10
5
rt
16 15
6
rs
21 20
00 0000 0000
26 25
000000
SPECIAL
MIPS32® Architecture For Programmers Volume II, Revision 2.50
31
Multiply Word
MULT
31
immediate
MIPS32
0
242
31
SB
21 20
Operation:
Operation:
None
Exceptions:
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
219
None
None
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
TLB Refill, TLB Invalid, TLB Modified, Bus Error, Address Error, Watch
Exceptions:
vAddr
← sign_extend(offset) + GPR[base]
(pAddr, CCA)← AddressTranslation (vAddr, DATA, STORE)
pAddr
← pAddrPSIZE-1..2 || (pAddr1..0 xor ReverseEndian2)
bytesel
← vAddr1..0 xor BigEndianCPU2
dataword ← GPR[rt]31–8*bytesel..0 || 08*bytesel
StoreMemory (CCA, BYTE, dataword, pAddr, vAddr, DATA)
Restrictions:
Restrictions:
GPR[rt] ← GPR[rs] or zero_extend(immediate)
The least-significant 8-bit byte of GPR rt is stored in memory at the location specified by the effective address. The
16-bit signed offset is added to the contents of GPR base to form the effective address.
MIPS32
Description: memory[GPR[base] + offset] ← GPR[rt]
16
offset
0
Description: GPR[rt] ← GPR[rs] or immediate
5
rt
16 15
SB
The 16-bit immediate is zero-extended to the left and combined with the contents of GPR rs in a bitwise logical OR
operation. The result is placed into GPR rt.
To store a byte to memory
Format: SB rt, offset(base)
5
Purpose:
16
base
To do a bitwise logical OR with a constant
5
26 25
Purpose:
Format: ORI rt, rs, immediate
5
6
rt
16 15
Store Byte
6
rs
21 20
SB
101000
26 25
ORI
001101
ORI
Or Immediate
MIPS32® Architecture For Programmers Volume II, Revision 2.50
ORI
31
00000
5
000000
6
5
sa
6
5
6
0
MIPS32
000000
SLL
256
31
26 25
5
rs
Format: SLT rd, rs, rt
6
000000
SPECIAL
21 20
5
rt
6
5
6
0
MIPS32
101010
SLT
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
SLL r0, r0, 1, expressed as SSNOP, is the assembly idiom used to denote no operation that causes an issue break on
superscalar processors.
SLL r0, r0, 0, expressed as NOP, is the assembly idiom used to denote no operation.
Programming Notes:
None
Exceptions:
s
← sa
temp
← GPR[rt](31-s)..0 || 0s
GPR[rd]← temp
Operation:
None
Restrictions:
None
Exceptions:
MIPS32® Architecture For Programmers Volume II, Revision 2.50
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
if GPR[rs] < GPR[rt] then
GPR[rd] ← 0GPRLEN-1 || 1
else
GPR[rd] ← 0GPRLEN
endif
Operation:
None
Restrictions:
The arithmetic comparison does not cause an Integer Overflow exception.
Description: GPR[rd] ← (GPR[rs] < GPR[rt])
5
00000
0
Compare the contents of GPR rs and GPR rt as signed integers and record the Boolean result of the comparison in
GPR rd. If GPR rs is less than GPR rt, the result is 1 (true); otherwise, it is 0 (false).
5
rd
11 10
Description: GPR[rd] ← GPR[rt] << sa
16 15
SLT
The contents of the low-order 32-bit word of GPR rt are shifted left, inserting zeros into the emptied bits; the word
result is placed in GPR rd. The bit-shift amount is specified by sa.
To record the result of a less-than comparison
5
rd
11 10
Set on Less Than
Purpose:
5
rt
16 15
SLT
To left-shift a word by a fixed number of bits
21 20
SLL
Purpose:
Format: SLL rd, rt, sa
0
SPECIAL
26 25
Shift Word Left Logical
254
SLL
26 25
4
0000
5
6
0
MIPS32
000010
SRL
266
31
26 25
5
rs
Format: SUB rd, rs, rt
6
000000
SPECIAL
21 20
5
rt
16 15
(logical)
None
Exceptions:
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
5
00000
0
6
5
6
0
MIPS32
100010
SUB
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
SUBU performs the same arithmetic operation but does not trap on overflow.
Programming Notes:
Integer Overflow
Exceptions:
temp ← (GPR[rs]31||GPR[rs]31..0) − (GPR[rt]31||GPR[rt]31..0)
if temp32 ≠ temp31 then
SignalException(IntegerOverflow)
else
GPR[rd] ← temp31..0
endif
Operation:
None
Operation:
s
← sa
temp
← 0s || GPR[rt]31..s
GPR[rd]← temp
Restrictions:
263
Description: GPR[rd] ← GPR[rs] - GPR[rt]
5
rd
11 10
SUB
The 32-bit word value in GPR rt is subtracted from the 32-bit value in GPR rs to produce a 32-bit result. If the subtraction results in 32-bit 2’s complement arithmetic overflow, then the destination register is not modified and an Integer Overflow exception occurs. If it does not overflow, the 32-bit result is placed into GPR rd.
None
Restrictions:
The contents of the low-order 32-bit word of GPR rt are shifted right, inserting zeros into the emptied bits; the word
result is placed in GPR rd. The bit-shift amount is specified by sa.
Description: GPR[rd] ← GPR[rt] >> sa
To subtract 32-bit integers. If overflow occurs, then trap
5
sa
6
Purpose:
5
rd
11 10
Subtract Word
SUB
To execute a logical right-shift of a word by a fixed number of bits
5
rt
16 15
SRL
Purpose:
1
R
0
22 21 20
Format: SRL rd, rt, sa
6
000000
SPECIAL
MIPS32® Architecture For Programmers Volume II, Revision 2.50
31
Shift Word Right Logical
SRL
31
code
6
5
SYSCALL
5
16
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
TLB Refill, TLB Invalid, TLB Modified, Address Error, Watch
Exceptions:
vAddr ← sign_extend(offset) + GPR[base]
if vAddr1..0 ≠ 02 then
SignalException(AddressError)
endif
(pAddr, CCA)← AddressTranslation (vAddr, DATA, STORE)
dataword← GPR[rt]
StoreMemory (CCA, WORD, dataword, pAddr, vAddr, DATA)
Operation:
The effective address must be naturally-aligned. If either of the 2 least-significant bits of the address is non-zero, an
Address Error exception occurs.
Restrictions:
The least-significant 32-bit word of GPR rt is stored in memory at the location specified by the aligned effective
address. The 16-bit signed offset is added to the contents of GPR base to form the effective address.
Description: memory[GPR[base] + offset] ← GPR[rt]
To store a word to memory
Purpose:
Format: SW rt, offset(base)
5
MIPS32
0
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
MIPS32® Architecture For Programmers Volume II, Revision 2.50
System Call
Exceptions:
SignalException(SystemCall)
Operation:
None
Restrictions:
285
The code field is available for use as software parameters, but is retrieved by the exception handler only by loading
the contents of the memory word containing the instruction.
A system call exception occurs, immediately and unconditionally transferring control to the exception handler.
Description:
To cause a System Call exception
Purpose:
Format: SYSCALL
20
6
MIPS32
26 25
001100
31
6
offset
0
000000
rt
16 15
SYSCALL
6
base
21 20
System Call
SYSCALL
SPECIAL
26 25
SW
101011
SW
Store Word
270
SW
0
6
5
6
0
MIPS32
100110
XOR
XOR
None
Exceptions:
GPR[rd] ← GPR[rs] xor GPR[rt]
Operation:
None
Restrictions:
Copyright © 2001-2003,2005 MIPS Technologies Inc. All rights reserved.
313
Combine the contents of GPR rs and GPR rt in a bitwise logical Exclusive OR operation and place the result into
GPR rd.
Description: GPR[rd] ← GPR[rs] XOR GPR[rt]
To do a bitwise logical Exclusive OR
Purpose:
Format: XOR rd, rs, rt
5
rd
11 10
5
5
rt
16 15
00000
5
rs
21 20
6
26 25
000000
SPECIAL
MIPS32® Architecture For Programmers Volume II, Revision 2.50
31
Exclusive OR
XOR
Téléchargement