Ecriture et debugage d'algorithmes simples en assembleur x86

De Ensiwiki
Aller à : navigation, rechercher
AttentionCette page est maintenue uniquement par les enseignants. Afin de ne pas perturber le déroulement des cours, elle n'a pas vocation à être modifiée par les élèves. Mais si vous avez des modifications à proposer, merci d'en discuter ou d'envoyer un e-mail aux auteurs de la page (cf. historique)

Laptop.png  Première Année  CDROM.png  Informatique 

Introduction

Le but de cette séance machine est d'écrire et mettre au point quelques programmes simples en assembleur.

Note : il est important de bien suffixer vos sources assembleur par un S majuscule (e.g. mon_prog.S).

Vous pouvez récupérer les sources fournies pour cette séance dans cette archive Zip. Pour les apprentis, les squelettes se trouvent dans le répertoire asm/1-asm-intro/ de votre archive Git.

Attention, on rappelle que les conventions de nommage des fonctions sont légèrement différentes entre Linux et MacOS :

  • sous Linux, la fonction main du C doit aussi s'appeler main en assembleur ;
  • sous MacOS, cette fonction doit s'appeller _main en assembleur.

Vous pouvez mettre systématiquement les deux noms d'étiquettes si vous voulez que vos programmes fonctionnent sur les deux systèmes.

Premier programme assembleur : PGCD

Edition et compilation

Ouvrez dans votre éditeur de texte favori le fichier pgcd.S récupéré dans l'archive ci-dessus.

Assembler ce programme en utilisant la commande gcc -o pgcd pgcd.S -g -m32 -gstabs. Vous pouvez l'exécuter en exécutant ./pgcd mais vu que le programme ne fait pas d'affichage, ce n'est pas très parlant.

Exécution pas à pas avec gdb

On va donc utiliser gdb pour tracer l'exécution du programme instruction par instruction :

  • charger le programme dans gdb en tapant gdb ./pgcd ;
  • ajouter un point d'arrêt au début du programme en utilisant la commande « break main » (ou pour moins se fatiguer, simplement b main) ;
  • lancer son exécution en tapant run : noter que l'exécution s'arrête sur le point d'arrêt positionné sur main : gdb affiche alors la prochaine instruction à exécuter (i.e. elle n'a pas encore été exécutée) ;
  • utiliser step pour avancer et exécuter l'instruction movl $15, %ecx ;
  • afficher le contenu du registre %ecx en tapant print $ecx (attention, pour GDB, les registres s'appellent $eax, $ebx, ... alors que pour l'assembleur ils s'appellent %eax, %ebx, ...). Vérifier qu'il contient bien 15.
    Note : on peut également faire tui reg general pour afficher la valeur de tous les registres d'une manière semi-graphique, ou bien display $ecx pour afficher la valeur de %ecx à chaque pas. Une dernière solution est d'utiliser la commande info registers (ou i reg), avec éventuellement un argument (i reg eax)
  • continuer l'exécution du programme pas à pas avec step en affichant les valeurs des registres après l'exécution de chaque instruction qui les modifie (l'utilisation de la commande display, qui affiche à chaque pas le contenu d'un registre voulu sans avoir à retaper la commande peut être pratique : display $ecx, mais attention à la syntaxe : il faut bien mettre un $ devant le nom du registre) ;
  • vérifier qu'on fait bien deux itérations de la boucle while avant d'en sortir (i.e. le branchement conditionnel je fin_while n'est pas effectué lors des deux premières itérations) ;
  • vérifier aussi qu'on saute bien vers else la première fois qu'on exécute l'instruction jb else mais pas la deuxième ;
  • vérifier enfin que le PGCD (5) est bien dans %ecx et %edx lorsqu'on arrive à la fin de la boucle (i.e. sur fin_while) ;
  • ensuite on peut terminer proprement l'exécution du programme en tapant continue.

On fourni un aide-mémoire pour gdb qui contient beaucoup plus d'information que ce qui vous sera nécessaire ce semestre.

Multiplication simple

Code en C à traduire

On va maintenant implanter l'opération res := x * y par additions successives. On donne ci-dessous le code C que l'on va devoir traduire le plus littéralement possible en assembleur :

int main(void)
{
    unsigned x, y, res;
    x = 5;
    y = 4;
    res = 0;
    while (y != 0) {
        res = res + x;
        y--;
    }
    return 0;
}

Version assembleur à produire

Ouvrez le fichier mult.S et complétez le avec le code effectuant la multiplication. On stockera x dans %ecx, y dans %edx et res dans %eax.

Testez que le programme fonctionne en l'exécutant dans GDB et en suivant l'évolution des valeurs des registres avec la commande display.

Multiplication égyptienne

Code en C à traduire

Refaite le même exercice en implantant cette fois-ci l'algorithme de la multiplication égyptienne :

int main(void)
{
    unsigned x, y, res;
    res = 0;
    x = 5;
    y = 4;
    while (y != 0) {
        if (y % 2 == 1) { /* % est l'operateur modulo */
            res = res + x;
        }
        x = x * 2;
        y = y / 2;
    }
    return 0;
}

Version assembleur à produire

Ouvrez le fichier mult_egypt.S et complétez le pour implanter l'algorithme ci-dessus.

Pour implanter les opérations modulo, multiplier et diviser par 2, vous aurez sûrement besoin des instructions suivantes :

  • testl A, B compare A et B en effectuant l'opération A and B ;
  • shll $n, A décale A de n bits vers la gauche ;
  • shrl $n, A décale A de n bits vers la droite (c'est un décalage logique, pas arithmétique).

Testez que le programme fonctionne en l'exécutant dans GDB et en suivant l'évolution des valeurs des registres avec la commande display.