Appels de fonctions 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 est de pratiquer l'appel de fonctions C depuis du code assembleur. Pendant toute la séance, vous devez traduire le code le plus systématiquement possible, sans chercher à faire d'optimisation (notamment, sans laisser de variables locales ou de paramètres dans des registres).

On rappelle que les registres scratch (utilisables librement) sont %eax, %edx et %ecx sur Pentium. Si vous souhaitez en utiliser d'autres, vous devez auparavant les sauvegarder.

Les squelettes des fichiers à compléter sont disponibles dans cette archive Zip.

Note : certaines versions récentes de Valgrind ne supportent pas l'instruction enter que l'on utilise au début des fonctions écrites en assembleur. Si vous obtenez une erreur du genre vex x86->IR: unhandled instruction bytes: 0xC8 à l'exécution, vous pouvez remplacer la ligne enter $N, $0 par les trois lignes suivantes (où N est bien sûr à remplacer par la taille de la zone contenant les variables locales dans votre programme) :

pushl %ebp
movl %esp, %ebp
subl $N, %esp

Première fonction

Le fichier age.c contient une fonction main qui fait appel à la fonction calculer_age que vous allez devoir écrire en assembleur. Le code C équivalent à cette fonction (que vous devez traduire le plus systématiquement possible) est donné ci-dessous :

unsigned calculer_age(unsigned annee_naissance)
{
    unsigned annee_courante = 2012;
    unsigned age = annee_courante - annee_naissance;
    return age;
}

Vous écrirez le code assembleur dans un fichier calculer_age.S. Pour compiler, vous pourrez utiliser la commande suivante :

gcc -Wall -Wextra -std=c99 -m32 -g -gstabs age.c calculer_age.S -o age

Factorielle

On demande maintenant d'implanter en assembleur dans un fichier calculer_fact.S une fonction récursive bien connue :

unsigned calculer_fact(unsigned n)
{
    if (n <= 1) {
        return 1;
    } else {
        return n * calculer_fact(n - 1);
    }
}

Pour implanter cette fonction, vous aurez besoin d'une instruction de multiplication. Le Pentium en fourni une dont la syntaxe est un peu particulière. Si on suppose qu'on ne travaille ici que sur des valeurs sur 32 bits, lorsqu'on écrit mull x, on effectue le produit de la valeur de la variable x avec le contenu du registre %eax : ce registre est donc un paramètre implicite de l'instruction.

Le résultat (sur 64 bits vu qu'on multiplie deux nombres sur 32 bits), est toujours stocké dans %edx:%eax, c'est à dire que les 32 bits de poids fort du résultat sont dans %edx, et les 32 bits de poids faibles dans %eax (par exemple si le résultat de la multiplication tient sur 32 bits, mul va écrire le résultat dans %eax et 0 dans %edx. Si vous aviez mis quelque chose d'important dans %edx, la donnée est perdue !).

Le fichier fact.c contient le programme principal appelant la fonction fact. Vous pouvez compiler votre programme avec la commande :

gcc -Wall -Wextra -std=c99 -m32 -g -gstabs fact.c calculer_fact.S -o fact

Si vous êtes sous MacOS, vous devez ajouter le paramètre -mstackrealign à la ligne de commande ci-dessus. Dans le cas contraire, vous aurez une segmentation fault en implantant l'extension ci-dessous.

Tester le programme sur les valeurs de n suivantes : 0, 1, 2, 5 et 13. Comme chacun sait, 13! = 6 227 020 800 : est-ce bien la valeur obtenue ? Sinon, que se passe-t'il et comment détecter ce phénomène ? Modifier votre programme assembleur pour qu'il appelle la fonction erreur_fact fournie dans le fichier fact.c quand l'erreur est détectée.

Palindromes

Pour finir, on va implanter en assembleur une fonction détectant si une chaine de caractères est un palindrome, c'est à dire si elle représente le même mot qu'on la lise en partant de la gauche ou de la droite. Par convention, la chaine vide n'est pas un palindrome.

Le code C à traduire systématiquement en assembleur dans un fichier est_palindrome est donc le suivant :

int est_palindrome(char *ch)
{
    unsigned inf, sup;
    for (inf = 0, sup = taille_chaine(ch) - 1; (inf < sup) && (ch[inf] == ch[sup]); inf++, sup--);
    return inf >= sup;
}

Le fichier palindrome.c contient le programme principal appelant cette fonction ainsi que la fonction taille_chaine. Vous pouvez compiler en utilisant la commande :

gcc -Wall -Wextra -std=c99 -m32 -g -gstabs palindrome.c est_palindrome.S -o palindrome

Si vous êtes sous MacOS, vous devez là-encore ajouter le paramètre -mstackrealign à la ligne de commande ci-dessus.

Si vous avez fini en avance : modifier votre programme pour qu'il gère correctement les espaces, la ponctuation et les majuscules dans la chaine passée en paramètre (e.g. les chaines "Esope reste ici et se repose" et "Dammit, I'm mad!" doivent être considérées comme des palindromes).