Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Le langage assembleur
  3. Programmation
Extrait - Le langage assembleur Maîtrisez le code des processeurs de la famille X86
Extraits du livre
Le langage assembleur Maîtrisez le code des processeurs de la famille X86 Revenir à la page d'achat du livre

Programmation

Premier programme

Le principal défi de l’apprentissage d’un langage de programmation, c’est le premier programme. Ce programme est le lien entre la théorie et la pratique.

Il permet d’acquérir la compréhension essentielle à tout apprentissage et entrée en matière.

L’exemple suivant est simple, c’est le « hello world ».

1. Programme .COM

Ce programme s’exécute en mode réel, et doit être chargé à CS:100h, c’est-à-dire sous DOS ou compatible. Il fait intervenir les notions d’adressage, de copie de mémoire, et d’utilisation du drapeau d’égalité. La notion de mémoire vidéo n’est pas indispensable pour comprendre la mécanique du programme.

Hello world, premier programme :

colonne  equ 50  ;définition de la valeur colonne
ligne    equ 10  ;définition de la valeur ligne

org 100h         ; adresse de chargement

push word 0b800h ; empiler le segment vidéo mode texte
pop es           ; dépiler dans le registre de segment es
mov ah,04fh      ; attributs = texte blanc sur fond rouge
xor edi,edi      ; edi=0
@@:              ; étiquette de rebouclage
mov al,[.texte+edi]
cmp al,0         ; tester si caractère null=0
je .fin          ; fin si caractère null

mov [es:colonne+ligne*160+edi*2],ax ;écrire le caractère

inc edi         ; pointer le caractère suivant
jmp @b          ; reboucler
.fin:           ; étiquette de fin
ret             ; quitter le programme

.texte db 'bonjour',0 
Dans ce petit exemple, on utilise l’accès direct à la mémoire vidéo qui est localisée sur le bus d’adresses à 0b8000h linéaire.

Pour cela on empile l’adresse 0b8000h divisée par 16, puis on la dépile dans le registre de segment ES. Ainsi, ES pointe vers la mémoire vidéo en mode texte.

La valeur 4fh copiée dans AH sera utilisée comme attribut dans la mémoire vidéo en mode texte. La partie haute (4) est utilisée pour indiquer la couleur de fond (ici, 4= rouge), la partie basse (f) est utilisée pour indiquer la couleur des lettres (ici, 0fh = blanc).

L’instruction XOR est utilisée pour inverser l’état des bits de la destination en fonction des bits de la source. Lorsque l’on...

Construction autour des données

L’exécution d’un programme utilise presque tout le temps des données, aussi rares soient-elles.

S’agit-il d’une valeur immédiate chargée dans un registre, c’est une donnée.

Dans le premier exemple, Hello world, premier programme, il y a 4 types de données :

  • Constantes :

push word 0b800h 

Cette constante (0b800h) est une donnée figée, directement liée au matériel qu’est la mémoire vidéo en mode texte.

cmp al,0 

Ici, la donnée est 0, elle est comprise dans l’instruction et ne peut théoriquement pas être modifiée.

La constante 0 est souvent employée pour symboliser la constante NULL, cette constante peut prendre bien d’autres valeurs, mais la valeur 0 a l’avantage d’être simple. Cependant, lorsque 0 fait partie des valeurs admissibles, il y a lieu de trouver une autre constante pour symboliser NULL, comme par exemple -1.

colonne=50 

Cette ligne ne génère pas de donnée à proprement parler, elle déclare juste une constante virtuelle, qui sera utilisée par la suite dans les expressions numériques. C’est un symbole, remplacé par le nombre 50 lorsqu’il est utilisé.

  • Chaîne de caractères :

.texte   db 'bonjour',0 

Suite d’octets servant à encoder du texte, terminée par un octet à 0 pour signifier la fin de la chaîne.

Ceci est la donnée principale, la raison d’être du programme.

  • Attribut :

mov ah,4fh 

Octet utilisé comme paramètre de couleurs pour l’affichage.

C’est une donnée secondaire, induite par le besoin d’afficher du texte.

  • Pointeurs :

mov [es:colonne+ligne*160+edi*2],ax 

Utilisés pour accéder à la mémoire de l’écran et à la chaîne de caractères.

Cette donnée est aussi secondaire, et induite par la mécanique utilisée pour afficher à l’écran.

Lors de l’écriture d’un bout de code, il faut tout d’abord définir les données sur lesquelles vont agir les instructions en assembleur.

La définition des données peut être faite n’importe où dans le fichier source, en veillant à ne pas les mettre...

Formats de fichiers

