Lasagnes

Lasagnes

Exercice d'apprentissage

Introduction

Les bases

Les commentaires

Le parcours x86-64 d'Exercism utilise NASM (The Netwide Assembler) comme assembleur. En NASM, les commentaires commencent par un point-virgule (;).

Les commentaires peuvent être placés n'importe où dans le programme, et tout ce qui suit un ; est ignoré par l'assembleur.

Les constantes

On peut définir une constante au moment de l'assemblage en NASM avec equ. Par exemple, ceci définit une constante nommée UNIVERSE avec la valeur 42 :

UNIVERSE equ 42

Les constantes sont évaluées une seule fois, au moment de leur définition, et ne peuvent pas être redéfinies par la suite.

Les registres à usage général (GPRs)

En assembleur, au lieu de variables, on utilise généralement des registres pour stocker des valeurs. Un registre de processeur est une petite mémoire rapide située à l'intérieur du processeur d'un ordinateur. La plupart des calculs sont effectués dans les registres du processeur.

Certains registres servent à toutes sortes de calculs différents et sont appelés registres à usage général (GPRs). D'autres ont des rôles spéciaux ou dédiés.

En x86-64, il y a 16 registres à usage général (GPRs) de 64 bits, auxquels on peut aussi accéder en 32, 16 ou 8 bits. Les GPRs sont décrits ci-dessous, où n dans rn va de 8 à 15 : r8, r9, r10, r11, r12, r13, r14 et r15.

64-bit 32-bit 16-bit 8-bit
rax eax ax ah/al
rbx ebx bx bh/bl
rcx ecx cx ch/cl
rdx edx dx dh/dl
rsi esi si sil
rdi edi di dil
rbp ebp bp bpl
rsp esp sp spl
rn rnd rnw rnb

Lorsqu'on utilise moins de 64 bits, les bits accédés proviennent généralement de la partie basse du registre. L'exception à cette règle concerne ah, bh, ch et dh, qui accèdent aux 8 bits de poids fort de la partie 16 bits du registre.

Illustration de la façon dont les bits sont accédés pour le registre rax :

+--------+---------------------------------------+
| 64-bit |                  rax                  |
+--------+-------------------+-------------------+
| 32-bit |                   |        eax        |
+--------+-------------------+---------+---------+
| 16-bit |                             |    ax   |
+--------+-----------------------------+----+----+
| 8-bit  |                             | ah | al |
+--------+-----------------------------+----+----+

Certains de ces registres doivent être préservés lors des appels de fonction : rbp, rsp, rbx, r12, r13, r14 et r15. Ne pas les préserver peut entraîner une erreur ou un comportement indéfini.

Les autres ne sont pas préservés et peuvent être utilisés librement : rax, rcx, rdx, rdi, rsi, r8, r9, r10 et r11.

Caution

Plus tard dans le programme, tu apprendras à préserver les registres. Pour l'instant, n'utilise que le second type de registres : rax, rcx, rdx, rdi, rsi, r8, r9, r10 et r11.

Les instructions

Les instructions sont des opérations élémentaires qu'un processeur peut effectuer. Elles ont généralement la forme suivante :

name destination, source

Ainsi, le nom de l'instruction vient en premier, suivi d'au moins un espace, puis de l'opérande destination, d'une virgule (,) et enfin d'un opérande source. En général, une instruction ne modifie pas l'opérande source, seulement l'opérande destination.

Sauf indication contraire, les deux opérandes doivent avoir la même taille. Par exemple, si l'opérande source fait 16 bits, l'opérande destination doit aussi faire 16 bits.

Par exemple, pour stocker une valeur dans un registre, on peut utiliser l'instruction mov :

mov rax, rdx  ; rax = rdx
              ; mov is the name, rax is the destination operand and rdx is the source operand

L'extrait ci-dessus copie le contenu des 64 bits de rdx, l'opérande source, vers les 64 bits de rax, qui est l'opérande destination de l'instruction.

Dans la plupart des instructions, on peut utiliser des nombres entiers constants comme opérandes sources (mais pas comme opérandes destinations) :

mov rcx, 42 ; rcx = 42
            ; rcx is the destination operand and 42 is the source operand

