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