Cet ouvrage n’étant pas un traité sur les formats de fichiers, nous n’en ferons qu’un survol, afin que vous soyez informé de leur existence et puissiez ainsi trouver la documentation associée.

Le format binaire de base est celui qui englobe tous les autres, c’est le format .BIN. N’importe quel fichier peut être considéré comme un fichier .BIN, car les fichiers ne sont que des suites d’octets.

L’application associée à ce type de fichier est l’éditeur hexadécimal.

1. Texte et ASCII

Les fichiers texte sont encodés en ASCII. Chaque caractère utilise un octet.

Les fichiers sources sont des fichiers texte en ASCII.

Le format Unicode utilise un encodage sur 16 bits.

Les 256 premiers caractères sont les mêmes qu’en ASCII, les autres caractères sont utilisés pour encoder les caractères spéciaux des autres langues, telles que le chinois, le japonais, l’arabe ou le cyrillique.

2. Images (pixels)

Une image est un fichier binaire, avec ou sans en-tête, dont le formatage le plus simple est la suite continue de pixels, précédée par les données de géométrie, largeur, hauteur, et peut être aussi de la valeur bpp.

Les pixels d’une image peuvent avoir différents formats et sont décrits par la donnée bpp (bits par pixels) :

  • 1 bpp pour le monochrome.

  • 2 bpp pour 4 couleurs issues d’une palette.

  • 4 bpp pour 16 couleurs issues d’une palette.

  • 8 bpp pour...

Les fonctions

1. Créer la fonction

Un programme exécute souvent les mêmes séquences d’instructions. Ce qui amène souvent à créer une fonction, qui permettra de simplifier l’écriture et la correction du code.

Mais une autre raison d’écrire des fonctions est principalement de permettre une meilleure lecture du code, voire une optimisation.

Par exemple :

        mov ah,al
        and al,0fh
;;;;;;;;;;;;;;;;;;;;;;;;;
        cmp al,10       ;
        cmc             ;
        sbb dl,dl       ;
        and dl,7        ;
        add al,dl       ;
        add al,'0'      ;
;;;;;;;;;;;;;;;;;;;;;;;;;
        xchg al,ah
        shr al,4
;;;;;;;;;;;;;;;;;;;;;;;;;
        cmp al,10       ;
        cmc             ;
        sbb dl,dl       ;
        and dl,7        ;
        add al,dl       ;
        add al,'0'      ;
;;;;;;;;;;;;;;;;;;;;;;;;;
        ret 

Cette suite d’instructions sera mise sous forme de fonction en ajoutant une étiquette et l’instruction ret, ce qui permet de l’exécuter en l’appelant avec l’instruction call :

programme_principal:
call bin_hexa

 

Dorénavant, nous appelons notre fonction, bin_hexa.

a. Factorisation du code

Dans ce petit programme, un bloc d’instructions est répété deux fois :

;;;;;;;;;;;;;;;;;;;;;;;;;
        cmp al,10       ;
        cmc             ;
        sbb dl,dl       ;
        and dl,7        ;
        add al,dl       ;
        add al,'0'      ;
;;;;;;;;;;;;;;;;;;;;;;;;; 

Une factorisation de ce code permet d’éviter de répéter deux fois le même code :

bin_hexa:
        mov ah,al
        and al,0fh
        call @f        ;exécuter une première fois
        xchg al,ah
        shr al,4
        call @f        ;exécuter une seconde fois
        ret
@@:
;;;;;;;;;;;;;;;;;;;;;;;;
        cmp al,10      ;
        cmc            ;
        sbb dl,dl      ;
        and dl,7       ;
        add al,dl      ;
        add al,'0'     ;
;;;;;;;;;;;;;;;;;;;;;;;;
        ret 

Une technique permet de gagner quelques octets, mais aussi quelques cycles d’exécution :

bin_hexa:
        mov ah,al
        and al,0fh
        call bloc
        xchg al,ah
        shr al,4
bloc:
        cmp al,10
        cmc
        sbb dl,dl
        and dl,7
        add al,dl
        add al,'0'
        ret 

Ici, le code appelle une première fois la sous-fonction bloc, et repasse par cette fonction, mais sans l’appeler.

Nous économisons un call et un ret.

b. Découpage du code

La création de fonctions peut aussi avoir pour but de simplifier le code en découpant ce dernier en blocs séparés.

Par exemple, un programme qui affiche une barre de progression et un bouton d’annulation, le tout dans une fenêtre, doit traiter chaque élément l’un après l’autre séparément....

Programmation d’applications

1. BOOT/BIOS