À noter qu'écrire dans un registre 32 bits efface aussi les bits de poids fort, donc mov eax, 42 équivaut à mov rax, 42. Ce n'est pas le cas pour les registres 16 bits et 8 bits.

Pour les opérations arithmétiques d'addition, de soustraction et de multiplication, on peut utiliser les instructions add, sub et imul :

add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8

Les fonctions

Les instructions sont organisées en fonctions. Toutes les fonctions sont placées dans la section .text du fichier source.

Une déclaration de fonction se compose de :

  1. Une étiquette portant le nom de la fonction, suivie d'un :.
  2. Les instructions qui définissent la fonction.
  3. L'instruction de retour, ret.

Ce parcours utilise la convention d'appel System V AMD64 ABI, et les six premiers arguments entiers sont passés à une fonction dans des registres. Ils sont passés dans l'ordre suivant : rdi, rsi, rdx, rcx, r8 et r9.

Une valeur entière est renvoyée par la fonction dans le registre rax.

Pour appeler une fonction, on utilise l'instruction call. Une fonction peut être appelée n'importe où dans le même fichier source. Pour rendre une fonction visible depuis d'autres fichiers sources, on doit utiliser la directive global.

Par exemple, ceci déclare une fonction sum :

section .text ; functions are placed here

global sum ; sum is visible to other source files

sum:
    ; first argument is passed in rdi
    ; second argument is passed in rsi
    ; return value is placed in rax

    mov rax, rdi ; rax is now equal to rdi
    add rax, rsi ; rax = rax + rsi

    ret ; function return

Et ceci appelle notre fonction sum avec les arguments 3 et 5 :

mov rdi, 3  ; First argument in rdi
mov rsi, 5  ; Second argument in rsi
call sum

; The rax register now contains the value 8 (3 + 5), after sum returns

Instructions

Dans cet exercice, tu vas écrire du code pour t'aider à cuisiner une excellente lasagne tirée de ton livre de cuisine préféré.

Tu as quatre tâches à réaliser, toutes liées au temps passé à cuisiner la lasagne.

Note

Voici les instructions mentionnées dans ce concept :

Instruction Description
mov a, b copies the contents from b to a
add a, b a = a + b
sub a, b a = a - b
imul a, b a = a * b
call a calls function a
ret returns from a function

1. Définis le temps de cuisson attendu en minutes

Définis la fonction expected_minutes_in_oven qui ne prend aucun paramètre et qui renvoie le nombre de minutes pendant lesquelles la lasagne doit rester au four. D'après le livre de cuisine, le temps de cuisson attendu est de 40 minutes :

expected_minutes_in_oven();
// => 40

2. Calcule le temps de cuisson restant en minutes

Définis la fonction remaining_minutes_in_oven qui prend comme paramètre le nombre de minutes que la lasagne a déjà passées au four et qui renvoie combien de minutes la lasagne doit encore rester au four, d'après le temps de cuisson attendu de la tâche précédente.

remaining_minutes_in_oven(30);
// => 10

3. Calcule le temps de préparation en minutes

Définis la fonction preparation_time_in_minutes qui prend comme paramètre le nombre de couches que tu as ajoutées à la lasagne et qui renvoie le nombre de minutes que tu as passées à préparer la lasagne, en supposant que chaque couche te prend 2 minutes à préparer.

preparation_time_in_minutes(2);
// => 4

4. Calcule le temps écoulé en minutes

Définis la fonction elapsed_time_in_minutes qui prend deux paramètres, dans cet ordre :

  1. Le nombre de couches que tu as ajoutées à la lasagne.
  2. Le nombre de minutes que la lasagne a passées au four.

La fonction doit renvoyer le nombre de minutes que tu as passées à cuisiner la lasagne. C'est la somme du temps de préparation, en minutes, et du temps, en minutes, que la lasagne a déjà passé au four.

elapsed_time_in_minutes(3, 20);
// => 26
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
x86-64 Assembly Exercism

Prêt à commencer Lasagnes ?

Inscris-toi sur Exercism pour apprendre et maîtriser x86-64 Assembly avec 22 concepts130 exercices, et un vrai mentorat humain, le tout gratuitement.