Lors du démarrage d’un PC, le BIOS est chargé de l’initialisation de la carte mère. Une fois cette initialisation effectuée, le BIOS va alors charger un petit programme d’amorçage sur un disque, une extension de ROM, ou depuis un réseau local en PXE. Cette dernière méthode nécessite la présence d’un serveur DHCP sur le réseau local, ce qui sort du cadre de ce chapitre.

Le secteur d’amorçage d’un disque est nommé secteur de boot, ou Master Boot Record (MBR).

Sur les disquettes, les disques durs et les émulations de ces deux types de volumes (clé USB, CDROM amorçable), le secteur de boot mesure pratiquement toujours 512 octets, et dans l’hypothèse où cela ne serait pas le cas, il existe un moyen de s’en assurer avec la commande DriveID de l’interruption BIOS de gestion de disques.

Pour faire simple, un secteur de boot contient 512 octets.

Le BIOS charge le secteur de boot à l’adresse linéaire 7C00h, en mode réel, ce qui est la seule garantie. L’adresse contenue dans le couple CS:IP [segment:offset] peut être sous n’importe quelle forme, par exemple, [0:7C00h], [7C0h:0], [700:C00h], ce qui oblige à recharger les registres CS:IP sur l’adresse voulue.

Ensuite, le secteur chargé en mémoire est exécuté, par un jmp CS:IP.

Dans l’immédiat, il est utile de sauvegarder le numéro de disque contenu dans le registre DL, puis de relocaliser le secteur de BOOT afin de permettre le lancement d’un autre secteur d’amorçage.

Les disques possèdent un MBR, mais peuvent aussi être partitionnés. Ce qui induit la présence d’autres secteurs de boot, un pour chaque partition.

Le secteur de boot actif sera indiqué dans la table de partitions.

Un gestionnaire de BOOT permettra de choisir de démarrer sur telle ou telle partition.

a. Le BOOT

Lorsque le PC démarre, le chipset connecte la ROM du BIOS aux accès mémoire du CPU.

Le CPU pointe vers l’adresse linéaire 0FFFF0h, qui contient la première instruction exécutée par le processeur.

Cette suite d’instructions est exécutée de sorte à copier le contenu de la ROM dans...

Optimisation du code (et des données)

1. Optimisation de code

Une règle de base de l’optimisation est de travailler au niveau de l’algorithme. C’est-à-dire qu’il faut de préférence chercher des moyens plus rapides au niveau abstrait du traitement.

Le niveau physique (low level) ne fait intervenir que les instructions du CPU, qui ne peuvent en aucun cas être accélérées.

Un exemple concret est la factorisation du code déjà évoquée précédemment.

Comme en mathématiques, la factorisation de code permet d’écrire un algorithme sous forme canonique, c’est-à-dire avec le moins d’opérations possible.

Par exemple :

y=x*x+x*4+5 

4 opérations, 2 multiplications et deux additions donnent sous forme canonique :

y=x*(x+4)+5 

La forme canonique est réduite à 3 opérations, 2 additions et une seule multiplication.

Le gain sur cette factorisation sera conséquent lors de traitements de gros paquets de données étant donné que la multiplication est l’une des opérations les plus lentes.

Lors de l’optimisation du code, il faut bien veiller à n’utiliser que les instructions les plus rapides.

imul eax,3 ;la plus lente manière de multiplier par 3 

Contre :

mov ebx,eax ;la manière intermédiaire
shl eax,2   ;fonctionne aussi en 16 bits
add eax,ebx 

Contre :

lea eax,[eax*2+eax] ; la manière la plus rapide, 32 bits  

Les boucles peuvent être déroulées, de façon à économiser un certain nombre d’instructions par itération.

Méthode avec boucle :

mov cx,4...

Conclusion

Avec toutes ces données en main, vous devriez être capable de vous y retrouver dans les méandres de la programmation assembleur sur PC.

Le principal obstacle à la programmation assembleur n’est pas la méthode, ou l’utilisation des instructions, les algorithmes étant réduits à leur forme la plus élémentaire, leur traduction en assembleur est très simple.

Le plus grand obstacle est le manque certain de points de repère. Ce livre a pour but de vous fournir une carte du monde x86 vous permettant de savoir vers où vous diriger.

Avec ce livre en main, vous saurez quels documents chercher pour quel problème.

Certains chapitres détaillent énormément les structures et d’autres non, ceci est dû au fait que seuls les points essentiels sont nécessaires, le reste étant tellement spécialisé qu’il devient rébarbatif de le développer.

Pour les parties vraiment techniques et complexes, la communauté assembleur sera toujours là pour vous répondre et vous aider. De plus, il existe quelques forums en français sur la programmation assembleur.