Vous êtes sur la page 1sur 204

Langage Assembleur PC

Paul A. Carter 20 mars 2005

Traduction par Sbastien Le Ray

Copyright c 2001, 2002, 2004 by Paul Carter

Ce document peut tre reproduit et distribu dans sa totalit (en incluant cette note de l'auteur, le copyright et l'indication de l'autorisation), sous rserve qu'aucun frais ne soit demand pour le document lui-mme, sans le consentement de l'auteur. Cela inclut une utilisation raisonnable d'extraits pour des revues ou de la publicit et les travaux drivs comme les traductions. Notez que ces restrictions ne sont pas destins empcher la participation aux frais pour l'impression ou la copie du document. Les enseignants sont encourags utiliser ce document comme support de cours ; cependant, l'auteur aprcierait d'tre averti dans ce cas.

Table des matires


Prface 1 Introduction
1.1 Systmes Numriques 1.1.1 1.1.2 1.1.3 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 Binaire . . . . . . . . . . . . . . . . . . . . . . Dcimal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Hexadecimal

v 1
1 1 1 3 4 4 5 6 7 8 9 9 10 11 11 11 12 13 13 14 17 18 19 19 23 24 24 24 25

Organisation de l'Ordinateur

Mmoire . . . . . . . . . . . . . . . . . . . . . . . . . . Le CPU (processeur) . . . . . . . . . . . . . . . . . . . La famille des processeurs 80x86 . . . . . . . . . . . . Regitres 16 bits du 8086 . . . . . . . . . . . . . . . . . Registres 32 bits du 80386 . . . . . . . . . . . . . . . . Mode Rel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mode Protg 16 bits Mode Protg 32 bits Interruptions

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Langage Assembleur

Langage Machine . . . . . . . . . . . . . . . . . . . . . Langage d'Assembleur . . . . . . . . . . . . . . . . . . Oprandes d'Instruction . . . . . . . . . . . . . . . . . Instructions de base . . . . . . . . . . . . . . . . . . . Directives . . . . . . . . . . . . . . . . . . . . . . . . . Entres et Sorties . . . . . . . . . . . . . . . . . . . . . Dbogage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Crer un Programme . . . . . . . . . . . . . . . . . . . . . . . Premier programme Dpendance vis vis du compilareur . . . . . . . . . . Assembler le code . . . . . . . . . . . . . . . . . . . . . Compiler le code C . . . . . . . . . . . . . . . . . . . . Lier les chiers objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Comprendre un listing assembleur i

ii

TABLE DES MATIRES

1.5

Fichier Squelette

. . . . . . . . . . . . . . . . . . . . . . . . .

26

2 Bases du Langage Assembleur


2.1 Travailler avec les Entiers 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2 2.2.1 2.2.2 2.2.3 2.3 2.3.1 2.3.2 2.3.3 2.4 Extension de signe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Reprsentation des entiers . . . . . . . . . . . . . . . . Arithmtique en complment deux Programme exemple Arithmtique en prcision tendue Comparaison

29
29 29 32 35 37 39 40 40 41 44 44 44 45 45 46

Structures de Contrle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Instructions de branchement . . . . . . . . . . . . . . . Les instructions de boucle . . . . . . . . . . . . . . . . Instructions if . . . . . . . . . . . . . . . . . . . . . . . Boucles while . . . . . . . . . . . . . . . . . . . . . . . Boucles do while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Traduire les Structures de Contrle Standards . . . . . . . . .

Exemple : Trouver des Nombres Premiers

3 Oprations sur les Bits


3.1 Oprations de Dcalage 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.2 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3 3.4 Dcalages logiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utilisation des dcalages . . . . . . . . . . . . . . . . . Dcalages arithmtiques . . . . . . . . . . . . . . . . . Dcalages circulaires . . . . . . . . . . . . . . . . . . . Application simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

49
49 49 50 50 51 51 52 52 53 53 53 54 54 55 58 58 59 60 61 63 63 64 66

Oprations Boolennes Niveau Bit

ET . L'opration OU . L'opration XOR L'opration NOT


L'opration L'instruction

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

TEST

Utilisation des oprations sur les bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Eviter les Branchements Conditionnels . . . . . . . . . . . . . Manipuler les bits en C 3.4.1 3.4.2 Les oprateurs niveau bit du C

Utiliser les oprateurs niveau bit en C

3.5 3.6

Reprsentations Big et Little Endian . . . . . . . . . . . . . . 3.5.1 3.6.1 3.6.2 3.6.3 Quand se Soucier du Caractre Big ou Little Endian Mthode une Compter les Bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Mthode deux . . . . . . . . . . . . . . . . . . . . . . . Mthode trois . . . . . . . . . . . . . . . . . . . . . . .

TABLE DES MATIRES

iii

4 Sous-Programmes
4.1 4.2 4.3 4.4 4.5 Adressage Indirect . . . . . . . . . . . . . . . . . . . . . . . . Exemple de Sous-Programme Simple . . . . . . . . . . . . . . La pile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les Instructions CALL et RET 4.5.1 4.5.2 4.6 4.7 . . . . . . . . . . . . . . . . . Conventions d'Appel . . . . . . . . . . . . . . . . . . . . . . . Passer les paramtres via la pile . . . . . . . . . . . . . Variables locales sur la pile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

69
69 70 72 73 74 74 80 82 85 86 86 87 87 88 88 89 93 93 94 96

Programme Multi-Modules 4.7.1 4.7.2 4.7.3 4.7.4 4.7.5 4.7.6 4.7.7 4.7.8

Interfacer de l'assembleur avec du C

Sauvegarder les registres . . . . . . . . . . . . . . . . . Etiquettes de fonctions . . . . . . . . . . . . . . . . . . Passer des paramtres Retourner des valeurs Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Calculer les adresses des variables locales Autres conventions d'appel

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Appeler des fonctions C depuis l'assembleur . . . . . . Sous-programmes rcursifs . . . . . . . . . . . . . . . . Rvision des types de stockage des variables en C . . .

4.8

Sous-Programmes Rentrants et Rcursifs 4.8.1 4.8.2

5 Tableaux
5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.2 5.2.1 5.2.2 5.2.3 5.2.4 5.2.5 Dnir des tableaux . . . . . . . . . . . . . . . . . . . Accder aux lments de tableaux Adressage indirect plus avanc Tableaux Multidimensionnels Lire et crire en mmoire Le prxe d'instruction

99
99 99 . . . . . . . . . . . 100

. . . . . . . . . . . . . 102 . . . . . . . . . . . . . . 107

Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . 103

Instructions de Tableaux/Chanes . . . . . . . . . . . . . . . . 110 . . . . . . . . . . . . . . . . 111 . . . . . . . . . . . . . . . 113 . . . . . . . . . . . . . 114

REP

Instructions de comparaison de chanes . . . . . . . . . 114 Les prxes d'instruction

REPx

Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . 115

6 Virgule Flottante
6.1 Reprsentation en Virgule Flottante 6.1.1 6.1.2 6.2 6.2.1 6.2.2 Nombres binaires non entiers

121
. . . . . . . . . . . . . . 121 . . . . . . . . . . . . . . 121 . . . . . . . . . . . . . . . 126

Reprsentation en virgule ottante IEEE . . . . . . . . 123 Addition . . . . . . . . . . . . . . . . . . . . . . . . . . 127 Soustraction . . . . . . . . . . . . . . . . . . . . . . . . 127

Arithmtique en Virgule Flottante

iv

TABLE DES MATIRES

6.2.3 6.2.4 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6

Multiplication et division

. . . . . . . . . . . . . . . . 128 . . . . . . . . . . 128 . . . . . . . . . . . . . . . . . 129

Consquences sur la programmation

Le Coprocesseur Arithmtique

Matriel . . . . . . . . . . . . . . . . . . . . . . . . . . 129 Instructions . . . . . . . . . . . . . . . . . . . . . . . . 130 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . 136 . . . . . . . . . . . . 139 Formule quadratique . . . . . . . . . . . . . . . . . . . 136 Lire un tableau depuis un chier Renchercher les nombres premiers . . . . . . . . . . . . 141

7 Structures et C++
7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.2 7.2.1 7.2.2 7.2.3 7.2.4 7.2.5 7.2.6 Alignement en mmoire

149
. . . . . . . . . . . . . . . . . 150

Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 Introduction . . . . . . . . . . . . . . . . . . . . . . . . 149 Champs de Bits . . . . . . . . . . . . . . . . . . . . . . 153 Utiliser des structures en assembleur . . . . . . . . . . 156 . . . . . . . . . . . . . . . . . . . . . . . 157 Surcharge et Dcoration de Noms . . . . . . . . . . . . 157 Rfrences . . . . . . . . . . . . . . . . . . . . . . . . . 161 Fonctions inline . . . . . . . . . . . . . . . . . . . . . . 161 Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 Hritage et Polymorphisme . . . . . . . . . . . . . . . 172 Autres fonctionnalits C++ . . . . . . . . . . . . . . . 179

Assembleur et C++

A Instructions 80x86
A.1 A.2

181 190

Instructions hors Virgule Flottante . . . . . . . . . . . . . . . 181 Instruction en Virgule Flottante . . . . . . . . . . . . . . . . . 188

Index

Prface
Objectif
L'objectif de ce livre est de permettre au lecteur de mieux comprendre comment les ordinateurs fonctionnent rellement un niveau plus bas que les langages de programmation comme Pascal. En ayant une comprhension plus profonde de la faon dont fonctionnent les ordinateurs, le lecteur peu devenir plus productif dans le dveloppement de logiciel dans des langages de plus haut niveau comme le C et le C++. Apprendre programmer en assembleur est un excellent moyen d'atteindre ce but. Les autres livres d'assembleur pour PC apprennent toujours programmer le processeur 8086 qu'utilisait le PC originel de 1981 ! Le processeur 8086 ne supportait que le mode

rel.

Dans ce mode, tout programme peu adresser n'importe quel endroit de la mmoire ou n'importe quel priphrique de l'ordinateur. Ce mode n'est pas utilisable pour un systme d'exploitation scuris et multitche. Ce livre parle plutt de la faon de programmer les processeurs 80386 et plus rcents en mode

protg

(le mode dans lequel fonctionnent Windows et Linux). Ce

mode supporte les fonctionnalits que les systmes d'exploitation modernes orent, comme la mmoire virtuelle et la protection mmoire. Il y a plusieurs raisons d'utiliser le mode protg : 1. Il est plus facile de programmer en mode protg qu'en mode rel 8086 que les autres livres utilisent. 2. Tous les systmes d'exploitation PC modernes fonctionnent en mode protg. 3. Il y a des logiciels libres disponibles qui fonctionnent dans ce mode. Le manque de livres sur la programmation en assembleur PC en mode protg est la principale raison qui a conduit l'auteur crire ce livre. Comme nous y avons fait allusion ci-dessus, ce dexte utilise des logiciels Libres/Open Source : l'assembleur NASM et le compilateur C/C++ DJGPP. Les deux sont disponibles en tlchargement sur Internet. Ce texte parle galement de l'utilisation de code assembleur NASM sous Linux et avec les compilateurs C/C++ de Borland et Microsoft sous Windows. Les v

vi

PRFACE

exemples pour toutes ces plateformes sont disponibles sur mon site Web :

http://www.drpaulcarter.com/pcasm. Vous devez

tlcharger le code exemple

si vous voulez assembler et excuter la plupart des exemples de ce tutorial. Soyez conscient que ce texte ne tente pas de couvrir tous les aspects de la programmation assembleur. L'auteur a essay de couvrir les sujets les plus importants avec lesquels

tous

les programmeurs devraient tre familiers.

Remerciements
L'auteur voutrait remercier les nombreux programmeurs qui ont contribu au mouvement Libre/Open Source. Tous les programmes et mme ce livre lui-mme ont t crs avec des logiciels gratuits. L'auteur voudrait remercier en particulier John S. Fine, Simon Tatham, Julian Hall et les autres dveloppeurs de l'assembleur NASM sur lequel tous les exemples de ce livre sont bass ; DJ Delorie pour le dveloppement du compilateur C/C++ DJGPP utilis ; les nombreuses personnes qui ont contribu au compilateur GNU gcc sur lequel DJGPP est bas ; Donald Knuth et les autres pour avoir

A X 2 qui ont t utiliss pour dvelopp les langages composition T EX et L T E


produire le livre ; Richard Stallman (fondateur de la Free Software Foundation), Linus Torvalds (crateur du noyau Linux) et les autres qui ont cr les logiciels sous-jacents utiliss pour ce travail. Merci aux personnes suivantes pour leurs corrections :  John S. Fine  Marcelo Henrique Pinto de Almeida  Sam Hopkins  Nick D'Imperio  Jeremiah Lawrence  Ed Beroset  Jerry Gembarowski  Ziqiang Peng  Eno Compton  Josh I Cates  Mik Miin  Luke Wallis  Gaku Ueda  Brian Heward  Chad Gorshing  F. Gotti  Bob Wilkinson  Markus Koegel  Louis Taber

vii

Ressources sur Internet

Page de l'auteur Page NASM sur SourceForge DJGPP Assembleur Linux The Art of Assembly USENET Documentation Intel

http://www.drpaulcarter.com/ http://sourceforge.net/projects/nasm/ http://www.delorie.com/djgpp http://www.linuxassembly.org/ http://webster.cs.ucr.edu/ comp.lang.asm.x86 http://developer.intel.com/design/Pentium4/documentation.htm

Ractions
L'auteur accepte toute raction sur ce travailThe author welcomes any feedback on this work.

E-mail: pacman128@gmail.com WWW: http://www.drpaulcarter.com/pcasm

viii

PRFACE

Chapitre 1

Introduction
1.1 Systmes Numriques
La mmoire d'un ordinateur est constitue de nombres. Cette mmoire ne stocke pas ces nombres en dcimal (base 10). Comme cela simplie grandement le matriel, les ordinateurs stockent toutes les informations au format binaire (base 2). Tout d'abord, revoyons ensemble le systme dcimal.

1.1.1 Dcimal
Les nombres en base 10 sont constitus de 10 chires possibles (0-9). Chaque chire d'un nombre est associ une puissance de 10 selon sa position dans le nombre. Par exemple :

234 = 2 102 + 3 101 + 4 100

1.1.2 Binaire
Les nombres en base 2 sont composs de deux chires possibles (0 et 1). Chaque chire est associ une puissance de 2 selon sa position dans le nombre (un chire binaire isol est appel bit). Par exemple :

110012 = 1 24 + 1 23 + 0 22 + 0 21 + 1 20 = 16 + 8 + 1 = 25
Cet exemple montre comment passer du binaire au dcimal. Le tableau 1.1 montre la reprsentation binaire des premiers nombres.

La gure 1.1 montre comment des chires binaires individuels ( bits) sont additionns. Voici un exemple : 1

i.e., des

CHAPITRE 1.

INTRODUCTION

Decimal 0 1 2 3 4 5 6 7

Binary 0000 0001 0010 0011 0100 0101 0110 0111

Decimal 8 9 10 11 12 13 14 15

Binary 1000 1001 1010 1011 1100 1101 1110 1111

Tab. 1.1  Dcimaux 0 15 en Binaire

No previous carry 0 +0 0 0 +1 1 1 +0 1 1 +1 0 c 0 +0 1

Previous carry 0 +1 0 c 1 +0 0 c 1 +1 1 c

Fig. 1.1  Addition binaire (c signie

carry, retenue)

110112 +100012 1011002


Si l'on considre la division dcimale suivante :

1234 10 = 123 r 4
on peut voir que cette division spare le chire le plus droite du nombre et dcale les autres chires d'une position vers la droite. Diviser par deux eectue une opration similaire, mais pour les chires binaires du nombre. Considrons la division binaire suivante

1 :

11012 102 = 1102 r 1


Cette proprit peut tre utilise pour convertir un nombre dcimal en son quivalent binaire comme le montre la Figure 1.2. Cette mthode trouve le chire binaire le plus droite en premier, ce chire est appele le

signicatif (lsb, least signicant bit). Le chire le plus gauche est appel le bit le plus signicatif (msb, most signicant bit). L'unit de base de la mmoire consiste en un jeu de 8 bits appel octet (byte).
1 L'indice 2 est utilis pour indiquer que le nombre est reprsent en binaire, pas en dcimal

bit le moins

1.1.

SYSTMES NUMRIQUES

Decimal 12 2 = 6 r 0 62=3r 0 32=1r 1 12=0r 1

Binary 1100 10 = 110 r 0 110 10 = 11 r 0 11 10 = 1 r 1 1 10 = 0 r 1

25 2 = 12 r 1 11001 10 = 1100 r 1

Donc

2510 = 110012

Fig. 1.2  Conversion dcimale

589 16 = 36 r 13 36 16 = 2 r 4 2 16 = 0 r 2

Donc

589 = 24D16

Fig. 1.3 

1.1.3 Hexadecimal
Les nombres hexadcimaux utilisent la base 16. L'hexadcimal (ou

hexa

en abrg) peut tre utilis comme notation pour les nombres binaires. L'hexa a 16 chires possibles. Cela pose un problme car il n'y a pas de symbole utiliser pour les chires supplmentaires aprs 9. Par convention, on utilise des lettres pour les reprsenter. Les 16 chires de l'hexa sont 0-9 puis A, B, C, D, E et F. Le chire A quivaut 10 en dcimal, B 11, etc. Chaque chire d'un nombre en hexa est associ une puissance de 16. Par exemple :

2BD16 = 2 162 + 11 161 + 13 160 = 512 + 176 + 13 = 701

Pour convertir depuis le dcimal vers l'hexa, utilisez la mme ide que celle utilise pour la conversion binaire en divisant par 16. Voyez la Figure 1.3 pour un exemple.

CHAPITRE 1.

INTRODUCTION

La raison pour laquelle l'hexa est utile est que la conversion entre l'hexa et le binaire est trs simple. Les nombres binaires deviennent grands et incomprhensibles rapidement. L'hexa fournit une faon beaucoup plus compacte pour reprsenter le binaire. Pour convertir un nombre hexa en binaire, convertissez simplement chaque chire hexa en un nombre binaire de 4 bits. Par exemple, en

24D16

est converti

0010 0100 11012 .

Notez que les 0 de tte des 4 bits sont importants ! Si

le 0 de tte pour le chire du milieu de

24D16

n'est pas utilis, le rsultat

est faux. Le conversion du binaire vers l'hexa est aussi simple. On eectue la mme chose, dans l'autre sens. Convertissez chaque segment de 4 bits du binaire vers l'hexa. Commencez droite, pas gauche, du nombre binaire. Cela permet de s'assurer que le procd utilise les segments de 4 bits corrects . Exemple :

110 6

0000 0

0101 5

1010
A

0111 7

11102 E16

Un nombre de 4 bits est appel

quadruplet (nibble). Donc, chaque chire

hexa correspond un quadruplet. Deux quadruplets forment un octet et donc un octet peut tre reprsent par un nombre hexa deux chires. La valeur d'un bit va de 0 11111111 en binaire, 0 FF en hexa et 0 255 en dcimal.

1.2

Organisation de l'Ordinateur

1.2.1 Mmoire
La mmoire est mesure en L'unit mmoire de base est l'octet. Un ordinateur avec 32 mega octets 10 kilo octets ( 2 = 1024 oc- de mmoire peut stocker jusqu' environ 32 millions d'octets d'informations. 20 tets), mega octets (2 = Chaque octet en mmoire est tiquet par un nombre unique appel son ococ-

1048576 octets) et giga 30 tets (2 = 1073741824


tets).

adresse comme le montre la Figure 1.4. Adresse Mmoire 0 2A 1 45 2 B8 3 20 4 8F 5 CD 6 12 7 2E

Fig. 1.4 

Adresses Mmoire

Souvent, la mmoire est utilise par bouts plus grand que des octets isoles. Sur l'architecture PC, on a donn des noms ces portions de mmoire plus grandes, comme le montre le Tableau 1.2.

2 S'il n'est pas clair que le point de dpart est important, essayez de convertir l'exemple en partant de la gauche

1.2.

ORGANISATION DE L'ORDINATEUR

mot double mot quadruple mot paragraphe


Tab. 1.2 

2 octets 4 octets 8 octets 16 octets

Units Mmoire

Toutes les donnes en mmoire sont numriques. Les caractres sont stocks en utilisant un

code caractre

qui fait correspondre des nombres aux

caractres. Un des codes caractre les plus connus est appel

ASCII

(Ameri-

can Standard Code for Information Interchange, Code Amricain Standard pour l'Echange d'Informations). Un nouveau code, plus complet, qui supplante l'ASCII est l'Unicode. Une des dirences cls entre les deux codes est que l'ASCII utilise un octet pour encoder un caractre alors que l'Unicode en utilise deux (ou un

mot ). Par exemple, l'ASCII fait correspondre l'octet 4116 (6510 ) au caractre majuscule A ; l'Unicode y fait correspondre le mot
004116 .
Comme l'ASCII n'utilise qu'un octet, il est limit 256 caractres dirrents au maximum . L'Unicode tend les valeurs ASCII des mots et permet de reprsenter beaucoup plus de caractres. C'est important an de reprsenter les caractres de tous les langages du monde.

1.2.2 Le CPU (processeur)


Le processeur (CPU, Central Processing Unit) est le dispositif physique qui excute les instructions. Les instructions que les processeurs peuvent excuter sont gnralement trs simples. Elles peuvent ncessiter que les donnes sur lesquelles elles agissent soient prsentes dans des emplacements de stockage spciques dans le processeur lui-mme appels

registres. Le processeur

peut accder aux donnes dans les registres plus rapidement qu'aux donnes en mmoire. Cependant, le nombre de registres d'un processeur est limit, donc le programmeur doit faire attention n'y conserver que les donnes actuellement utilises. le Les instructions que peut excuter un type de processeur constituent Les programmes machine ont une structure beaucoup

langage machine.

plus basique que les langages de plus haut niveau. Les instructions du langage machine sont encodes en nombres bruts, pas en format texte lisible. Un processeur doit tre capable de dcoder les instructions trs rapidement pour fonctionner ecacement. Le langage machine est conu avec ce but en tte, pas pour tre facilement dchirable par les humains. Les programmes crits dans d'autres langages doivent tre convertis dans le langage machine

3 En fait, l'ASCII n'utilise que les 7 bits les plus faible et donc n'a que 128 valeurs utiliser.

CHAPITRE 1.

INTRODUCTION

natif du processeur pour s'excuter sur l'ordinateur. Un

compilateur

est un

programme qui traduit les programmes crits dans un langage de programmation en langage machine d'une architecture d'ordinateur particulire. En gnral, chaque type de processeur a son propre langage machine unique. C'est une des raisons pour lesquelles un programme crit pour Mac ne peut pas tre excut sur un PC. Les ordinateurs utilisent une

GHz

signie gigahertz ou instructions. L'horloge tourne une frquence xe (appele

un milliards de cycles par

seconde. Un processeur de cette horloge. L'horloge ne dcompte pas les minutes et les secondes. Elle 1,5GHz a 1,5 milliards bat simplement un rythme constant. Les composants lectroniques du prod'impulsions horloge par cesseur utilisent les pulsations pour eectuer leurs oprations correctement, seconde. comme le battement d'un mtronome aide jouer de la musique un rythme

vitesse d'horloge ). Lorsque vous achetez un ordinateur 1,5GHz, 1,5GHz est la frquence

horloge

pour synchroniser l'excution des

correct. Le nombre de battements (ou, comme on les appelle couramment du processeur. Le nombre de cycles dpend de l'instruction.

cycles ) que recquiert une instruction dpend du modle et de la gnration

1.2.3 La famille des processeurs 80x86


Les PC contiennent un processeur de la famille des Intel 80x86 (ou un clone). Les processeurs de cette famille ont tous des fonctionnalits en commun, y compris un langage machine de base. Cependant, les membre les plus rcents amliorent grandement les fonctionnalits.

8088,8086:

Ces processeurs, du point de vue de la programmation sont

identiques. Ils taient les processeurs utiliss dans les tous premiers PC. Il orent plusieurs registres 16 bits : AX, BX, CX, DX, SI, DI, BP, SP, CS, DS, SS, ES, IP, FLAGS. Ils ne supportent que jusqu' 1Mo de mmoire et n'oprent qu'en

mode rel. Dans ce mode, un pro-

gramme peut accder n'importe quelle adresse mmoire, mme la mmoire des autres programmes ! Cela rend le dbogage et la scurit trs diciles ! De plus, la mmoire du programme doit tre divise en

segments. Chaque segment ne peut pas dpasser les 64Ko. 80286: Ce processeur tait utilis dans les PC de type AT.
Cependant, sa principale nouvelle fonctionnalit est le

Il apporte

quelques nouvelles instructions au langage machine de base des 8088/86.

bits.

mode protg 16

Dans ce mode, il peut accder jusqu' 16Mo de mmoire et em-

pcher les programmes d'accder la mmoire des uns et des autres. Cependant, les programmes sont toujours diviss en segments qui ne peuvent pas dpasser les 64Ko.

80386:

Ce processeur a grandement amlior le 80286. Tout d'abord, il

tend la plupart des registres 32 bits (EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EIP) et ajoute deux nouveaux registres 16 bits : FS

1.2.

ORGANISATION DE L'ORDINATEUR

AX AH AL

Fig. 1.5  Le registre AX

et GS. Il ajoute galement un nouveau

mode protg 32 bits. Dans ce

mode, il peut accder jusqu' 4Go de mmoire. Les programmes sont encore diviss en segments mais maintenant, chaque segment peut galement faire jusqu' 4Go !

80486/Pentium/Pentium Pro:
cution des instructions.

Ces membres de la famille 80x86 apportent

trs peu de nouvelles fonctionnalits. Il acclrent principalement l'ex-

Pentium MMX: Pentium II:

Ce processeur ajoute les instructions MMX (MultiMedia

eXentions) au Pentium. Ces instructions peuvent acclrer des oprations graphiques courantes. C'est un processeur Pentium Pro avec les instructions MMX

(Le Pentium III est grossirement un Pentium II plus rapide).

1.2.4 Regitres 16 bits du 8086


Le processeur 8086 original fournissait quatre registres gnraux de 16 bits : AX, BX, CX et DX. Chacun de ces registres peut tre dcompos en deux registres de 8 bits. Par exemple, le registre AX pouvait tre dcompos en AH et AL comme le montre la Figure 1.5. Le registre AH contient les 8 bits de poids fort de AX et AL contient les 8 bits de poids faivble. Souvent, AH et AL sont utiliss comme des registres d'un octet indpendants ; cependant, il est important de raliser qu'ils ne sont pas indpendants de AX. Changer la valeur de AX changera les valeurs de AL et BL et et instructions arithmtiques. Il y a deux registres d'index de 16 bits : SI et DI. Ils sont souvent utiliss comme des pointeurs, mais peuvent tre utiliss pour la plupart des mmes choses que les registres gnraux. Cependant, ils ne peuvent pas tre dcomposs en registres de 8 bits. Les registres 16 bits BP et SP sont utiliss pour pointer sur des donnes dans la pile du langage machine et sont appels le pointeur de base et le ponteur de pile, respectivement. Nous en reparlerons plus tard. Les registres 16 bits CS, DS, SS et ES sont des

vice versa . Les

registres gnraux sont utiliss dans beaucoup de dplacements de donnes

registres de segment. Ils

indiquent quelle zone de la mmoire est utilise pour les direntes parties d'un programme. CS signie Code Segment, DS Data Segment, SS Stack Segment (segment de pile) et ES Extra Segment. ES est utilis en tant que

CHAPITRE 1.

INTRODUCTION

registre de segment temporaire. Des dtails sur ces registres se trouvent dans les Sections 1.2.6 et 1.2.7. Le registre de pointeur d'instruction (IP) est utilis avec le registre CS pour mmoriser l'adresse de la prochaine instruction excuter par le processeur. Normalement, lorsqu'une instruction est excute, IP est incrment pour pointer vers la prochaine instruction en mmoire. Le registre FLAGS stocke des informations importantes sur les rsultats d'une instruction prcdente. Ces rsultats sont stocks comme des bits individuels dans le registre. Par exemple, le bit Z est positionn 1 si le rsultat de l'instruction prcdente tait 0 ou 0 sinon. Toutes les instructions ne modient pas les bits dans FLAGS, consultez le tableau dans l'appendice pour voir comment chaque instruction aecte le registre FLAGS.

1.2.5 Registres 32 bits du 80386


Les processeurs 80386 et plus rcents ont des registres tendus. Par exemple le registre AX 16 bits est tendu 32 bits. Pour la compatibilit ascendante, AX fait toujours rfrence au registre 16 bits et on utilise EAX pour faire rfrence au registre 32 bits. AX reprsente les 16 bits de poids faible de EAX tout comme AL reprsente les 8 bits de poids faible de AX (et de EAX). Il n'y a pas de moyen d'accder aux 16 bits de poids fort de EAX directement. Les autres registres tendus sont EBX, ECX, EDX, ESI et EDI. La plupart des autres registres sont galement tendus. BP devient EBP ; SP devient ESP ; FLAGS devient EFLAGS et IP devient EIP. Cependant, contrairement aux registres gnraux et d'index, en mode protg 32 bits (dont nous parlons plus loin) seules les versions tendues de ces registres sont utilises. Les registres de segment sont toujours sur 16 bits dans le 80386. Il y a galement deux registres de segment supplmentaires : FS et GS. Leurs noms n'ont pas de signication particulire. Ce sont des segments temporaires supplmentaires (comme ES). Une des dnitions du terme

mot

se rfre la taille des registres de

donnes du processeur. Pour la famille du 80x86, le terme est dsormais un peu confus. Dans le Tableau 1.2, on voit que le terme

mot

est dni comme

faisant 2 octets (ou 16 bits). Cette signication lui a t attribue lorsque le premier 8086 est apparu. Lorsque le 80386 a t dvelopp, il a t dcid de laisser la dnition de chang.

mot

inchange, mme si la taille des registres avait

1.2.

ORGANISATION DE L'ORDINATEUR

1.2.6 Mode Rel


En mode rel, la mmoire est limite seulement un mga octet (2

20 Alors d'o vient l'infme

des 640Ko du octet). Les adresses valides vont de 00000 FFFFF (en hexa). Ces adresses limite DOS ? Le BIOS requiert ncessitent un nombre sur 20 bits. Cependant, un nombre de 20 bits ne

tiendrait dans aucun des registres 16 bits du 8086. Intel a rsolu le problme, en utilisant deux valeurs de 16 bits pour dterminer une adresse. La premire valeur de 16 bits est appele le

une partie des 1Mo pour

son propre code et pour les priphriques matriels comme l'cran.

slecteur.

Les valeurs du slecteur doivent

appele le dplacement (oset). L'adresse physique identie par un couple slecteur :dplacement 32 bits est calcule par la formule

tre stockes dans des registres de segment. La seconde valeur de 16 bits est

16 s electeur + d eplacement
Multiplier par 16 en hexa est facile, il sut d'ajouter un 0 la droite du nombre. Par exemple, l'adresse physique rfrence par 047C:0048 est obtenue de la faon suivante : 047C0 +0048 04808 De fait, la valeur du slecter est un numro de paragraphe (voir Tableau 1.2). Les adresses relles segmentes ont des inconvnients :  Une seule valeur de slecteur peut seulement rfrencer 64Ko de mmoire (la limite suprieure d'un dplacement de 16 bits). Que se passet-il si un programme a plus de 64Ko de code ? Une seule valeur de CS ne peut pas tre utilise pour toute l'excution du programme. Le programme doit tre divis en sections (appeles

segments )

de moins de

64Ko. Lorsque l'excution passe d'un segment l'autre, la valeur de CS doit tre change. Des problmes similaires surviennent avec de grandes quantits de donnes et le registre DS. Cela peut tre trs gnant !  Chaque octet en mmoire n'a pas une adresse segmente unique. L'adresse physique 04808 peut tre rfrence par 047C:0048, 047D:0038, 047E:0028 ou 047B:0058. Cela complique la comparaison d'adresses segmentes.

1.2.7 Mode Protg 16 bits


Dans le mode protg 16 bits du 80286, les valeurs du slecteur sont interprtes de faon totalement dirente par rapport au mode rel. En mode rel, la valeur d'un slecteur est un numro de paragraphe en mmoire. En mode protg, un slecteur est un

indice dans un tableau de descripteurs.

Dans les deux modes, les programmes sont diviss en segments. En mode rel, ces segments sont des positions xes en mmoire et le slecteur indique le

10

CHAPITRE 1.

INTRODUCTION

numro de paragraphe auquel commence le segment. En mode protg, les segments ne sont pas des positions xes en mmoire physique. De fait, il n'ont mme pas besoin d'tre en mmoire du tout ! Le mode protg utilise une technique appele

mmoire virtuelle . L'ide

de base d'un systme de mmoire virtuelle est de ne garder en mmoire que les programmes et les donnes actuellement utiliss. Le reste des donnes et du code sont stocks temporairement sur le disque jusqu' ce qu'on aie nouveau besoin d'eux. Dans le mode protg 16 bits, les segments sont dplacs entre la mmoire et le disque selon les besoins. Lorsqu'un segment est recharg en mmoire depuis le disque, il est trs probable qu'il sera un endroit en mmoire dirent de celui o il tait avant d'tre plac sur le disque. Tout ceci est eectu de faon transparente par le systme d'exploitation. Le programme n'a pas a tre crit diremment pour que la mmoire virtuelle fonctionne. En mode protg, chaque segment est assign une entre dans un tableau de descripteurs. Cette entre contient toutes les informations dont le systme a besoin propos du segment. Ces informations indiquent : s'il est actuellement en mmoire ; s'il est en mmoire, o il se trouve ; les droits d'accs ( slecteur stocke dans les registres de segment.
Un journaliste PC bien

p.e., lecture seule). L'indice de l'entre du segment est la valeur du

Un gros inconvnient du mode protg 16 bits est que les dplacements

connu a baptis le proces- sont toujours des quantits sur 16 bits. En consquence, Les tailles de segseur 286 cerveau mort ment sont toujours limites au plus 64Ko. Cela rend l'utilisation de grands (brain dead) tableaux problmatique.

1.2.8 Mode Protg 32 bits


Le 80386 a introduit le mode protg 32 bits. Il y a deux dirences majeures entre les modes protgs 32 bits du 386 et 16 bits du 286 : 1. Les dplacements sont tendus 32 bits. Cela permet un dplacement d'aller jusqu' 4 milliards. Ainsi, les segments peuvent avoir des tailles jusqu' 4Go. 2. Les segments peuvent tre diviss en units plus petites de 4Ko appeles

pages .

Le systme de mmoire virtuelle fonctionne maintenant

avec des pages plutt qu'avec des segments. Cela implique que seules certaines parties d'un segment peuvent tre prsentes en mmoire un instant donn. En mode 16 bits du 286, soit le segment en entier est en mmoire, soit rien n'y est. Ce qui n'aurait pas t pratique avec les segments plus grands que permet le mode 32 bits. Dans Windows 3.x, le bits du 286 et le

mode standard fait rfrence au mode protg 16 mode amlior (enhanced) fait rfrence au mode 32 bits.

1.3.

LANGAGE ASSEMBLEUR

11

Windows 9X, Windows NT/2000/XP, OS/2 et Linux fonctionnent tous en mode protg 32 bits pagin.

1.2.9 Interruptions
Quelques fois, le ot ordinaire d'un programme doit tre interrompu pour traiter des vnements qui requirent une rponse rapide. Le matriel d'un ordinateur ore un mcanisme appel

interruptions

pour grer ces vne-

ments. Par exemple, lorsqu'une souris est dplace, la souris interrompt le programme en cours pour grer le dplacement de la souris (pour dplacer le curseur

etc.). Les interruptions provoquent le passage du contrle un gestionnaire d'interruptions. Les gestionnaires d'interruptions sont des routines
qui traitent une interruption. Chaque type d'interruption est assigne un

teurs d'interuptions
dans ce tableau.

nombre entier. Au dbut de la mmoire physique, rside un tableau de

vec-

qui contient les adresses segmentes des gestionnaires

d'interruption. Le numro d'une interruption est essentiellement un indice

Les interruptions externes proviennent de l'extrieur du processeur (la souris est un exemple de ce type). Beaucoup de priphriques d'E/S soulvent des interruptions (

p.e.,

le clavier, le timer, les lecteurs de disque, le

CD-ROM et les cartes son). Les interruptions internes sont souleves depuis le processeur, cause d'une erreur ou d'une instruction d'interruption. Les interruptions erreur sont galement appeles

interruptions logicielles. Le DOS utilise ce type d'interruption pour implmenter son


gnres par l'instruction d'interruption sont galement appeles API(Application Programming Interface). Les systmes d'exploitation plus rcents (comme Windows et Unix) utilisent une interface base sur C . Beaucoup de gestionnaires d'interruptions redonnent le contrle au pro-

traps.

Les interruptions

gramme interrompu lorsqu'ils se terminent. Ils restaurent tous les registres aux valeurs qu'ils avaient avant l'interruption. Ainsi, le programme interrompu s'excute comme si rien n'tait arriv (except qu'il perd quelques cycles processeur). Les traps ne reviennent gnralement jamais. Souvent, elles arrtent le programme.

1.3

Langage Assembleur

1.3.1 Langage Machine


Chaque type de processeur comprend son propre langage machine. Les instructions dans le langage machine sont des nombres stocks sous forme

4 Cependant, ils peuvent galement utiliser une interface de plus bas niveau au niveau du noyau

12

CHAPITRE 1.

INTRODUCTION

d'octets en mmoire. Chaque instruction a son propre code numrique unique appel tions des processeurs 80x86 varient en taille. L'opcode est toujours au dbut

code d'opration ou opcode (operation code) en raccourci. Les instruc-

de l'instruction. Beaucoup d'instructions comprennent galement les donnes (

p.e. des constantes ou des adresses) utilises par l'instruction.

Le langage machine est trs dicile programmer directement. Dchiffrer la signication d'instructions codes numriquement est fatigant pour des humains. Par exemple, l'instruction qui dit d'ajouter les registres EAX et EBX et de stocker le rsultat dans EAX est encode par les codes hexadcimaux suivants : 03 C3 C'est trs peu clair. Heureusement, un programme appel un peut faire ce travail laborieux la place du programmeur.

assembleur

1.3.2 Langage d'Assembleur


Un programme en langage d'assembleur est stock sous forme de texte (comme un programme dans un langage de plus haut niveau). Chaque instruction assembleur reprsente exactement une instruction machine. Par exemple, l'instruction d'addition dcrite ci-dessus serait reprsente en langage assembleur comme suit :

add eax, ebx


Ici, la signication de l'instruction est chine. Le mot

add est un mnmonique

beaucoup plus

claire qu'en code ma-

pour l'instruction d'addition. La forme

gnrale d'une instruction assembleur est :

mnmonique oprande(s)
Un

assembleur

est un programme qui lit un chier texte avec des instruc-

tions assembleur et convertit l'assembleur en code machine. Les

compilateurs

sont des programmes qui font des conversions similaires pour les langages de programmation de haut niveau. Un assembleur est beaucoup plus simple
Cela a pris plusieurs an- qu'un compilateur. Chaque instruction du langage d'assembleur reprsente nes aux scientiques de directement une instruction machine. Les instructions d'un langage de plus l'informatique pour conce- haut niveau sont plus complexes et peuvent requrir beaucoup voir le simple fait d'crire d'instructions machine. un compilateur ! Une autre dirence importante entre l'assembleur et les langages de

beaucoup

haut niveau est que comme chaque type de processeur a son propre langage machine, il a galement son propre langage d'assemblage. Porter des programmes assembleur entre direntes architectures d'ordinateur est plus dicile qu'avec un langage de haut niveau. Les exemples de ce livre utilisent le Netwide Assembler ou NASM en raccourci. Il est disponible gratuitement sur Internet (voyez la prface pour

beaucoup

1.3.

LANGAGE ASSEMBLEUR

13

l'URL). Des assembleurs plus courants sont l'Assembleur de Microsoft (MASM) ou l'Assembleur de Borland (TASM). Il y a quelques dirences de syntaxe entre MASM/TASM et NASM.

1.3.3 Oprandes d'Instruction


Les instructions en code machine ont un nombre et un type variables d'oprandes ; cependant, en gnral, chaque instruction a un nombre x d'oprandes (0 3). Les oprandes peuvent avoir les types suivants :

registre :

Ces oprandes font directement rfrence au contenu des registres

du processeur.

mmoire :

Ils font rfrence aux donnes en mmoire. L'adresse de la don-

ne peut tre une constante code en dur dans l'instruction ou calcule en utilisant les valeurs des registres. Les adresses sont toujours des dplacements relatifs au dbut d'un segment.

immdiat : implicite :

Ce sont des valeurs xes qui sont listes dans l'instruction elle-

mme. Elles sont stockes dans l'instruction (dans le segment de code), pas dans le segment de donnes. Ces oprandes ne sont pas entrs explicitement. Par exemple,

l'instruction d'incrmentation ajoute un un registre ou la mmoire. Le un est implicite.

1.3.4 Instructions de base


L'instruction la plus basique est l'instruction de haut niveau). Elle prend deux oprandes :

MOV. Elle dplace les donnes

d'un endroit un autre (comme l'oprateur d'assignement dans un langage

mov dest, src


La donne spcie par

src

est copie vers

dest .

Une restriction est que

les deux oprandes ne peuvent pas tre tous deux des oprandes mmoire. Cela nous montre un autre caprice de l'assembleur. Il y a souvent des rgles quelque peu arbitraires sur la faon dont les direntes instructions sont utilises. Les oprandes doivent galement avoir la mme taille. La valeur de AX ne peut pas tre stocke dans BL. Voici un exemple (les points-virgules marquent un commentaire) :

mov mov add add

eax, 3 ; stocke 3 dans le registre EAX (3 est un operande immediat) bx, ax ; stocke la valeur de AX dans le registre BX ADD
est utilise pour additionner des entiers.

L'instruction

eax, 4 al, ah

; eax = eax + 4 ; al = al + ah

14

CHAPITRE 1.

INTRODUCTION

L'instruction

SUB

soustrait des entiers.

sub sub

bx, 10 ; bx = bx - 10 ebx, edi ; ebx = ebx - edi INC


et

Les instructions

DEC

incrmentent ou dcrmentent les valeurs de

1. Comme le un est un oprande implicite, le code machine pour est plus petit que celui des instructions

ADD

et

SUB

INC

et

DEC

quivalentes.

inc dec

ecx dl

; ecx++ ; dl--

1.3.5 Directives
Une

directive

est destine l'assembleur, pas au processeur. Elles sont

gnralement utilises pour indiquer l'assembleur de faire quelque chose ou pour l'informer de quelque chose. Elles ne sont pas traduites en code machine. Les utilisations courantes des directives sont :

la dnition de constantes la dnition de mmoire pour stocker des donnes grouper la mmoire en segment inclure des codes sources de faon conditionnelle inclure d'autres chiers

Le code NASM est analys par un prprocesseur, exactement comme en C. Il y a beaucoup de commandes identiques celles du prprocesseur C. Cependant, les directives du prprocesseur NASM commencent par un % au lieu d'un # comme en C.

La directive equ
La directive

equ

peut tre utilise pour dnir un

symbole. Les symboles

sont des constantes nommes qui peuvent tre utilises dans le programme assembleur. Le format est le suivant :

symbole equ valeur


Les valeurs des symboles ne peuvent

pas

tre rednies plus tard.

La directive %dene
Cette directive est semblable la directive

#define du C. Elle est le plus

souvent utilise pour dnir des macros, exactement comme en C.

%define SIZE 100 mov eax, SIZE

1.3.

LANGAGE ASSEMBLEUR

15

Unit
octet mot double mot quadruple mot dix octets

Lettre
B W D Q T

Tab. 1.3  Lettres des Directives

RESX

et

DX

Le code ci-dessus dnit une macro appele dans une instruction nombres constants.

MOV.

SIZE

et montre son utilisation

Les macros sont plus exibles que les symboles de

deux faons. Elles peuvent tre rednies et peuvent tre plus que de simples

Directives de donnes
Les directives de donnes sont utilises dans les segments de donnes pour rserver de la place en mmoire. Il y a deux faons de rserver de la mmoire. La premire ne fait qu'allouer la place pour les donnes ; la seconde alloue la place et donne une valeur initiale. La premire mthode utilise une des directives possibles. La seconde mthode (qui dnit une valeur initiale) utilise une des directives

RESX.

Le

X est remplac par une lettre qui dtermine la taille

de l'objet (ou des objets) qui sera stock. Le Tableau 1.3 montre les valeurs

DX.

Les lettres

X sont les mmes que celles de la directive RESX.

Il est trs courant de marquer les emplacements mmoire avec des cements mmoire dans le code. Voici quelques exemples :

labels

(tiquettes). Les labels permettent de faire rfrence facilement aux empla-

L1 L2 L3 L4 L5 L6 L7 L8

db dw db db db dd resb db

0 1000 110101b 12h 17o 1A92h 1 "A"

; ; ; ; ; ; ; ;

octet libelle L1 avec une valeur initiale de 0 mot labelle L2 avec une valeur initiale de 1000 octet initialise a la valeur binaire 110101 (53 en decimal) octet initialise a la valeur hexa 12 (18 en decimal) octet initialise a la valeur octale 17 (15 en decimal) double mot initialise a la valeur hexa 1A92 1 octet non initialise octet initialise avec le code ASCII du A (65)

Les doubles et simples quotes sont traites de la mme faon. Les dnitions de donnes conscutives sont stockes squentiellement en mmoire. C'est dire que le mot L2 est stock immdiatement aprs L1 en mmoire. Des squences de mmoire peuvent galement tre dnies.

L9

db

0, 1, 2, 3

; definit 4 octets

16

CHAPITRE 1.

INTRODUCTION

L10 L11

db db

"w", "o", "r", 'd', 0 'word', 0


La directive

; definit une chaine C = "word" ; idem L10


5

DD

peut tre utilise pour dnir la fois des entiers et des

constantes virgule ottante en simple prcision . Cependant, la directive

DQ ne peut tre utilise que pour dnir des constantes virgule ottante en
double prcision. Pour les grandes squences, la directive de NASM

TIMES est souvent utile.

Cette directive rpte son oprande un certain nombre de fois. Par exemple :

L12 L13

times 100 db 0 resw 100

; equivalent a 100 (db 0) ; reserve de la place pour 100 mots

Souvenez vous que les labels peuvent tre utiliss pour faire rfrence des donnes dans le code. Il y a deux faons d'utiliser les labels. Si un label simple est utilis, il fait rfrence l'adresse (ou oset) de la donne. Si le label est plac entre crochets ([]), il est interprt comme la donne cette adresse. En d'autres termes, il faut considrer un label comme un

pointeur

vers la donne et les crochets drfrencent le pointeur, exactement

comme l'astrisque en C (MASM/TASM utilisent une convention dirente). En mode 32 bits, les adresses sont sur 32 bits. Voici quelques exemples :

1 2 3 4 5 6 7

mov mov mov mov add add mov

al, [L1] eax, L1 [L1], ah eax, [L6] eax, [L6] [L6], eax al, [L6]

; ; ; ; ; ; ;

Copie l'octet situe en L1 dans AL EAX = addresse de l'octet en L1 copie AH dans l'octet en L1 copie le double mot en L6 dans EAX EAX = EAX + double mot en L6 double mot en L6 += EAX copie le premier octet du double mot en L6 dans AL

La ligne 7 de cet exemple montre une proprit importante de NASM. L'assembleur ne garde

pas

de trace du type de donnes auquel se rfre le label.

C'est au programmeur de s'assurer qu'il (ou elle) utilise un label correctement. Plus tard, il sera courant de stocker des adresses dans les registres et utiliser les registres comme un pointeur en C. L encore, aucune vrication n'est faite pour savoir si le pointeur est utilis correctement. A ce niveau, l'assembleur est beaucoup plus sujet erreur que le C. Considrons l'instruction suivante :

mov

[L6], 1

; stocke 1 en L6 operation size not specified. Pour-

Cette instruction produit une erreur

quoi ? Parce que l'assembleur ne sait pas s'il doit considrer 1 comme un

5 Les nombres virgule ottante en simple prcision sont quivalent aux variables float en C.

1.3.

LANGAGE ASSEMBLEUR

17

octet, un mot ou un double mot. Pour rparer cela, il faut ajouter un spcicateur de taille :

mov
en

dword [L6], 1

; stocke 1 en L6 BYTE, WORD, QWORD


et

Cela indique l'assembleur de stocker un 1 dans le double mot qui commence

L6.

Les autres spcicateurs de taille sont :

TWORD6 .

1.3.6 Entres et Sorties


Les entres et sorties sont des activits trs dpendantes du systme. Cela implique un interfaage avec le matriel. Les langages de plus haut niveau, comme C, fournissent des bibliothques standards de routines pour une interface de programmation simple, uniforme pour les E/S. Les langages d'assembleur n'ont pas de bibliothque standard. Ils doivent soit accder directement au matriel (avec une opration privilgie en mode protg) ou utiliser les routines de bas niveau ventuellement fournies par le systme d'exploitation. Il est trs courant pour les routines assembleur d'tre interfaces avec du C. Un des avantages de cela est que le code assembleur peut utiliser les routines d'E/S de la bibliothque standard du C. Cependant, il faut connatre les rgles de passage des informations aux routines que le C utilise. Ces rgles sont trop compliques pour en parler ici (nous en parlerons plus tard !). Pour simplier les E/S, l'auteur a dvelopp ses propres routines qui masquent les rgles complexes du C et fournissent une interface beaucoup plus simple. Le Tableau 1.4 dcrit les routines fournies. Toutes les routines prservent les valeurs de tous les registres, except les routines read. Ces routines modient la valeur du registre EAX. Pour utiliser ces routines, il faut inclure un chier contenant les informations dont l'assembleur a besoin pour les utiliser. Pour inclure un chier dans NASM, utilisez la directive du prprocesseur

7 : routines d'E/S de l'auteur

%include.

La ligne suivante inclut le chier requis par les

%include "asm_io.inc"
Pour utiliser une de ces routines d'achage, il faut charger EAX avec la

CALL pour l'invoquer. L'instruction CALL est quivalente un appel de fonction dans un langage de haut niveau.
valeur correcte et utiliser une instruction Elle saute une autre portion de code mais revient son origine une fois

6 TWORD dnit une zone mmoire de 10 octets. Le coprocesseur virgule ottante utilise ce type de donnes. 7 Le chier asm_io.inc (et le chier objet asm_io que recquiert asm_io.inc) sont dans les exemples de code tlcharger sur la page web de ce tutorial,

http://www.drpaulcarter.com/pcasm

18

CHAPITRE 1.

INTRODUCTION

print_int print_char print_string print_nl read_int read_char

ache l'cran la valeur d'un entier stock dans EAX ache l'cran le caractre dont le code ASCII est stock dans AL ache l'cran le contenu de la chane l' type C (

adresse

stocke dans EAX. La chane doit tre une chane de

i.e. termine par 0).

ache l'cran un caractre de nouvelle ligne. lit un entier au clavier et le stocke dans le registre EAX. lit un caractre au clavier et stocke son code ASCII dans le registre EAX.
Tab. 1.4  Routine d'E/S de l'assembleur

la routine termine. Le programme d'exemple ci-dessous montre plusieurs exemples d'appel ces routines d'E/S.

1.3.7 Dbogage
La bibliothque de l'auteur contient galement quelques routines utiles pour dboguer les programmes. Ces routines de dbogage achent des informations sur l'tat de l'ordinateur sans le modier. Ces routines sont en fait des

macros

qui sauvegardent l'tat courant du processeur puis font appel

une sous-routine. Les macros sont dnies dans le chier

asm_io.inc

dont

nous avons parl plus haut. Les macros sont utilises comme des instructions ordinaires. Les oprandes des macros sont spars par des virgules. Il y a quatre routines de dbogage nommes et

dump_math ;

dump_regs, dump_mem, dump_stack

elles achent respectivement les valeurs des registres, de la

mmoire, de la pile et du coprocesseur arithmtique.

dump_regs

Cette macro ache les valeurs des registres (en hexadcimal)

de l'ordinateur sur (zero ag) est 1,

stdout (i.e.

l'cran). Elle ache galement les bits

positionns dans le registre FLAGS . Par exemple, si le drapeau zro

ZF

est ach. S'il est 0, il n'est pas ach. Elle

prend en argument un entier qui est ach galement. Cela peut aider distinguer la sortie de direntes commandes

dump_regs.

dump_mem

Cette macro ache les valeurs d'une rgion de la mmoire

(en hexadcimal et galement en caractres ASCII). Elle prend trois arguments spars par des virgules. Le premier est un entier utilis pour tiqueter la sortie (comme l'argument de

dump_regs).

Le second

argument est l'adresse acher (cela peut tre un label). Le dernier argument est le nombre de paragraphes de 16 octets acher aprs

Le Chapitre 2 parle de ce registre

1.4.

CRER UN PROGRAMME

19

l'adresse. La mmoire ache commencera au premier multiple de paragraphe avant l'adresse demande.

dump_stack

Cette macro ache les valeurs de la pile du processeur (la

pile sera prsente dans le Chapitre 4). La pile est organise en double mots et cette routine les ache de cette faon. Elle prend trois arguments dlimits par des virgules. Le premier est un entier (comme pour

dump_regs).

Le second est le nombre de double mots acher

l'adresse que le registre

EBP

aprs

contient et le troisime argument est le

nombre de double mots acher

dump_math

avant

l'adresse contenue dans

EBP.

Cette macro ache les valeurs des registres du coprocesseur

arithmtique. Elle ne prend qu'un entier en argument qui est utilis pour tiqueter la sortie comme le fait l'argument de

dump_regs.

1.4

Crer un Programme

Aujourd'hui, il est trs peu courant de crer un programme autonome crit compltement en langage assembleur. L'assembleur est habituellement utilis pour optimiser certaines routines critiques. Pourquoi ? Il est

beaucoup

plus simple de programmer dans un langage de plus haut niveau qu'en assembleur. De plus, utiliser l'assembleur rend le programme trs dur porter sur d'autres plateformes. En fait, il est rare d'utiliser l'assembleur tout court. Alors, pourquoi apprendre l'assembleur ? 1. Quelques fois, le code crit en assembleur peut tre plus rapide et plus compact que le code gnr par un compilateur. 2. L'assembleur permet l'accs des fonctionnalits matrielles du systme directement qu'il pourrait tre dicile ou impossible utiliser depuis un langage de plus haut niveau. 3. Apprendre programmer en assembleur aide acqurir une comprhension plus profonde de la faon dont fonctionne un ordinateur. 4. Apprendre programmer en assembleur aide mieux comprendre comment les compilateurs et les langage de haut niveau comme C fonctionnent. Ces deux dernier points dmontrent qu'apprendre l'assembleur peut tre utile mme si on ne programme jamais dans ce langage plus tard. En fait, l'auteur programme rarement en assembleur, mais il utilise les leons qu'il en a tir tous les jours.

1.4.1 Premier programme


Les programmes qui suivront dans ce texte partiront tous du programme de lancement en C de la Figure 1.6. Il appelle simplement une autre fonction

20

CHAPITRE 1.

INTRODUCTION

1 2 3 4 5 6

int main() { int ret_status ; ret_status = asm_main(); return ret_status ; }


Fig. 1.6  Code de

driver.c

nomme

asm_main.

C'est la routine qui sera crite en assemble proprement

dit. Il y a plusieurs avantages utiliser un programme de lancement en C. Tout d'abord, cela laisse le systme du C initialiser le programme de faon fonctionner correctement en mode protg. Tous les segments et les registres correspondants seront initialiss par le C. L'assembleur n'aura pas se proccuper de cela. Ensuite, la bibliothque du C pourra tre utilise par le code assembleur. Les routines d'E/S de l'auteur en tirent partie. Elles utilisent les fonctions d'E/S du C (printf, un programme assembleur simple.

etc.). L'exemple suivant montre

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

; ; ; ; ; ; ;

first.asm fichier: first.asm Premier programme assembleur. Ce programme attend la saisie de deux entiers et affiche leur somme. Pour creer l'executable en utilisant djgpp : nasm -f coff first.asm gcc -o first first.o driver.c asm_io.o

%include "asm_io.inc" ; ; Les donnees initialisees sont placees dans le segment .data ; segment .data ; ; Ces labels referencent les chaines utilisees pour l'affichage ; prompt1 db "Entrez un nombre : ", 0 ; N'oubliez pas le 0 final prompt2 db "Entrez un autre nombre : ", 0 outmsg1 db "Vous avez entre ", 0 outmsg2 db " et ", 0 outmsg3 db ", leur somme vaut ", 0

1.4.

CRER UN PROGRAMME

21

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

; ; Les donnees non initialisees sont placees dans le segment .bss ; segment .bss ; ; Ces labels referencent les doubles mots utilises pour stocker les entrees ; input1 resd 1 input2 resd 1 ; ; Le code est place dans le segment .text ; segment .text global _asm_main _asm_main: enter 0,0 ; initialisation pusha mov call call mov mov call call mov mov add mov eax, prompt1 print_string read_int [input1], eax eax, prompt2 print_string read_int [input2], eax eax, [input1] eax, [input2] ebx, eax ; affiche un message ; lit un entier ; le stocke dans input1 ; affiche un message ; lit un entier ; le stocke dans input2 ; eax = dword en input1 ; eax += dword en input2 ; ebx = eax ; affiche les valeurs des registres ; affiche le contenu de la memoire

; ; Ce qui suit affiche le message resultat en plusieurs etapes ; mov eax, outmsg1 call print_string ; affiche le premier message

dump_regs 1 dump_mem 2, outmsg1, 1

22

CHAPITRE 1.

INTRODUCTION

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80

mov call mov call mov call mov call mov call call popa mov leave ret

eax, [input1] print_int eax, outmsg2 print_string eax, [input2] print_int eax, outmsg3 print_string eax, ebx print_int print_nl eax, 0

; affiche input1 ; affiche le second message ; affiche input2 ; affiche le troisieme message ; affiche la somme (ebx) ; affiche une nouvelle ligne ; retourne dans le programme C first.asm .data).

La ligne 13 du programme dnit une section qui spcie la mmoire stocker dans le segment de donnes (dont le nom est Seules les donnes initialises doivent tre dnies dans ce segment. Dans les lignes 17 21, plusieurs chanes sont dclares. Elle seront aches avec la bibliothque C et doivent donc se terminer par un caractre vous qu'il y a une grande dirence entre

null (code ASCII 0). Souvenez


'0'.

et

Les donnes non initialises doivent tre dclares dans le segment bss (appel

.bss

la ligne 26). Ce segment tient son nom d'un vieil oprateur

assembleur UNIX qui signiait block started by symbol. Il y a galement un segment de pile. Nous en parlerons plus tard. Le segment de code est appel

.text

historiquement. C'est l que les

instructions sont places. Notez que le label de code pour la routine main (ligne 38) a un prxe de soulignement. Cela fait partie de la

convention d'appel C . Cette convention spcie les rgles que le C utilise lorsqu'il compile le code. Il est trs important de connatre cette convention lorsque l'on

interface du C et de l'assembleur. Plus loin, la convention sera prsente dans son intgralit ; cependant, pour l'instant, il sut de savoir que tous les symboles C (

i.e., les fonctions et les variables globales) on un prxe de

soulignement qui leur est ajout par le compilateur C (cette rgle s'applique spciquement pour DOS/Windows, le compilateur C Linux n'ajoute rien du tout aux noms des symboles).

_asm_main global. Contrairement au C, les labels ont une porte interne


La directive

La directive

global

ligne 37 indique l'assembleur de rendre le label par Ce

dfaut. Cela signie que seul le code du mme module peut utiliser le label.

global

donne au(x) label(s) spci(s) une

porte externe.

1.4.

CRER UN PROGRAMME

23

type de label peut tre accd par n'importe quel module du programme. Le module C'est pourquoi l'on peut les utiliser dans le module

asm_io dclare les labels print_int, et.al. comme tant globaux. first.asm.

1.4.2 Dpendance vis vis du compilareur


Le code assembleur ci-dessus est spcique au compilateur C/C++ GNU gratuit DJGPP

10 . Ce compilateur peut tre tlcharg gratuitement depuis

Internet. Il ncessite un PC base de 386 ou plus et tourne sous DOS, Windows 95/98 ou NT. Ce compilateur utilise des chiers objets au format COFF (Common Object File Format). Pour assembler le chier au format COFF, utilisez l'option code). L'extension du chier objet sera

-f coff avec nasm (comme l'indiquent les commentaires du o.

Le compilateur C Linux est galement un compilateur GNU. Pour convertir le code ci-dessus an qu'il tourne sous Linux, retirez simplement les prxes de soulignement aux lignes 37 et 38. Linux utilise le format ELF (Executable and Linkable Format) pour les chiers objet. Utilisez l'option pour Linux. Il produit galement un chier objet avec l'extension

o.

-f elf
Les chiers spciques au

disponibles Borland C/C++ est un autre compilateur populaire. Il utilise le for- compilateur, sur le site de l'auteur, mat Microsoft OMF pour les chier objets. Utilisez l'option -f obj pour les ont dj t modis compilateurs Borland. L'extension du chier objet sera obj. Le format OMF pour fonctionner avec le utilise des directives segment direntes de celles des autres formats objet. compilateur appropri. Le segment data (ligne 13) doit tre chang en :

segment _DATA public align=4 class=DATA use32


Le segment bss (ligne 26) doit tre chang en :

segment _BSS public align=4 class=BSS use32


Le segment text (ligne 36) doit tre chang en :

segment _TEXT public align=1 class=CODE use32


De plus, une nouvelle ligne doit tre ajoute avant la ligne 36 :

group DGROUP _BSS _DATA


Le compilateur Microsoft C/C++ peut utiliser soit le format OMF, soit le format Win32 pour les chiers objet (Si on lui passe un format OMF, il convertit les informations au format Win32 en interne). Le format Win32 permet de dnir les segment comme pour DJGPP et Linux. Utilisez l'option

-f win32
9 10

pour produire un chier objet dans ce format. L'extension du

chier objet sera

obj.

http://www.delorie.com/djgpp

GNU est un projet de Free Software Foundation (http://www.fsf.org)

24

CHAPITRE 1.

INTRODUCTION

1.4.3 Assembler le code


La premire tape consiste assembler le code. Depuis la ligne de commande, tappez :

nasm -f format-objet first.asm


o

format-objet est soit co , soit elf , soit obj soit win32 selon le compilateur

C utilis (Souvenez vous que le chier source doit tre modi pour Linux et pour Borland).

1.4.4 Compiler le code C


Compilez le chier utilisez :

driver.c en utilisant un compilateur C. Pour DJGPP,

gcc -c driver.c
L'option

-c signie de compiler uniquement, sans essayer de lier. Cette option

fonctionne la fois sur les compilateurs Linux, Borland et Microsoft.

1.4.5 Lier les chiers objets


L'dition de liens est le procd qui consiste combiner le code machine et les donnes des chiers objet et des bibliothques an de crer un chier excutable. Comme nous allons le voir, ce processus est compliqu.

marrage

Le code C ncessite la bibliothque standard du C et un spcial an de s'excuter. Il est

beaucoup

code de d-

plus simple de laisser le

compilateur C appeler l'diteur de liens avec les paramtres corrects que d'essayer d'appeler l'diteur de liens directement. Par exemple, pour lier le code du premier programme en utilisant DJGPP, utilisez :

gcc -o first driver.o first.o asm_io.o


Cela cre un excutable appel

first.exe

(ou juste

first

sous Linux).

Avec Borland, on utiliserait :

bcc32 first.obj driver.obj asm_io.obj


Borland utilise le nom du premier chier an de dterminer le nom de l'excutable. Donc, dans le cas ci-dessus, le programme s'appelera

first.exe.

Il est possible de combiner les tapes de compilation et d'dition de liens. Par exemple :

gcc -o first driver.c first.o asm_io.o


Maintenant

gcc

compilera

driver.c

puis liera.

1.4.

CRER UN PROGRAMME

25

1.4.6 Comprendre un listing assembleur


L'option

-l fichier-listing

peut tre utilise pour indiquer

nasm

de crer un chier listing avec le nom donn. Ce chier montre comment le code a t assembl. Voici comment les lignes 17 et 18 (dans le segment data) apparassent dans le chier listing (les numros de ligne sont dans le chier listing ; cependant, notez que les numro de ligne dans le chier source peuvent ne pas tre les mmes).
Les nombres dirent sur la version franaise car ce pas les mmes caractres.

48 49 50 51 52

00000000 00000009 00000011 0000001A 00000023

456E7465722061206E756D6265723A2000 456E74657220616E6F74686572206E756D6265723A2000

prompt1 db prompt2 db

"Entrez un nombre : ne ", sont 0

"Entrez un autre nombre : ", 0

La premire colonne de chaque ligne est le numro de la ligne et la seconde est le dplacement (en hexa) de la donne dans le segment. La troisime colonne montre les donnes hexa brutes qui seront stockes. Dans ce cas, les donnes hexa correspondent aux codes ASCII. Enn, le texte du chier source est ach sur la droite. Les dplacements lists dans la seconde colonne ne sont trs probalement

pas

les dplacements rels auxquels les donnes seront

places dans le programme termin. Chaque module peut dnir ses propres labels dans le segment de donnes (et les autres segments galement). Lors de l'tape d'dition de liens (voir Section 1.4.5), toutes ces dnitions de labels de segment de donnes sont combines pour ne former qu'un segment de donnes. Les dplacements naux sont alors calculs par l'diteur de liens. Voici une petite portion (lignes 54 56 du chier source) du segment text dans le chier listing :

94 0000002C A1[00000000] 95 00000031 0305[04000000] 96 00000037 89C3

mov add mov

eax, [input1] eax, [input2] ebx, eax

La troisime colonne montre le code machine gnr par l'assembleur. Souvent le code complet pour une instruction ne peut pas encore tre calcul. Par exemple, ligne 94, le dplacement (ou l'adresse) de l'instruction

input1

n'est pas

connu avant que le code ne soit li. L'assembleur peut calculer l'opcode pour

mov (qui, d'aprs le listing, est A1), mais il crit le dplacement input1
est au dbut

entre crochets car la valeur exacte ne peut pas encore tre calcule. Dans ce cas, un dplacement temporaire de 0 est utilis car du segment bss dclar dans ce chier. Souvenez vous que cela ne signie

pas

qu'il sera au dbut du segment bss nal du programme. Lorsque le code

est li, l'diteur de liens insrera le dplacement correct la place. D'autres instructions, comme ligne 96, ne font rfrence aucun label. Dans ce cas, l'assembleur peut calculer le code machine complet.

26

CHAPITRE 1.

INTRODUCTION

Rprsentations Big et Little Endian


Si l'on regarde attentivement la ligne 95, quelque chose semble trs bizarre propos du dplacement enrte crochets dans le code machine. Le label

input2

est au dplacement 4 (comme dni dans ce chier) ; cepedant, le

dplacement qui apparat en mmoire n'est pas 00000004 mais 04000000. Pourquoi ? Des processeurs dirents stockent les entiers multioctets dans des ordres dirents en mmoire. Il y a deux mthodes populaires pour stoEndian comme est
indian.

prononc cker les entiers :

big endian

et

little endian.

Le big endian est la mthode

qui semble la plus naturelle. L'octet le plus fort ( est stock en premier, puis le second plus fort,

etc.

i.e.

le plus signicatif )

Par exemple, le dword

00000004 serait stock sous la forme des quatre octets suivants : 00 00 00 04. Les mainframes IBM, la plupart des processeurs RISC et les processeur Motorola utilisent tous cette mthode big endian. Cependant, les processeurs de type Intel utilisent la mthode little endian ! Ici, l'octet le moins signicatif est stock en premier. Donc, 00000004 est stock en mmoire sous la forme 04 00 00 00. Ce format est cod en dur dans le processeur et ne peut pas tre chang. Normalement, le programmeur n'a pas besoin de s'inquiter du format utilis. Cependant, il y a des circonstances o c'est important. 1. Lorsque des donnes binaires sont transfres entre dirents ordinateurs (soit via des chiers, soit via un rseau). 2. Lorsque des donnes binaires sont crites en mmoire comme un entier multioctet puis relues comme des octets individuels ou

vice versa.

Le caractre big ou little endian ne s'applique pas l'ordre des lments d'un tableau. Le premier lment d'un tableau est toujours l'adresse la plus petite. C'est galement valable pour les chanes (qui sont juste des tableaux de caractres). Cependant, le caractre big ou little endian s'applique toujours aux lments individuels des tableaux.

1.5

Fichier Squelette

La Figure 1.7 montre un chier squelette qui peut tre utilis comme point de dpart pour l'criture de programmes assembleur.

1.5.

FICHIER SQUELETTE

27

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

skel.asm %include "asm_io.inc" segment .data ; ; Les donnees initialisees sont placees dans ce segment de donnees ; segment .bss ; ; Les donnees non initialisees sont placees dans le segment bss ; segment .text global _asm_main _asm_main: enter 0,0 pusha

; initialisation

; ; Le code est place dans le segment text. Ne modifiez pas le code ; place avant ou apres ce commentaire. ; popa mov leave ret eax, 0 ; retour au C skel.asm
Fig. 1.7  Squelette de Programme

28

CHAPITRE 1.

INTRODUCTION

Chapitre 2

Bases du Langage Assembleur


2.1 Travailler avec les Entiers

2.1.1 Reprsentation des entiers


Les entiers se dcomposent en deux catgories : signs et non signs. Les entiers non signs (qui sont positifs) sont reprsents d'une manire binaire trs intuitive. Les nombre 200 en tant qu'entier non sign sur un octet serait reprsent par 11001000 (ou C8 en hexa). Les entiers signs (qui peuvent tre positifs ou ngatifs) sont reprsents d'une faon plus complique. Par exemple, considrons prsent par l'octet 00111000. Sur papier, on peut

56. +56 serait rereprsenter 56 comme

111000,

mais comment cela serait-il reprsent dans un octet en mmoire

de l'ordinateur. Comment serait stock le signe moins ? Il y a trois techniques principales qui ont t utilises pour reprsenter les entiers signs dans la mmoire de l'ordinateur. Toutes ces mthodes utilisent le bit le plus signicatif de l'entier comme nombre est positif et 1 s'il est ngatif.

bit de signe.

Ce bit vaut 0 si le

Grandeur signe
La premire mthode est la plus simple, elle est appele

grandeur signe.

Elle reprsente l'entier en deux parties. La premire partie est le bit de signe et la seconde est la grandeur entire. Donc 56 serait reprsent par l'octet

00111000

(le bit de signe est soulign) et

d'octet la plus grande serait sur un octet serait

56 serait 10111000. La valeur 01111111 soit +127 et la plus petite valeur 11111111 soit 127. Pour obtenir l'oppos d'une valeur, +0 (00000000)
et

on inverse le bit de signe. Cette mthode est intuitive mais a ses inconvnients. Tout d'abord, il y a deux valeurs possibles pour 0 :

0 (10000000).

Comme zro n'est ni positif ni ngatif, ces deux reprsenta-

tions devraient se comporter de la mme faon. Cela complique la logique de 29

30

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

l'arithmtique pour le processeur. De plus, l'arithmtique gnrale est galement complique. Si l'on ajoute 10

56,

cela doit tre transform en 10

moins 56. L encore, cela complique la logique du processeur.

Complment 1
La seconde mthode est appele reprsentation en

complment un.
00111000

Le complment un d'un nombre est trouv en inversant chaque bit du nombre (une autre faon de l'obtenir est que la valeur du nouveau bit est

1 anciennevaleurdubit). Par exemple, (+56) est 11000111. Dans la notation en


tion de

le complment un de

complment un, calculer le com-

plment un est quivalent la ngation. Donc

11000111

est la reprsenta-

56.

Notez que le bit de signe a t automatiquement chang par le

complment un et que, comme l'on s'y attendait, appliquer le complment un deux fois redonne le nombre de dpart. Comme pour la premire mthode, il y a deux reprsentation pour 0 :

00000000 (+0)

et

11111111 (0).

L'arithmtique des nombres en complment un est complique. Voici une astuce utile pour trouver le complment un d'un nombre en hexadcimal sans repasser en binaire. L'astuce est d'ter le chire hexa de F (ou 15 en dcimal). Cette mthode suppose que le nombre de bits dans le nombre est multiple de 4. Voici un exemple :

+56

est reprsent par 38 en

hexa. pour trouver le complment un, tez chaque chire de F pour obtenir C7 en hexa. Cela concorde avec le rsultat ci-dessus.

Complment deux
Les deux premires mthodes dcrites ont t utilises sur les premiers ordinateurs. Les ordinateurs modernes utilisent une troisime mthode appele reprsentation en

complment deux.

On trouve le e complment

deux d'un nombre en eectuant les deux oprations suivantes : 1. Trouver le complment un du nombre 2. Ajouter un au rsultat de l'tape 1 Voici un exemple en utilisant complment un :

00111000

(56). Tout d'abord, on calcule le

11000111.

Puis, on ajoute un :

11000111 + 1 11001000
Dans la notation en complment deux, calculer le complment deux est quivalent trouver l'oppos d'un nombre. Donc, sentation en complment deux de

11001000

est la repr-

56.

Deux ngations devraient repro-

duire le nombre original. Curieusement le complment deux ne rempli pas

2.1.

TRAV AILLER A VEC LES ENTIERS

31

Nombre 0 1 127 -128 -127 -2 -1

Reprsentation Hexa 00 01 7F 80 81 FE FF

Tab. 2.1  Two's Complement Representation

cette condition. Prenez le complment deux de au complment un.

11001000

en ajoutant un

00110111 + 1 00111000
Lorsque l'on eectue l'addition dans l'opration de complmentation deux, l'addition du bit le plus gauche peut produire une retenue. Cette retenue n'est sont d'une taille xe (en terme de nombre de bits). Ajouter deux octets produit toujours un rsultat sur un octet (comme ajouter deux mots donne un mot,

pas utilise. Souvenez vous que toutes les donnes sur un ordinateur
Cette proprit est importante pour la notation en complment

etc .)

deux. Par exemple, considrons zro comme un nombre en complment deux sur un octet (00000000). Calculer son complment deux produit la somme :

11111111 + 1 c 00000000
o

reprsente une retenue (Plus tard, nous montrerons comment dtecter

cette retenue, mais elle n'est pas stocke dans le rsultat). Donc, en notation en complment deux, il n'y a qu'un zro. Cela rend l'arithmtique en complment deux plus simple que les mthodes prcdentes. En utilisant la notation en complment deux, un octet sign peut tre utilis pour reprsenter les nombres

128

+127.

Le Tableau 2.1 montre

quelques valeurs choisies. Si 16 bits sont utiliss, les nombres signs

32, 768 +32, 767 peuvent tre reprsents. +32, 767 est reprsent par 7FFF, 32, 768 2
milliards

par 8000, -128 par FF80 et -1 par FFFF. Les nombres en complment deux sur 32 bits vont de

+2

milliards environ.

Le processeur n'a aucune ide de ce qu'un octet en particulier (ou un mot ou un double mot) est suppos reprsent. L'assembleur n'a pas le concept de types qu'un langage de plus haut niveau peut avoir. La faon dont les donnes sont interprtes dpendent de l'instruction dans laquelle on les

32

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

utilise. Que la valeur FF soit considre comme reprsentant un un

1 sign ou

+255

non sign dpend du programmeur. Le langage C dnit des types

entiers signs et non signs. Cela permet au compilateur C de dterminer les instructions correctes utiliser avec les donnes.

2.1.2 Extension de signe


En assembleur, toutes les donnes ont une taille bien spcie. Il n'est pas rare de devoir changer la taille d'une donne pour l'utiliser avec d'autres. Rduire la taille est le plus simple.

Rduire la taille des donnes


Pour rduire la taille d'une donne, il sut d'en retirer les bits les plus signicatifs. Voici un exemple trivial :

mov mov

ax, 0034h cl, al

; ax = 52 (stock sur 16 bits) ; cl = les 8 bits de poids faible de ax AX valait mme CL

Bien sr, si le nombre ne peut pas tre reprsent correctement dans une plus petite taille, rduire la taille ne fonctionne pas. Par exemple, si 0134h (ou 308 en dcimal) alors le code ci-dessus mettrait quand signs. Considrons les nombres signs, si alors dans

34h. Cette mthode fonctionne la fois avec les nombres signs et non

CL vaudrait FFh (1 sur un octet). Cependant, notez que si la valeur AX) tait sign, elle aurait t tronqu et le rsultat aurait t faux !

AX valait FFFFh (1 sur un mot),

La rgle pour les nombres non signs est que tous les bits retirs soient 0 an que la convertion soit correcte. La rgle pour les nombres signs est que les bits retirs doivent soit tous tre des 1 soit tous des 0. De plus, Le premier bit ne pas tre retir doit valoir la mme chose que ceux qui l'ont t. Ce bit sera le bit de signe pour la valeur plus petite. Il est important qu'il ait la mme valeur que le bit de signe original !

Augmenter la taille des donnes


Augmenter la taille des donnes est plus compliqu que la rduire. Considrons l'octet hexa FF. S'il est tendu un mot, quelle valeur aura-t-il ? Cela dpend de la faon dont FF est interprt. Si FF est un octet non sign (255 en dcimal), alors le mot doit tre 00FF ; cependant, s'il s'agit d'un octet sign (1 en dcimal), alors le mot doit tre FFFF. En gnral, pour tendre un nombre non sign, on met tous les bits supplmentaires du nouveau nombre 0. Donc, FF devient 00FF. Cependant, pour tendre un nombre sign, on doit

tendre

le bit de signe. Cela signie

que les nouveaux bits deviennent des copies du bit de signe. Comme le bit

2.1.

TRAV AILLER A VEC LES ENTIERS

33

de signe de FF est 1, les nouveaux bits doivent galement tre des uns, pour donner FFFF. Si le nombre sign 5A (90 en dcimal) tait tendu, le rsultat serait 005A. Il y a plusieurs instructions fournies par le 80386 pour tendre les nombres. Souvenez vous que l'ordinateur ne sait pas si un nombre est sign ou non. C'est au programmeur d'utiliser l'instruction adquate. Pour les nombres non signs, on peut placer des 0 dans les bits de poids fort de faon simple en utilisant l'instruction

MOV. Par axemple pour tendre

l'octet de AL en un mot non sign dans AX :

mov

ah, 0

; met les 8 bits de poids for 0 MOV pour convertir

Cependant, il n'est pas possible d'utiliser une instruction

le mot non sign de AX en un double mot non sign dans EAX. Pourquoi pas ? Il n'y a aucune faon d'accder aux 16 bits de poids fort de EAX dans un truction :

MOV. Le 80386 rsoud ce problme MOVZX. Cette instruction a deux

en fournissant une nouvelle insoprandes. La destination (pre-

mier oprande) doit tre un registre de 16 ou 32 bits. La source (deuxime oprande) doit tre un registre 8 ou 16 bits. L'autre restriction est que la destination doit tre plus grande que la source (la plupart des instructions recquirent que la source soit de la mme taille que la destination). Voici quelques exemples :

movzx movzx movzx movzx MOV

eax, ax eax, al ax, al ebx, ax

; ; ; ;

tend tend tend tend

ax al al ax

eax eax ax ebx

Pour les nombres signs, il n'y a pas de faon simple d'utiliser l'instruction quelque soit le cas. Le 8086 fournit plusieurs instructions pour tendre les nombres signs. L'instruction implicites. L'instruction

CBW

(Convert Byte to Word, Convertir un

Octet en Mot) tend le signe du registre AL dans AX. Les oprandes sont

CWD

(Convert Word to Double word, Convertir un

Mot en Double mot) tend le signe de AX dans DX:AX. La notation DX:AX signie qu'il faut considrer les registres DX et AX comme un seul registre 32 bits avec les 16 bits de poids forts dans DX et les bits de poids faible dans AX (souvenez vous que le 8086 n'avait pas de registre 32 bits du tout !). Le 80386 a apport plusieurs nouvelles instructions. L'instruction tend le signe de AX dans EAX. L'instruction

CWDE (Convert

Word to Double word Extended, convertir un mot en double mot tendue)

CDQ (Convert Double word to MOVSX fonctionne comme

Quad word, Convertir un Double mot en Quadruple mot) tend le signe de EAX dans EDX:EAX (64 bits !) Enn, l'instruction

MOVZX

except qu'elle utilise les rgles des nombres signs.

34

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

1 2 3 4

unsigned char uchar = 0xFF; signed char schar = 0xFF; int a = (int ) uchar ; / a = 255 (0x000000FF) / int b = (int ) schar ; / b = 1 (0xFFFFFFFF) /
Fig. 2.1  Extension de signe en C

char ch; while ( (ch = fgetc(fp )) != EOF ) { / faire qqch avec ch / }


Fig. 2.2  Utilisation de la fonction fgetc

Application la programmation en C
Le C ANSI ne dnit pas si le type ou variables en C peuvent tre dclares soit comme signes soit comme non non, c'est chaque compi- signes (les int sont signs). Considrons le code de la Figure 2.1. A la lateur de le dcider. C'est ligne 3, la variable a est tendue en utilisant les rgles pour les valeurs non pourquoi on dnit le type signes (avec MOVZX), mais la ligne 4, les rgles signes sont utilises pour explicitement dans la Fib (avec MOVSX). gure 2.1. Il existe un bug de programmation courant en C qui est directement

char est sign

L'extension d'entiers signs et non signs a galement lieu en C. Les

li notre sujet. Considrons le code de la Figure 2.2. La prototype de

fgetc()est

int fgetc( FILE * );


On peut se demander pourquoi est-ce que la fonction renvoie un (tendu une valeur

int

puis-

qu'elle lit des caractres ? La raison est qu'elle renvoie normalement un

int

char

en utilisant l'extension de zro). Cependant, il y a

une valeur qu'elle peut retourner qui n'est pas un caractre, macro habituellement dnie comme valant un

char EOF (qui


un

tendu une valeur ressemble

int FFFFFFFF en

(qui

EOF. C'est une 1. Donc, fgetc() retourne soit ressemble 000000xx en hexa) soit char. fgetc() renvoie char. Le FFFFFFFF seront

hexa).

Le problme avec le programme de la Figure 2.2 est que

les bits de poids fort pour faire tenir la valeur de l'int dans le seul problme est que les nombres (en hexa) tous deux tronqu pour donner l'octet distinguer la lecture de l'octet

int,

mais cette valeur est stocke dans un

Le C tronquera alors

FF

000000FF et FF. Donc, la boucle while ne peut pas char est ch est compar avec EOF. Comme

dans le chier et la n de ce chier.

Ce que fait exactement le code dans ce cas change selon que le sign ou non. Pourquoi ? Parce que ligne 2,

2.1.

TRAV AILLER A VEC LES ENTIERS

35

EOF est un int1 , ch sera tendu un int an que les deux valeurs compares
soient de la mme taille . Comme la Figure 2.1 le montrait, le fait qu'une variable soit signe ou non est trs important. Si le est jamais ! So le

char n'est pas sign, FF est tendu et donne 000000FF. Cette valeur compare EOF (FFFFFFFF) et est dirente. Donc la boucle ne nit char
est sign,

FF

est tendu et donne

FFFFFFFF.

Les deux valeurs

sont alors gale et la boucle se termine. Cependant, comme l'octet La solution ce problme est de dnir la variable pas un

FF

peut

avoir t lu depuis le chier, la boucle peut se terminer prmaturment.

char.

ch

comme un

int,

Dans ce cas, aucune troncation ou extension n'est eectue

la ligne 2. Dans la boucle, il est sans danger de tronquer la valeur puisque

ch doit

alors tre rellement un octet.

2.1.3 Arithmtique en complment deux


Comme nous l'avons vu plus tt, l'instruction tion et l'instruction capacit) et

sub

add

eectue une addi(dpassement de

eectue une soustraction. Deux des bits du registre

EFLAGS que ces instructions positionnent sont

carry ag

overow

(retenue). Le drapeau d'overow est 1 si le rsultat

rel de l'opration est trop grand et ne tient pas dans la destination pour l'arithmtique signe. Le drapeau de retenue est 1 s'il y a une retenue dans le bit le plus signicatif d'une addition d'une soustraction. Donc, il peut tre utilis pour dtecter un dpassement de capacit en arithmtique non signe. L'utilisation du drapeau de retenue pour l'arithmtique signe va tre vu sous peu. Un des grands avantages du complment 2 est qu les rgles pour l'addition et la soustraction sont exactement les mmes que pour les entiers non signs. Donc, signs ou non.

add

et

sub

peuvent tre utilises pour les entiers

002C + FFFF 002B

44 + (1) 43

Il y a une retenue de gnre mais elle ne fait pas partie de la rponse. Il y a deux instructions de multiplication et de division direntes. Tout d'abord, pour multiplier, utilisez les instructions

MUL

MUL

ou

est utilise pour multiplier les nombres non signs et

IMUL. L'instruction IMUL est utili-

se pour multiplier les entiers signs. Pourquoi deux instructions direntes sont ncessaires ? Les rgles pour la multiplication sont direntes pour les nombres non signs et les nombres signs en complment 2. Pourquoi cela ?

Il est courant de penser que les chiers ont un carctre EOF comme dernier caractre. Ce n'est pas vrai ! 2 La raison de cette ncessit sera expose plus tard.

36

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

dest

source1
reg/mem8 reg/mem16 reg/mem32

source2

Action
AX = AL*source1 DX:AX = AX*source1 EDX:EAX = EAX*source1 dest *= source1 dest *= source1 dest *= immed8 dest *= immed8 dest *= immed16 dest *= immed32

reg16 reg32 reg16 reg32 reg16 reg32 reg16 reg32 reg16 reg32

reg/mem16 reg/mem32 immed8 immed8 immed16 immed32 reg/mem16 reg/mem32 reg/mem16 reg/mem32 immed8 immed8 immed16 immed32

dest = source1*source2 dest = source1*source2 dest = source1*source2 dest = source1*source2

Tab. 2.2 

Instructions imul

Considrons la multiplication de l'octet FF avec lui-mme donnant un rsultat sur un mot. En utilisant la multiplication non signe, il s'agit de 255 fois 255 soit 65025 (ou FE01 en hexa). En utilisant la multiplication signe, il s'agit de

fois

soit 1 (ou 0001 en hexa).

Il y a plusieurs formes pour les instructions de multiplication. La plus ancienne ressemble cela :

mul
La

source
est soit un registre soit une rfrence mmoire. Cela ne peut pas

source

tre une valeur immdiate. Le type exact de la multiplication dpend de la taille de l'oprande source. Si l'oprande est un octet, elle est multiplie par l'octet situ dans le registre AL et le rsultat est stock dans les 16 bits de AX. Si la source fait 16 bits, elle est multiplie par le mot situ dans AX et le rsultat 32 bits est stock dans DX:AX. Si la source fait 32 bits, elle est multiplie par EAX et le rsultat 64 bits est stock dans EDX:EAX. L'instruction

IMUL

a les mmes formats que

MUL,

mais ajoute galement

d'autres formats d'instruction. Il y a des formats deux et trois oprandes :

imul imul

dest, source1 dest, source1, source2 DIV


et

Le Tableau 2.2 montre les combinaisons possibles. Les deux oprateurs de division sont format gnral est :

IDIV.

Il eectuent respec-

tivement une division entire non signe et une division entire signe. Le

div

source

2.1.

TRAV AILLER A VEC LES ENTIERS

37

Si la source est sur 8 bits, alors AX est divis par l'oprande. Le quotient est stock dans AL et le reste dans AH. Si la source est sur 16 bits, alors DX:AX est divis par l'oprande. Le quotient est stock dans AX et le reste dans DX. Si la source est sur 32 bits, alors EDX:EAX est divis par l'oprande, le quotient est stock dans EAX et le reste dans EDX. L'instruction fonctionne de la mme faon. Il n'y a pas d'instructions pour

IMUL.

IDIV IDIV spciales comme

Si le quotient est trop grand pour tenir dans son registre ou que

le diviseur vaut 0, le programme et interrompu et se termine. Une erreur courante est d'oublier d'initialiser DX ou EDX avant la division. L'instruction

NEG

inverse son oprande en calculant son complment

deux. L'oprande peut tre n'importe quel registre ou emplacement mmoire sur 8 bits, 16 bits, or 32 bits.

2.1.4 Programme exemple


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

%include "asm_io.inc" segment .data prompt db square_msg db cube_msg db cube25_msg db quot_msg db rem_msg db neg_msg db segment .bss input resd 1

math.asm ; Chaines affichees "Entrez un nombre : ", 0 "Le carre de l'entree vaut ", 0 "Le cube de l'entree vaut ", 0 "Le cube de l'entree fois 25 vaut ", 0 "Le quotient de cube/100 vaut ", 0 "Le reste de cube/100 vaut ", 0 "La ngation du reste vaut ", 0

segment .text global _asm_main _asm_main: enter 0,0 pusha mov call call mov imul mov eax, prompt print_string read_int [input], eax eax ebx, eax

; routine d'initialisation

; edx:eax = eax * eax ; sauvegarde le rsultat dans ebx

38

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

mov call mov call call mov imul mov call mov call call imul mov call mov call call mov cdq mov idiv mov mov call mov call call mov call mov call call neg mov call mov call

eax, square_msg print_string eax, ebx print_int print_nl ebx, eax ebx, [input] eax, cube_msg print_string eax, ebx print_int print_nl ecx, ebx, 25 eax, cube25_msg print_string eax, ecx print_int print_nl eax, ebx ecx, 100 ecx ecx, eax eax, quot_msg print_string eax, ecx print_int print_nl eax, rem_msg print_string eax, edx print_int print_nl edx eax, neg_msg print_string eax, edx print_int ; ebx *= [entree]

; ecx = ebx*25

; ; ; ;

initialise edx avec extension de signe on ne peut pas diviser par une valeur immdiate edx:eax / ecx sauvegarde le rsultat dans ecx

; inverse le reste

2.1.

TRAV AILLER A VEC LES ENTIERS

39

70 71 72 73 74 75

call popa mov leave ret

print_nl eax, 0 ; retour au C math.asm

2.1.5 Arithmtique en prcision tendue


Le langage assembleur fournit galement des instructions qui permettent d'eectuer des additions et des soustractions sur des nombres plus grands que des doubles mots. Ces instructions utilisent le drapeau de retenue. Comme nous l'avons dit plus haut, les instructions

ADD

et

SUB

modient le drapeau

de retenue si une retenue est gnre. Cette information stocke dans le drapeau de retenue peut tre utilise pour additionner ou soustraire de grands nombres en morcelant l'opration en doubles mots (ou plus petit). Les instructions peau de retenue. L'instruction

ADC eectue l'opration suivante : oprande1 = oprande1 + drapeau de retenue + oprande2 SBB
eectue :

ADC

et

SBB

utilisent les informations donnes par le dra-

L'instruction

oprande1 = oprande1 - drapeau de retenue - oprande2


Comment sont elles utilises ? Considrons la somme d'entiers 64 bits dans EDX:EAX et EBX:ECX. Le code suivant stockerait la somme dans EDX:EAX :

1 2

add adc

eax, ecx edx, ebx

; additionne les 32 bits de poids faible ; additionne les 32 bits de poids fort et la retenue

La soustraction est trs similaire. Le code suivant soustrait EBX:ECX de EDX:EAX :

1 2

sub sbb

eax, ecx edx, ebx

; soustrait les 32 bits de poids faible ; soustrait les 32 bits de poids fort et la retenue

Pour les nombres truction

vraiment

grands, une boucle peut tre utilise (voir

Section 2.2). Pour une boucle de somme, il serait pratique d'utiliser l'ins-

ADC

pour toutes les itrations (au lieu de toutes sauf la premire).

Cela peut tre fait en utilisant l'instruction

CLC

(CLear Carry) juste avant

que la boucle ne commence pour initialiser le drapeau de retenue 0. Si le drapeau de retenue vaut 0, il n'y a pas de dirence entre les instructions

ADD

et

ADC.

La mme ide peut tre utilise pour la soustraction.

40

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

2.2

Structures de Contrle

Les langages de haut niveau fournissent des structures de contrle de haut niveau (

p.e., les instructions if

et

while ) qui contrlent le ot d'excution. Le est


possible d'crire des pro-

langage assembleur ne fournit pas de structures de contrle aussi complexes. Il utilise la place l'infme

goto

et l'utiliser de manire inapproprie peut

conduire du code spaghetti ! Cependant, il

grammes structurs en assembleur. La procdure de base est de concevoir la logique du programme en utilisant les structures de contrle de haut niveau habituelles et de traduire cette conception en langage assembleur (comme le ferait un compilateur).

2.2.1 Comparaison
Les structures de contrle dcident ce qu'il faut faire en comparant des donnes. En assembleur, le rsultat d'une comparaison est stock dans le registre FLAGS pour tre utilis plus tard. Le 80x86 fournit l'instruction

CMP pour eectuer des comparaisons. Le registre FLAGS est positionn selon la dirence entre les deux oprandes de l'instruction CMP. Les oprandes
sont soustraites et le registre FLAGS est positionn selon le rsultat, mais ce rsultat n'est stock l'instruction

SUB

nulle part.
CMP.

Si vous avez besoin du rsultat, utilisez

au lieu de

Pour les entiers non signs, il y a deux drapeaux (bits dans le registre FLAGS) qui sont importants : les drapeaux zro (ZF) et retenue (CF) . Le drapeau zero est allum (1) si le rsultat de la dirence serait 0. Considrons une comparaison comme :

cmp

vleft, vright vleft - vright


est calcule et les drapeaux sont positionns

La dirence

en fonction. Si la dirence calcule par ZF est allum(

i.e.

1) et CF est teint (

teint et CF galement. Si allum (retenue).

vleft < vright,

i.e.

CMP

vaut 0, 0). Si

vleft = vright, alors vleft > vright, ZF est

alors ZF est teint et CF est

Pour les entiers signs, il y a trois drapeaux importants : le drapeau


Pourquoi

vleft > vright ?

SF

OF

si zro (ZF), le drapeau d'overow (OF) et le drapeau de signe (SF). Le dra-

S'il n'y peau d'overow est allum si le rsultat d'une opration dpasse la capaa pas d'overow, alors cit (de trop ou de trop peu). Le drapeau de signe est allum si le rsultat la dirence aura la vad'une opration est ngatif. Si vleft = vright, ZF est allum (comme pour leur correcte et doit tre les entiers non signs). Si vleft > vright, ZF est teint et SF = OF. Si positive ou nulle. Donc, vleft < vright, ZF est teint et SF = OF. SF = OF = 0. Par contre, N'oubliez pas que d'autres instructions peuvent aussi changer le registre s'il y a un overow, la dirence valeur en fait n'aura pas (et la FLAGS, pas seulement

CMP.

correcte

sera Donc,

ngative).

SF = OF = 1.

2.2.

STRUCTURES DE CONTRLE

41

2.2.2 Instructions de branchement


Les instructions de branchement peuvent transfrer l'excution n'importe quel point du programme. En d'autres termes, elles agissent comme un

goto. Il y a deux types de branchements : conditionnel et inconditionnel. Un

branchement inconditionnel est exactement comme un goto, il eectue toujours le branchement. Un branchement conditionnel peut eectuer le branchement ou non selon les drapeaux du registre FLAGS. Si un branchement conditionnel n'eecute pas le branchement, le contrle passe l'instruction suivante. L'instruction

JMP

(abbrviation de

jump )

eectue les branchements in-

conditionnels. Son seul argument est habituellement l'

tiquette de code

de

l'instruction laquelle se brancher. L'assembleur ou l'diteur de liens remplacera l'tiquette par l'adresse correcte de l'instruction. C'est une autre des oprations pnibles que l'assembleur fait pour rendre la vie du programmeur plus simple. Il est important de raliser que l'instruction immdiatement aprs l'instruction

JMP ne sera jamais excute moins qu'une autre instruc-

tion ne se branche dessus ! Il y a plusieurs variantes de l'instruction de saut :

SHORT

Ce saut est trs limit en porte. Il ne peut bouger que de plus

ou moins 128 octets en mmoire. L'avantage de ce type de saut est qu'il utilise moins de mmoire que les autres. Il utilise un seul octet sign pour stocker le

dplacement

du saut. Le dplacement est le

nombre d'octets sauter vers l'avant ou vers l'arrire (le dplacement est ajout EIP). Pour spcier un saut court, utilisez le mot cl immdiatement avant l'tiquette dans l'instruction

JMP.

SHORT

NEAR

Ce saut est le saut par dfaut, que ce soit pour les branchements

inconditionnels ou conditionnels, il peut tre utilis pour sauter vers n'importe quel emplacement dans un segment. En fait, le 80386 supporte deux types de sauts proches. L'un utilise 2 ocets pour le dplacement. Cela permet de se dplacer en avant ou en arrire d'environ 32 000 octets. L'autre type utilise quatre octets pour le dplacement, ce qui, bien sr, permet de se dplacer n'importe o dans le segment. Le type de saut deux octets peut tre spci en plaant le mot cl

WORD

avant l'tiquette dans l'instruction

JMP.

FAR

Ce saut permet de passer le contrle un autre segment de code. C'est une chose trs rare en mode protg 386.

Les tiquettes de code valides suivent les mmes rgles que les tiquettes de donnes. Les tiquettes de code sont dnies en les plaant dans le segment de code au dbut de l'instruction qu'ils tiquettent. Deux points sont placs la n de l'tiquette lors de sa dnition. Ces deux points ne font du nom.

pas

partie

42

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

JZ JNZ JO JNO JS JNS JC JNC JP JNP

saute uniquement si ZF est allum saute uniquement si ZF est teint saute uniquement si OF est allum saute uniquement si OF est teint saute uniquement si SF est allum saute uniquement si SF est teint saute uniquement si CF est allum saute uniquement si CF est teint saute uniquement si PF est allum saute uniquement si PF est teint

Tab. 2.3  Branchements Conditionnels Simples

Il y a beaucoup d'instructions de branchement conditionnel direntes. Elles prennent toutes une tiquette de code comme seule oprande. Les plus simples se contentent de regarder un drapeau dans le registre FLAGS pour dterminer si elles doivent sauter ou non. Voyez le Tableau 2.3 pour une liste de ces instructions (PF est le Le pseudo-code suivant :

drapeau de parit

qui indique si le nombre de

bits dans les 8 bits de poids faible du rsultat est pair ou impair).

if ( EAX == 0 ) EBX = 1; else EBX = 2;


peut tre crit de cette faon en assembleur :

1 2 3 4 5 6 7

cmp jz mov jmp thenblock: mov next:

eax, 0 thenblock ebx, 2 next ebx, 1

; ; ; ;

positionne les drapeaux (ZF allum si eax - 0 = 0) si ZF est allum, branchement vers thenblock partie ELSE du IF saute par dessus la partie THEN du IF

; partie THEN du IF

Les autres comparaisons ne sont pas si simples si l'on se contente des branchements conditionnels du Tableau 2.3. Pour illustrer, considrons le pseudo-code suivant :

if ( EAX >= 5 ) EBX = 1; else EBX = 2;

2.2.

STRUCTURES DE CONTRLE

43

Sign
JE JNE JL, JNGE JLE, JNG JG, JNLE JGE, JNL saute si saute si saute si saute si saute si saute si

vleft vleft vleft vleft vleft vleft

= vright = vright < vright vright > vright vright

Non Sign
JE JNE JB, JNAE JBE, JNA JA, JNBE JAE, JNB saute si saute si saute si saute si saute si saute si

vleft vleft vleft vleft vleft vleft

= vright = vright < vright vright > vright vright

Tab. 2.4  Instructions de Comparaison Signes et Non Signes

Si EAX est plus grand ou gal 5, ZF peut tre allum ou non et SF sera gal OF. Voici le code assembleur qui teste ces conditions (en supposant que EAX est sign) :

1 2 3 4 5 6 7 8 9 10 11 12

cmp js jo jmp signon: jo elseblock: mov jmp thenblock: mov next:

eax, 5 signon elseblock thenblock thenblock ebx, 2 next ebx, 1

; goto signon si SF = 1 ; goto elseblock si OF = 1 et SF = 0 ; goto thenblock si SF = 0 et OF = 0 ; goto thenblock si SF = 1 et OF = 1

Le code ci-dessus est trs confus. Heureusement, le 80x86 fournit des instructions de branchement additionnelles pour rendre ce type de test

coup

beau-

plus simple. Il y a des versions signes et non signes de chacune. Le

Tableau 2.4 montre ces instructions. Les branchements gal et non gal (JE et JNE) sont les mmes pour les entiers signs et non signs (en fait, JE et JNE sont rellement identiques JZ et JNZ, respectivement). Chacune des autres instructions de branchement a deux synonymes. Par exemple, regardez JL (jump less than, saut si infrieur ) et JNGE (jump not greater than or equal to, saut si non plus grand ou gal). Ce sont les mmes instructions car :

x < y = not(x y )

below

Les branchements non signs utilisent A pour (en dessous) la place de L et G.

above

(au dessus) et B pour

En utilisant ces nouvelles instructions de branchement, le pseudo-code ci-dessus peut tre traduit en assembleur plus facilement.

44

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

1 2 3 4 5 6 7

cmp jge mov jmp thenblock: mov next:

eax, 5 thenblock ebx, 2 next ebx, 1

2.2.3 Les instructions de boucle


Le 80x86 fournit plusieurs instructions destines implmenter des boucles de style

for. Chacune de ces instructions prend une tiquette de code comme

seule oprande.

LOOP Decrmente ECX, si ECX = 0, saute vers l'tiquette LOOPE, LOOPZ Dcremente ECX (le registre FLAGS n'est pas modi),
si ECX

0 et ZF = 1, saute Dcremente ECX (FLAGS inchang), si ECX

LOOPNE, LOOPNZ
et ZF = 0, saute

Les deux dernires instructions de boucle sont utiles pour les boucles de recherche squentielle. Le pseudo-code suivant :

sum = 0; for ( i=10; i >0; i ) sum += i;


peut tre traduit en assembleur de la faon suivante :

1 2 3 4 5

mov mov loop_start: add loop


2.3

eax, 0 ecx, 10 eax, ecx loop_start

; eax est sum ; ecx est i

Traduire les Structures de Contrle Standards

Cette section dcrit comment les structures de contrle standards des langages de haut niveau peuvent tre implmentes en langage assembleur.

2.3.1 Instructions if
Le pseudo-code suivant :

2.3.

TRADUIRE LES STRUCTURES DE CONTRLE STANDARDS 45

if ( condition ) then_block; else else_block;


peut tre implment de cette faon :

1 2 3 4 5 6 7

; code jxx ; code jmp else_block: ; code endif:

pour positionner FLAGS else_block ; choisir xx afin de sauter si la condition est fausse pour le bloc then endif pour le bloc else else_block peut tre remplac par

S'il n'y a pas de else, le branchement un branchement

endif.

1 2 3 4

; code pour positionner FLAGS jxx endif ; choisir xx afin de sauter si la condition est fausse ; code pour le bloc then endif:

2.3.2 Boucles while


while est une boucle avec un test au dbut : while ( condition ) { corps de la boucle; }
La boucle Cela peut tre traduit en :

1 2 3 4 5 6

while: ; code jxx ; body jmp endwhile:

pour positionner FLAGS selon la condition endwhile ; choisir xx pour sauter si la condition est fausse of loop while

2.3.3 Boucles do while


La boucle

do while

est une boucle avec un test la n :

do { corps de la boucle; } while ( condition );

46

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

unsigned guess; / candidat courant pour tre premier / unsigned factor ; / diviseur possible de guess / unsigned limit ; / rechercher les nombres premiers jusqu' cette valeur /
printf ("Rechercher les nombres premier jusqu'a : "); scanf("%u", &limit); printf ("2\n"); / traite les deux premier nombres premiers comme / printf ("3\n"); / des cas spciaux / guess = 5; / candidat initial / while ( guess <= limit ) { / recherche un diviseur du candidat / factor = 3; while ( factor factor < guess && guess % factor != 0 ) factor += 2; if ( guess % factor != 0 ) printf ("%d\n", guess); guess += 2; / ne regarder que les nombres impairs / }
Fig. 2.3 

Cela peut tre traduit en :

1 2 3 4

do:

; corps de la boucle ; code pour positionner FLAGS selon la condition jxx do ; choisir xx pour se brancher si la condition est vraie

2.4

Exemple : Trouver des Nombres Premiers

Cette section dtaille un programme qui trouve des nombres premiers. Rappelez vous que les nombres premiers ne sont divisibles que par 1 et par eux-mmes. Il n'y a pas de formule pour les rechercher. La mthode de base qu'utilise ce programme est de rechercher les diviseurs de tous les nombres impairs

3 sous une limite donne. Si aucun facteur ne peut tre trouv pour

un nombre impair, il est premier. La Figure 2.3 montre l'algorithme de base crit en C. Voici la version assembleur :

2 est le seul nombre premier pair.

2.4.

EXEMPLE : TROUVER DES NOMBRES PREMIERS

47

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

%include "asm_io.inc" segment .data Message db segment .bss Limit Guess resd resd

prime.asm "Rechercher les nombres premiers jusqu'a : ", 0 1 1 ; rechercher les nombres premiers jusqu' cette limite ; candidat courant pour tre premier

segment .text global _asm_main _asm_main: enter 0,0 pusha mov call call mov mov call call mov call call mov while_limit: mov cmp jnbe mov while_factor: mov mul jo cmp jnb mov mov eax, Message print_string read_int [Limit], eax eax, 2 print_int print_nl eax, 3 print_int print_nl dword [Guess], 5 eax,[Guess] eax, [Limit] end_while_limit ebx, 3 eax,ebx eax end_while_factor eax, [Guess] end_while_factor eax,[Guess] edx,0

; routine d'intialisation

; scanf("%u", &limit); ; printf("2\n"); ; printf("3\n");

; Guess = 5; ; while ( Guess <= Limit ) ; on utilise jnbe car les nombres ne sont pas signes ; ebx est factor = 3; ; edx:eax = eax*eax ; si la rponse ne tient pas dans eax seul ; si !(factor*factor < guess)

48

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61

div cmp je

ebx edx, 0 end_while_factor

; edx = edx:eax % ebx ; si !(guess % factor != 0) ; factor += 2; ; si !(guess % factor != 0) ; printf("%u\n")

add ebx,2 jmp while_factor end_while_factor: je end_if mov eax,[Guess] call print_int call print_nl end_if: add dword [Guess], 2 jmp while_limit end_while_limit: popa mov leave ret eax, 0

; guess += 2

; retour au C prime.asm

Chapitre 3

Oprations sur les Bits


3.1 Oprations de Dcalage
Le langage assembleur permet au programmeur de manipuler individuellement les bits des donnes. Une opration courante sur les bits est appele

dcalage. Une opration de dcalage dplace la position des bits d'une donne. Les dcalages peuvent tre soit vers la gauche (i.e. vers les bits les plus
signicatifs) soit vers la droite (les bits les moins signicatifs).

3.1.1 Dcalages logiques


Le dcalage logique est le type le plus simple de dcalage. Il dcale d'une manire trs simple. La Figure 3.1 montre l'exemple du dcalage d'un nombre sur un octet. Original Dcal gauche Dcal droite 1 1 0 1 1 1 1 0 1 0 1 1 1 0 0 0 1 1 1 0 0 0 0 1

Fig. 3.1  Dcalages logiques

Notez que les nouveaux bits sont toujours 0. Les instructions

SHR

SHL

et

sont respectivement utilises pour dcaler gauche et droite. Ces

instructions permettent de dcaler de n'importe quel nombre de positions. Le nombre de positions dcaler peut soit tre une contante, soit tre stock dans le registre

CL.

Le dernier bit dcal de la donne est stock dans le

drapeau de retenue. Voici quelques exemples :

1 2 3 4

mov shl shr shr

ax, ax, ax, ax,

0C123H 1 1 1

; dcale d'1 bit gauche, ax = 8246H, CF = 1 ; dcale d'1 bit droite, ax = 4123H, CF = 0 ; dcale d'1 bit droite, ax = 2091H, CF = 1
49

50

CHAPITRE 3.

OPRATIONS SUR LES BITS

5 6 7 8

mov shl mov shr

ax, ax, cl, ax,

0C123H 2 3 cl

; dcale de 2 bits gauche, ax = 048CH, CF = 1 ; dcale de 3 bits droite, ax = 0091H, CF = 1

3.1.2 Utilisation des dcalages


La multiplication et la division rapides sont les utilisations les plus courantes des oprations de dclage. Rappelez vous que dans le systme dcimal, la multiplication et la division par une puissance de dix sont simples, on dcale simplement les chires. C'est galement vrai pour les puissances de deux en binaire. Par exemple, pour multiplier par deux le nombre binaire

10112 (ou 11 en dcimal), dcalez d'un cran vers la gauche pour obtenir 101102 (ou
22). Le quotient d'un division par une puissance de deux est le rsultat d'un dcalage droite. Pour diviser par deux, utilisez un dcalage d'une position droite ; pour diviser par 4 (2 ), dcalez droite de 2 positions ; pour divi-

3 ser par 8 (2 ), dcalez de 3 positions vers la droite,


dcalage sont trs basiques et sont

MUL

et

DIV

beaucoup plus rapides que les instructions

etc. Les instructions de

correspondantes !

En fait, les dcalages logiques peuvent tre utiliss pour multiplier ou diviser des valeurs non signes. Il ne fonctionnent gnralement pas pour les valeurs signes. Considrons la valeur sur deux octets FFFF (1 sign). Si on lui applique un dcalage logique d'une position vers la droite, le rsultat est 7FFF ce qui fait

+32, 767 !

Un autre type de dcalage doit tre utilis

pour les valeurs signes.

3.1.3 Dcalages arithmtiques


Ces dcalages sont conus pour permettre des nombres signs d'tre rapidement multiplis et diviss par des puissances de 2. Il assurent que le bit de signe est trait correctement.

SAL

Shift Arithmetic Left (Dcalage Arithmtique Gauche) - Cette instruction est juste un synonyme pour ner exactement le mme code machine que

SHL. Elle est assemble pour donSHL. Tant que le bit de signe

n'est pas chang par le dcalage, le rsultat sera correct.

SAR

Shift Arithmetic Right (Dcalage Arithmtique Droite) - C'est une nouvelle instruction qui ne dcale pas le bit de signe (

i.e.

le msb) de

son oprande. Les autres bits sont dcals normalement sauf que les nouveaux bits qui entrent par la gauche sont des copies du bit de signe (c'est--dire que si le bit de signe est 1, les nouveaux bits sont galement 1). Donc, si un octet est dcal avec cette instruction, seuls les 7 bits de poids faible sont dcals. Comme pour les autres dcalages, le dernier bit sorti est stock dans le drapeau de retenue.

3.1.

OPRATIONS DE DCALAGE

51

1 2 3 4

mov sal sal sar

ax, ax, ax, ax,

0C123H 1 1 2

; ax = 8246H, CF = 1 ; ax = 048CH, CF = 1 ; ax = 0123H, CF = 0

3.1.4 Dcalages circulaires


Les instructions de dcalage circulaire fonctionnent comme les dcalages logiques except que les bits perdus un bout sont rintgrs l'autre. Donc, la donne est traite comme s'il s'agissait d'une structure circulaire. Les deux instructions de rotation les plus simples sont

ROL

et

ROR

qui eectuent

des rotations gauche et droite, respectivement. Comme pour les autres dcalages, ceux-ci laissent une copie du dernier bit sorti dans le drapeau de retenue.

1 2 3 4 5 6

mov rol rol rol ror ror

ax, ax, ax, ax, ax, ax,

0C123H 1 1 1 2 1

; ; ; ; ;

ax ax ax ax ax

= = = = =

8247H, 048FH, 091EH, 8247H, C123H,

CF CF CF CF CF

= = = = =

1 1 0 1 1
et

Il y a deux instructions de rotation supplmentaires qui dcalent les bits de la donne et le drapeau de retenue appeles on applique au registre aux 17 bits constitus de

AX une rotation avec ces instructions, AX et du drapeau de retenue. ; ; ; ; ; ;

RCL

RCR.

Par exemple, si elle applique

1 2 3 4 5 6 7

mov clc rcl rcl rcl rcr rcr

ax, 0C123H ax, ax, ax, ax, ax, 1 1 1 2 1

eteint le drapeau de retenue (CF = 0) ax = 8246H, CF = 1 ax = 048DH, CF = 1 ax = 091BH, CF = 0 ax = 8246H, CF = 1 ax = C123H, CF = 0

3.1.5 Application simple


(

i.e. 1) dans le registre EAX.


mov mov bl, 0 ecx, 32

Voici un extrait de code qui compte le nombre de bits qui sont allums

1 2

; bl contiendra le nombre de bits ALLUMES ; ecx est le compteur de boucle

52

CHAPITRE 3.

OPRATIONS SUR LES BITS

X
0 0 1 1

Y
0 1 0 1

ET 0 0 0 1

Tab. 3.1  L'opration ET

1 ET 1 1

0 1 0

1 0 0

0 0 0

1 1 1

0 0 0

1 0 0

0 1 0

Fig. 3.2  Eectuer un ET sur un octet

3 4 5 6 7 8

count_loop: shl jnc inc skip_inc: loop

eax, 1 skip_inc bl count_loop

; decale un bit dans le drapeau de retenue ; si CF == 0, goto skip_inc

Le code ci-dessus dtruit la valeur originale de remplace par

la boucle). Si l'on voulait conserver la valeur de

rol eax, 1.

EAX (EAX vaut zro la n de EAX, la ligne 4 pourrait tre

3.2

Oprations Boolennes Niveau Bit

table de vrit

Il y a quatre oprateurs boolens courants :

AND, OR, XOR et NOT. Une

montre le rsultat de chaque opration pour chaque valeur

possible de ses oprandes.

3.2.1 L'opration ET
Le rsultat d'un Tableau 3.1. Les processeurs supportent ces oprations comme des instructions agissant de faon indpendante sur tous les bits de la donne en parallle. Par exemple, si on applique un

ET

sur deux bits vaut 1 uniquement si les deux bits

sont 1, sinon, le rsultat vaut 0, comme le montre la table de vrit du

ET

au contenu de

AL

et

BL,

l'opration

ET

de

base est applique chacune des 8 paires de bits correspondantes dans les deux registres, comme le montre la Figure 3.2. Voici un exemple de code :

1 2

mov and

ax, 0C123H ax, 82F6H

; ax = 8022H

3.2.

OPRATIONS BOOLENNES NIVEAU BIT

53

X
0 0 1 1

Y
0 1 0 1

OU 0 1 1 1

Tab. 3.2  L'opration OU

X
0 0 1 1

Y
0 1 0 1

XOR 0 1 1 0

Tab. 3.3  L'opration XOR

3.2.2 L'opration OU
Le

OU

de 2 bits vaut 0 uniquement si les deux bits valent 0, sinon le

rsultat vaut 1 comme le montre la table de vrit du Tableau 3.2. Voici un exemple de code :

1 2

mov or

ax, 0C123H ax, 0E831H

; ax = E933H

3.2.3 L'opration XOR


Le

OU

exclusif de 2 bits vaut 0 uniquement si les deux bits sont gaux,

sinon, le rsultat vaut 1 comme le montre la table de vrit du Tableau 3.3. Voici un exemple de code :

1 2

mov xor

ax, 0C123H ax, 0E831H

; ax = 2912H

3.2.4 L'opration NOT


L'opration

NOT

est une opration

oprande, pas deux comme les oprations Tableau 3.4. Voici un exemple de code :

unaire (i.e. elle agit sur une seule binaires de type ET ). Le NOT

d'un bit est la valeur oppose du bit comme le montre la table de vrit du

1 2

mov not

ax, 0C123H ax

; ax = 3EDCH

54

CHAPITRE 3.

OPRATIONS SUR LES BITS

X
0 1

NOT 1 0

Tab. 3.4  L'opration NOT

Allumer le bit

i i i

Eectuer un d'allum) Eectuer un

OU ET

sur le nombre avec

2i

(qui

est le nombre binaire avec uniquement le bit Eteindre le bit

sur le nombre avec le nombre

binaire qui n'a que le bit Inverser le bit Eectuer un

d'teint. Cette op-

rande est souvent appele

XOR

masque

sur le nombre avec

2i

Tab. 3.5  Utilisation des oprations boolennes

Notez que le

NOT

donne le complment un. Contrairement aux autres

oprations niveau bit, l'instruction

FLAGS.

NOT ne change aucun des bits du registre

3.2.5 L'instruction TEST


L'instruction

TEST

eectue une opration

rsultat. Elle positionne le registre ne fait que positionner allum.

aprs un AND (comme l'instruction

FLAGS). Par exemple, si le rsultat tait zro, ZF serait

FLAGS selon ce que ce CMP qui eectue une

AND

mais ne stocke pas le dernier aurait t soustraction mais

3.2.6 Utilisation des oprations sur les bits


Les oprations sur les bits sont trs utiles pour manipuler individuellement les bits d'une donne sans modier les autres bits. Le Tableau 3.5 montre trois utilisations courantes de ces oprations. Voici un exemple de code, implmentant ces ides.

1 2 3 4 5 6 7 8

mov or and xor or and xor xor

ax, ax, ax, ax, ax, ax, ax, ax,

0C123H 8 0FFDFH 8000H 0F00H 0FFF0H 0F00FH 0FFFFH

; ; ; ; ; ; ;

allumer le bit 3, ax = C12BH teindre le bit 5, ax = C10BH inverser le bit 31, ax = 410BH allumer un quadruplet, ax = 4F0BH teindre un quadruplet, ax = 4F00H inverser des quadruplets, ax = BF0FH complment 1, ax = 40F0H

3.3.

EVITER LES BRANCHEMENTS CONDITIONNELS

55

L'opration

ET ET

peut aussi tre utilise pour trouver le reste d'une disur le nombre avec un masque valant

vision par une puissance de deux. Pour trouver le reste d'une division par

2i ,

eectuez un

2i 1.

Ce masque

contientra des uns du bit 0 au bit

i 1. Ce sont tout simplement ces bits qui

correspondent au reste. Le resultat du la division de 100 par 16.

ET

conservera ces bits et mettra les

autres zro. Voici un extrait de code qui trouve le quotient et le reste de

1 2 3

mov mov and

eax, 100 ; 100 = 64H ebx, 0000000FH ; masque = 16 - 1 = 15 ou F ebx, eax ; ebx = reste = 4 CL
il est possible de modier n'importe quel(s) bit(s)

En utilisant le registre

d'une donne. Voici un exemple qui allume un bit de allumer est stock dans

BH.

EAX.

Le numro du bit

1 2 3 4

mov mov shl or

cl, bh ebx, 1 ebx, cl eax, ebx

; tout d'abord, construire le nombre pour le OU ; dcalage gauche cl fois ; allume le bit

Eteindre un bit est un tout petit peut plus dur.

1 2 3 4 5

mov mov shl not and

cl, bh ebx, 1 ebx, cl ebx eax, ebx

; tout d'abord, construire le nombre pour le ET ; dcalage gauche cl fois ; inverse les bits ; teint le bit

Le code pour inverser un bit est laiss en exercice au lecteur. Il n'est pas rare de voir l'instruction droutante suivante dans un programme 80x86 :

xor

eax, eax

; eax = 0

Un nombre auquel on applique un truction

XOR avec lui-mme donne toujours zro.

Cette instruction est utilise car son code machine est plus petit que l'ins-

MOV

correspondante.

3.3

Eviter les Branchements Conditionnels

Les processeurs modernes utilisent des techniques trs sophistiques pour excuter le code le plus rapidement possible. Une technique rpandue est appele

excution spculative . Cette technique utilise les possibilits de trai-

tement en parallle du processeur pour excuter plusieurs instructions la

56

CHAPITRE 3.

OPRATIONS SUR LES BITS

1 2 3 4 5 6

mov mov count_loop: shl adc loop

bl, 0 ecx, 32 eax, 1 bl, 0 count_loop

; bl contiendra le nombre de bits allums ; ecx est le compteur de boucle ; dcale un bit dans le drapeau de retenue ; ajoute le drapeau de retenue bl

Fig. 3.3  Compter les bits avec

ADC

fois. Les branchements conditionnels posent un problme ce type de fonctionnement. Le processeur, en gnral, ne sait pas si le branchement sera eectu ou pas. Selon qu'il est eectu ou non, un ensemble d'instructions dirent sera excut. Les processeurs essaient de prvoir si le branchement sera eectu. Si la prvision est mauvaise, le processeur a perdu son temps en excutant le mauvais code. Une faon d'viter ce problme est d'viter d'utiliser les branchements conditionnels lorsque c'est possible. Le code d'exemple de 3.1.5 fournit un exemple simple de la faon de le faire. Dans l'exemple prcdent, les bits allums du registre EAX sont dcompts. Il utilise un branchement pour viter l'instruction de retenue. Les instructions tre retir en utilisant l'instruction

INC. La Figure 3.3 montre comment le branchement peut ADC pour ajouter directement le drapeau SETxx
fournissent un moyen de retirer les branchements

dans certains cas. Ces instructions positionnent la valeur d'un registre ou d'un emplacement mmoire d'un octet zro ou un selon l'tat du registre FLAGS. Les caractres aprs conditionnels. Si la

SET sont les mmes que pour les branchements condition correspondante au SETxx est vraie, le rsultat ; AL = 1 si ZF est allume, sinon 0

stock est un, s'il est faux, zro est stock. Par exemple :

setz

al

En utilisant ces instructions, il est possible de dvelopper des techniques ingnieuses qui calculent des valeurs sans branchement. Par exemple, considrons le problme de la recherche de la plus grande de deux valeurs. L'approche standard pour rsoudre ce problme serait d'utiliser un

CMP

et un branchement conditionnel pour dterminer la valeur la plus

grande. Le programme exemple ci dessous montre comment le maximum peut tre trouv sans utiliser aucun branchement.

1 2

; file: max.asm %include "asm_io.inc"

3.3.

EVITER LES BRANCHEMENTS CONDITIONNELS

57

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

segment .data message1 db "Entrez un nombre : ",0 message2 db "Entrez un autre nombre : ", 0 message3 db "Le plus grand nombre est : ", 0 segment .bss input1 resd 1 ; premier nombre entre

segment .text global _asm_main _asm_main: enter 0,0 pusha mov call call mov mov call call xor cmp setg neg mov and not and or mov call mov call call popa eax, message1 print_string read_int [input1], eax eax, message2 print_string read_int ebx, eax, bl ebx ecx, ecx, ebx ebx, ecx, ebx [input1] ebx eax [input1] ebx

; routine d'initialisation ; affichage du premier message ; saisie du premier nombre ; affichage du second message ; saisie du second nombre (dans eax) ; ; ; ; ; ; ; ; ; ebx = 0 compare le premier et le second nombre ebx = (input2 > input1) ? 1 : ebx = (input2 > input1) ? 0xFFFFFFFF : ecx = (input2 > input1) ? 0xFFFFFFFF : ecx = (input2 > input1) ? input2 : ebx = (input2 > input1) ? 0 : ebx = (input2 > input1) ? 0 : ecx = (input2 > input1) ? input2 :

0 0 0 0 0xFFFFFFFF input1 input1

eax, message3 print_string eax, ecx print_int print_nl

; affichage du rsultat

58

CHAPITRE 3.

OPRATIONS SUR LES BITS

45 46 47

mov leave ret

eax, 0

; retour au C

L'astuce est de crer un masque de bits qui peut tre utilis pour slectionner la valeur correcte pour le maximum. L'instruction

SETG la ligne 30

positionne BL 1 si la seconde saisie est la plus grande ou 0 sinon. Ce n'est pas le masque de bits dsir. Pour crer le masque ncessaire, la ligne 31 utilise l'instruction

NEG sur le registre EBX (notez que EBX a t positionn

0 prcdemment). Si EBX vaut 0, cela ne fait rien ; cependant, si EBX vaut 1, le rsultat est la reprsentation en complment deux de -1 soit 0xFFFFFFFF. C'est exactement le masque de bits dsir. Le code restant utilise ce masque de bits pour slectionner la saisie correspondant au plus grand nombre. Une autre astuce est d'utiliser l'instruction DEC. Dans le code ci-dessus, si NEG est remplac par DEC, le rsultat sera galement 0 ou 0xFFFFFFFF. Cependant, les valeurs sont inverses par rapport l'utilisation de l'instruction

NEG.
3.4 Manipuler les bits en C

3.4.1 Les oprateurs niveau bit du C


Contrairement certains langages de haut niveau, le C fournit des oprateurs pour les oprations niveau bit. L'opration l'oprateur binaire

|. L'opration XOR est reprsente par l'oprateur binaire ^ NOT est reprsente par l'oprateur unaire ~ .
naires

&1 . L'opration OU

ET

est reprsente par . Et l'opration

est reprsente par l'oprateur binaire

Les oprations de dcalage sont eectues au moyen des oprateurs birateur

 et  du C. L'oprateur  eectue les dcalages gauche et l'op eectue les dcalages droite. Ces oprateurs prennent deux opint),

randes. L'oprande de gauche est la valeur dcaler et l'oprande de droite est le nombre de bits dcaler. Si la valeur dcaler est d'un type non sign, un dcalage logique est eectu. Si la valeur est d'un type sign (comme ces oprateurs : alors un dcalage arithmtique est utilis. Voici un exemple en C utilisant

1 2 3 4

short int s ; short unsigned u; s = 1; u = 100;


1

/ on suppose que les short int font 16 bits / / s = 0xFFFF (complment 2) / / u = 0x0064 /

Ces oprateur est dirent des oprateurs && binaire et & unaire !

3.4.

MANIPULER LES BITS EN C

59

Macro

Signication l'utilisateur peut lire l'utilisateur peut crire l'utilisateur peut excuter le groupe peut lire le groupe peut crire le groupe peut excuter les autres peuvent lire les autres peuvent crire les autres peuvent excuter

S_IRUSR S_IWUSR S_IXUSR S_IRGRP S_IWGRP S_IXGRP S_IROTH S_IWOTH S_IXOTH

Tab. 3.6  Macros POSIX pour les Permissions de Fichiers

5 6 7 8 9

u = u | 0x0100; s = s & 0xFFF0; s = s ^ u; u = u << 3; s = s >> 2;

/ u = 0x0164 / / s = 0xFFF0 / / s = 0xFE94 / / u = 0x0B20 (dcalage logique) / / s = 0xFFA5 (dcalage arithmetique) /

3.4.2 Utiliser les oprateurs niveau bit en C


Les oprateurs niveau bit sont utiliss en C pour les mmes raisons qu'ils le sont en assembleur. Ils permettent de manipuler les bits d'une donne individuellement et peuvent tre utiliss pour des multiplications et des divisions rapides. En fait, un compilateur C malin utilisera automatiquement un dcalage pour une multiplication du type Beaucoup d'API

2 de systmes d'exploitation (comme

x *= 2.

POSIX 3 et Win32) utilisa-

contiennent des fonctions qui utilisent des oprandes donc les donnes sont codes sous forme de bits. Par exemple, les systmes POSIX conservent les permissions sur les chiers pour trois dirents types d'utilisateurs :

teur

(un nom plus appropri serait

propritaire ), groupe

et

autres.

Chaque

type d'utilisateur peut recevoir la permission de lire, crire et/ou excuter un chier. Pour changer les permissions d'un chier, le programmeur C doit manipuler des bits individuels. POSIX dnit plusieurs macros pour l'aider (voir Tableau 3.6). La fonction

chmod

peut tre utilise pour dnir les per-

missions sur un chier. Cette fonction prend deux paramtres, une chaine avec le nom du chier modier et un entier

4 avec les bits appropris d'al-

Application Programming Interface, Interface pour la Programmation d'Applications signie Portable Operating System Interface for Computer Environments, Interface Portable de Systme d'Exploitation pour les Environnements Informatiques. Un standard dvelopp par l'IEEE bas sur UNIX. 4 En fait, un paramtre de type mode_t qui est dnit comme entier par un typedef.
3

60

CHAPITRE 3.

OPRATIONS SUR LES BITS

lums pour les permissions dsires. Par exemple, le code ci-dessous dnit les permissions pour permettre au propritaire du chier de lire et d'crire dedans, au groupe de lire le chier et interdire l'accs aux autres.

chmod("foo", S_IRUSR | S_IWUSR | S_IRGRP );


La fonction POSIX

stat

peut tre utilise pour rcuprer les bits de

permission en cours pour un chier. En l'utilisant avec la fonction

chmod,

il est possible de modier certaines des permissions sans changer les autres. Voici un exemple qui retire l'accs en criture aux autres et ajoute les droits de lecture pour le propritaire. Les autres permissions ne sont pas altres.

1 2 3 4

struct stat le_stats ; / structure utilisee par stat () / stat ("foo", & le_stats ); / lit les infos du chier le_stats .st_mode contient les bits de permission / chmod("foo", ( le_stats .st_mode & ~S_IWOTH) | S_IRUSR);
3.5 Reprsentations Big et Little Endian

Le Chapitre 1 a introduit les concepts de reprsentations big et little endian des donnes multioctets. Cependant, l'auteur s'est rendu compte que pour beaucoup de gens, ce sujet est confus. Cette section couvre le sujet plus en dtails. Le lecteur se souvient srement que le caractre big ou little endian fait rfrence l'ordre dans lequel les octets (

pas les bits) d'un lment de donnes gros (big)

multi-octets sont stocks en mmoire. La reprsentation big endian est la mthode la plus intuitive. Elle stocke l'octet le plus signicatif en premier, puis le second octet le plus signicatif, etc. En d'autres termes, les bits sont stocks en premier. La mthode little endian stocke les octets dans l'ordre inverse (moins signcatif en premier). La famille des processeurs x86 utilise la reprsentation little endian. Par exemple, considrons le double mot reprsentant

1234567816 .

En re-

prsentation big endian, les octets seraient stocks 12 34 56 78. En reprsentation little endian, les octets seraient stocks 78 56 34 12. Le lecteur est probablement en train de se demander pourquoi n'importe quelle concepteur de puce sain d'esprit utiliserait la reprsentation little endian ? Les ingnieurs de chez Intel taient-ils sadiques pour iniger une multitude de programmeurs cette reprsentation qui prte confusion ? Il peut sembler que le processeur ait faire du travail supplmentaire pour stocker les octets en mmoire dans l'odre inverse (et pour les rinverser lorsqu'il lit partir de la mmoire). En fait, le processeur ne fait aucun travail supplmentaire pour lire et crire en mmoire en utilisant le format little endian. Il faut comprendre que le processeur est constitu de beaucoup de

3.5.

REPRSENTATIONS BIG ET LITTLE ENDIAN

61

unsigned short word = 0x1234; / on suppose que sizeof ( short) == 2 / unsigned char p = (unsigned char ) &word; if ( p[0] == 0x12 ) printf ("Machine Big Endian\n"); else printf ("Machine Little Endian\n");
Fig. 3.4  Comment dterminer le caractre big ou little endian

circuits lectroniques qui ne travaillent que sur des valeurs de un bit. Les bits (et les octets) peuvent tre dans n'importe quel ordre dans le processeur.

AX. Il peut tre dcompos en AH et AL. Il y a des circuits dans le processeur qui conservent les valeurs de AH and AL. Ces circuits n'ont pas d'ordre dans le processeur. C'est--dire que les circuits pour AH ne sont pas avant ou aprs les circuits pour AL. Une instruction MOV qui copie la valeur de AX en mmoire copie la valeur de AL puis de AH. Ce n'est pas plus dur pour le processeur que de stocker AH en premier.
Considrons le registre de deux octets deux registres d'un octet : Le mme argument s'applique aux bits individuels d'un octet. Il ne sont pas rellement dans un ordre dtermin dans les circuits du processeur (ou en mmoire en ce qui nous concerne). Cependant, comme les bits individuels ne peuvent pas tre adresss dans le processeur ou en mmoire, il n'y a pas de faon de savoir (et aucune raison de s'en soucier) l'ordre dans lequel ils sont conservs l'intrieur du processeur. Le code C de la Figure 3.4 montre comment le caractre big ou little endian d'un processeur peut tre dtermin. Le pointeur comme un tableau de caractres de deux

word

pond au premier octet de

word en mmoire dont la valeur dpend du caractre

p traite la variable lments. Donc, p[0] corres-

big ou little endian du processeur.

3.5.1 Quand se Soucier du Caractre Big ou Little Endian


Pour la programmation courante, le caractre big ou little endian du processeur n'est pas important. Le moment le plus courant o cela devient important est lorsque des donnes binaires sont transfres entre dirents systmes informatiques. Cela se fait habituellement en utilisant un type quelconque de mdia physique (comme un disque) ou via un rseau. Comme les Avec l'avnement des jeux donnes ASCII sont sur un seul octet, le caractre big ou little endian n'est de caractres multioctets comme UNICODE, le pas un problme. Tous les en-ttes internes de TCP/IP stockent les entiers au format big endian (appel
caractre mme big ou little

ordre des octets rseau ).

Les bibliothques TCP/IP orent

endian devient important pour les donnes texte. UNICODE supporte les deux types de reprsentation et a un mcanisme pour indiquer celle qui est utilise pour reprsenter les donnes.

62

CHAPITRE 3.

OPRATIONS SUR LES BITS

1 2 3 4 5 6 7 8 9 10 11 12 13

unsigned invert_endian( unsigned x ) { unsigned invert; const unsigned char xp = (const unsigned char ) &x; unsigned char ip = (unsigned char ) & invert;
ip [0] = ip [1] = ip [2] = ip [3] = } xp [3]; xp [2]; xp [1]; xp [0]; / inverse les octets individuels /

return invert ;

/ renvoie les octets inverses /

Fig. 3.5  Fonction invert_endian

des fonctions C pour rsoudre les problmes de reprsentation big ou little endian d'une faon portable. Par exemple, la fonction La fonction function

htonl ()

convertit un

double mot (ou un entier long) depuis le format hte vers le format rseau.

ntohl () eectue la transformation inverse5 . Pour un sys-

tme big endian les deux fonctions retournent leur paramtre inchang. Cela permet d'crire des programmes rseau qui compileront et s'excuteront correctement sur n'importe quel systme sans tenir compte de la reprsentation utilise. Pour plus d'information sur les reprsentations big et little endian et la programmation rseau, voyez l'excellent livre de W. Richard Steven :

UNIX Network Programming.


truction machine appele

La Figure 3.5 montre une fonction C qui passe d'une reprsentation l'autre pour un double mot. Le processeur 486 a introduit une nouvelle ins-

BSWAP

qui inverse les octets de n'importe quel re-

gistre 32 bits. Par exemple,

bswap

edx XCHG

; change les octets de edx


peut tre utilise pour changer les octets des registres

L'instruction ne peut pas tre utilise sur des registres de 16 bits. Cependant, l'instruction 16 bits pouvant tre dcomposs en registres de 8 bits. Par exemple :

xchg
5

ah,al

; change les octets de ax

En fait, passer d'une reprsentation l'autre pour entier consiste inverser l'ordre des octets ; donc, convertir de little vers big ou de big vers little est la mme opration. Donc, ces deux fonctions font la mme chose.

3.6.

COMPTER LES BITS

63

1 2 3 4 5 6 7 8 9 10

int count_bits( unsigned int data ) { int cnt = 0; while ( data != 0 ) { data = data & (data 1); cnt++; } return cnt;

Fig. 3.6  Compter les Bits : Method Une

3.6

Compter les Bits

Plus haut, nous avons donn une technique intuitive pour compter le nombre de bits allums dans un double mot. Cette section dcrit d'autres mthodes moins directes de le faire pour illustrer les oprations sur les bits dont nous avons parl dans ce chapitre.

3.6.1 Mthode une


La premire mthode est trs simple, mais pas vidente. La Figure 3.6 en montre le code. Comment fonctionne cette mthode ? A chaque itration de la boucle, un bit est teint dans

data.

Quand tous les bits sont teints ((

i.e. lorsque data


data. data

vaut zro), la boucle s'arrte. Le nombre d'itration requises pour mettre

data

zro est gal au nombre de bits dans la valeur original de

La ligne 6 est l'endroit o un bit de

data

est teint. Comment cela

marche ? Considrons la forme gnrale de la reprsentation binaire de aprs ce 1 est zro. Maintenant, que sera la reprsentation de Les bits gauche du 1 le plus droite seront les mme que pour de

et le 1 le plus droite dans cette reprsentation. Par dnition, chaque bit

data - 1 ? data, mais

partir du 1 le plus droite, les bits seront les complments des bits originaux

data. Par exemple : data = xxxxx10000 data - 1 = xxxxx01111 data


avec

o les x sont les mmes pour les deux nombres. Lorsque l'on applique un sur

data - 1,

le rsultat mettra le 1 le plus droite de

data

ET

zro et laissera les autres bits inchangs.

64

CHAPITRE 3.

OPRATIONS SUR LES BITS

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

static unsigned char byte_bit_count[256]; / tableau de recherche / void initialize_count_bits () { int cnt , i , data; for ( i = 0; i < 256; i++ ) { cnt = 0; data = i ; while ( data != 0 ) { / methode une / data = data & (data 1); cnt++; } byte_bit_count[i] = cnt; }

int count_bits( unsigned int data ) { const unsigned char byte = ( unsigned char ) & data; return byte_bit_count[byte[0]] + byte_bit_count[byte[1]] + byte_bit_count[byte[2]] + byte_bit_count[byte [3]];

Fig. 3.7  Mthode Deux

3.6.2 Mthode deux


Un tableau de recherche peut galement tre utilis pour contrer les bits de n'importe quel double mot. L'approche intuitive serait de prcalculer le nombre de bits de chaque double mot et de le stocker dans un tableau. Cependant, il y a deux problmes relatifs cette approche. Il y a peu prs

4 milliards

de valeurs de doubles mots ! Cela signie que le tableau serait

trs gros et que l'initialiser prendrait beaucoup de temps (en fait, moins que l'on ait l'intention d'utiliser le tableau plus de 4 milliards de fois, cela prendrait plus de temps de l'initialiser que cela n'en prendrait de calculer le nombre de bits avec la mthode une !). Une mthode plus raliste calculerait le nombre de bits pour toutes les valeurs possibles d'octet et les stockerait dans un tableau. Le double mot peut alors tre dcompos en quatre valeurs d'un octet. Le nombre de bits pour chacune de ces 4 valeurs d'un octet est recherch dans le tableau et

3.6.

COMPTER LES BITS

65

1 2 3 4 5 6 7 8 9 10 11 12 13 14

int count_bits(unsigned int x ) { static unsigned int mask[] = { 0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF, 0x0000FFFF }; int i ; int shift ; / nombre de position dcaler droite / for ( i=0, shift =1; i < 5; i++, shift = 2 ) x = (x & mask[i ]) + ( ( x >> shift) & mask[i ] ); return x;

Fig. 3.8  Mthode Trois

additionn aux autres pour trouver le nombre de bits du double mot original. La Figure 3.7 montre le code implmentant cette approche.

initialize_count_bits doit tre appele avant le premier appel la fonction count_bits. Cette fonction initialise le tableau global byte_bit_count. La fonction count_bits ne considre pas la variable data
La fonction comme un double mot mais comme un tableau de quatre octets. Donc,

dword[0]

est un des octets de

data

(soit le moins signicatif, soit le plus

signicatif selon que le matriel est little ou big endian, respectivement). Bien sr, on peut utiliser une instruction comme :

(data >> 24) & 0x000000FF


pour trouver la valeur de l'octet le plus signicatif et des oprations similaires pour les autres octets ; cependant, ces oprations seraient plus lentes qu'une rfrence un tableau. Un dernier point, une boucle

for

pourrait facilement tre utilise pour

calculer la somme des lignes 22 et 23. Mais, cela inclurait le supplment de l'initialisation de l'indice, sa comparaison aprs chaque itration et son incrmentation. Calculer la somme comme une somme explicite de quatre valeurs est plus rapide. En fait, un compilateur intelligent convertirait la version avec une boucle compilateur appele

for

en une somme explicite. Ce procd de rduire (droulage de boucle).

ou liminer les itrations d'une boucle est une technique d'optimisation de

loop unrolling

66

CHAPITRE 3.

OPRATIONS SUR LES BITS

3.6.3 Mthode trois


Il y a encore une mthode intelligente de compter les bits allums d'une donne. Cette mthode ajoute littralement les uns et les zros de la donne. La somme est gale au nombre de 1 dans la donne. Par exemple, considrons le comptage des uns d'un octet stock dans une variable nomme premire tape est d'eectuer l'opration suivante :

data.

La

data = (data & 0x55) + ((data >> 1) & 0x55);


Pourquoi faire cela ? La constante hexa bits sur des positions la premire oprande de l'addition, on eectue un

 1) & 0x55)

0x55 vaut 01010101 en binaire. Dans ET sur data avec elle, les impaires sont supprims. La seconde oprande ((data

commence par dplacer tous les bits des positions paires

vers une position impaire et utilise le mme masque pour supprimer ces bits. Maintenant, la premire oprande contient les bits impairs et la seconde les bits pairs de

data.

Lorsque ces deux oprandes sont additionnes, les bits

pairs et les bits impairs de

data sont additionns. Par exemple, si data vaut


00 soit + 01 01 01 01 10 00 00 00 01 01 10

101100112 ,
+

data & 010101012 (data  1) & 010101012

alors :

L'addition droite montre les bits additionns ensembles. Les bits de l'octet sont diviss en deux champs de 2 bits pour montrer qu'il y a en fait quatre additions indpendantes. Comme la plus grande valeur que ces sommes peuvent prendre est deux, il n'est pas possibile qu'une somme dborde de son champ et corrompe l'une des autres sommes. Bien sr, le nombre total de bits n'a pas encore t calcul. Cependant, la mme technique que celle qui a t utilise ci-dessus peut tre utilise pour calculer le total en une srie d'tapes similaires. L'tape suivante serait :

data = (data & 0x33) + ((data >> 2) & 0x33);


En continuant l'exemple du dessus (souvenez vous que

data vaut maintenant

011000102 ):
+

data & 001100112 (data  2) & 001100112

0010 soit + 0001 0011

0010 0000 0010

Il y a maintenant deux champs de 4 bits additionns individuellement. La prochaine tape est d'additionner ces deux sommes de bits ensemble pour former le rsultat nal :

data = (data & 0x0F) + ((data >> 4) & 0x0F);

3.6.

COMPTER LES BITS

67

En utilisant l'exemple ci-dessus (avec +

data & 000011112 (data  4) & 000011112 data

data

gale

001100102 ):

00000010 soit + 00000011 00000101

Maintenant,

vaut 5 ce qui est le rsultat correct. La Figure 3.8 montre

une implmentation de cette mthode qui compte les bits dans un double mot. Elle utilise une boucle

for pour calculer la somme. Il serait plus rapide

de drouler la boucle ; cependant, la boucle rend plus claire la faon dont la mthode se gnralise direntes tailles de donnes.

68

CHAPITRE 3.

OPRATIONS SUR LES BITS

Chapitre 4

Sous-Programmes
Ce chapitre explique comment utiliser des sous-programmes pour crer des programmes modulaires et s'interfacer avec des langages de haut niveau (comme le C). Les fonctions et les procdures sont des exemples de sousprogrammes dans les langages de haut niveau. Le code qui appelle le sous-programme et le sous-programme lui-mme doivent se mettre d'accord sur la faon de se passer les donnes. Ces rgles sur la faon de passer les donnes sont appeles

conventions d'appel .

Une

grande partie de ce chapitre traitera des conventions d'appel standards du C qui peuvent tre utilises pour interfacer des sous-programmes assembleur avec des programmes C. Celle-ci (et d'autres conventions) passe souvent les adresses des donnes ( d'accder aux donnes en mmoire.

i.e. des pointeurs) pour permettre au sou-programme

4.1

Adressage Indirect

L'adressage indirect permet aux registres de se comporter comme des pointeurs. Pour indiquer qu'un registre est utilis indirectement comme un pointeur, il est entour par des crochets ([]). Par exemple :

1 2 3

mov mov mov

ax, [Data] ebx, Data ax, [ebx]

; adressage mmoire direct normal d'un mot ; ebx = & Data ; ax = *ebx

Comme AX contient un mot, la ligne 3 lit un mot commenant l'adresse stocke dans EBX. Si AX tait remplac par AL, un seul octet serait lu. Il est important de raliser que les registres n'ont pas de types comme les variable en C. Ce sur quoi EBX est cens pointer est totalement dtermin par les instructions utilises. Si EBX est utilis de manire incorrecte, il n'y aura souvent pas d'erreur signale de la part de l'assembleur ; cependant, le programme ne fonctionnera pas correctement. C'est une des nombreuses 69

70

CHAPITRE 4.

SOUS-PROGRAMMES

raisons pour lesquelles la programmation assembleur est plus sujette erreur que la programmation de haut niveau. Tous les registres 32 bits gnraux (EAX, EBX, ECX, EDX) et d'index (ESI, EDI) peuvent tre utiliss pour l'adressage indirect. En gnral, les registres 16 et 8 bits ne le peuvent pas.

4.2

Exemple de Sous-Programme Simple

Un sous-programme est une unit de code indpendante qui peut tre utilise depuis direntes parties du programme. En d'autres termes, un sous-programme est comme une fonction en C. Un saut peut tre utilis pour appeler le sous-programme, mais le retour prsente un problme. Si le sousprogramme est destin tre utilis par direntes parties du programme, il doit revenir la section de code qui l'a appel. Donc, le retour du sousprogramme ne peut pas tre cod en dur par un saut vers une tiquette. Le code ci-dessous montre comment cela peut tre ralis en utilisant une forme indirecte de l'instruction un

JMP.

Cette forme de l'instruction utilise la valeur

d'un registre pour dterminer o sauter (donc, le registre agit plus comme

pointeur de fonction

du C). Voici le premier programme du Chapitre 1

rcrit pour utiliser un sous-programme.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

sub1.asm ; fichier : sub1.asm ; Programme d'exemple de sous-programme %include "asm_io.inc" segment prompt1 prompt2 outmsg1 outmsg2 outmsg3 .data db db db db db "Entrez un nombre : ", 0 ; ne pas oublier le zro terminal "Entrez un autre nombre : ", 0 "Vous avez entr ", 0 " et ", 0 ", la somme des deux vaut ", 0

segment .bss input1 resd 1 input2 resd 1 segment .text global _asm_main _asm_main: enter 0,0 pusha

; routine d'initialisation

4.2.

EXEMPLE DE SOUS-PROGRAMME SIMPLE

71

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63

mov call mov mov jmp mov call mov mov jmp mov add mov mov call mov call mov call mov call mov call mov call call

eax, prompt1 print_string ebx, input1 ecx, ret1 short get_int eax, prompt2 print_string ebx, input2 ecx, $ + 7 short get_int eax, [input1] eax, [input2] ebx, eax eax, outmsg1 print_string eax, [input1] print_int eax, outmsg2 print_string eax, [input2] print_int eax, outmsg3 print_string eax, ebx print_int print_nl

; affiche l'invite ; stocke l'adresse de input1 dans ebx ; stocke l'adresse de retour dans ecx ; lit un entier ; affiche l'invite

ret1:

; ecx = cette addresse + 7 ; eax = dword dans input1 ; eax += dword dans input2 ; ebx = eax ; affiche le premier message ; affiche input1 ; affiche le second message ; affiche input2 ; affiche le troisime message ; affiche la somme (ebx) ; retour la ligne

; ; ; ; ; ;

popa mov eax, 0 ; retour au C leave ret subprogram get_int Paramtres: ebx - addresse du dword dans lequel stocker l'entier ecx - addresse de l'instruction vers laquelle retourner Notes: la valeur de eax est perdue

72

CHAPITRE 4.

SOUS-PROGRAMMES

64 65 66 67

get_int: call mov jmp

read_int [ebx], eax ecx

; stocke la saisie en mmoire ; retour l'appelant sub1.asm

Le sous-programme

get_int utilise une convention d'appel simple, base

sur les registres. Il s'attend ce que le registre EBX contienne l'adresse du DWORD dans lequel stocker le nombre saisi et ce que le registre ECX contiennent l'adresse de l'instruction vers laquelle retourner. Dans les lignes 25 28, l'tiquette de retour. L'oprateur L'expression

ret1

est utilise pour calculer cette adresse de re-

tour. Dans les lignes 32 34, l'oprateur

$ + 7

calcule l'adresse de l'instruction

$ retourne l'adresse de la ligne sur laquelle il apparat. MOV de la ligne 36.

est utilis pour calculer l'adresse

Ces deux calculs d'adresses de code de retour sont compliqus. La premire mthode requiert la dnition d'une tiquette pour tout appel de sousprogramme. La seconde mthode ne requiert pas d'tiquette mais ncessite de rchir attentivement. Si un saut proche avait t utilis la place d'un saut court, le nombre ajouter la

n'aurait pas t 7! Heureusement, il y a

une faon plus simple d'appeler des sous-programmes. Cette mthode utilise

pile.

4.3

La pile

Beaucoup de processeurs ont un support intgr pour une pile. Une pile est une liste Last-In First-Out (

LIFO, dernier entr, premier sorti). La pile


POP
retire une donne. La

est une zone de la mmoire qui est organise de cette faon. L'instruction

PUSH

ajoute des donnes la pile et l'instruction

donne retire est toujours la dernire donne ajoute (c'est pourquoi on appelle ce genre de liste dernier entr, premier sorti). Le registre de segment SS spcie le segment qui contient la pile (habituellement c'est le mme que celui qui contient les donnes). Le registre ESP contient l'adresse de la donne qui sera retire de la pile. On dit que cette donne est au

sommet

de la pile. Les donnes ne peuvent tre ajoutes

que par units de double mots. C'est--dire qu'il est impossible de placer un octet seul sur la pile. L'instruction mot en

PUSH

insre un double mot

1 sur la pile en tant 4 de ESP


L'instruction

puis en stockant le double mot en

[ESP]

[ESP].

POP

lit le double

puis ajoute 4 ESP. Le code ci-dessous montre comment

fonctionnent ces instructions en supposant que ESP vaut initialement

1000H.

1 En fait, il est galement possible d'empiler des mots, mais en mode protg 32 bits, il est mieux de ne travailler qu'avec des doubles mots sur la pile.

4.4.

LES INSTRUCTIONS CALL ET RET

73

1 2 3 4 5 6

push push push pop pop pop

dword 1 dword 2 dword 3 eax ebx ecx

; ; ; ; ; ;

1 est 2 est 3 est EAX = EBX = ECX =

stock stock stock 3, ESP 2, ESP 1, ESP

en 0FFCh, ESP = 0FFCh en 0FF8h, ESP = 0FF8h en 0FF4h, ESP = 0FF4h = 0FF8h = 0FFCh = 1000h

La pile peut tre utilise comme un endroit appropri pour stocker des donnes temporairement. Elle est galement utilise pour eectuer des appels de sous-programmes, passer des paramtres et des variables locales. Le 80x86 fournit galement une instruction, L'instruction

PUSHA, qui empile les valeurs

des registres EAX, EBX, ECX, EDX, ESI, EDI et EBP (pas dans cet ordre).

POPA

peut tre utilise pour les dpiler tous.

4.4

Les Instructions CALL et RET

Le 80x86 fournit deux instructions qui utilisent la pile pour eectuer des appels de sous-programmes rapidement et facilement. L'instruction CALL eectue un saut inconditionnel vers un sous-programme et l'instruction suivante. L'instruction RET

dpile

empile l'adresse de

une adresse et saute cette

adresse. Lors de l'utilisation de ces instructions, il est trs important de grer la pile correctement an que le chire correct soit dpil par l'instruction RET ! Le programme prcdent peut tre rcrit pour utiliser ces nouvelles instructions en changeant les lignes 25 34 en ce qui suit :

mov call mov call

ebx, input1 get_int ebx, input2 get_int get_int


en :

et en changeant le sous-programme

get_int: call mov ret

read_int [ebx], eax

Il y a plusieurs avantages utiliser CALL et RET:  C'est plus simple !

74

CHAPITRE 4.

SOUS-PROGRAMMES

 Cela permet d'imbriquer des appels de sous-programmes facilement. Notez que

get_int

appelle

adresse. la n du code de l'adresse de retour et saute RET de vers LIFO. Souvenez vous, il est

get_int est asm_main. Cela

read_int. Cet appel empile une autre read_int se trouve un RET qui dpile vers le code de get_int. Puis, lorsque le

excut, il dpile l'adresse de retour qui revient fonctionne correctement car il s'agit d'une pile important de dpiler toute donne qui est em-

trs

pile. Par exemple, considrons le code suivant :

1 2 3 4 5

get_int: call mov push ret

read_int [ebx], eax eax ; dpile la valeur de EAX, pas l'adresse de retour !!

Ce code ne reviendra pas correctement !

4.5

Conventions d'Appel

(l'

appel )

Lorsqu'un sous-programme est appel, le code appelant et le sous-programme doivent s'accorder sur la faon de se passer les donnes. Les lanPour interfacer du code de haut niveau avec le

gages de haut niveau ont des manires standards de passer les donnes appeles

conventions d'appel.

langage assembleur, le code assembleur doit utiliser les mmes conventions que le langage de haut niveau. Les conventions d'appel peuvent direr d'un compilateur l'autre ou peuvent varier selon la faon dont le code est compil ( un

p.e.

selon que les optimisations sont actives ou pas). Une convention

universelle est que le code est appel par une instruction

RET.

CALL et revient par

Tous les compilateurs C PC supportent une convention d'appel qui sera dcrite dans le reste de ce chapitre par tape. Ces conventions permettent de crer des sous-programmes

rentrants.

Un sous-programme rentrant peut

tre appel depuis n'importe quel endroit du programme en toute scurit (mme depuis le sous-programme lui-mme).

4.5.1 Passer les paramtres via la pile


Les paramtres d'un sous-programme peuvent tre passs par la pile. Ils sont empils avant l'instruction sa

CALL. Comme en C, si le paramtre doit tre

modi par le sous-programme, l'

valeur.

adresse

de la donne doit tre passe, pas

Si la taille du paramtre est infrieure un double mot, il doit

tre converti en un double mot avant d'tre empil.

4.5.

CONVENTIONS D'APPEL

75

ESP + 4 ESP

Paramtre Adresse de retour

Fig. 4.1  tat de la pile lors du passage d'un paramtre

ESP + 8 ESP + 4 ESP

Paramtre Adresse de retour Donn'ee du sous-programme

Fig. 4.2  Etat de la pile aprs empilage d'une donne

Les paramtres sur la pile ne sont pas dpils par le sous-programme, la place, ils sont accds depuis la pile elle-mme. Pourquoi ?  Comme ils doivent tre empils avant l'instruction

CALL,

l'adresse de

retour devrait tre dpile avant tout (puis rempile ensuite).  Souvent, les paramtres sont utiliss plusieurs endroits dans le sousprogramme. Habituellement, ils ne peuvent pas tre conservs dans un registre durant toute la dure du sous-programme et devront tre stocks en mmoire. Les laisser sur la pile conserve une copie de la donne en mmoire qui peut tre accde depuis n'importe quel endroit du sous-programme. Considrons un sous-programme auquel on passe un paramtre unique Lors
de l'utilisation de indirect, le via la pile. Lorsque le sous-programme est appel, la pile ressemble la l'adressage processeur 80x86 accde Figure 4.1. On peut accder au paramtre en utilisant l'adressage indirect

([ESP+4]

2 ).

dirents

segments

selon

Si la pile est galement utilise dans le sous-programme pour stocker des donnes, le nombre ajouter ESP changera. Par exemple, la Figure 4.2 paramtre se trouve en

les registres utiliss dans l'expression indirect. ESP le d'adressage (et EBP) de segment

montre quoi ressemble la pile si un DWORD est empil. Maintenant, le utilisent

ESP + 8, plus en ESP + 4. Donc, utiliser ESP lorsque

pile alors que EAX, EBX, et EDX utilisent

l'on fait rfrence des paramtres peut tre une source d'erreurs. Pour ECX

rsoudre ce problme, Le 80386 fournit un autre registre utiliser : EBP. La le segment de donnes. Cependant, ce n'est habiseule utilit de ce registre est de faire rfrence des donnes sur la pile. La tuellement pas important convention d'appel C stipule qu'un sous-programme doit d'abord empiler la pour la plupart des provaleur de EBP puis dnir EBP pour qu'il soit gal ESP. Cela permet ESP grammes en mode protg, de changer au fur et mesure que des donnes sont empiles ou dpiles sans car pour eux, les segments modier EBP. A la n du programme, la valeur originale de EBP doit tre de pile et de donnes sont restaure (c'est pourquoi elle est sauvegarde au dbut du sous-programme). les mmes.

Il est autoris d'ajouter une constante un registre lors de l'utilisation de l'adressage indirect. Des expressions plus complexes sont galement possible. Ce sujet est trait dans le chapitre suivant

76

CHAPITRE 4.

SOUS-PROGRAMMES

1 2 3 4 5 6

etiquette_sousprogramme: push ebp mov ebp, esp ; code du sousprogramme pop ebp ret

; empile la valeur originale de EBP ; EBP = ESP ; restaure l'ancienne valeur de EBP

Fig. 4.3  Forme gnrale d'un sous-programme

ESP + 8 ESP + 4 ESP

EBP + 8 EBP + 4 EBP

Parametre Adresse de retour EBP sauvegard

Fig. 4.4  Etat de la pile en suivant la convention d'appel C

La Figure 4.3 montre la forme gnrale d'un sous-programme qui suit ces conventions.

Les lignes 2 et 3 de la Figure 4.3 constituent le sous-programme. Les lignes 5 et 6 consituent l' paramtre peut tre accd avec sous-programme.

prologue gnrique d'un pilogue. La Figure 4.4 montre

quoi ressemble la pile immdiatement aprs le prologue. Maintenant, le

[EBP + 8]

depuis n'importe quel endroit

du programme sans se soucier de ce qui a t empil entre temps par le

Une fois le sous-programme termin, les paramtres qui ont t empils doivent tre retirs. La convention d'appel C spcie que c'est au code appelant de le faire. D'autres conventions sont direntes. Par exemple, la convention d'appel Pascal spcie que c'est au sous-programme de retirer les paramtres (Il y une autre forme de l'instruction RET qui permet de le faire facilement). Quelques compilateurs C supportent cette convention galement. Le mot cl la convention

pascal

est utilis dans le prototype et la dnition de

la fonction pour indiquer au compilateur d'utiliser cette convention. En fait,

stdcall,

que les fonctions de l'API C MS Windows utilisent,

fonctionne galement de cette faon. Quel est son avantage ? Elle est un petit peu plus ecace que la convention C. Pourquoi toutes les fonctions C n'utilisent pas cette convention alors ? En gnral, le C autorise une fonction a avoir un nombre variable d'arguments (

p.e., les fonctions printf et scanf).

Pour ce type de fonction, l'opration consistant retirer les paramtres de la pile varie d'un appel l'autre. La convention C permet aux instructions ncessaires la ralisation de cette opration de varier facilement d'un appel l'autre. Les conventions Pascal et stdcall rendent cette opration trs compli-

4.5.

CONVENTIONS D'APPEL

77

1 2 3

push call add

dword 1 fun esp, 4

; passe 1 en paramtre ; retire le paramtre de la pile

Fig. 4.5  Exemple d'appel de sous-programme

que. Donc, la convention Pascal (comme le langage Pascal) n'autorise pas ce type de fonction. MS Windows peut utiliser cette convention puisqu'aucune de ses fonctions d'API ne prend un nombre variable d'arguments. La Figure 4.5 montre comment un sous-programme utilisant la convention d'appel C serait appel. La ligne 3 retire le paramtre de la pile en manipulant directement le pointeur de pile. Une instruction

POP pourrait ga-

lement tre utilise, mais cela ncessiterait le stockage d'un paramtre inutile dans un registre. En fait, dans ce cas particulier, beaucoup de compilateurs utilisent une instruction utilise un

POP ECX pour retirer le paramtre. Le compilateur POP plutot qu'un ADD car le ADD ncessite plus d'octets pour stocker l'instruction. Cependant, le POP change galement la valeur de ECX ! Voici
un autre programme exemple avec deux sous-programmes qui utilisent les conventions d'appel C dont nous venons de parler. La ligne 54 (et les autres) montre que plusieurs segments de donnes et de texte peuvent tre dclars dans un mme chier source. Ils seront combins en des segments de donnes et de texte uniques lors de l'dition de liens. Diviser les donnes et le code en segments spars permet d'avoir les donnes d'un sous-programme dnies proximit de celui-ci.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

%include "asm_io.inc" segment .data sum dd 0 segment .bss input resd 1 ; ; ; ; ; ; ;

sub3.asm

algorithme en pseudo-code i = 1; sum = 0; while( get_int(i, &input), input != 0 ) { sum += input; i++;

78

CHAPITRE 4.

SOUS-PROGRAMMES

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57

; } ; print_sum(num); segment .text global _asm_main _asm_main: enter 0,0 pusha mov while_loop: push push call add mov cmp je add inc jmp end_while: push call pop popa leave ret edx, 1 edx dword input get_int esp, 8 eax, [input] eax, 0 end_while [sum], eax edx short while_loop dword [sum] print_sum ecx

; routine d'initialisation ; edx est le 'i' du pseudo-code ; empile i ; empile l'adresse de input ; dpile i et &input

; sum += input

; empile la valeur de sum ; dpile [sum]

; sous-programme get_int ; Paramtres (dans l'ordre de l'empilement) ; nombre de saisies (en [ebp + 12]) ; adresse du mot o stocker la saisie (en [ebp + 8]) ; Notes: ; les valeurs de eax et ebx sont dtruites segment .data prompt db ") Entrez un nombre entier (0 pour quitter): ", 0

4.5.

CONVENTIONS D'APPEL

79

58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98

segment .text get_int: push mov mov call mov call call mov mov pop ret

ebp ebp, esp eax, [ebp + 12] print_int eax, prompt print_string read_int ebx, [ebp + 8] [ebx], eax ebp

; stocke la saisie en mmoire ; retour l'appelant

; sous-programme print_sum ; affiche la somme ; Paramtre: ; somme afficher (en [ebp+8]) ; Note: dtruit la valeur de eax ; segment .data result db "La somme vaut ", 0 segment .text print_sum: push mov mov call mov call call pop ret

ebp ebp, esp eax, result print_string eax, [ebp+8] print_int print_nl ebp sub3.asm

80

CHAPITRE 4.

SOUS-PROGRAMMES

1 2 3 4 5 6 7 8

etiquette_sousprogramme: push ebp mov ebp, esp sub esp, LOCAL_BYTES ; code du sousprogramme mov esp, ebp pop ebp ret

; empile la valeur originale de EBP ; EBP = ESP ; = nb octets ncessaires pour les locales ; dsalloue les locales ; restaure la valeur originalede EBP

Fig. 4.6  Forme gnrale d'un sous-programme avec des variables locales

1 2 3 4 5 6 7 8

void calc_sum( int n, int sump ) { int i , sum = 0; for ( i=1; i <= n; i++ ) sum += i; sump = sum;

Fig. 4.7  Version C de sum

4.5.2 Variables locales sur la pile


La pile peut tre utilise comme un endroit pratique pour stocker des variables locales. C'est exactement ce que fait le C pour les variables normales (ou

automatiques

en C lingo). Utiliser la pile pour les variables est

important si l'on veut que les sous-programmes soient rentrants. Un programme rentrant fonctionnera qu'il soit appel de n'importe quel endroit, mme partir du sous-programme lui-mme. En d'autres termes, les sousprogrammes rentrants peuvent tre appels

rcursivement. global
ou

Utiliser la pile

pour les variables conomise galement de la mmoire. Les donnes qui ne sont pas stockes sur la pile utilisent de la mmoire du dbut la n du programme (le C appelle ce type de variables dans lequel elles sont dnies est actif. Les variables locales sont stockes immdiatement aprs la valeur de EBP sauvegarde dans la pile. Elles sont alloues en soustrayant le nombre d'octets requis de ESP dans le prologue du sous-programme. La Figure 4.6 montre le nouveau squelette du sous-programme. Le registre EBP est utilis pour accder des variables locales. Considrons la fonction C de la Figure 4.7. La Figure 4.8 montre comment le sous-programme quivalent pourrait tre stockes sur la pile n'utilisent de la mmoire que lorsque le sous-programme

static ). Les donnes

4.5.

CONVENTIONS D'APPEL

81

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

cal_sum: push mov sub mov mov for_loop: cmp jnle add inc jmp end_for: mov mov mov mov pop ret

ebp ebp, esp esp, 4

; fait de la place pour le sum local ; sum = 0 ; ebx (i) = 1 ; i <= n ? ; sum += i

dword [ebp - 4], 0 ebx, 1 ebx, [ebp+12] end_for [ebp-4], ebx ebx short for_loop ebx, [ebp+8] eax, [ebp-4] [ebx], eax esp, ebp ebp

; ebx = sump ; eax = sum ; *sump = sum;

Fig. 4.8  Version assembleur de sum

crit en assembleur. La Figure 4.9 montre quoi ressemble la pile aprs le prologue du programme de la Figure 4.8. Cette section de la pile qui contient les paramtres, les informations de retour et les variables locales est appele la pile.

cadre de pile
En dpit du fait que

(stack frame). Chaque appel de fonction C cre un nouveau cadre de pile sur Le prologue et l'pilogue d'un sous-programme peuvent tre simplis en et LEAVE simplient le utilisant deux instructions spciales qui sont conues spcialement dans ce prologue et l'pilogue, ils but. L'instruction L'instruction

ENTER

qu'ils sont plus lent que les d'appel C, la deuxime oprande est toujours 0. La premire oprande est instructions plus simples le nombre d'octets ncessaires pour les variables locales. L'instruction LEAVE quivalentes ! C'est un des

ENTER eectue le prologue et l'instruction LEAVE l'pilogue. ENTER prend deux oprandes immdiates. Dans la convention

ne sont pas utiliss trs souvent. Pourquoi ? Parce

n'a pas d'oprande. La Figure 4.10 montre comment ces instructions sont exemples o il ne faut pas utilises. Notez que le squelette de programme (Figure 1.7) utilise galement supposer qu'une instruction est plus rapide qu'une ENTER et LEAVE.
squence de plusieurs instructions.

82

CHAPITRE 4.

SOUS-PROGRAMMES

ESP + 16 ESP + 12 ESP + 8 ESP + 4 ESP

EBP + 12 EBP + 8 EBP + 4 EBP EBP - 4

n sump
Adresse de retour EBP sauv

sum

Fig. 4.9  tat de la pile aprs le prologue de sum

1 2 3 4 5

etiquette_sousprogramme: enter LOCAL_BYTES, 0 ; code du sous-programme leave ret

; = nb octets pour les locales

Fig. 4.10  Forme gnrale d'un sous-programme avec des variables locales

utilisant

ENTER

et

LEAVE

4.6
Un

Programme Multi-Modules

programme multi-modules

est un programme compos de plus d'un

chier objet. Tous les programmes prsents jusqu'ici sont des programmes multi-modules. Il consistent en un chier objet C pilote et le chier objet assembleur (plus les chiers objet de la bibliothque C). Souvenez vous que l'diteur de liens combine les chier objets en un programme excutable unique. L'diteur de liens doit rapprocher toutes les rfrences faites chaque tiquette d'un module ( module B, la directive

i.e.

un chier objet) de sa dnition dans un autre doit tre utilise. Aprs la directive

module. An que le module A puisse utiliser une tiquette dnie dans le

extern

extern

vient une liste d'tiquettes dlimites par des virgules. La directive indique l'assembleur de traiter ces tiquettes comme dnies dans un autre. Le chier

externes au module. C'est--dire


dnit les routines

qu'il s'agit d'tiquettes qui peuvent tre utilises dans ce module mais sont

etc. comme externes.

asm_io.inc

read_int,

En assembleur, les tiquettes ne peuvent pas tre accdes de l'extrieur par dfaut. Si une tiquette doit pouvoir tre accde depuis d'autres modules que celui dans lequel elle est dnie, elle doit tre dclare comme dans son module, par le biais de la directive programme squelette de la Figure 1.7 montre que l'tiquette

global . La ligne 13 du listing du _asm_main est

globale

dnie comme globale. Sans cette dclaration, l'diteur de liens indiquerait une erreur. Pourquoi ? Parce que le code C ne pourrait pas faire rfrence l'tiquette

interne _asm_main.

4.6.

PROGRAMME MULTI-MODULES

83

Voici le code de l'exemple prcdent rcrit an d'utiliser deux modules. Les deux sous-programmes (get_int et sources distincts de celui de la routine

print_sum) _asm_main.

sont dans des chiers

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

%include "asm_io.inc" segment .data sum dd 0 segment .bss input resd 1

main4.asm

segment .text global _asm_main extern get_int, print_sum _asm_main: enter 0,0 ; routine d'initialisation pusha mov while_loop: push push call add mov cmp je add inc jmp end_while: push call pop popa leave edx, 1 edx dword input get_int esp, 8 eax, [input] eax, 0 end_while [sum], eax edx short while_loop dword [sum] print_sum ecx ; empile la valeur de sum ; dpile [sum] ; sum += input ; edx est le 'i' du pseudo-code ; empile i ; empile l'adresse de input ; dpile i et &input

84

CHAPITRE 4.

SOUS-PROGRAMMES

39

ret

main4.asm sub4.asm

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

%include "asm_io.inc" segment .data prompt db

") Entrez un nombre entier (0 pour quitter): ", 0

segment .text global get_int, print_sum get_int: enter 0,0 mov call mov call call mov mov leave ret segment .data result db segment .text print_sum: enter mov call mov call call leave ret "La somme vaut ", 0 eax, [ebp + 12] print_int eax, prompt print_string read_int ebx, [ebp + 8] [ebx], eax

; stocke la saisie en mmoire ; retour l'appelant

0,0 eax, result print_string eax, [ebp+8] print_int print_nl

sub4.asm

4.7.

INTERFACER DE L'ASSEMBLEUR A VEC DU C

85

L'exemple ci-dessus n'a que des tiquettes de code globales ; cependant, les tiquettes de donne globales fonctionnent exactement de la mme faon.

4.7

Interfacer de l'assembleur avec du C

Aujourd'hui, trs peu de programmes sont crits compltement en assembleur. Les compilateurs sont trs performants dans la conversion de code de haut niveau en code machine ecace. Comme il est plus facile d'crire du code dans un langage de haut niveau, ils sont plus populaires. De plus, le code de haut niveau est

beaucoup

plus portable que l'assembleur !

Lorsque de l'assembleur est utilis, c'est souvent pour de petites parties du code. Cela peut tre fait de deux faons : en appelant des sous-routines assembleur depuis le C ou en incluant de l'assembleur. Inclure de l'assembleur permet au programmeur de placer des instructions assembleur directement dans le code C. Cela peut tre trs pratique ; cependant, il y a des inconvnients inclure l'assembleur. Le code assembleur doit tre crit dans le format que le compilateur utilise. Aucun compilateur pour le moment ne supporte le format NASM. Des compilateurs dirents demandent des formats dirents. Borland et Microsoft demandent le format MASM. DJGPP et gcc sous Linux demandent le format GAS sous-routine assembleur est beaucoup plus standardise sur les PC. Les routines assembleur sont habituellement utilises avec le C pour les raisons suivantes :  Un accs direct au fonctionnalits matrielles de l'ordinateur est ncessaire car il est dicle ou impossible d'y accder en C.  La routine doit tre la plus rapide possible et le programmeur peut optimiser le code la main mieux que le compilateur. La dernire raison n'est plus aussi valide qu'elle l'tait. La technologie des compilateurs a t amliore au l des ans et les compilateurs gnrent souvent un code trs performant (en particulier si les optimisations actives). Les inconvnients des routines assembleur sont une portabilit et une lisibilit rduites. La plus grande partie des conventions d'appel C a dj t prsente. Cependant, il y a quelques fonctionnalits supplmentaires qui doivent tre dcrites.

3 . La technique d'appel d'une

GAS est l'assembleur GNU que tous les compilateurs GNU utilisent. Il utilise la syntaxe AT&T qui est trs direntes des syntaxes relativement similaires de MASM, TASM et NASM.

86

CHAPITRE 4.

SOUS-PROGRAMMES

1 2 3 4 5 6 7 8 9 10

segment .data x dd format db

0 "x = %d\n", 0

segment .text ... push dword [x] push dword format call _printf add esp, 8

; ; ; ;

empile la valeur de x empile l'adresse de la chane format notez l'underscore! dpile les paramtres

Fig. 4.11  Appel de

printf x

EBP + 12 EBP + 8 EBP + 4 EBP

Valeur de

Adresse de la chaine format Adresse de retour Sauvegarde de EBP

Fig. 4.12  Pile l'intrieur de

printf

4.7.1 Sauvegarder les registres


Le mot cl

register

peut

Tout d'abord, Le C suppose qu'une sous-routine maintient les valeurs des

tre utilis dans une dcla- registres suivants : EBX, ESI, EDI, EBP, CS, DS, SS, ES. Cela ne signie ration de variable C pour pas que la sous-routine ne les change pas en interne. Cela signie que si elle suggrer au compilateur change leur valeurs, elle doit les restaurer avant de revenir. Les valeurs de d'utiliser un registre pour EBX, ESI et EDI doivent tre inchanges car le C utilise ces registres pour cette variable plutot qu'un les . Habituellement, la pile est utilise pour sauvegarder emplacement mmoire. On appelle ces variables, va- les valeurs originales de ces registres.

variables de registre

riables

de

registre.

Les

compilateurs modernes le font automatiquement sans qu'il y ait besoin d'une suggestion.

4.7.2 Etiquettes de fonctions


La plupart des compilateurs C ajoutent un caractre underscore(_) au dbut des noms des fonctions et des variables global/static. Par exemple, une fonction appele gcc, n'ajoute

sera assigne l'tiquette

routine assembleur, elle

aucun

doit

tre tiquete

_f. Donc, s'il s'agit d'une _f, pas f. Le compilateur Linux


Cependant, le gcc de DJGPP

caractre. Dans un executable Linux ELF, on utiliserait

simplement l'tiquette

pour la fonction

f.

ajoute un underscore. Notez que dans le squelette de programme assembleur (Figure 1.7), l'tiquette de la routine principale est

_asm_main.

4.7.

INTERFACER DE L'ASSEMBLEUR A VEC DU C

87

4.7.3 Passer des paramtres


Dans les conventions d'appel C, les arguments d'une fonction sont empils sur la pile dans l'ordre l'appel de la fonction. Considrons l'expression C suivante :

inverse

de celui dans lequel ils apparassent dans

printf("x = %d\n",x);

la Fi-

gure 4.11 montre comment elle serait compile (dans le format NASM quivalent). La Figure 4.12 montre quoi ressemble la pile aprs le prologue de la fonction

printf.

La fonction

printf

est une des fonctions de la biblio-

thque C qui peut prendre n'importe quel nombre d'arguments. Les rgles des conventions d'appel C ont t spcialement crite pour autoriser ce type de fonctions. Comme l'adresse de la chane format est empile en dernier, Il son emplacement sur la pile sera
n'est pas ncessaire l'assembleur un nombre d'arguments des macros en

toujours EBP + 8 quel que soit le nombre

d'utiliser pour C. grer L'en-tte alatoire dnit peuvent

de paramtres passs la fonction. Le code de les rcuprer sur la pile. Bien sr, s'il y a une erreur, achera quand mme la valeur ne sera pas la valeur de

printf peut alors analyser la

chane format pour dterminer combien de paramtres ont du tre passs et

stdarg.h

x!

printf("x = %d\n"), le code de printf double mot en [EBP + 12]. Cependant, ce

qui

tre

utilises

pour l'eectuer de faon portable. Voyez n'importe quel bon livre sur le C pour plus de dtails.

4.7.4 Calculer les adresses des variables locales


Trouver l'adresse d'une tiquette dnie dans les segments

data

ou

bss

est simple. Basiquement, l'diteur de liens le fait. Cependant, calculer l'adresse d'une variable locale (ou d'un paramtre) sur la pile n'est pas aussi intuitif. Nanmoins, c'est un besoin trs courant lors de l'appel de sous-routines. Considrons le cas du passage de l'adresse d'une variable (appelons la une fonction (appelons la

foo).

Si

x)

est situ en EBP

8 sur la pile, on ne

peut pas utiliser simplement :

mov

eax, ebp - 8 MOV


stocke dans EAX doit tre calcule par l'as-

Pourquoi ? La valeur que

sembleur (c'est--dire qu'elle doit donner une constante). Cependant, il y a une instruction qui eectue le calcul dsir. Elle est appele l'adresse de

LEA (pour Load Eective Address, Charger l'Adresse Eective). L'extrait suivant calculerait x
et la stockerait dans EAX :

lea

eax, [ebp - 8] foo. x et peut tre plac sur la pile lors de


Ne vous mprenez pas, au niveau de la syntaxe,

Maintenant, EAX contient l'adresse de l'appel de la fonction ce n'est

c'est comme si cette instruction lisait la donne en [EBP8] ; cependant,

pas

vrai. L'instruction

LEA

ne lit

jamais

la mmoire ! Elle calcule

simplement l'adresse qui sera lue par une autre instruction et stocke cette

88

CHAPITRE 4.

SOUS-PROGRAMMES

adresse dans sa premire oprande registre. Comme elle ne lit pas la mmoire, aucune taille mmoire (

p.e. dword) n'est ncessaire ni autorise.

4.7.5 Retourner des valeurs


Les fonctions C non void retournent une valeur. Les conventions d'appel C spcient comment cela doit tre fait. Les valeurs de retour sont passes via les registres. Tous les types entiers (char,

int, enum, etc.) sont retourns

dans le registre EAX. S'ils sont plus petits que 32 bits, ils sont tendus 32 bits lors du stockage dans EAX (la faon dont ils sont tendus dpend du fait qu'ils sont signs ou non). Les valeurs 64 bits sont retournes dans la paire de registres EDX:EAX. Les valeurs de pointeurs sont galement stockes dans EAX. Les valeurs en virgule ottante sont stockes dans le registre STP du coprocesseur arithmtique (ce registre est dcrit dans le chapitre sur les nombres en virgule ottante).

4.7.6 Autres conventions d'appel


Les rgles ci-dessus dcrivent les conventions d'appel C supportes par tous les compilateurs C 80x86. Souvent, les compilateurs supportent galement d'autres conventions d'appel. Lorsqu'il y a une interface avec le langage assembleur, il est

trs

important de connatre les conventions utilises par

le compilateur lorsqu'il appelle votre fonction. Habituellement, par dfaut, ce sont les conventions d'appel standard qui sont utilises ; cependant, ce n'est pas toujours le cas . Les compilateurs qui utilisent plusieurs conventions ont souvent des options de ligne de commende qui peuvent tre utiliss pour changer la convention par dfaut. Il fournissent galement des extensions la syntaxe C pour assigner explicitement des conventions d'appel des fonctions de manire individuelle. Cependant, ces extensions ne sont pas standardises et peuvent varier d'un compilateur l'autre. Le compilateur GCC autorise direntes conventions d'appel. La convention utilise par une fonction peut tre dclare explicitement ent utilisant l'extension ramtre

__attribute__

. Par exemple, pour dclarer une fonction void

qui utilise la convention d'appel standard appele

int,

qui ne prend qu'un pa-

utilisez la syntaxe suivante pour son prototype :

void f ( int ) __attribute__((cdecl));


GCC supporte galement la convention d'appel le

standard call

. La fonction

ci-dessus pourrait tre dclare an d'utiliser cette convention en remplaant

cdecl par stdcall. La dirence entre stdcall et cdecl est que stdcall

impose la sous-routine de retirer les paramtres de la pile (comme le fait la

4 Le compiltateur C Watcom est un exemple qui n'utilise pas les conventions standards par dfaut. Voyez les sources exemple pour Watcom pour plus de dtails

4.7.

INTERFACER DE L'ASSEMBLEUR A VEC DU C

89

convention d'appel Pascal). Donc, la convention qui ne sont pas comme

stdcall ne peut tre utilise

que par des fonctions qui prennent un nombre xe d'arguments (

printf

et

scanf).

i.e.

celles

GCC supporte galement un attribut supplmentaire appel

regparm qui

indique au compilateur d'utiliser les registres pour passer jusqu' 3 arguments entiers une fonction au lieu d'utiliser la pile. C'est un type d'optimisation courant que beaucoup de compilateurs supportent. Borland et Microsoft utilisent une syntaxe commune pour dclarer les conventions d'appel. Ils ajoutent les mots cls

__cdecl

et

__stdcall

au

C. Ces mots cls se comportent comme des modicateurs de fonction et appraissent immdiatement avant le nom de la fonction dans un prototype. Par exemple, la fonction et Microsoft:

ci-dessus serait dnie comme suit par Borland

void __cdecl f( int );


Il y a des avantages et des inconvnients chacune des conventions d'appel. Les principaux avantages de

cdecl est qu'elle est simple et trs exible.

Elle peut tre utilise pour n'importe quel type de fonction C et sur n'importe quel compilateur C. Utiliser d'autres conventions peut limiter la portabilit de la sous-routine. Son principal inconvnient est qu'elle peut tre plus lente que certaines autres et utilise plus de mmoire (puisque chaque appel de fonction ncessite du code pour retirer les paramtres de la pile).

stdcall est qu'elle utilise moins de mmoire que cdecl. Aucun nettoyage de pile n'est requis aprs l'instruction CALL. Son
L'avantage de la convention principal inconvnient est qu'elle ne peut pas tre utilise avec des fonctions qui ont un nombre variable d'arguments. L'avantage d'utiliser une convention qui se sert des registres pour passer des paramtres entiers est la rapidit. Le principal inconvnient est que la convention est plus complexe. Certains paramtres peuvent se trouver dans des registres et d'autres sur la pile.

4.7.7 Exemples
Voici un exemple qui montre comment une routine assembleur peut tre interface avec un programme C (notez que ce programme n'utilise pas le programme assembleur squelette (Figure 1.7) ni le module driver.c).

main5.c
1 2 3 4

#include <stdio.h> / prototype de la routine assembleur / void calc_sum( int , int ) __attribute__((cdecl));

90

CHAPITRE 4.

SOUS-PROGRAMMES

5 6 7 8 9 10 11 12 13 14

int main( void ) { int n, sum;


printf ("Somme des entiers jusqu ' : "); scanf("%d", &n); calc_sum(n, &sum); printf ("La somme vaut %d\n", sum); return 0;

main5.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

; ; ; ; ; ; ; ; ; ; ; ; ;

sub5.asm sous-routine _calc_sum trouve la somme des entiers de 1 n Paramtres: n - jusqu'o faire la somme (en [ebp + 8]) sump - pointeur vers un entier dans lequel stocker la somme (en [ebp + 12]) pseudo-code C: void calc_sum( int n, int * sump ) { int i, sum = 0; for( i=1; i <= n; i++ ) sum += i; *sump = sum; }

segment .text global _calc_sum ; ; variable locale : ; sum en [ebp-4] _calc_sum: enter 4,0 push ebx mov dword [ebp-4],0 dump_stack 1, 2, 4 mov ecx, 1 for_loop: cmp ecx, [ebp+8] jnle end_for

; Fait de la place pour sum sur la pile ; IMPORTANT ! ; sum = 0 ; affiche la pile de ebp-8 ebp+16 ; ecx est le i du pseudocode ; cmp i et n ; si non i <= n, quitter

4.7.

INTERFACER DE L'ASSEMBLEUR A VEC DU C

91

Somme des entiers jusqu'a : 10 Stack Dump # 1 EBP = BFFFFB70 ESP = BFFFFB68 +16 BFFFFB80 080499EC +12 BFFFFB7C BFFFFB80 +8 BFFFFB78 0000000A +4 BFFFFB74 08048501 +0 BFFFFB70 BFFFFB88 -4 BFFFFB6C 00000000 -8 BFFFFB68 4010648C La somme vaut 55

Fig. 4.13  Excution du programme sub5

30 31 32 33 34 35 36 37 38 39 40 41 42

add inc jmp end_for: mov mov mov pop leave ret

[ebp-4], ecx ecx short for_loop ebx, [ebp+12] eax, [ebp-4] [ebx], eax ebx

; sum += i

; ebx = sump ; eax = sum ; restaure ebx sub5.asm

Pourquoi la ligne 22 de

sub5.asm est si importante ? Parce que les conven-

tions d'appel C imposent que la valeur de EBX ne soit pas modie par l'appel de fonction. Si ce n'est pas respect, il est trs probable que le programme ne fonctionne pas correctement. La ligne 25 montre la faon dont fonctionne la macro

dump_stack.

Sou-

venez vous que le premier paramtre est juste une tiquette numrique et les deuxime et troisime paramtres dterminent respectivement combien de doubles mots elle doit acher en-dessous et au-dessus de EBP. La Figure 4.13 montre une excution possible du programme. Pour cette capture, on peut voir que l'adresse du dword o stocker la somme est BFFFFB80 (en EBP + 12) ; le nombre jusqu'auquel additionner est 0000000A (en EBP + 8) ; l'adresse de retour pour la routine est 08048501 (en EBP + 4) ; la valeur sauvegarde de EBP est BFFFFB88 (en EBP); la valeur de la variable locale est

92

CHAPITRE 4.

SOUS-PROGRAMMES

0 en (EBP - 4) ; et pour nir, la valeur sauvegarde de EBX est 4010648C (en EBP - 8). La fonction

calc_sum

pourrait tre rcrite pour retourner la somme

plutt que d'utiliser un pointeur. Comme la somme est une valeur entire, elle doit tre place dans le registre EAX. La ligne 11 du chier deviendrait :

main5.c

sum = calc_sum(n);
De plus, le prototype de bleur modi :

calc_sum

devrait tre altr. Voici le code assem-

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

sub6.asm ; sous-routine _calc_sum ; trouve la somme des entiers de 1 n ; Paramtres: ; n - jusqu'o faire la somme (en [ebp + 8]) ; Valeur de retour : ; valeur de la somme ; pseudo-code C : ; int calc_sum( int n ) ; { ; int i, sum = 0; ; for( i=1; i <= n; i++ ) ; sum += i; ; return sum; ; } segment .text global _calc_sum ; ; variable locale : ; sum en [ebp-4] _calc_sum: enter 4,0 ; fait de la place pour la somme sur la pile mov mov for_loop: cmp jnle add inc jmp dword [ebp-4],0 ecx, 1 ecx, [ebp+8] end_for [ebp-4], ecx ecx short for_loop ; sum = 0 ; ecx est le i du pseudocode ; cmp i et n ; si non i <= n, quitter ; sum += i

4.8.

SOUS-PROGRAMMES RENTRANTS ET RCURSIFS

93

1 2 3 4 5 6 7 8 9 10 11

segment .data format db "%d", 0 segment .text ... lea eax, [ebp-16] push eax push dword format call _scanf add esp, 8 ...

Fig. 4.14  Appeler

scanf

depuis l'assembleur

32 33 34 35 36 37

end_for: mov leave ret

eax, [ebp-4]

; eax = sum

sub6.asm

4.7.8 Appeler des fonctions C depuis l'assembleur


Un des avantages majeurs d'interfacer le C et l'assembleur est que cela permet au code assembleur d'accder la grande bibliothque C et aux fonctions utilisateur. Par exemple, si l'on veut appeler la fonction chose trs importante se rappeler est que

scanf pour

lire un entier depuis le clavier ? La Figure 4.14 montre comment le faire. Une

scanf suit les conventions d'appel

C standards la lettre. Cela signie qu'elle prserve les valeurs des registres EBX, ESI et EDI ; cependant, les registres EAX, ECX et EDX peuvent tre modis ! En fait, EAX sera modi car il contiendra la valeur de retour de l'appel

scanf. Pour d'autres exemple d'interface entre l'assembleur et le C, observez le code dans asm_io.asm qui a t utilis pour crer asm_io.obj.
4.8 Sous-Programmes Rentrants et Rcursifs

Un sous-programme rentrant rempli les critres suivants :  Il ne doit pas modier son code. Dans un langage de haut niveau, cela serait dicile mais en assembleur, il n'est pas si dur que cela pour un programme de modier son propre code. Par exemple :

94

CHAPITRE 4.

SOUS-PROGRAMMES

mov add

word [cs:$+7], 5 ax, 2

; copie 5 dans le mot 7 octets plus loin ; l'expression prcdente change 2 en 5 !

Ce code fonctionnerait en mode rel, mais sur les systmes d'exploitation en mode protg, le segment de code est marqu en lecture seule. Lorsque la premire ligne ci-dessus s'excute, le programme est interrompu sur ces systmes. Cette faon de programmer est mauvaise pour beaucoup de raison. Elle porte confusion, est dicile maintenir et ne permet pas le partage de code (voir ci-dessous).  Il ne doit pas modier de donnes globales (comme celles qui se trouvent dans les segments la pile. Il y a plusieurs avantages crire du code rentrant.  Un sous-programme rentrant peut tre appel rcursivement.  Un programme rentrant peut tre partag par plusieurs processus. Sur beaucoup de systmes d'exploitation multi-tches, s'il y a plusieurs instances d'un programme en cours, seule

data

et

bss).

Toutes les variables sont stockes sur

une

copie du code se

trouve en mmoire. Les bibliothques partages et les DLLs (

Link Libraries,
principe.

Dynamic

Bibliothques de Lien Dynamique) utilisent le mme

 Les sous-programmes rentrants fonctionnent beaucoup mieux dans les programmes multi-threads . Windows 9x/NT et la plupart des systmes d'exploitation de style Unix (Solaris, Linux, les programmes multi-threads.

etc.) supportent

4.8.1 Sous-programmes rcursifs


Ce type de sous-programmes s'appellent eux-mmes. La rcursivit peut tre soit

directe

soit

programme, disons

foo, foo

indirecte. La rcursivit directe survient lorsqu'un souss'appelle lui-mme dans le corps de

foo.

La rcur-

sivit indirecte survient lorsqu'un sous-programme ne s'appelle pas directement lui-mme mais via un autre sous-programme qu'il appelle. Par exemple, le sous-programme pourrait appeler

bar

et

bar

pourrait appeler

son.

Les sous-programmes rcursifs doivent avoir une

condition de terminai-

foo.

Lorsque cette condition est vraie, il n'y a plus d'appel rcursif. Si une

routine rcursive n'a pas de condition de terminaison ou que la condition n'est jamais remplie, la rcursivit ne s'arrtera jamais (exactement comme une boucle innie). La Figure 4.15 montre une fonction qui calcule une factorielle rcursivement. Elle peut tre appele depuis le C avec :

x = fact (3);

/ trouve 3! /

5 Un programme multi-thread a plusieurs threads d'excution. C'est--dire que le programme lui-mme est multi-tches.

4.8.

SOUS-PROGRAMMES RENTRANTS ET RCURSIFS

95

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

; trouve n! segment .text global _fact _fact: enter 0,0 mov cmp jbe dec push call pop mul jmp term_cond: mov end_fact: leave ret eax, [ebp+8] ; eax = n eax, 1 term_cond ; si n <= 1, termin eax eax _fact ; appel fact(n-1) rcursivement ecx ; rponse dans eax dword [ebp+8] ; edx:eax = eax * [ebp+8] short end_fact eax, 1

Fig. 4.15  Fonction factorielle rcursive

cadre n=3

n(3)
Adresse de retour

EBP Sauv n(2) cadre n=2


Adresse de retour

EBP Sauv n(1) cadre n=1


Adresse de retour

EBP Sauv
Fig. 4.16  Cadres de pile pour la fonction factorielle

96

CHAPITRE 4.

SOUS-PROGRAMMES

1 2 3 4 5 6 7 8

void f ( int x ) { int i ; for ( i=0; i < x; i++ ) { printf ("%d\n", i); f( i ); } }
Fig. 4.17  Un autre exemple (version C)

La Figure 4.16 montre quoi ressemble la pile au point le plus profond pour l'appel de fonction ci-dessus. Les Figures 4.17 et 4.18 montrent un exemple rcursif plus compliqu en

f(3) ? Notez que ENTER cre un nouveau i sur la pile pour chaque appel rcursif. Donc, chaque instance rcursive de f a sa propre variable i indpendante. Dnir i comme un double mot dans le segment data ne fonctionnerait pas
C et en assmbleur, respectivement. Quelle est la sortie pour l'instruction pareil.

4.8.2 Rvision des types de stockage des variables en C


Le C fournit plusieurs types de stockage des variables.

global

Ces variables sont dclares en dehors de toute fonction et sont sto-

ckes des emplacements mmoire xes (dans les segments

bss)

data

ou

et existent depuis le dbut du programme jusqu' la n. Par

dfaut, on peut y accder de n'importe quelle fonction dans le programme ; cependant, si elles sont dclares comme sembleur, l'tiquette est interne, pas externe).

static,

fonctions dans le mme module peuvent y accder (

i.e.

seules les

en termes as-

static

Il s'agit des variables

locales d'une fonctions qui sont dclares static


static
avec deux sens diou

(Malheureusement, le C utilise le mot cl mmoire xes (dans

rents !) Ces variables sont galement stockes dans des emplacements

data

bss),

mais ne peuvent tre accdes di-

rectement que dans la fonction o elles sont dnies.

automatic

C'est le type par dfaut d'une variable C dnie dans une fonc-

tion. Ces variables sont alloues sur la pile lorsque la fonction dans laquelle elles sont dnies est appele et sont dsalloues lorsque la fonction revient. Donc, elles n'ont pas d'emplacement mmoire xe.

register

Ce mot cl demande au compilateur d'utiliser un registre pour la

donne dans cette variable. Il ne s'agit que d'une

requte.

Le compi-

4.8.

SOUS-PROGRAMMES RENTRANTS ET RCURSIFS

97

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

%define i ebp-4 %define x ebp+8 ; macros utiles segment .data format db "%d", 10, 0 ; 10 = '\n' segment .text global _f extern _printf _f: enter 4,0 ; alloue de la place sur la pile pour i lp: mov mov cmp jnl push push call add push call pop inc jmp leave ret dword [i], 0 eax, [i] eax, [x] quit eax format _printf esp, 8 dword [i] _f eax dword [i] short lp ; i = 0 ; i < x?

; appelle printf

; appelle f

; i++

quit:

Fig. 4.18  Un autre exemple (version assembleur)

98

CHAPITRE 4.

SOUS-PROGRAMMES

lateur n'a

pas

l'honorer. Si l'adresse de la variable est utilise un

endroit quelconque du programme, elle ne sera pas respecte (puisque les registres n'ont pas d'adresse). De plus, seuls les types entiers simples peuvent tre des valeurs registres. Les types structures ne peuvent pas l'tre ; ils ne tiendraient pas dans un registre ! Les compilateurs C placent souvent les variables automatiques normales dans des registres sans aide du programmeur.

volatile

Ce mot cl indique au compilateur que la valeur de la variable

peut changer tout moment. Cela signie que le compilateur ne peut faire aucune supposition sur le moment o la variable est modie. Souvent, un compilateur stocke la valeur d'une variable dans un registre temporairement et utilise le registre la place de la variable dans une section de code. Il ne peut pas faire ce type d'optimisations avec les variables

volatile.

Un exemple courant de variable volatile serait

une variable qui peut tre modie par deux threads d'un programme multi-thread. Considrons le code suivant :

1 2 3

x = 10; y = 20; z = x;
Si

pouvait tre altr par un autre thread, il est possible que l'autre

thread change Cependant, si

x x

entre les lignes 1 et 3, alors

z z

ne vaudrait pas 10. 10.

n'a pas t dclar comme volatile, le compilateur

peut supposer que

est inchang et positionner

Une autre utilisation de

volatile est d'empcher le compilateur d'uti-

liser un registre pour une variable.

Chapitre 5

Tableaux
5.1
Un

Introduction

tableau est un bloc contigu de donnes en mmoire. Tous les lments

de la liste doivent tre du mme type et occuper exactement le mme nombre d'octets en mmoire. En raison de ces proprits, les tableaux permettent un accs ecace une donne par sa position (ou indice) dans le tableau. L'adresse de n'importe quel lment peut tre calcule en connaissant les trois choses suivantes :  L'adresse du premier lment du tableau  Le nombre d'octets de chaque lment  L'indice de l'lment Il est pratique de considrer l'indice du premier lment du tableau comme tant 0 (comme en C). Il est possible d'utiliser d'autres valeurs pour le premier indice mais cela complique les calculs.

5.1.1 Dnir des tableaux


Dnir des tableaux dans les segments data et bss
Pour dnir un tableau initialis dans le segment tives

db, dw, etc.

data, utilisez les direc-

normales. directives. NASM fournit galement une direc-

tive utile appele

TIMES qui peut tre utilise pour rpter une expression de bss,

nombreuses fois sans avoir la dupliquer la main. La Figure 5.1 montre plusieurs exemples. Pour dnir un tableau non initialis dans le segment directives

resb, resw, etc. Souvenez vous que ces directives ont une oprande

utilisez les

qui spcie le nombre d'units mmoire rserver. La Figure 5.1 montre galement des exemples de ces types de dnitions. 99

100

CHAPITRE 5.

TABLEAUX

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

segment .data ; dfinit un tableau de 10 doubles mots initialiss 1,2,..,10 a1 dd 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ; dfinit un tableau de 10 mots initialiss 0 a2 dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; idem mais en utilisant TIMES a3 times 10 dw 0 ; dfinit un tableau d'octets avec 200 0 puis 100 1 a4 times 200 db 0 times 100 db 1 segment .bss ; dfinit un a5 ; dfinit un a6 tableau de 10 doubles mots non initialiss resd 10 tableau de 100 mots non initialiss resw 100

Fig. 5.1  Dnir des tableaux

Dnir des tableaux comme variables locales


Il n'y a pas de manire directe de dnir un tableau comme variable locale sur la pile. Comme prcdemment, on calcule le nombre total d'octets requis pour

toutes

les variables locales, y compris les tableaux, et on l'te de

ESP (soit directement, soit en utilisant l'instruction tableau de 50 lments d'un mot, il faudrait

ENTER). Par exemple, si


octets.

une fonction a besoin d'une variable caractre, deux entiers double-mot et un

1 + 2 4 + 50 2 = 109

Cependant, le nombre t de ESP doit tre un multiple de quatre (112 dans ce cas) pour maintenir ESP sur un multiple de double mot. On peut organiser les variables dans ces 109 octets de plusieurs faons. La Figure 5.2 montre deux manires possibles. La partie inutilise sert maintenir les doubles mots sur des adresses multiples de doubles mots an d'acclrer les accs mmoire.

5.1.2 Accder aux lments de tableaux


Il n'y a pas d'oprateur

[ ]

en langage assembleur comme en C. Pour

accder un lment d'un tableau, son adresse doit tre calcule. Considrons les deux dnitions de tableau suivantes :

array1 array2

db dw

5, 4, 3, 2, 1 5, 4, 3, 2, 1

; tableau d'octets ; tableau de mots

5.1.

INTRODUCTION

101

EBP - 1 EBP - 8 EBP - 12

char unused dword 1 dword 2 word array word array EBP - 100 EBP - 104 EBP - 108 EBP - 109 dword 1 dword 2 char unused

EBP - 112

Fig. 5.2  Organisations possibles de la pile

Voici quelques exemple utilisant ces tableaux :

1 2 3 4 5 6 7

mov mov mov mov mov mov mov

al, [array1] al, [array1 + [array1 + 3], ax, [array2] ax, [array2 + [array2 + 6], ax, [array2 +

1] al 2] ax 1]

; ; ; ; ; ; ;

al = array1[0] al = array1[1] array1[3] = al ax = array2[0] ax = array2[1] (PAS array2[2]!) array2[3] = ax ax = ??

A la ligne 5, l'lment 1 du tableau de mots est rfrenc, pas l'lment 2. Pourquoi ? Les mots sont des units de deux octets, donc pour se dplacer l'lment suivant d'un tableau de mots, on doit se dplacer de deux octets, pas d'un seul. La ligne 7 lit un octet du premier lment et un octet du suivant. En C, le compilateur regarde la type de pointeur pour dterminer de combien d'octets il doit se dplacer dans une expression utilisant l'arithmtique des pointeurs, an que le programmeur n'ait pas le faire. Cependant, en assembleur, c'est au programmeur de prendre en compte a taille des lments du tableau lorsqu'il se dplace parmi les lments. La Figure 5.3 montre un extrait de code qui additionne tous les lments de

array1

de l'exemple de code prcdent. A la ligne 7, AX est ajout

DX. Pourquoi pas AL ? Premirement, les deux oprandes de l'instruction

ADD

doivent avoir la mme taille. Deuximement, il serait facile d'ajouter

des octets et d'obtenir une somme qui serait trop grande pour tenir sur un octet. En utilisant DX, la somme peut atteindre 65 535. Cependant, il est important de raliser que AH est galement additionn. C'est pourquoi, AH est positionn zro

1 la ligne 3.

1 Positionner AH zro quivaut supposer implicitement que AL est un nombre non sign. S'il est sign, l'action approprie serait d'insrer une instruction CBW entre les lignes 6

102

CHAPITRE 5.

TABLEAUX

1 2 3 4 5 6 7 8 9

lp:

mov mov mov mov mov add inc loop

ebx, array1 dx, 0 ah, 0 ecx, 5 al, [ebx] dx, ax ebx lp

; ebx = adresse de array1 ; dx contiendra sum ; ? ; al = *ebx ; dx += ax (pas al!) ; bx++

Fig. 5.3  Faire la somme des lments d'un tableau (Version 1)

1 2 3 4 5 6 7 8 9 10

lp:

mov mov mov add jnc inc inc loop

ebx, array1 dx, 0 ecx, 5 dl, [ebx] next dh ebx lp

; ebx = adresse de array1 ; dx contiendra la somme ; dl += *ebx ; si pas de retenue goto next ; incrmente dh ; bx++

next:

Fig. 5.4  Faire la somme des lments d'un tableau (Version 2)

Les Figures 5.4 et 5.5 montrent deux manires alternatives de calculer la somme. Les lignes en italique remplacent les lignes 6 et 7 de la Figure 5.3.

5.1.3 Adressage indirect plus avanc


Ce n'est pas tonnant, l'adressage indirect est souvent utilis avec les tableaux. La forme la plus gnrale d'une rfrence mmoire indirecte est :

[ reg de base + facteur *reg d'index + constante ]


o :

reg de base
ou EDI.

est un des registres EAX, EBX, ECX, EDX, EBP, ESP, ESI

facteur
et 7.

est 1, 2, 4 ou 8 (S'il vaut 1, le facteur est omis).

5.1.

INTRODUCTION

103

1 2 3 4 5 6 7 8

lp:

mov mov mov add adc inc loop

ebx, array1 dx, 0 ecx, 5 dl, [ebx] dh, 0 ebx lp

; ebx = adresse de array1 ; dx contiendra la somme ; dl += *ebx ; dh += drapeau de retenue + 0 ; bx++

Fig. 5.5  Faire la somme des lments d'un tableau (Version 3)

reg d'index constante

est un des registres EAX, EBX, ECX, EDX, EBP, ESI, EDI

(Notez que ESP n'est pas dans la liste). est une constante 32 bits. Cela peut tre une tiquette (ou une

expression d'tiquette).

5.1.4 Exemple
Voici un exemple qui utilise un tableau et le passe une fonction. Il utilise le programme programme

array1c.c driver.c.

(dont le listing suit) comme pilote, pas le

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

%define ARRAY_SIZE 100 %define NEW_LINE 10 segment .data FirstMsg Prompt SecondMsg ThirdMsg InputFormat segment .bss array db db db db db

array1.asm

"10 premiers lments du tableau", 0 "Entrez l'indice de l'lment afficher : ", 0 "L'lment %d vaut %d", NEW_LINE, 0 "Elments 20 29 du tableau", 0 "%d", 0

resd ARRAY_SIZE

segment .text extern _puts, _printf, _scanf, _dump_line global _asm_main _asm_main: enter 4,0 ; variable locale dword en EBP - 4

104

CHAPITRE 5.

TABLEAUX

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

push push

ebx esi

; initialise le tableau 100, 99, 98, 97, ... mov mov init_loop: mov add loop push call pop push push call add ecx, ARRAY_SIZE ebx, array [ebx], ecx ebx, 4 init_loop dword FirstMsg _puts ecx dword 10 dword array _print_array esp, 8 ; affiche FirstMsg

; affiche les 10 premiers lments du tableau

; demande l'utilisateur l'indice de l'lment Prompt_loop: push dword Prompt call _printf pop ecx lea push push call add cmp je call jmp InputOK: mov push push eax, [ebp-4] ; eax = adresse du dword local eax dword InputFormat _scanf esp, 8 eax, 1 ; eax = valeur de retour de scanf InputOK _dump_line ; ignore le reste de la ligne et recommence Prompt_loop ; si la saisie est invalide esi, [ebp-4] dword [array + 4*esi] esi

5.1.

INTRODUCTION

105

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

push call add push call pop push push call add pop pop mov leave ret ; ; ; ; ; ; ; ; ;

dword SecondMsg _printf esp, 12 dword ThirdMsg _puts ecx dword 10 dword array + 20*4 _print_array esp, 8 esi ebx eax, 0

; affiche la valeur de l'lment

; affiche les lments 20 29

; adresse de array[20]

; retour au C

routine _print_array Routine appelable depuis le C qui affiche les lments d'un tableau de doubles mots comme des entiers signs. Prototype C: void print_array( const int * a, int n); Paramtres: a - pointeur vers le tableau afficher (en ebp+8 sur la pile) n - nombre d'entiers afficher (en ebp+12 sur la pile) db "%-5d %5d", NEW_LINE, 0

segment .data OutputFormat segment .text global _print_array: enter push push xor mov mov

_print_array 0,0 esi ebx esi, esi ecx, [ebp+12] ebx, [ebp+8] ; esi = 0 ; ecx = n ; ebx = adresse du tableau

106

CHAPITRE 5.

TABLEAUX

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119

print_loop: push push push push call add inc pop loop pop pop leave ret

ecx dword [ebx + 4*esi] esi dword OutputFormat _printf esp, 12 esi ecx print_loop ebx esi array1.asm

; printf change ecx ! ; empile tableau[esi]

; retire les paramtres (laisse ecx !)

array1c.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

#include <stdio.h> int asm_main( void ); void dump_line( void ); int main() { int ret_status ; ret_status = asm_main(); return ret_status ; }
/
fonction dump_line retire tous les caractres restant sur la ligne courante dans le buer d' entre /

void dump_line() { int ch;

while ( (ch = getchar()) != EOF && ch != '\n') / null body/ ;

5.1.

INTRODUCTION

107

array1c.c L'instruction LEA revisite


L'instruction code suivant :

LEA peut tre utilise dans d'autres cas que le calcul d'adresse.

Elle est assez couramment utilise pour les calculs rapides. Considrons le

lea

ebx, [4*eax + eax]


5 EAX dans EBX. Utiliser LEA dans ce cas est la fois MUL. Cependant, il faut tre conscient

Il stocke la valeur de

plus simple et plus rapide que d'utiliser du fait que l'expression entre crochets par 6 rapidement.

doit

tre une adresse indirecte lgale.

Donc, par exemple, cette instruction ne peut pas tre utilise pour multiplier

5.1.5 Tableaux Multidimensionnels


Les tableaux multidimensionnels ne sont pas vraiments dirents de ceux une dimension dont nous avons dj parl. En fait, il sont reprsents en mmoire exactement comme cela, un tableau une dimension.

Tableaux Deux Dimensions


Ce n'est pas tonnant, le tableau multidimensionnel le plus simple est celui deux dimensions. Un tableau deux dimensions est souvent reprsent comme une grille d'lments. Chaque lment est identi par une paire d'indices. Par convention, le premier indice est associ la ligne et le second la colonne. Considrons un tableau avec trois lignes et deux colonnes dni de la manire suivante :

int a [3][2];
Le compilateur C rserverait de la place pour un tableau d'entiers de 6 (=

2 3)

et placerait les lments comme suit : Indice Elment 0 a[0][0] 1 a[0][1] 2 a[1][0] 3 a[1][1] 4 a[2][0] 5 a[2][1]

Ce que ce tableau tente de montrer et que l'lment rferenc comme

a[0][0]

est stock au dbut du tableau de 6 lments une dimension. L'lment

a[0][1]

est stock la position suivante (indice 1)

etc.

Chaque ligne du

tableau deux dimensions est stocke en mmoire de faon contige. Le dernier lment d'une ligne est suivi par le premier lment de la suivante. On

108

CHAPITRE 5.

TABLEAUX

1 2 3 4 5

mov sal add mov mov

eax, eax, eax, eax, [ebp

[ebp 1 [ebp [ebp + - 52],

44]

; ; 48] ; 4*eax - 40] ; eax ;

ebp - 44 est l'emplacement de i multiplie i par 2 ajoute j ebp - 40 est l'adresse de a[0][0] stocke le rsultat dans x (en ebp - 52)

Fig. 5.6 

Assembleur correspondant

x = a[ i ][ j ]

appelle cela la reprsentation

au niveau ligne

(rowwise) du tableau et c'est

comme cela qu'un compilateur C/C++ reprsenterait le tableau. Comment le compilateur dtermine o et

a[i][j]

se trouve dans la repr-

sentation au niveau ligne ? Une formule simple calcule l'indice partir de

j. La formule dans ce cas est 2i + j . Il n'est pas compliqu de voir comment


i est l'emplacement 2i. Puis on obtient l'emplaj 2i. Cette analyse montre galement gnralise un tableau de N colonnes : N i + j .

on obtient cette formule. Chaque ligne fait deux lments de long ; donc le premier lment de la ligne cement de la colonne

en ajoutant

comment la formule est

Notez que la formule ne dpend

pas

du nombre de lignes.

Pour illustrer, voyons comment tableau

gcc

compile le code suivant (utilisant le

dni plus haut) :

x = a[ i ][ j ];
La Figure 5.6 montre le code assembleur correspondant. Donc, le compilateur convertit grossirement le code en :

x = (&a[0][0] + 2 i + j );
et en fait, le programmeur pourrait l'crire de cette manire et obtenir le mme rsultat. Il n'y a rien de magique propos du choix de la reprsentation niveau ligne du tableau. Une reprsentation niveau colonne fonctionnerait galement : Indice Elment 0 a[0][0] 1 a[1][0] 2 a[2][0] 3 a[0][1] 4 a[1][1] 5 a[2][1]

Dans la reprsentation niveau colonne, chaque colonne est stocke de manitre contige. L'lment

[i][j] est stock l'emplacement i +3j . D'autres

langages (FORTRAN, par exemple) utilisent la reprsentation niveau colonne. C'est important lorsque l'on interface du code provenant de multiples langages.

5.1.

INTRODUCTION

109

Dimensions Suprieures Deux


Pour les dimensions suprieures deux, la mme ide de base est applique. Considrons un tableau trois dimensions :

int b [4][3][2];
Ce tableau serait stock comme s'il tait compos de trois tableaux deux dimensions, chacun de taille

[3][2] stocks conscutivement en mmoire. Le


1 2 b[0][1][0] 8 b[1][1][0] 3 b[0][1][1] 9 b[1][1][1] est 4 b[0][2][0] 10 b[1][2][0] 5 b[0][2][1] 11 b[1][2][1] Le

tableau ci-dessous montre comment il commence : Indice Elment Indice Elment 0 b[0][0][0] 6 b[1][0][0]

b[0][0][1] 7 b[1][0][1]

La formule pour calculer la position de 6 est dtermin par la taille des tableaux bleau de dimension dans la formule.

a[L][M][N] l'emplacement de l'lment a[i][j][k] sera M N i + N j + k . Notez, l encore, que la dimension L n'apparat pas
Pour les dimensions plus grandes, le mme procd est gnralis. Pour un tableau

b[i][j][k] [3][2]. En

6i + 2j + k .

gnral, pour un ta-

dimensions de dimension

D1

Dn ,

l'emplacement d'un lment

repr par les indices

i1

in

est donn par la formule :

D2 D3 Dn i1 + D3 D4 Dn i2 + + Dn in1 + in
ou pour les fanas de maths, on peut l'crire de faon plus concise :

n j =1
La premire dimension,

Dk ij
C'est prenez l que vous l'auteur comest

k=j +1

D1 ,

n'apparat pas dans la formule.

Pour la reprsentation au niveau colonne, la formule gnrale serait :

que

un major de physique (ou

i1 + D1 i2 + + D1 D2 Dn2 in1 + D1 D2 Dn1 in


ou dans la notation des fanas de maths :

la rfrence FORTRAN vous avait dj mis sur la voie ?)

n j =1

j 1 k=1

Dk ij Dn ,
qui n'apparat pas dans la

Dans ce cas, c'est la dernire dimension, formule.

110

CHAPITRE 5.

TABLEAUX

Passer des Tableaux Multidimensionnels comme Paramtres en C


La reprsentation au niveau ligne des tableaux multidimensionnels a un eet direct sur la programmation C. Pour les tableaux une dimension, la taille du tableau n'est pas ncessaire pour calculer l'emplacement en mmoire de n'importe quel lment. Ce n'est pas vrai pour les tableaux multidimensionnels. Pour accder aux lments de ces tableaux, le compilateur doit connatre toutes les dimensions sauf la premire. Cela saute aux yeux lorsque l'on observe le prototype d'une fonction qui prend un tableau multidimensionnel comme paramtre. Ce qui suit ne compilera pas :

void f ( int a [ ][ ] ); void f ( int a [ ][2] );

/ pas d' information sur la dimension /

Cependant, ce qui suit compile :

Tout tableau deux dimensions deux colonnes peut tre pass cette fonction. La premire dimension n'est pas ncessaire . Ne confondez pas avec une fonction ayant ce prototype :

void f ( int a [ ] );
Cela dnit un tableau une dimension de pointeurs sur des entiers (qui peut son tour tre utilis pour crer un tableau de tableaux qui se comportent plus comme un tableau deux dimensions). Pour les tableaux de dimensions suprieures, toutes les dimensions sauf la premire doivent tre donnes pour les paramtres. Par exemple, un paramtre tableau quatre dimensions peut tre pass de la faon suivante :

void f ( int a [ ][4][3][2] );


5.2 Instructions de Tableaux/Chanes

La famille des processeurs 80x86 fournit plusieurs instructions conues pour travailler avec les tableaux. Ces instructions sont appeles

de chanes.

instructions

Elles utilisent les registres d'index (ESI et EDI) pour eectuer

une opration puis incrmentent ou dcrmentent automatiquement l'un des registres d'index ou les deux. Le

drapeau de direction

(DF) dans le registre

FLAGS dtermine si les registres d'index sont incrments ou dcrments. Il y a deux instructions qui modient le drapeau de direction :

CLD STD
2

teint le drapeau de direction. Les registres d'index sont alors incrments. allume le drapeau de direction. Les registres d'index sont alors dcrments.

On peut indiquer une taille mais elle n'est pas prise en compte par le compilateur.

5.2.

INSTRUCTIONS DE TABLEAUX/CHANES

111

LODSB LODSW LODSD

AL = [DS:ESI] ESI = ESI 1 AX = [DS:ESI] ESI = ESI 2 EAX = [DS:ESI] ESI = ESI 4

STOSB STOSW STOSD

[ES:EDI] = AL EDI = EDI 1 [ES:EDI] = AX EDI = EDI 2 [ES:EDI] = EAX EDI = EDI 4

Fig. 5.7  Instructions de lecture et d'criture de chane

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

segment .data array1 dd 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 segment .bss array2 resd 10 segment .text cld mov esi, array1 mov edi, array2 mov ecx, 10 lp: lodsd stosd loop lp ; ne pas oublier !

Fig. 5.8  Exemples de chargement et de stockage

Une erreur

trs

courante dans la programmation 80x86 est d'oublier de po-

sitionner le drapeau de direction. Cela conduit souvent un code qui fonctionne la plupart du temps (lorsque le drapeau de direction est dans la position dsire), mais ne fonctionne pas

tout

le temps.

5.2.1 Lire et crire en mmoire


Les instructions de chane les plus simples lisent ou crivent en mmoire, ou les deux. Elles peuvent lire ou crire un octet, un mot ou un double-mot la fois. La Figure 5.7 montre ces instructions avec une brve description de ce qu'elles font en pseudo-code. Il y a plusieurs choses noter ici. Tout d'abord, ESI est utilis pour lire et EDI pour crire. C'est facile retenir si l'on se souvient que SI signie

Index

Source Index (Indice Source) et DI Destination

(Indice de Destination). Ensuite, notez que le registre qui contient la

112

CHAPITRE 5.

TABLEAUX

MOVSB MOVSW MOVSD

byte [ES:EDI] = byte [DS:ESI] ESI = ESI 1 EDI = EDI 1 word [ES:EDI] = word [DS:ESI] ESI = ESI 2 EDI = EDI 2 dword [ES:EDI] = dword [DS:ESI] ESI = ESI 4 EDI = EDI 4

Fig. 5.9  Instructions de dplacement de chane en mmoire

1 2 3 4 5 6 7 8 9

segment .bss array resd 10 segment .text cld mov edi, array mov ecx, 10 xor eax, eax rep stosd ; ne pas oublier !

Fig. 5.10  Exemple de tableau mise zro d'un tableau

donne est xe (AL, AX ou EAX). Enn, notez que les instructions de stockage utilisent ES pour dterminer le segment dans lequel crire, pas DS. En programmation en mode protg, ce n'est habituellement pas un problme, puisqu'il n'y a qu'un segment de donnes et ES est automatiquement initialis pour y faire rfrence (tout comme DS). Cependant, en programmation en mode rel, il est

trs

important pour le programmeur d'initialiser ES avec

la valeur de slecteur de segment correcte . La Figure 5.8 montre un exemple de l'utilisation de ces instructions qui copie un tableau dans un autre. La combinaison des instructions

LODSx et STOSx (comme aux lignes 13 et MOVSx.


La Figure 5.9 dcrit les

14 de la Figure 5.8) est trs courante. En fait, cette combinaison peut tre eectue par une seule instruction de chane oprations eectues par cette instruction. Les lignes 13 et 14 de la Figure 5.8 pourraient tre remplaces par une seule instruction

MOVSD

avec le mme

Une autre complication est qu'on ne peut pas copier la valeur du registre DS dans ES directement en utilisant une instruction MOV. A la place, la valeur de DS doit tre copie dans un registre universel (comme AX) puis tre copi depuis ce registre dans ES, ce qui utilise deux instruction MOV.

5.2.

INSTRUCTIONS DE TABLEAUX/CHANES

113

CMPSB

CMPSW

CMPSD

SCASB SCASW SCASD

compare l'octet en [DS:ESI] avec celui en [ES:EDI] ESI = ESI 1 EDI = EDI 1 compare le mot en [DS:ESI] avec celui en [ES:EDI] ESI = ESI 2 EDI = EDI 2 compare de double-mot en [DS:ESI] avec celui en [ES:EDI] ESI = ESI 4 EDI = EDI 4 compare AL et [ES:EDI] EDI 1 compare AX et [ES:EDI] EDI 2 compare EAX et [ES:EDI] EDI 4

Fig. 5.11  Instructions de comparaison de chanes

rsultat. La seule dirence serait que le registre EAX ne serait pas utilis du tout dans la boucle.

5.2.2 Le prxe d'instruction REP


La famille 80x86 fournit un prxe d'instruction spcial

4 appel

REP

qui

peut tre utilis avec les instructions de chane prsentes ci-dessus. Ce prxe indique au processeur de rpter l'instruction de chane qui suit un nombre prcis de fois. Le registre ECX est utilis pour compter les itrations (exactement comme pour l'instruction

REP,

LOOP).

En utilisant le prxe

la boucle de la Figure 5.8 (lignes 12 15) pourrait tre remplace par

une seule ligne :

rep movsd
La Figure 5.10 montre un autre exemple qui met zro le contenu d'un tableau.

Un prxe d'instruction n'est pas une instruction, c'est un octet spcial qui est plac avant une instruction de chane qui modie son comportement. D'autres prxes sont galement utiliss pour modier le segment par dfaut des accs mmoire

114

CHAPITRE 5.

TABLEAUX

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

segment .bss array resd 100 segment .text cld mov edi, array ; pointeur vers le dbut du tableau mov ecx, 100 ; nombre d'lments mov eax, 12 ; nombre rechercher lp: scasd je found loop lp ; code excuter si non trouv jmp onward found: sub edi, 4 ; edi pointe maintenant vers 12 ; code excuter si trouv onward:

Fig. 5.12  Exemple de recherche

5.2.3 Instructions de comparaison de chanes


La Figure 5.11 montre plusieurs nouvelles instructions de chanes qui peuvent tre utilises pour comparer des donnes en mmoire entre elles ou avec des registres. Elles sont utiles pour comparer des tableaux ou eectuer des recherches. Elles positionnent le registre FLAGS exactement comme l'instruction

CMP.

Les instructions

CMPSx

comparent les emplacements m-

moire correspondants et les instructions mmoire pour une valeur particulire.

SCASx

scannent des emplacements

La Figure 5.12 montre un court extrait de code qui recherche le nombre 12 dans un tableau de double mots. L'instruction

SCASD

la ligne 10 ajoute

toujours 4 EDI, mme si la valeur recherche est trouve. Donc, si l'on veut trouver l'adresse du 12 dans le tableau, il est ncessaire de soustraire 4 de EDI (comme le fait la ligne 16).

5.2.4 Les prxes d'instruction REPx


Il y a plusieurs autres prxes d'instruction du mme genre que

REP

qui peuvent tre utiliss avec les instructions de comparaison de chanes. La Figure 5.13 montre les deux nouveaux prxes et dcrit ce qu'ils font.

5.2.

INSTRUCTIONS DE TABLEAUX/CHANES

115

REPE, REPZ REPNE, REPNZ

rpte l'instruction tant que le drapeau Z est allum ou au plus ECX fois rpte l'instruction tant que le drapeau Z est teint ou au plus ECX fois
Fig. 5.13  Prxes d'instruction

REPx

1 2 3 4 5 6 7 8 9 10 11 12

segment .text cld mov esi, block1 ; adresse du premier bloc mov edi, block2 ; adresse du second bloc mov ecx, size ; taille des blocs en octets repe cmpsb ; rpter tant que Z est allum je equal ; Z est allum => blocs gaux ; code excuter si les blocs ne sont pas gaux jmp onward equal: ; code excuter s'ils sont gaux onward:

Fig. 5.14  Comparer des blocs mmoire

REPE

et

le sont

REPZ sont simplement des synonymes pour le mme REPNE et REPNZ). Si l'instruction de comparaison de

prxe (comme chanes rpte

stoppe cause du rsultat de la comparaison, le ou les registres d'index sont quand mme incrments et ECX dcrment ; cependant, le registre FLAGS contient toujours l'tat qu'il avait la n de la rptition. Donc, il Pourquoi ne peut pas pas
regarder si est possible d'utiliser le drapeau Z pour dterminer si la comparaison s'est simplement ECX est zro aprs la arrte cause de son rsultat ou si c'est parce que ECX a atteint zro.

La Figure 5.14 montre un extrait de code qui dtermine si deux blocs de mmoire sont gaux. Le

comparaison rpte ?

JE

de la ligne 7 de l'exemple vrie le rsultat

de l'instruction prcdente. Si la comparaison s'est arrte parce qu'elle a trouve deux octets dirents, le drapeau Z sera toujours teint et aucun branchement n'est eectu ; cependant, si la comparaison s'est arrte parce que ECX a atteint zro, le drapeau Z sera toujours allum et le code se branche l'tiquette

equal.

5.2.5 Exemple
Cette section contient un chier source assembleur avec plusieurs fonctions qui implmentent des oprations sur les tableaux en utilisant des instructions de chane. Beaucoup de ces fonctions font doublons avec des fonc-

116

CHAPITRE 5.

TABLEAUX

tions familires de la bibliothque C.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

memory.asm global _asm_copy, _asm_find, _asm_strlen, _asm_strcpy segment .text ; fonction _asm_copy ; copie deux blocs de mmoire ; prototype C ; void asm_copy( void * dest, const void * src, unsigned sz); ; paramtres: ; dest - pointeur sur le tampon vers lequel copier ; src - pointeur sur le tampon depuis lequel copier ; sz - nombre d'octets copier ; ci-dessous, quelques symboles utiles sont dfinis %define dest [ebp+8] %define src [ebp+12] %define sz [ebp+16] _asm_copy: enter 0, 0 push esi push edi mov mov mov cld rep pop pop leave ret ; ; ; ; ; esi, src edi, dest ecx, sz movsb edi esi ; esi = adresse du tampon depuis lequel copier ; edi = adresse du tampon vers lequel copier ; ecx = nombre d'octets copier ; teint le drapeau de direction ; excute movsb ECX fois

fonction _asm_find recherche un octet donn en mmoire void * asm_find( const void * src, char target, unsigned sz); paramtres : src - pointeur sur le tampon dans lequel chercher

5.2.

INSTRUCTIONS DE TABLEAUX/CHANES

117

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

; target - octet rechercher ; sz - nombre d'octets dans le tampon ; valeur de retour : ; si l'lment recherch est trouv, un pointeur vers sa premire occurence dans le tampon ; est retourn ; sinon, NULL est retourn ; NOTE : l'lment rechercher est un octet, mais il est empil comme une valeur dword. ; La valeur de l'octet est stocke dans les 8 bits de poids faible. ; %define src [ebp+8] %define target [ebp+12] %define sz [ebp+16] _asm_find: enter push mov mov mov cld repne je mov jmp found_it: mov dec quit: pop leave ret ; ; ; ; ; ; 0,0 edi eax, target edi, src ecx, sz scasb found_it eax, 0 short quit eax, edi eax edi ; al a la valeur recherche

; scanne jusqu' ce que ECX == 0 ou [ES:EDI] == AL ; si le drapeau zro est allum, on a trouv ; si pas trouv, retourner un pointeur NULL

; si trouv retourner (DI - 1)

fonction _asm_strlen retourne la taille d'une chane unsigned asm_strlen( const char * ); paramtre : src - pointeur sur la chane valeur de retour :

118

CHAPITRE 5.

TABLEAUX

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124

nombre de caractres dans la chane (sans compter le 0 terminal) (dans EAX)

%define src [ebp + 8] _asm_strlen: enter 0,0 push edi mov mov xor cld repnz edi, src ; edi = pointeur sur la chane ecx, 0FFFFFFFFh ; utilise la plus grande valeur possible de ECX al,al ; al = 0 scasb ; recherche le 0 terminal

; ; repnz ira un cran trop loin, donc la longueur vaut FFFFFFFE - ECX, ; pas FFFFFFFF - ECX ; mov eax,0FFFFFFFEh sub eax, ecx ; longueur = 0FFFFFFFEh - ecx pop leave ret edi

; fonction _asm_strcpy ; copie une chane ; void asm_strcpy( char * dest, const char * src); ; paramtres : ; dest - pointeur sur la chaine vers laquelle copier ; src - pointeur sur la chane depuis laquelle copier ; %define dest [ebp + 8] %define src [ebp + 12] _asm_strcpy: enter 0,0 push esi push edi mov mov cld edi, dest esi, src

5.2.

INSTRUCTIONS DE TABLEAUX/CHANES

119

125 126 127 128 129 130 131 132 133 134

cpy_loop: lodsb stosb or jnz pop pop leave ret

al, al cpy_loop edi esi

; ; ; ;

charge AL & incrmente SI stocke AL & incrmente DI positionne les drapeaux de condition si l'on est pas aprs le 0 terminal, on continue

memory.asm

memex.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

#include <stdio.h> #dene STR_SIZE 30 / prototypes / void asm_copy( void , const void , unsigned ) __attribute__((cdecl)); void asm_nd( const void , char target , unsigned ) __attribute__((cdecl)); unsigned asm_strlen( const char ) __attribute__((cdecl)); void asm_strcpy( char , const char ) __attribute__((cdecl)); int main() { char st1 [STR_SIZE] = "chane test"; char st2 [STR_SIZE]; char st ; char ch;
asm_copy(st2, st1, STR_SIZE); / copie les 30 caractres de la chane / printf ("%s\n", st2); printf ("Entrez un caractre : " ); / recherche un octet dans la chane / scanf("%c%[^\n]", &ch); st = asm_nd(st2, ch, STR_SIZE); if ( st ) printf ("Trouv : %s\n", st ); else printf ("Pas trouv\n");

120

CHAPITRE 5.

TABLEAUX

30 31 32 33 34 35 36 37 38 39

st1 [0] = 0; printf ("Entrez une chane :"); scanf("%s", st1); printf ("longueur = %u\n", asm_strlen(st1)); asm_strcpy( st2, st1 ); printf ("%s\n", st2 ); } / copie des donnes dans la chane /

return 0;

memex.c

Chapitre 6

Virgule Flottante
6.1 Reprsentation en Virgule Flottante

6.1.1 Nombres binaires non entiers


Lorsque nous avons parl des systmes numriques dans le premier chapitre, nous n'avons abord que le nombres entiers. Evidemment, il est possible de reprsenter des nombres non entiers dans des bases autres que le dcimal. En dcimal, les chires droite de la virgule sont associs des puissances ngatives de dix :

0, 123 = 1 101 + 2 102 + 3 103


Ce n'est pas tonnant, les nombres binaires fonctionnent de la mme faon :

0, 1012 = 1 21 + 0 22 + 1 23 = 0, 625
Cette ide peut tre associe avec les mthodes appliques aux entiers dans le Chapitre 1 pour convertir un nombre quelconque :

110, 0112 = 4 + 2 + 0, 25 + 0, 125 = 6, 375


Convertir du binaire vers le dcimal n'est pas trs dicile non plus. Divisez le nombre en deux parties : entire et dcimale. Convertissez la partie entire en binaire en utilisant les mthodes du Chapitre 1, la partie dcimale est convertie en utilisant la mthode dcrite ci-dessous. Considrons une partie dcimale binaire dont les bits sont nots Le nombre en binaire ressemble alors :

a, b, c, . . .

0, abcdef . . .
Multipliez le nombre par deux. La reprsentation du nouveau nombre sera :

a, bcdef . . .
121

122

CHAPITRE 6.

VIRGULE FLOTTANTE

0, 5625 2 = 1, 125 0, 125 2 = 0, 25 0, 25 2 = 0, 5 0, 5 2 = 1, 0

premier bit deuxime bit troisime bit quatrime bit

= 1 = 0 = 0 = 1

Fig. 6.1  Convertir 0,5625 en binaire

0, 85 2 = 1, 7 0, 7 2 = 1 , 4 0, 4 2 = 0 , 8 0, 8 2 = 1 , 6 0, 6 2 = 1 , 2 0, 2 2 = 0 , 4 0, 4 2 = 0 , 8 0, 8 2 = 1 , 6

Fig. 6.2  Convertir 0,85 en binaire

Notez que le premier bit est maintenant en tte. Remplacez le obtenir :

par

pour

0, bcdef . . .
et multipliez nouveau par deux, vous obtenez :

b, cdef . . .
Maintenant le deuxime bit (b) est la premire place. Cette procdure peut tre rpte jusqu' ce qu'autant de bits que ncessaires soient trouvs. La Figure 6.1 montre un exemple rel qui converti 0,5625 en binaire. La mthode s'arrte lorsque la partie dcimale arrive zro. Prenons un autre exemple, considrons la conversion de 23,85 en binaire, il est facile de convertir la partie entire (23

= 101112 ),

mais qu'en est-il de

la partie dcimale (0, 85) ? La Figure 6.2 montre le dbut de ce calcul. Si

6.1.

REPRSENTATION EN VIRGULE FLOTTANTE

123

l'on regarde les nombres attentivement, on trouve une boucle innie ! Cela signie que 0,85 est un binaire rptitif (par analogie un dcimal rptitif en base 10) . Il y a un motif dans les nombres du calcul. En regardant le motif, on peut voir que

0, 85 = 0, 1101102 .

Donc,

23, 85 = 10111, 1101102 .

Une consquence importante du calcul ci-dessus est que 23,85 ne peut pas tre reprsent

1 (Tout comme 3 ne peut pas tre reprsent en dcimal avec un nombre ni
de chires). Comme le montre ce chapitre, les variables

exactement

en binaire en utilisant un nombre ni de bits

float et double en C

sont stockes en binaire. Donc, les valeurs comme 23,85 ne peuvent pas tre stockes exactement dans ce genre de variables. Seule une approximation de 23,85 peut tre stocke. Pour simplier le matriel, les nombres en virgule ottante sont stocks dans un format standard. Ce format utilise la notation scientique (mais en binaire, en utilisant des puissances de deux, pas de dix). Par exemple, 23,85 ou

10111, 11011001100110 . . .2

serait stock sous la forme :

1, 011111011001100110 . . . 2100
(o l'exposant (100) est en binaire). Un nombre en virgule ottante a la forme :

normalis

1, ssssssssssssssss 2eeeeeee
o

1, sssssssssssss

est la

mantisse

et

eeeeeeee

est l'

exposant.

6.1.2 Reprsentation en virgule ottante IEEE


L'IEEE (Institute of Electrical and Electronic Engineers) est une organisation internationale qui a conu des formats binaires spciques pour stocker les nombres en virgule ottante. Ce format est utilis sur la plupart des ordinateurs (mais pas tous !) fabriqu de nos jours. Souvent, il est support par le matriel de l'ordinateur lui-mme. Par exemple, les coprocesseurs numriques (ou arithmtiques) d'Intel (qui sont intgrs tous leurs processeurs depuis le Pentium) l'utilisent. L'IEEE dnit deux formats dirents avec des prcisions direntes : simple et double prcision. La simple prcision est utilise pour les variables

double.
cision plus leve appele

float en C et la double prcision pour les variables

Le coprocesseur arithmtique d'Intel utilise galement une troisime pr-

prcision tendue. En fait toutes les donnes dans

le coprocesseur sont dans ce format. Lorsqu'elles sont stockes en mmoire depuis le coprocesseur, elles sont converties soit en simple, soit en double

Cela n'est pas surprenant qu'un nombre puisse tre rptitif dans une base, mais pas dans une autre. Pensez 1 , il se rpte en dcimal, mais en ternaire (base 3) il vaudrait 3 0, 13 .

124

CHAPITRE 6.

VIRGULE FLOTTANTE

31 s s e f

30 e

23

22 f

bit de signe - 0 = positif, 1 = negatif Exposant dcal (8-bits) = exposant rel + 7F (127 en dcimal). Les valeurs 00 et FF ont des signications spciales (voir texte). partie dcimale - les 23 premiers bits aprs le 1, dans la mantisse.
Fig. 6.3  Simple prcision IEEE

prcision automatiquement . La prcision tendue utilise un format gnral lgrement dirent des formats oat et double de l'IEEE, nous n'en parlerons donc pas ici.

Simple prcision IEEE


La virgule ottante simple prcision utilise 32 bits pour encoder le nombre. Elle est gnralement prcise jusqu' 7 chires signicatifs. Les nombres en virgule ottante sont gnralement stocks dans un format beaucoup plus compliqu que celui des entiers. La Figure 6.3 montre le format de base d'un nombre en simple prcision IEEE. Ce format a plusieurs inconvnients. Les nombres en virgule ottante n'utilisent pas la reprsentation en complment deux pour les nombres ngatifs. Ils utilisent une reprsentation en grandeur signe. Le bit 31 dtermine le signe du nombre. L'exposant binaire n'est pas stock directement. A la place, la somme de l'exposant et de 7F est stocke dans les bits 23 30, Cet toujours positif ou nul. La partie dcimale suppose une mantisse normalise (de la forme Comme le premier bit est toujours un 1, le 1 de gauche n'est la prcision. Cette ide est appele
Il 41 faut l'esprit BE toujours que les CC CD garder peuvent

exposant dcal pas stock !

est

1, sssssssss).
Cela

permet le stockage d'un bit additionnel la n et augmente donc lgrement

reprsentation en un masqu .

Comment serait stock 23,85 ? Tout d'abord, il est positif, donc le bit

octets de signe est 0, Ensuite, l'exposant rel est 4, donc l'exposant dcal est

tre interprts de faons direntes selon ce qu'en fait le programme ! un nombre Vus en comme

7F + 4 = 8316 .

Enn, la fraction vaut 01111101100110011001100 (souvenez

vous que le un de tte est masqu). En les mettant bout bout (pour clarier les direntes sections du format en virgule ottante, le bit de signe et la fraction on t souligns et les bits ont t regroups en groupes de 4 bits) :

virgule ottante en simple prcision, ils reprsentent 23,850000381, comme mot, un ils mais vus entier double

0 100 0001 1 011 1110 1100 1100 1100 11002 = 41BECCCC16


Le type long double de certains compilateurs (comme celui de Borland) utilise cette prcision tendue. Par contre, d'autres compilateurs utilisent la double prcision la fois pour les double et long double (C'est autoris par le C ANSI).
2

reprsentent

1,103,023,309 ! Le processeur ne sait pas quelle est la bonne interprtation !

6.1.

REPRSENTATION EN VIRGULE FLOTTANTE

125

e = 0 et f = 0 e = 0 et f = 0 e = FF et f = 0 e = FF et f = 0

indique le nombre zro (qui ne peut pas tre normalis). Notez qu'il y a +0 et -0. indique un rons dans la section suivante. indique l'inni (). Il y a un inni positif et un inni ngatif. indique un rsultat indni, appel a Number, pas un nombre).

nombre dnormalis. Nous en parle-

NaN

(Not

Tab. 6.1  Valeurs spciales de

et

Cela ne fait pas exactement 23,85 (puisqu'il s'agit d'un binaire rptitif ). Si l'on convertit le chire ci-dessus en dcimal, on constate qu'il vaut approximativement 23,849998474, ce nombre est trs proche de 23,85 mais n'est pas exactement le mme. En ralit, en C, 23,85 ne serait pas reprsent exactement comme ci-dessus. Comme le bit le plus gauche qui a t supprim de la reprsentation exacte valait 1, le dernier bit est arrondi 1, donc 23,85 serait reprsent par 41 BE CC CD en hexa en utilisant la simple prcision. En dcimal, ce chire vaut 23,850000381 ce qui est une meilleure approximation de 23,85. Comment serait reprsent -23,85 ? Il sut de changer le bit de signe : C1 BE CC CD. Ne prenez

pas

Certaines combinaisons de

le complment deux ! et

ont une signication spciale pour les

ottants IEEE. Le Tableau 6.1 dcrit ces valeurs. Un inni est produit par un dpassement de capacit ou une division par zro. Un rsultat indni est produit par une opration invalide comme rechercher la racine care d'un nombre ngatif, additionner deux innis, de

etc.

Les nombres en simple prcision normaliss peuvent prendre des valeurs

1, 0 2126 ( 1, 1755 1035 )

1, 11111 . . . 2127 ( 3, 4028 1035 ).

Nombres dnormaliss
Les nombres dnormaliss peuvent tre utiliss pour reprsenter des nombres avec des grandeurs trop petites normaliser ( Par exemple, considrons le nombre

1, 0012 2129 ( 1, 6530 1039 ). 0, 010012 2127 .


Pour stocker

i.e. infrieures 1, 0 2126 ).

Dans la forme normalise, l'exposant est trop petit. Par contre, il peut tre reprsent sous une forme non normalise : ce nombre, l'exposant dcal est positionn 0 (voir Tableau 6.1) et la partie dcimale est la mantisse complte du nombre crite comme un produit avec

2127

i.e. tous les bits sont stocks, y compris le un gauche du point


1, 001 2129
est donc :

dcimal). La reprsentation de

0 000 0000 0 001 0010 0000 0000 0000 0000

126

CHAPITRE 6.

VIRGULE FLOTTANTE

63 s

62 e

52

51 f

Fig. 6.4  Double prcision IEEE

Double prcision IEEE


La double prcision IEEE utilise 64 bits pour reprsenter les nombres et est prcise jusqu' environ 15 chires signicatifs. Comme le montre la Figure 6.4, le format de base est trs similaire celui de la simple prcision. Plus de bits sont utiliss pour l'exposant dcal (11) et la partie dcimale (52). La plage de valeurs plus grande pour l'exposant dcal a deux consquences. La premire est qu'il est calcul en faisant la somme de l'exposant rel et de 3FF (1023) (pas 7F comme pour la simple prcision). Deuximement, une grande plage d'exposants rels (et donc une plus grande plage de grandeurs) est disponible. Les grandeurs en double prcision peuvent prendre des valeurs entre environ

10308

et

10308 .

C'est la plus grande taille du champ rserv la partie dcimale qui est reponsable de l'augmentation du nombre de chires signicatifs pour les valeurs doubles. Pour illustrer cela, reprenons 23,85. L'exposant dcal sera en hexa. Donc, la reprsentation double sera :

4+3FF = 403

0 100 0000 0011 0111 1101 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010
ou 40 37 D9 99 99 99 99 9A en hexa. Si l'on reconvertit ce nombre en dcimal, on trouve 23,8500000000000014 (il y a 12 zros !) ce qui est une approximation de 23,85 nettement meilleure. La double prcision a les mmes valeurs spciales que la simple prcision . Les nombres dnormaliss sont galement trs similaires. La seule dirence principale est que les nombres doubles dnormaliss utilisent

21023

au lieu

127 . de 2

6.2

Arithmtique en Virgule Flottante

L'arithmtique en virgule ottante sur un ordinateur est dirente de celle des mathmatiques. En mathmatiques, tous les nombres peuvent tre

3 La seule dirence est que pour l'inni et les valeurs indnies, l'exposant dcal vaut 7FF et non pas FF.

6.2.

ARITHMTIQUE EN VIRGULE FLOTTANTE

127

considrs comme exacts. Comme nous l'avons montr dans la section prcdente, sur un ordinateur beaucoup de nombres ne peuvent pas tre reprsents exactement avec un nombre ni de bits. Tous les calculs sont eectus avec une prcision limite. Dans les exemples de cette section, des nombres avec une mantisse de 8 bits seront utiliss pour plus de simplicit.

6.2.1 Addition
Pour additionner deux nombres en virgule ottante, les exposants doivent tre gaux. S'ils ne le sont pas dj, alors, il faut les rendre gaux en dcalant la mantisse du nombre ayant le plus petit exposant. Par exemple, considrons

10, 375 + 6, 34375 = 16, 71875

ou en binaire :

1, 0100110 23 + 1, 1001011 22
Ces deux nombres n'ont pas le mme exposant, donc on dcale la mantisse pour rendre les exposants gaux, puis on eectue l'addition :

1.0100110 23 + 0.1100110 23 10.0001100 23


Notez que le dcalage de

1, 100101122 supprime le un de tte et arrondi plus 3 3 tard le rsultat en 0, 1100110 2 . Le rsultat de l'addition, 10, 0001100 2 4 (ou 1, 00001100 2 ), est gal 10000, 1102 ou 16,75, ce n'est pas gal
la rponse exacte (16,71875) ! Il ne s'agit que d'une approximation due aux erreurs d'arrondi du processus d'addition. Il est important de raliser que l'arithmtique en virgule ottante sur un ordinateur (ou une calculatrice) est toujours une approximation. Les lois des mathmatiques ne fonctionnent pas toujours avec les nombres en virgule ottante sur un ordinateur. Les mathmatiques supposent une prcision innie qu'aucun ordinateur ne peut atteindre. Par exemple, les mathmatiques nous apprennent que

(a + b) b = a;

cependant, ce n'est pas forcment

exactement vrai sur un ordinateur !

6.2.2 Soustraction
La soustraction fonctionne d'une faon similaire l'addition et soure des mmes problmes. Par exemple, considrons

16, 75 15, 9375 = 0, 8125:

1, 0000110 24 1, 1111111 23

128

CHAPITRE 6.

VIRGULE FLOTTANTE

Dcaler

1, 1111111 23

donne (en arrondissant)

1, 0000000 24

1, 0000110 24 1, 0000000 24 0, 0000110 24 0, 0000110 24 = 0, 112 = 0, 75


ce qui n'est pas exactement correct.

6.2.3 Multiplication et division


Pour la multiplication, les mantisses sont multiples et les exposants sont additionns. Considrons

10, 375 2, 5 = 25, 9375:

1, 0100110 23 1, 0100000 21 10100110 + 10100110 1, 10011111000000 24


Bien sr, le rsultat rel serait arrondi 8 bits pour donner :

1, 1010000 24 = 11010, 0002 = 26


La division est plus complique, mais soure des mmes problmes avec des erreurs d'arrondi.

6.2.4 Consquences sur la programmation


Le point essentiel de cette section est que les calculs en virgule ottante ne sont pas exacts. Le programmeur doit en tre conscient. Une erreur courante que les programmeurs font avec les nombres en virgule ottante est de les comparer en supposant qu'un calcul est exact. Par exemple, considrons une fonction appele

f(x)

qui eectue un calcul complexe et un programme

qui essaie de trouver la racine de la fonction . On peut tre tent d'utiliser l'expression suivante pour vrier si

x est une racine :

if ( f(x) == 0.0 )
Mais, que se passe-t-il si ment que

x est une trs

f(x) retourne 1 1030 ? Cela signie trs probable-

bonne approximation d'une racine relle ; cependant,

l'galit ne sera pas vrie. Il n'y a peut tre aucune valeur en virgule ottante IEEE de dans

f(x).

x qui renvoie exactement zro, en raison des erreurs d'arrondi

Une meilleur mthode serait d'utiliser :

La racine d'une fonction est une valeur x telle que f (x) = 0

6.3.

LE COPROCESSEUR ARITHMTIQUE

129

if ( fabs(f(x)) < EPS )


o

1 1010 ).

EPS

est une macro dnissant une trs petite valeur positive (du genre Cette expression est vraie ds que

f(x)

est trs proche de zro.

En gnral, pour comparer une valeur en virgule ottante (disons autre ( ), on utilise :

x)

une

if ( fabs(x y)/fabs(y) < EPS )

6.3

Le Coprocesseur Arithmtique

6.3.1 Matriel
Les premiers processeurs Intel n'avaient pas de support matriel pour les oprations en virgule ottante. Cela ne signie pas qu'ils ne pouvaient pas eectuer de telles oprations. Cela signie seulement qu'elles devaient tre ralises par des procdures composes de beaucoup d'instructions qui n'taient pas en virgule ottante. Pour ces systmes, Intel fournissait une puce appele

coprocesseur mathmatique.

Un coprocesseur mathmatique a

des instructions machine qui eectuent beaucoup d'oprations en virgule ottante beaucoup plus rapidement qu'en utilisant une procdure logicielle (sur les premiers processeurs, au moins 10 fois plus vite !). Le coprocesseur pour le 8086/8088 s'appelait le 8087, pour le 80286, il y avait un 80287 et pour le 80386, un 80387 ; le processeur 80486DX intgrait le coprocesseur mathmatique dans le 80486 lui-mme . Depuis le Pentium, toutes les gnrations de processeurs 80x86 on un coprocesseur mathmatique intgr ; cependant, on le programme toujours comme s'il s'agissait d'une unit spare. Mme les systmes plus anciens sans coprocesseur peuvent installer un logiciel qui mule un coprocesseur mathmatique. Cet mulateur est automatiquement activ lorsqu'un programme excute une instruction du coprocesseur et lance la procdure logicielle qui produit le mme rsultat que celui qu'aurait donn le coprocesseur (bien que cela soit plus lent, bien sr). Le coprocesseur arithmtique a huit registres de virgule ottante. Chaque registre contient 80 bits de donnes. Les nombres en virgule ottante sont

toujours stocks sous forme de nombres 80 bits en prcision tendue dans ces
registres. Les registres sont appels

ST0, ST1, ST2, . . . ST7.

Les registres en

virgule ottante sont utiliss diremment des registres entiers du processeur principal. Ils sont organiss comme une une liste

Last-In First-Out

pile.

Souvenez vous qu'une pile est

(LIFO, dernier entr, premier sorti).

ST0 fait tou-

jours rfrence la valeur au sommet de la pile. Tous les nouveaux nombres

5 Cependant, le 80486SX n'avait 80487SX pour ces machines.

pas

de coprocesseur intgr. Il y avait une puce

130

CHAPITRE 6.

VIRGULE FLOTTANTE

sont ajouts au sommet de la pile. Les nombres existants sont dcals vers le bas pour faire de la place au nouveau. Il y a galement un registre de statut dans le coprocesseur arithmtique. Il a plusieurs drapeaux. Seuls les 4 drapeaux utiliss pour les comparaisons seront traits : C0 , C1 , C2 et C3 . Leur utilisation est traite plus tard.

6.3.2 Instructions
Pour faciliter la distinction entre les instructions du processeur normal et celles du coprocesseur, tous les mnmoniques du coprocesseur commencent par un

F.

Chargement et stockage
Il y a plusieurs instructions qui chargent des donnes au sommet de la pile du coprocesseur :

FLD source

Charge un nombre en virgule ottante depuis la mmoire vers le sommet de la pile. La double ou prcision tendue ou un registre du coprocesseur.

source peut tre un nombre en simple, source


peut tre

FILD source FLD1 FLDZ

Lit un

entier

depuis la mmoire, le convertit en ottant et

stocke le rsultat au sommet de la pile. La Stocke un un au sommet de la pile. Stocke un zro au sommet de la pile.

un mot, un double mot ou un quadruple mot.

Il y a galement plusieurs instructions qui dplacent les donnes depuis la pile vers la mmoire. Certaines de ces instruction retirent le nombre de la pile en mme temps qu'elles le dplacent.

6.3.

LE COPROCESSEUR ARITHMTIQUE

131

FST dest FSTP dest

Stocke le sommet de la pile (ST0) en mmoire. La registre du coprocesseur.

destination

peut tre un nombre en simple ou double prcision ou un Stocke le sommet de la pile en mmoire, exactement comme

FST ; cependant, une fois le nombre stock, sa valeur est retire


de la pile. La

destination

peut tre un nombre en simple,

double ou prcision tendue ou un registre du coprocesseur.

FIST dest

Stocke la valeur au sommet de la pile convertie en entier en mmoire. La

destination

peut tre un mot ou un double

mot. La pile en elle-mme reste inchange. La faon dont est converti le nombre en entier dpend de certains bits dans le

mot de contrle

du coprocesseur. Il s'agit d'un registre d'un

mot spcial (pas en virgule ottante) qui contrle le fonctionnement du coprocesseur. Par dfaut, le mot de contrle est initialis an qu'il arrondisse l'entier le plus proche lorsqu'il convertit vers un entier. Cependant, les instructions (Store Control Word, stocker le mot de contrle) et utilises pour changer ce comportement.

FSTCW FLDCW

(Load Control Word, charger le mot de contrle) peuvent tre

FISTP dest

Identique mot.

FIST sauf en deux points. Le sommet de la pile est

supprim et la

destination

peut galement tre un quadruple

Il y a deux autres instructions qui peuvent placer ou retirer des donnes de la pile.

FXCH STn FFREE STn

change les valeurs de

ST0

et

STn

sur la pile (o

est un

numro de registre entre 1 et 7). libre un registre sur la pile en le marquant comme inutilis ou vide.

Addition et soustraction
ST0

Chacune des instructions d'addition calcule la somme de seur.

et d'un

autre oprande. Le rsultat est toujours stock dans un registre du coproces-

132

CHAPITRE 6.

VIRGULE FLOTTANTE

1 2 3 4 5 6 7 8 9 10 11 12 13

segment .bss array resq SIZE sum resq 1 segment .text mov ecx, SIZE mov esi, array fldz lp: fadd qword [esi] add esi, 8 loop lp fstp qword sum

; ST0 = 0 ; ST0 += *(esi) ; passe au double suivant ; stocke le rsultat dans sum

Fig. 6.5  Exemple de somme d'un tableau

FADD source FADD dest, ST0 FADDP dest ou FADDP dest, STO FIADD source

ST0 += source .

La

source

peut tre n'importe quel

registre du coprocesseur ou un nombre en simple ou double prcision en mmoire. registre du coprocesseur.

dest += ST0. La destination peut tre n'importe quel dest += ST0

nation
seur.

puis ST0 est retir de la pile. La

destiLa

peut tre n'importe quel registre du coprocesAjoute un entier

ST0 += (float) source .

source doit tre un mot ou un double mot en mmoire.

ST0.

Il y a deux fois plus d'instructions pour la soustraction que pour l'addition car l'ordre des oprandes est important pour la soustraction ( mais

i.e. a+b = b+a, a b = b a!). Pour chaque instruction, il y a un mirroir qui eectue la
R
soit par

soustraction dans l'ordre inverse. Ces instructions inverses se nissent toutes soit par

RP.

La Figure 6.5 montre un court extrait de code qui

ajoute les lments d'un tableau de doubles. Au niveau des lignes 10 et 13, il faut spcier la taille de l'oprande mmoire. Sinon l'assembleur ne saurait pas si l'oprande mmoire est un oat (dword) ou un double (qword).

6.3.

LE COPROCESSEUR ARITHMTIQUE

133

FSUB source FSUBR source FSUB dest, ST0 FSUBR dest, ST0 FSUBP dest ou FSUBP dest, STO FSUBRP dest ou FSUBRP dest, ST0 FISUB source FISUBR source

ST0 -= source .

La

source
La

peut tre n'importe quel

registre du coprocesseur ou un nombre en simple ou double prcision en mmoire.

ST0 = source - ST0.

source

peut tre n'importe

quel registre du coprocesseur ou un nombre en simple ou double prcision en mmoire. registre du coprocesseur.

dest -= ST0. La destination peut tre n'importe quel dest = ST0 - dest .
La

destination

peut tre n'im-

porte quel registre du coprocesseur.

dest -= ST0 puis retire ST0 de la pile. La destination


peut tre n'importe quel registre du coprocesseur.

dest = ST0 - dest

destination
La

puis retire ST0 de la pile. La

peut tre n'importe quel registre du co-

processeur.

ST0 -= (float) source . Soustrait un entier de ST0.

source

doit tre un mot ou un double mot en mSoustrait

moire.

ST0 = (float) source - ST0.


entier. La

source

ST0

d'un

doit tre un mot ou un double mot

en mmoire.

Multiplication et division
Les instructions de multiplication sont totalement analogues celles d'addition.

FMUL source

ST0 *= source .

La

source

peut tre n'importe quel

registre du coprocesseur ou un nombre en simple ou double prcision en mmoire.

FMUL dest, ST0 FMULP dest ou FMULP dest, ST0 FIMUL source

dest *= ST0. La destination peut tre n'importe quel


registre du coprocesseur.

dest *= ST0 puis retire ST0 de la pile. La destination


peut tre n'importe quel registre du coprocesseur.

ST0 *= (float) source . Multiplie ST0. La source peut tre un mot ou


en mmoire.

un entier avec un double mot

Ce n'est pas tonnant, les instructions de division sont analogues celles de soustraction. La division par zro donne l'inni.

134

CHAPITRE 6.

VIRGULE FLOTTANTE

FDIV source FDIVR source FDIV dest, ST0 FDIVR dest, ST0 FDIVP dest ou FDIVP dest, ST0 FDIVRP dest ou FDIVRP dest, ST0 FIDIV source FIDIVR source

ST0 /= source .

La

source
La

peut tre n'importe quel

registre du coprocesseur ou un nombre en simple ou double prcision en mmoire.

ST0 = source / ST0.

source

peut tre n'importe

quel registre du coprocesseur ou un nombre en simple ou double prcision en mmoire. registre du coprocesseur.

dest /= ST0. La destination peut tre n'importe quel dest = ST0 / dest .
La

destination

peut tre n'im-

porte quel registre du coprocesseur.

dest /= ST0 puis retire ST0 de la pile. La destination


peut tre n'importe quel registre du coprocesseur.

dest = ST0 / dest

destination
La

puis retire ST0 de la pile. La

peut tre n'importe quel registre du coDivise

processeur.

ST0 /= (float) source .

ST0 = (float) source / ST0. Divise un entier par ST0. La source doit tre un mot ou un double mot en
mmoire.

src doit tre un mot ou un double mot en mmoire.

ST0

par un entier.

Comparaisons
Le coprocesseur eectue galement des comparaisons de nombres en virgule ottante. La famille d'instructions

FCOM source

compare compare

ST0

et et

FCOM est faite pour a. source . La source peut tre

un registre du

coprocesseur ou un oat ou un double en mmoire.

FCOMP source FCOMPP FICOM source FICOMP source FTST

ST0

source ,

puis retire ST0 de la pile. La

source

peut tre un registre du coprocesseur ou un oat ou un double en mmoire. compare compare compare La

ST0 et ST1, puis retire ST0 et ST1 de la pile. ST0 et (float) source . La source peut tre un enST0
et

tier sur un mot ou un double mot en mmoire.

source

(float)source ,

puis retire ST0 de la pile.

peut tre un entier sur un mot ou un double mot et 0,

en mmoire. compare

ST0

Ces instructions changent les bits C0 , C1 , C2 et C3 du registre de statut du coprocesseur. Malheureusement, il n'est pas possible pour le processeur d'accder ces bits directement. Les instructions de branchement conditionnel utilisent le registre FLAGS, pas le registre de statut du coprocesseur. Cependant, il est relativement simple de transfrer les bits du mot de statut dans les bits correspondants du registre FLAGS en utilisant quelques ins-

6.3.

LE COPROCESSEUR ARITHMTIQUE

135

1 2 3 4 5 6 7 8 9 10 11 12 13

; ;

if ( x > y ) qword [x] qword [y] ax else_part si vrai end_if si faux ; ST0 = x ; compare STO et y ; place les bits C dans FLAGS ; si x non < y goto else_part

fld fcomp fstsw sahf jna then_part: ; code jmp else_part: ; code end_if:

Fig. 6.6  Exemple de comparaison

tructions nouvelles :

FSTSW destination

Stocke le mot de statut du coprocesseur soit dans un mot en mmoire soit dans le registre AX. Stocke le registre AH dans le registre FLAGS. Charge le registre AH avec les bits du registre FLAGS.

SAHF LAHF

La Figure 6.6 montre un court extrait de code en exemple. Les lignes 5 et 6 transfrent les bits C0 , C1 , C2 et C3 du mot de statut du coprocesseur dans le registre FLAGS. Les bits sont transfrs de faon a tre identiques au rsultat d'une comparaison de deux entiers ligne 7 utilise une instruction

JNA.

non signs. C'est pourquoi la

Les Pentium Pro (et les processeurs plus rcents (Pentium II and III)) supportent deux nouveaux oprateurs de comparaison qui modient directement le registre FLAGS du processeur.

FCOMI source FCOMIP source

compare compare

ST0 ST0

et et

source . source ,

La

source

doit tre un registre du

coprocesseur. puis retire ST0 de la pile. La doit tre un registre du coprocesseur. La Figure 6.7 montre une sous-routine qui trouve le plus grand de deux doubles en utilisant l'instruction avec les fonctions de comparaisons avec un entier (FICOM et

source

FCOMIP.

Ne confondez pas ces instructions

FICOMP).

Instructions diverses
Cette section traite de diverses autres instructions fournies par le coprocesseur.

136

CHAPITRE 6.

VIRGULE FLOTTANTE

FCHS FABS FSQRT FSCALE

ST0 = - ST0 Change le signe de ST0 ST0 = |ST0| Extrait la valeur absolue de ST0 ST0 = STO Extrait la racine carre de ST0 ST0 = ST0 2 ST1 multiplie ST0 par une puissance de 2 rapidement. ST1 n'est pas retir de la pile du coprocesseur. La
Figure 6.8 montre un exemple d'utilisation de cette instruction.

6.3.3 Exemples 6.3.4 Formule quadratique


Le premier exemple montre comment la formule quadratique peut tre code en assembleur. Souvenez vous, la formule quadratique calcule les solutions de l'quation :

ax2 + bx + c = 0
La formule donne deux solutions pour

x: x1 et x2 . b b2 4ac x1 , x2 = 2a
2

L'expression sous la racine carre (b vrie pour les solutions.

4ac)

est appele le

dterminant. Sa

valeur est utile pour dterminer laquelle des trois possibilits suivantes est

1. Il n'y a qu'une seule solution double. 2. Il y a deux solutions relles.

b2 4ac = 0

b2 4ac > 0 b2 4ac < 0

3. Il y a deux solutions complexes.

Voici un court programme C qui utilise la sous-routine assembleur :

quadt.c
1 2 3 4 5 6 7 8 9 10 11 12

#include <stdio.h> int quadratic ( double, double, double, double , double ); int main() { double a,b,c , root1 , root2;
printf ("Entrez a , b, c : "); scanf("%lf %lf %lf", &a, &b, &c); if ( quadratic ( a , b, c, &root1, &root2 ) ) printf (" racines : %.10g %.10g\n", root1, root2 );

6.3.

LE COPROCESSEUR ARITHMTIQUE

137

13 14 15 16

else printf ("pas de racine relle \n"); return 0; }

quadt.c
Voici la routine assembleur :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

; ; ; ; ; ; ; ; ; ; ; ;

quad.asm fonction quadratic trouve les solutions l'quation quadratique : a*x**2 + b*x + c = 0 prototype C : int quadratic( double a, double b, double c, double * root1, double *root2 ) Paramtres: a, b, c - coefficients des puissances de l'quation quadratique (voir ci-dessus) root1 - pointeur vers un double o stocker la premire racine root2 - pointeur vers un double o stocker la deuxime racine Valeur de retour : retourne 1 si des racines relles sont trouves, sinon 0 a b c root1 root2 disc one_over_2a dw qword qword qword dword dword qword qword -4 [ebp+8] [ebp+16] [ebp+24] [ebp+32] [ebp+36] [ebp-8] [ebp-16]

%define %define %define %define %define %define %define

segment .data MinusFour

segment .text global _quadratic _quadratic: push ebp mov ebp, esp sub esp, 16 push ebx fild

; alloue 2 doubles (disc & one_over_2a) ; on doit sauvegarder l'ebx original

word [MinusFour]; pile : -4

138

CHAPITRE 6.

VIRGULE FLOTTANTE

34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75

fld fld fmulp fmulp fld fld fmulp faddp ftst fstsw sahf jb fsqrt fstp fld1 fld fscale fdivp fst fld fld fsubrp fmulp mov fstp fld fld fchs fsubrp fmul mov fstp mov jmp

a c st1 st1 b b st1 st1 ax

; ; ; ; ; ; ; ;

pile pile pile pile

: : : :

a, -4 c, a, -4 a*c, -4 -4*a*c

pile : b, b, -4*a*c pile : b*b, -4*a*c pile : b*b - 4*a*c compare avec 0 ; si < pile : stocke pile : pile : pile : pile : pile : pile : pile : pile : pile : stocke pile : pile : pile : pile : pile : 0, pas de solution relle sqrt(b*b - 4*a*c) et dcale la pile 1,0 a, 1,0 a * 2(1,0) = 2*a, 1 1/(2*a) 1/(2*a) b, 1/(2*a) disc, b, 1/(2*a) disc - b, 1/(2*a) (-b + disc)/(2*a) dans *root1 b disc, b -disc, b -disc - b (-b - disc)/(2*a)

no_real_solutions ; disc ; ; a ; ; st1 ; one_over_2a ; b ; disc ; st1 ; st1 ; ebx, root1 qword [ebx] ; b ; disc ; ; st1 ; one_over_2a ; ebx, root2 qword [ebx] ; eax, 1 ; short quit

stocke dans *root2 la valeur de retour est 1

no_real_solutions: mov eax, 0 quit: pop mov pop ebx esp, ebp ebp

; la valeur de retour est 0

6.3.

LE COPROCESSEUR ARITHMTIQUE

139

76

ret

quad.asm

6.3.5 Lire un tableau depuis un chier


Dans cet exemple, une routine assembleur lit des doubles depuis un chier. Voici un court programme de test en C :

readt.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

#include <stdio.h> extern int read_doubles( FILE , double , int ); #dene MAX 100 int main() { int i ,n; double a[MAX];
n = read_doubles(stdin, a , MAX);

Ce programme teste la procdure assembleur 32 bits read_doubles(). Il lit des doubles depuis stdin ( Utilisez une redirection pour lire depuis un chier ). /

for ( i=0; i < n; i++ ) printf ("%3d %g\n", i, a[i ]); return 0;

readt.c
Voici la routine assembleur

1 2 3 4 5 6 7 8 9

segment .data format db

read.asm "%lf", 0 ; format pour fscanf()

segment .text global _read_doubles extern _fscanf %define SIZEOF_DOUBLE %define FP 8 dword [ebp + 8]

140

CHAPITRE 6.

VIRGULE FLOTTANTE

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

%define ARRAYP %define ARRAY_SIZE %define TEMP_DOUBLE ; ; ; ; ; ; ; ; ; ; ; ;

dword [ebp + 12] dword [ebp + 16] [ebp - 8]

fonction _read_doubles prototype C : int read_doubles( FILE * fp, double * arrayp, int array_size ); Cette fonction lit des doubles depuis un fichier texte dans un tableau, jusqu' EOF ou que le tableau soit plein. Paramtres : fp - FILE pointeur partir duquel lire (doit tre ouvert en lecture) arrayp - pointeur vers le tableau de double vers lequel lire array_size - nombre d'lments du tableau Valeur de retour : nombre de doubles stocks dans le tableau (dans EAX)

_read_doubles: push ebp mov ebp,esp sub esp, SIZEOF_DOUBLE push mov xor esi esi, ARRAYP edx, edx

; dfinit un double sur la pile ; sauve esi ; esi = ARRAYP ; edx = indice du tableau (initialement 0)

while_loop: cmp edx, ARRAY_SIZE ; edx < ARRAY_SIZE ? jnl short quit ; si non, quitte la boucle ; ; appelle fscanf() pour lire un double dans TEMP_DOUBLE ; fscanf() peut changer edx, donc on le sauvegarde ; push edx ; sauve edx lea eax, TEMP_DOUBLE push eax ; empile &TEMP_DOUBLE push dword format ; emplie &format push FP ; emplie file pointer call _fscanf add esp, 12 pop edx ; restaure edx cmp eax, 1 ; fscanf a retourn 1?

6.3.

LE COPROCESSEUR ARITHMTIQUE

141

52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

jne

short quit

; si non, on quitte la boucle

; ; copie TEMP_DOUBLE dans ARRAYP[edx] ; (Les 8 octets du double sont copis en deux fois 4 octets) ; mov eax, [ebp - 8] mov [esi + 8*edx], eax ; copie des 4 octets de poids faible mov eax, [ebp - 4] mov [esi + 8*edx + 4], eax ; copie des 4 octets de poids fort inc jmp quit: pop mov mov pop ret edx while_loop esi eax, edx esp, ebp ebp read.asm ; restaure esi ; stocke la valeur de retour dans eax

6.3.6 Renchercher les nombres premiers


Ce dernier exemple recherche les nombres premiers, une fois de plus. Cette implmentation est plus ecace que la prcdente. Elle stocke les nombres premiers dj trouvs dans un tableau et ne divise que par ceux-ci au lieu de diviser par tous les nombres impairs pour trouver de nouveaux premiers. Une autre dirence est qu'il calcule la racine carre du candidat pour le prochain premier an de dterminer le moment o il peut s'arrter de rechercher des facteurs. Il altre le fonctionnement du coprocesseur an que lorsqu'il stocke la racine sous forme d'entier, il la tronque au lieu de l'arrondir. Ce sont les bits 10 et 11 du mot de contrle qui permettent de paramtrer cela. Ces bits sont appeles les bits RC (Rounding Control, contrle d'arrondi). S'ils sont tous les deux 0 (par dfaut), le coprocesseur arrondit lorsqu'il convertit vers un entier. S'ils sont tous les deux 1, le copresseur tronque les conversions en entiers. Notez que la routine fait attention sauvegarder le mot de contrle original et le restaurer avant de quitter. Voici le programme C pilote :

fprime.c

142

CHAPITRE 6.

VIRGULE FLOTTANTE

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

#include <stdio.h> #include <stdlib.h> / fonction nd_primes recherche le nombre indiqu de nombres premiers Paramtres: a tableau pour contenir les nombres premiers n nombre de nombres premiers trouver / extern void nd_primes( int a , unsigned n ); int main() { int statut ; unsigned i; unsigned max; int a;
printf ("Combien de nombres premiers voulez vous trouver ? "); scanf("%u", &max); a = calloc ( sizeof ( int ), max);

if ( a ) {
nd_primes(a,max); / ache les 20 derniers nombres premiers trouvs / for ( i= ( max > 20 ) ? max 20 : 0; i < max; i++ ) printf ("%3d %d\n", i+1, a[i]); free (a); statut = 0;

} else { fprintf ( stderr , "Impossible de crer un tableau de %u entiers\n", max); statut = 1; } }

return statut ;

6.3.

LE COPROCESSEUR ARITHMTIQUE

143

fprime.c
Voici la routine assembleur :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

prime2.asm segment .text global _find_primes ; ; fonction find_primes ; trouve le nombre indiqu de nombre premiers ; Paramtres: ; array - tableau pour contenir les nombres premiers ; n_find - nombre de nombres premiers trouver ; Prototype C : ;extern void find_primes( int * array, unsigned n_find ) ; %define array ebp + 8 %define n_find ebp + 12 %define n ebp - 4 ; Nombre de nombres premiers trouvs %define isqrt ebp - 8 ; racine du candidat %define orig_cntl_wd ebp - 10 ; mot de contrle original %define new_cntl_wd ebp - 12 ; nouveau mot de contrle _find_primes: enter push push fstcw mov or mov fldcw mov mov mov mov mov 12,0 ebx esi word [orig_cntl_wd] ax, [orig_cntl_wd] ax, 0C00h [new_cntl_wd], ax word [new_cntl_wd] esi, [array] dword [esi], 2 dword [esi + 4], 3 ebx, 5 dword [n], 2 ; fait de la place pour les variables locales ; sauvegarde les variables registre ventuelles ; rcupre le mot de contrle courant ; positionne les bits d'arrondi 11 (tronquer)

; ; Cette boucle externe trouve un nouveau nombre premier chaque itration qu'il ; ajoute la fin du tableau. Contrairement au programme de recherche de nombres

; ; ; ; ;

esi pointe sur array array[0] = 2 array[1] = 3 ebx = guess = 5 n = 2

144

CHAPITRE 6.

VIRGULE FLOTTANTE

39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80

; premiers prcdent, cette fonction ne dtermine pas la primaut du nombre en ; divisant par tous les nombres impairs. Elle ne divise que par les nombres ; premiers qu'elle a dj trouv (c'est pourquoi ils sont stocks dans un tableau). ; while_limit: mov eax, [n] cmp eax, [n_find] ; while ( n < n_find ) jnb short quit_limit mov push fild pop fsqrt fistp ecx, 1 ebx dword [esp] ebx ; ; ; ; ; ; ecx est utilis comme indice stocke le candidat sur la pile le charge sur la pile du coprocesseur retire le candidat de la pile calcule sqrt(guess) isqrt = floor(sqrt(quess))

dword [isqrt] ; ; Cette boucle interne divise le candidat (ebx) par les nombres premiers ; calculs prcdemment jusqu' ce qu'il trouve un de ses facteurs premiers ; (ce qui signifie que le candidat n'est pas premier) ou jusqu' ce que le ; nombre premier par lequel diviser soit plus grand que floor(sqrt(guess)) ; while_factor: mov eax, dword [esi + 4*ecx] ; eax = array[ecx] cmp eax, [isqrt] ; while ( isqrt < array[ecx] jnbe short quit_factor_prime mov eax, ebx xor edx, edx div dword [esi + 4*ecx] or edx, edx ; && guess % array[ecx] != 0 ) jz short quit_factor_not_prime inc ecx ; essaie le nombre premier suivant jmp short while_factor ; ; found a new prime ! ; quit_factor_prime: mov eax, [n] mov dword [esi + 4*eax], ebx inc eax mov [n], eax

; ajoute le candidat au tableau ; incrmente n

6.3.

LE COPROCESSEUR ARITHMTIQUE

145

81 82 83 84 85 86 87 88 89 90 91 92

quit_factor_not_prime: add ebx, 2 jmp short while_limit quit_limit: fldcw pop pop leave ret word [orig_cntl_wd] esi ebx

; essaie le nombre impair suivant

; restaure le mot de contrle ; restaure les variables registre

prime2.asm

146

CHAPITRE 6.

VIRGULE FLOTTANTE

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

global _dmax segment .text ; fonction _dmax ; retourne le plus grand de ses deux arguments double ; prototype C ; double dmax( double d1, double d2 ) ; Paramtres : ; d1 - premier double ; d2 - deuxime double ; Valeur de retour : ; le plus grand de d1 et d2 (dans ST0) %define d1 ebp+8 %define d2 ebp+16 _dmax: enter 0, 0 fld qword fld qword fcomip st1 jna short fcomp st0 fld qword jmp short d2_bigger: exit: leave ret [d2] [d1] d2_bigger [d1] exit ; ST0 = d1, ST1 = d2 ; ST0 = d2 ; retire d2 de la pile ; ST0 = d1 ; si d2 est le plus grand ; rien faire

Fig. 6.7  Exemple de

FCOMIP

6.3.

LE COPROCESSEUR ARITHMTIQUE

147

1 2 3 4 5 6 7 8

segment .data x dq 2,75 five dw 5 segment .text fild dword [five] fld qword [x] fscale

; converti au format double

; ST0 = 5 ; ST0 = 2,75, ST1 = 5 ; ST0 = 2,75 * 32, ST1 = 5

Fig. 6.8  Exemple d'utilisation de

FSCALE

148

CHAPITRE 6.

VIRGULE FLOTTANTE

Chapitre 7

Structures et C++
7.1 Structures

7.1.1 Introduction
Les structures sont utilises en C pour regrouper des donnes ayant un rapport entre elles dans une variable composite. Cette technique a plusieurs avantages : 1. Cela clarie le code en montrant que les donnes dnies dans la structure sont intimement lies. 2. Cela simplie le passage des donnes aux fonctions. Au lieu de passer plusieurs variables sparment, elles peuvent tre passes en une seule entit. 3. Cela augmente la

localit 1

du code.

Du point de vue de l'assembleur, une structure peut tre considre comme un tableau avec des lments de taille

variable. Les lments des vrais

tableaux sont toujours de la mme taille et du mme type. C'est cette proprit qui permet de calculer l'adresse de n'importe quel lment en connaissant l'adresse de dbut du tableau, la taille des lments et l'indice de l'lment voulu. Les lments d'une structure ne sont pas ncessairement de la mme taille (et habituellement, ils ne le sont pas). A cause de cela, chaque lment d'une structure doit tre explicitement spci et doit recevoir un lieu d'un indice numrique. En assembleur, on accde un lment d'une structure d'une faon similaire l'accs un lment de tableau. Pour accder un lment, il faut connatre l'adresse de dpart de la structure et le

tag

(ou nom) au

dplacement relatif

de cet

1 Votez le chapitre sur la gestion de la mmoire virtuelle de n'importe quel livre sur les Systmes d'Exploitation pour une explication de ce terme.
149

150

CHAPITRE 7.

STRUCTURES ET C++

Dplacement 0 2

Elment

x y

z
Fig. 7.1  Structure S

lment par rapport au dbut de la structure. Cependant, contrairement un tableau o ce dplacement peut tre calcul grce l'indice de l'lment, c'est le compilateur qui aecte un dplacement aux lments d'une structure. Par exemple, considrons la structure suivante :

struct S { short int x ; int y; double z ; };

/ entier sur 2 octets / / entier sur 4 octets / / ottant sur 8 octets /


S

La Figure 7.1 montre quoi pourrait ressembler une variable de type

pourrait ressembler en mmoire. Le standard ANSI C indique que les lments d'une structure sont organiss en mmoire dans le mme ordre que celui de leur dnition dans le

struct.

Il indique galement que le premier

lment est au tout dbut de la structure (

i.e. au dplacement zro). Il dstddef.h


appele

nit galement une macro utile dans le chier d'en-tte

offsetof().
est le nom du Figure 7.1.

Cette macro calcule et renvoie le dplacement de n'importe

quel lment d'une structure. La macro prend deux paramtres, le premier

type

de la structure, le second est le nom de l'lment dont on

veut le dplacement. Donc le rsultat de

offsetof(S, y) serait 2 d'aprs la

7.1.2 Alignement en mmoire


Si l'on utilise la macro
Souvenez vous qu'une Parce que

offsetof

pour trouver le dplacement de

en

utilisant le compilateur gcc, on s'aperoit qu'elle renvoie 4, pas 2 ! Pourquoi ?

gcc (et beaucoup d'autre compilateurs) aligne les variables sur des gcc

adresse est sur un multiple multiples de doubles mots par dfaut. En mode protg 32 bits, le processeur de double mot si elle est lit la mmoire plus vite si la donne commence sur un multiple de double divisible par 4 mot. La Figure 7.2 montre quoi ressemble la structure S en utilisant .

Le compilateur insre deux octets inutiliss dans la structure pour aligner (et

y z) sur un multiple de double mot. Cela montre pourquoi c'est une bonne

7.1.

STRUCTURES

151

Oset 0 2 4

Elment

inutilis
y

z
Fig. 7.2  Structure S

ide d'utiliser

offsetof pour calculer les dplacements, au lieu de les calculer

soi-mme lorsqu'on utilise des structures en C. Bien sr, si la structure est utilise uniquement en assembleur, le programmeur peut dterminer les dplacements lui-mme. Cependant, si l'on interface du C et de l'assembleur, il est trs important que l'assembleur et le C s'accordent sur les dplacements des lments de la structure ! Une des complications est que des compilateurs C dirents peuvent donner des dplacements dirents aux lments. Par exemple, comme nous l'avons vu, le compilateur

gcc

cre une structure

qui ressemble la Figure 7.2 ; ce-

pendant, le compilateur de Borland crerait une structure qui ressemble la Figure 7.1. Les compilateurs C fournissent le moyen de spcier l'alignement utilis pour les donnes. Cependant, le standard ANSI C ne spcie pas comment cela doit tre fait et donc, des compilateurs dirents procdent diremment. Le compilateur

gcc

a une mthode exible et complique de spcier

l'alignement. Le compilateur permet de spcier l'alignement de n'importe quel type en utilisant une syntaxe spciale. Par exemple, la ligne suivante :

typedef short int unaligned_int __attribute__((aligned(1)));


dnit un nouveau type appel cessaires !) Le paramtre 1 de mots, 4 sur les doubles mots, en un type tiples d'octet (Oui, toutes les parenthses suivant

__attribute__ sont naligned peut tre remplac par d'autres puisy z

unaligned_int

qui est align sur des mul-

sances de deux pour spcier d'autres alignements (2 pour s'aligner sur les

unaligned_int, gcc

etc.). Si l'lment y de la structure tait chang


placerait au dplacement 2. Cependant,

serait toujours au dplacement 8 puisque les doubles sont galement aligns sur des doubles mots par dfaut. La dnition du type de tre change pour le placer au dplacement 6. Le compilateur

devrait aussi

gcc permet galement de comprimer (pack) une structure.


S
pourrait tre rcrite de cette

Cela indique au compilateur d'utiliser le minimum d'espace possible pour la structure. La Figure 7.3 montre comment

152

CHAPITRE 7.

STRUCTURES ET C++

struct S { short int x ; / entier sur 2 octets / int y; / entier sur 4 octets / double z ; / ottant sur 8 octets / } __attribute__((packed));
Fig. 7.3  Structure comprime sous

gcc

#pragma pack(push) / sauve l ' tat de l 'alignement / #pragma pack(1) / dnit un alignement sur octet / struct S { short int x ; int y; double z ; };
/ entier sur 2 octets / / entier sur 4 octets / / ottant sur 8 octets /

#pragma pack(pop) / restaure l 'alignement original /


Fig. 7.4  Structure comprime sous les compilateurs Microsoft ou Borland

faon. Cette forme de

utiliserait le moins d'octets possible, soit 14 octets.

Les compilateurs de Microsoft et Borland supportent tous les deux la mme mthode pour indiquer l'alignement par le biais d'une directive

#pragma.

#pragma pack(1)
La directive ci-dessus indique au compilateur d'aligner les lments des structures sur des multiples d'un octet (

i.e., sans dcalage superu). Le un peut

tre remplac par deux, quatre, huit ou seize pour spcier un alignement sur des multiples de mots, doubles mots, quadruples mots ou de paragraphe, respectivement. La directive reste active jusqu' ce qu'elle soit crase par une autre. Cela peut poser des problmes puisque ces directives sont souvent utilises dans des chiers d'en-tte. Si le chier d'en-tte est inclus avant d'autres chiers d'en-tte dnissant des structures, ces structures peuvent tre organises diremment de ce qu'elles auraient t par dfaut. Cela peut conduire des erreurs trs diciles localiser. Les dirents modules d'un programmes devraient organiser les lments des structures droits ! Il y a une faon d'viter ce problme. Microsoft et Borland permettent la sauvegarde de l'tat de l'alignement courant et sa restauration. La Figure 7.4 montre comment on l'utilise.

dirents

en-

7.1.

STRUCTURES

153

struct S { unsigned f1 unsigned f2 unsigned f3 unsigned f4 };

: 3; : 10; : 11; : 8;

/ champ de 3 bits / / champ de 10 bits / / champ de 11 bits / / champ de 8 bits /

Fig. 7.5  Exemple de Champs de Bits

7.1.3 Champs de Bits


Les champs de bits permettent de dclarer des membres d'une structure qui n'utilisent qu'un nombre de bits donn. La taille en bits n'a pas besoin d'tre un multiple de huit. Un membre champ de bit est dni comme un

unsigned int

ou un

int

suivi de deux-points et de sa taille en bits. La Fi-

gure 7.5 en montre un exemple. Elle dnit une variable 32 bits dcompose comme suit : 8 bits f4 11 bits f3 10 bits f2 3 bits f1

Le premier champ de bits est assign aux bits les moins signicatifs du double mot . Nanmoins, le format n'est pas si simple si l'on observe comment les bits sont stocks en mmoire. La dicult apparat lorsque les champs de bits sont cheval sur des multiples d'octets. Car les octets, sur un processeur little endian seront inverss en mmoire. Par exemple, les champs de bits de la structure

S
f2l

ressembleront cela en mmoire : 3 bits f1 3 bits f3l 5 bits f2m 8 bits f3m 8 bits f4

5 bits

L'tiquette

f2l

fait rfrence aux cinq derniers bits (

moins signicatifs) du champ de bits cinq bits les plus signicatifs de et

limites d'octets. Si l'on inverse tous les octets, les morceaux des champs

f3

f2. Les lignes verticales doubles montrent les f2

f2.

L'tiquette

i.e., les cinq bits les f2m fait rfrence aux

seront runis correctement.

L'organisation de la mmoire physique n'est habituellement pas importante moins que des donnes de soient transfres depuis ou vers le programme (ce qui est en fait assez courant avec les champs de bits). Il est courant que les interfaces de priphriques matriels utilisent des nombres impairs de bits dont les champs de bits facilitent la reprsentation.

En fait, le standard ANSI/ISO C laisse une certaine libert au compilateur sur la faon d'organiser les bits. Cependant, les compilateurs C courants (gcc, Microsoft et Borland ) organisent les champs comme cela.

154

CHAPITRE 7.

STRUCTURES ET C++

Byte

Bit

0 1 2 3 4 5 N

Code Opration (08h)

d'Unit Logique

msb de l'ABL

milieu de l'Adresse de Bloc Logique lsb de l'Adresse de Bloc Logique Longueur du Transfert Contrle

Fig. 7.6  Format de la Commande de Lecture SCSI

Un bon exemple est SCSI . Une commande de lecture directe pour un priphrique SCSI est spcie en envoyant un message de six octets au priphrique selon le format indiqu dans la Figure 7.6. La dicult de reprsentation en utilisant les champs de bits est l'

adresse de bloc logique

qui

est cheval sur trois octets dirents de la commande. D'aprs la Figure 7.6, on constate que les donnes sont stockes au format big endian. La Figure 7.7 montre une dnition qui essaie de fonctionner avec tous les compilateurs. Les deux premires lignes dnissent une macro qui est vraie si le code est compil avec un compilateur Borland ou Microsoft. La partie qui peut porter confusion va des lignes 11 14. Tout d'abord, on peut se demander pourquoi les champs

lba_mid et lba_lsb sont dnis sparment et non pas comme lba_msb


et

un champ unique de 16 bits. C'est parce que les donnes sont stockes au format big endian. Un champ de 16 bits serait stock au format little endian par le compilateur. Ensuite, les champs

logical_unit

semblent

tre inverss ; cependant, ce n'est pas le cas. Ils doivent tre placs dans cet ordre. La Figure 7.8 montre comment les champs sont organiss sous forme d'une entit de 48 bits (les limites d'octets sont l encore reprsentes par des lignes doubles). Lorsqu'elle est stocke en mmoire au format little endian, les bits sont rarrangs au format voulu (Figure 7.6). Pour compliquer encore plus le problme, la dnition de

ne fonctionne pas correctement avec le C Microsoft. Si l'expression

SCSI_read_cmd sizeof (SCSI_read_cmd)

est value, le C Microsoft renvoie 8 et non pas 6 ! C'est parce que le compilateur Microsoft utilise le type du champ de bits pour dterminer comment organiser les bits. Comme tous les bits sont dclars comme

unsigned,

le

compilateur ajoute deux octets la n de la structure pour qu'elle comporte un nombre entier de double mots. Il est possible d'y remdier en dclarant tous les champs

unsigned short. Maintenant, le compilateur Microsoft

n'a plus besoin d'ajouter d'octets d'alignement puisque six octets forment

3
etc.

Small Computer Systems Interface, un standard de l'industrie pour les disques durs,

7.1.

STRUCTURES

155

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

#dene MS_OR_BORLAND (dened(__BORLANDC__) \ || dened (_MSC_VER)) #if MS_OR_BORLAND # pragma pack(push) # pragma pack(1) #endif struct SCSI_read_cmd { unsigned opcode : 8; unsigned lba_msb : 5; unsigned logical_unit : 3; unsigned lba_mid : 8; / bits du milieu / unsigned lba_lsb : 8; unsigned transfer_length : 8; unsigned control : 8; } #if dened(__GNUC__) __attribute__((packed)) #endif ; #if MS_OR_BORLAND # pragma pack(pop) #endif
Fig. 7.7  Structure du Format de la Commande de Lecture SCSI

un nombre entier de mots de deux octets . Les autres compilateurs fonctionnent galement correctement avec ce changement. La Figure 7.9 montre une autre dnition qui fonctionne sur les trois compilateurs. Il ne dclare plus que deux champs de bits en utilisant le type

unsigned char.

Le lecteur ne doit pas se dcourager s'il trouve la discussion ci-dessus confuse. C'est confus ! L'auteur trouve souvent moins confus d'viter d'utiliser des champs de bits en utilisant des oprations niveau bit pour examiner et modier les bits manuellement.

4 Mlanger dirents types de champs de bits conduit un comportement trs trange ! Le lecteur est invit tester.

156

CHAPITRE 7.

STRUCTURES ET C++

8 bits control

8 bits transfer_length

8 bits lba_lsb

8 bits lba_mid

3 bits logical_unit

5 bits lba_msb

8 bits opcode

Fig. 7.8  Organisation des champs de

SCSI_read_cmd

1 2 3 4 5 6 7 8 9 10 11 12 13

struct SCSI_read_cmd { unsigned char opcode; unsigned char lba_msb : 5; unsigned char logical_unit : 3; unsigned char lba_mid; / bits du milieu / unsigned char lba_lsb; unsigned char transfer_length; unsigned char control; } #if dened(__GNUC__) __attribute__((packed)) #endif ;
Fig. 7.9  Structure du Format de la Commande de Lecture SCSI Alternative

7.1.4 Utiliser des structures en assembleur


Comme nous l'avons dit plus haut, accder une structure en assembleur ressemble beaucoup accder un tableau. Prenons un exemple simple, regardons comment l'on pourrait crire une routine assembleur qui mettrait zro l'lment soit :

y d'une structure S. Supposons que le prototype de la routine

void zero_y( S s_p );


La routine assembleur serait :

1 2 3 4 5 6 7

%define _zero_y: enter mov mov leave ret

y_offset

0,0 eax, [ebp + 8] ; rcupre s_p depuis la pile dword [eax + y_offset], 0

Le C permet de passer une structure par valeur une fonction ; cependant, c'est une mauvaise ide la plupart du temps. Toutes les donnes de la

7.2.

ASSEMBLEUR ET C++

157

1 2 3 4 5 6 7 8 9 10 11

#include <stdio.h> void f ( int x ) { printf ("%d\n", x); } void f ( double x ) { printf ("%g\n", x); }
Fig. 7.10  Deux fonctions

f()

structure doivent tre copies sur sur la pile puis rcupres par la routine. Il est beaucoup plus ecace de passer un pointeur vers la structure la place. Le C permet aussi qu'une fonction renvoie une structure. Evidemment, une structure ne peut pas tre retourne dans le registre

EAX.

Des compi-

lateurs dirents grent cette situation de faon dirente. Une situation courante que les compilateurs utilisent est de rcrire la fonction en interne de faon ce qu'elle prenne un pointeur sur la structure en paramtre. Le pointeur est utilis pour placer la valeur de retour dans une structure dnie en dehors de la routine appele. La plupart des assembleur (y compris NASM) ont un support intgr pour dnir des structures dans votre code assembleur. Reportez vous votre documentation pour plus de dtails.

7.2

Assembleur et C++

Le langage de programmation C++ est une extension du langage C. Beaucoup des rgles valables pour interfacer le C et l'assembleur s'appliquent galement au C++. Cependant, certaines rgles doivent tre modies. De plus, certaines extension du C++ sont plus faciles comprendre en connaissant le langage assembleur. Cette section suppose une connaissance basique du C++.

7.2.1 Surcharge et Dcoration de Noms


Le C++ permet de dnir des fonctions (et des fonctions membres) diffrentes avec le mme nom. Lorsque plus d'une fonction partagent le mme nom, les fonctions sont dites le mme nom en C, l'diteur de liens produira une erreur car il trouvera deux

surcharges. Si deux fonctions sont dnies avec

158

CHAPITRE 7.

STRUCTURES ET C++

dnitions pour le mme symbole dans les chiers objets qu'il est en train de lier. Par exemple, prenons le code de la Figure 7.10. Le code assembleur quivalent dnirait deux tiquettes appeles erreur. Le C++ utilise le mme procd d'dition de liens que le C mais vite cette erreur en eectuant une

_f

ce qui serait bien sr une

dcoration de nom (name mangling) ou en mo-

diant le symbole utilis pour nommer une fonction. D'une certaine faon, le C utilise dj la dcoration de nom. Il ajoute un caractre de soulignement au nom de la fonction C lorsqu'il cre l'tiquette pour la fonction. Cependant, il dcorera le nom des deux fonctions de la Figure 7.10 de la mme faon et produira une erreur. Le C++ utilise un procd de dcoration plus sophistiqu qui produit deux tiquettes direntes pour les fonctions. Par exemple, la premire fonction de la Figure 7.10 recevrait l'tiquette et la seconde,

_f__Fi _f__Fd, sous DJGPP. Cela vite toute erreur d'dition de liens. @f$qi et @f$qd pour les

Malheureusement, il n'y a pas de standard sur la gestion des noms en C++ et des compilateurs dirents dcorent les noms de faon dirente. Par exemple, Borland C++ utiliserait les tiquettes arbitraires. Le nom dcor encode la deux fonctions de la Figure 7.10. Cependant, les rgles ne sont pas totalement

signature

de la fonction. La signature a un

d'une fonction est donne par l'ordre et le type de ses paramtres. Notez que la fonction qui ne prend qu'un argument

int

la n de son nom

dcor ( la fois sous DJGPP et Borland) et que celle qui prend un argument

double a un d la f avec le prototype

n de son nom dcor. S'il y avait une fonction appele suivant :

void f ( int x , int y , double z);


DJGPP dcorerait son nom en

_f__Fiid

Le type de la fonction ne fait

pas

et Borland en

@f$qiid.

partie de la signature d'une fonction et

n'est pas encod dans nom dcor. Ce fait explique une rgle de la surcharge en C++. Seules les fonctions dont les signatures sont uniques peuvent tre surcharges. Comme on le voit, si deux fonctions avec le mme nom et la mme signature sont dnies en C++, elle donneront le mme nom dcor et creront une erreur lors de l'dition de liens. Par dfaut, toutes les fonctions C++ sont dcores, mme celles qui ne sont pas surcharges. Lorsqu'il compile un chier, le compilateur n'a aucun moyen de savoir si une fonction particulire est surcharge ou non, il dcore donc touts les noms. En fait, il dcore galement les noms des variables globales en encodant le type de la variable d'une faon similaire celle utilise pour les signatures de fonctions. Donc, si l'on dnit une variable globale dans un chier avec un certain type puis que l'on essaie de l'utiliser dans un autre chier avec le mauvais type, l'diteur de liens produira une erreur. Cette caractristique du C++ est connue sous le nom de

typesafe linking

(dition de liens avec respect des

types) . Cela cre un autre type d'erreurs, les prototypes inconsistants. Cela

7.2.

ASSEMBLEUR ET C++

159

arrive lorsque la dnition d'une fonction dans un module ne correspond pas avec le prototype utilis par un autre module. En C, cela peut tre un problme trs dicile corriger. Le C ne dtecte pas cette erreur. Le programme compilera et sera li mais aura un comportement imprvisible car le code appelant placera sur la pile des types dirents de ceux que la fonction attend. En C++ cela produira une erreur lors de l'dition de liens. Lorsque le compilateur C++ analyse un appel de fonction, il recherche la fonction correspondante en observant les arguments qui lui sont passs . S'il trouve une correspondance, il cre un

CALL

vers la fonction adquate en

utilisant les rgles de dcoration du compilateur. Comme des compilateurs dirents utilisent direntes rgles de dcoration de nom, il est possible que des codes C++ compils par des compilateurs dirents ne puissent pas tre lis ensemble. C'est important lorsque l'on a l'intention d'utiliser une bibliothque C++ prcompile ! Si l'on veut crire une fonction en assembleur qui sera utilise avec du code C++, il faut connatre les rgles de dcoration de nom du compilateur C++ utilis (ou utiliser la technique explique plus bas). L'tudiant astucieux pourrait se demander si le code de la Figure 7.10 fonctionnera de la faon attendue. Comme le C++ dcore toutes les fonc-

printf sera dcore et le compilateur ne produira pas un CALL l'tiquette _printf. C'est une question pertinente. Si le prototype de printf tait simplement plac au dbut du chier, cela arriverait. Son
tions, alors la fonction prototype est :

int printf ( const char , ...);

pointeur, C pour const, c pour char


la fonction il existe une

DJGPP dcorerait ce nom en

printf de la bibliothque C standard ! Bien sr, il doit y avoir un

_printf__FPCce (F pour fonction, P pour et e pour ellipse). Cela n'appelerait pas

moyen pour que le C++ puisse appeler du code C. C'est trs important car

norme

quantit de vieux code C utile. En plus de permettre

l'accs au code hrit de C, le C++ permet galement d'appeler du code assembleur en utilisant les conventions de dcoration standards du C. Le C++ tend le mot-cl

extern

pour lui permettre de spcier que la

fonction ou la variable globale qu'il modie utilise les conventions C normales. Dans la terminologie C++, la fonction ou la variable globale utilise une ayant une dition de liens C, utilisez le prototype :

dition de liens C. Par exemple, pour dclarer la fonction printf comme

extern "C" int printf ( const char , ... );


La correspondance n'a pas tre exacte, le compilateur prendra en compte les correspondances trouves en transtypant les arguments. Les rgles de ce procd sont en dehors de la porte de ce livre. Consultez un livre sur le C++ pour plus de dtails.
5

160

CHAPITRE 7.

STRUCTURES ET C++

1 2 3 4 5 6 7 8 9 10

void f ( int \& x ) { x++; }

// le & indique un paramtre par rfrence

int main() { int y = 5; f(y ); // une rfrence sur y est passe, pas de & ici ! printf ("%d\n", y); // ache 6 ! return 0; }
Fig. 7.11  Exemple de rfrence

Cela impose au compilateur de ne pas utiliser les rgles de dcoration de nom du C++ sur la fonction, mais d'utiliser les rgles C la place. Cependant, en faisant cela, la fonction

printf

ne peut pas tre surcharge. Cela constitue

la faon la plus simple d'interfacer du C++ et de l'assembleur, dnir une fonction comme utilisant une dition de liens C puis utiliser la convention d'appel C. Pour plus de facilit, le C++ permet galement de dnir une dition de liens C sur un bloc de fonctions et de variables globales. Le bloc est indiqu en utilisant les accolades habituelles.

extern "C" { / variables globales et prototypes des fonction ayant une dition de liens C / }
Si l'on examine les chiers d'en-tte ANSI C fournis avec les compilateur C/C++ actuels, on trouve ce qui suit vers le dbut de chaque chier d'entte :

#ifdef __cplusplus extern "C" { #endif


Et une construction similaire, vers la n, contenant une accolade fermante. Les compilateurs C++ dnissent la macro d'en-tte dans un bloc

__cplusplus

(avec

deux

carac-

tres de soulignement au dbut). L'extrait ci-dessus entoure tout le chier

extern "C" si le chier d'en-tte est compil en C++, extern "C").


La mme technique peut tre utili-

mais ne fait rien s'il est compil en C (puisqu'un compilateur C gnrerait une erreur de syntaxe sur se par n'importe quel programmeur pour crer un chier d'en-tte pour des routines assembleur pouvant tre utilises en C ou en C++.

7.2.

ASSEMBLEUR ET C++

161

7.2.2 Rfrences
Les

rfrences

sont une autre nouvelle fonctionnalit du C++. Elles per-

mettent de passer des paramtres une fonction sans utiliser explicitement de pointeur. Par exemple, considrons le code de la Figure 7.11. En fait, les paramtres par rfrence sont plutt simples, ce sont des pointeurs. Le compilateur masque simplement ce fait aux yeux du programmeur (exactement de la mme faon que les compilateurs Pascal qui implmentent les paramtres

var f

comme des pointeurs). Lorsque le compilateur gnre l'assembleur pour

l'appel de fonction ligne 7, il passe l'

adresse

de

y.

Si l'on crivait la fonction

en assembleur, on ferait comme si le prototype tait

6 :

void f ( int xp);


Les rfrences sont juste une facilit qui est particulirement utile pour la surcharge d'oprateurs. C'est une autre fonctionnalit du C++ qui permet de donner une signication aux oprateurs de base lorsqu'ils sont appliqus des structures ou des classes. Par exemple, une utilisation courante est de dnir l'oprateur plus (+) de faon ce qu'il concatne les objets string. Donc, si

et

b.

a et b sont des strings, a + b renverra la concatnation des chanes

Le C++ appelerait en ralit une fonction pour ce faire (en fait, Pour plus d'ecacit, il est souhaitable de passer

cette expression pourrait tre rcrite avec une notation fonction sous la forme

operator +(a,b)).

l'adresse des objets string la place de les passer par valeur. Sans rfrence, cela pourrait tre fait en crivant confus. Par contre, en utilisant ce qui semble trs naturel. d'crire l'oprateur avec la syntaxe

operator +(&a,&b), mais cela imposerait &a + &b. Cela serait trs maladroit et les rfrences, il est possible d'crire a + b,

7.2.3 Fonctions inline


Les

fonctions inline

sont encore une autre fonctionnalit du C++ . Les

fonctions inline sont destines remplacer les macros du prprocesseur qui prennent des paramtres, sources d'erreur. Sourvenez vous en C, une macro qui lve un nombre au carr ressemble cela :

#dene SQR(x) ((x)(x))


Comme le prprocesseur ne comprend pas le C et ne fait que de simples substitutions, les parenthses sont requises pour calculer le rsultat correct dans la plupart des cas. Cependant, mme cette version ne donnerait pas la bonne rponse pour

SQR(x++).

Bien sr, il faudrait dclarer la fonction avec une dition de liens en C, comme nous en avons parl dans la Section 7.2.1 7 Les compilateurs supportent souvent cette fonctionnalit comme une extension du C ANSI.

162

CHAPITRE 7.

STRUCTURES ET C++

1 2 3 4 5 6 7 8 9 10 11 12 13

inline int inline_f ( int x ) { return xx; } int f ( int x ) { return xx; } int main() { int y , x = 5; y = f(x); y = inline_f (x); return 0; }
Fig. 7.12  Exemple d'inlining

Les macros sont utilises car elles liminent la surcharge d'un appel pour une fonction simple. Comme le chapitre sur les sous-programmes l'a dmontr, eectuer un appel de fonction implique plusieurs tapes. Pour une fonction trs simple, le temps pass l'appeler peut tre plus grand que celui pass dans la fonction ! Les fonctions inline sont une faon beaucoup plus pratique d'crire du code qui ressemble une fonction mais qui n'eectue

pas

de

CALL

un bloc commun. Au lieu de cela, les appels des fonctions

inline sont remplacs par le code de la fonction. Le C++ permet de rendre une fonction inline en plaant le mot-cl la fonction

inline y

au dbut de sa dnition.

Par exemple, considrons les fonctions dclares dans la Figure 7.12. L'appel

f,

ligne 10, est un appel de fonction normal (en assembleur, en

supposant que

est l'adresse

ebp-8

et

en

ebp-4):

1 2 3 4

push call pop mov

dword [ebp-8] _f ecx [ebp-4], eax inline_f,


ligne 11 ressemblerait :

Cependant, l'appel la fonction

1 2 3

mov imul mov

eax, [ebp-8] eax, eax [ebp-4], eax

Dans ce cas, il y a deux avantages inliner. Tout d'abord, la fonction inline est plus rapide. Aucun paramtre n'est plac sur la pile, aucun cadre

7.2.

ASSEMBLEUR ET C++

163

de pile n'est cr puis dtruit, aucun branchement n'est eectu. Ensuite, l'appel la fonction inline utilise moins de code ! Ce dernier point est vrai pour cet exemple, mais ne reste pas vrai dans tous les cas. La principal inconvnient de l'inlining est que le code inline n'est pas li et donc le code d'une fonction inline doit tre disponible pour

tous les chiers

qui l'utilisent. L'exemple de code assembleur prcdent le montre. L'appel de la fonction non-inline ne ncessite que la connaissance des paramtres, du type de valeur de retour, de la convention d'appel et du nom de l'tiquette de la fonction. Toutes ces informations sont disponibles par le biais du prototype de la fonction. Cependant, l'utilisation de la fonction inline ncessite la connaissance de tout le code de la fonction. Cela signie que si

porte quelle

partie de la fonction change,

tous

n'im-

les chiers source qui utilisent

la fonction doivent tre recompils. Souvenez vous que pour les fonctions non-inline, si le prototype ne change pas, souvent les chiers qui utilisent la fonction n'ont pas besoin d'tre recompils. Pour toutes ces raisons, le code des fonctions inline est gnralement plac dans les chiers d'en-tte. Cette pratique est contraire la rgle stricte habituelle du C selon laquelle on ne doit

jamais

placer de code excutable dans les chiers d'en-tte.

7.2.4 Classes
8 membres donnes et des membres fonctions . En d'autres termes, il s'agit
d'une Une classe C++ dcrit un type d'

objet.

Un objet possde la fois des

struct

laquelle sont associes des donnes et des fonctions. Consi-

drons la classe simple dnie dans la Figure 7.13. Une variable de type

Simple

ressemblerait une

Les fonctions ne sont

les fonctions membres sont direntes des autres fonctions. On leur passe un mot cl this pour accparamtre . Ce paramtre est un pointeur vers l'objet sur lequel agit la der au pointeur vers l'ob-

pas

struct

C normale avec un seul membre

int.

aectes la structure en mmoire. Cependant, En fait, le C++ utilise le

cach

fonction. Par exemple, considrons la mthode

jet lorsque l'on se trouve membre.

set_data

de la classe

Simple

de

l'intrieur d'une fonction

la Figure 7.13. Si elle tait crite en C, elle ressemblerait une fonction laquelle on passerait explicitement un pointeur vers l'objet sur lequel elle agit comme le montre le code de la Figure 7.14. L'option

DJGPP

(et des compilateurs

gcc

-S

du compilateur

et Borland galement) indique au compi-

lateur de produire un chier assembleur contenant l'quivalent assembleur du code. Pour

DJGPP

et

gcc

le chier assembleur possde une extension

.s

et utilise malheureusement la syntaxe du langage assembleur AT&T qui est assez dirente des syntaxes NASM et MASM

9 (les compilateurs Borland et

Souvent appels fonctions membres en C++ ou plus gnralement mthodes . Le compilateur gcc inclut son propre assembleur appel gas . L'assembleur gas utilise la syntaxe AT&T et donc le compilateur produit un code au format gas. Il y a plusieurs sites sur le web qui expliquent les dirences entre les formats INTEL et AT&T. Il existe ga9

164

CHAPITRE 7.

STRUCTURES ET C++

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

class Simple { public : Simple (); ~Simple(); int get_data() const; void set_data( int ); private : int data ; };
Simple :: Simple() { data = 0; } Simple::~Simple() { / rien / }

// constructeur par dfaut // destructeur // fonctions membres // donnes membres

int Simple :: get_data() const { return data; } void Simple :: set_data( int x ) { data = x; }
Fig. 7.13  Une classe C++ simple

MS gnrent un chier avec l'extension Figure 7.15 montre la sortie de

DJGPP

.asm utilisant la syntaxe MASM.) La


convertie en syntaxe NASM avec des

commentaires supplmentaires pour clarier le but des instructions. Sur la toute premire ligne, notez que la mthode

set_data reoit une tiquette d-

core qui encode le nom de la mthode, le nom de la classe et les paramtres. Le nom de la classe est encod car d'autres classes peuvent avoir une mthode appele

set_data

et les deux mthodes

doivent

recevoir des tiquettes dif-

frentes. Les paramtres sont encods an que la classe puisse surcharger la mthode

set_data

an qu'elle prenne d'autres paramtres, comme les fonc-

tions C++ normales. Cependant, comme prcdemment, des compilateurs dirents encoderont ces informations diremment dans l'tiquette. Ensuite, aux lignes 2 et 3 nous retrouvons le prologue habituel. A la ligne 5, le premier paramtre sur la pile est stock dans paramtre

x ! Il s'agit du paramtre cach10

EAX.

Ce n'est

pas

le

qui pointe vers l'objet sur lequel

lement un programme gratuit appel a2i (http://www.multimania.com/placr/a2i.html), qui passe du format AT&T au format NASM. 10 Comme d'habitude, rien n'est cach dans le code assembleur !

7.2.

ASSEMBLEUR ET C++

165

void set_data( Simple object , int x ) { object >data = x; }


Fig. 7.14  Version C de Simple::set_data()

1 2 3 4 5 6 7 8 9 10

_set_data__6Simplei: push ebp mov ebp, esp mov mov mov leave ret

; nom dcor

eax, [ebp + 8] ; eax = pointeur vers l'objet (this) edx, [ebp + 12] ; edx = paramtre entier [eax], edx ; data est au dplacement 0

Fig. 7.15  Traduction de Simple::set_data( int ) par le compilateur

on agit. La ligne 6 stocke le paramtre dans le double mot sur lequel pointe

Simple

EAX. Il s'agit du membre data de l'objet

dans

EDX

et la ligne 7 stocke

EDX

sur lequel on agit, qui, tant la seule donne de la classe, est stock

au dplacement 0 de la structure

Simple.

Exemple
Cette section utilise les ides de ce chapitre pour crer une classe C++ qui reprsente un entier non sign d'une taille arbitraire. Comme l'entier peut faire n'importe quelle taille, il sera stock dans un tableau d'entiers non signs (doubles mots). Il peut faire n'importe quelle taille en utilisant l'allocation dynamique. Les doubles mots sont stocks dans l'ordre inverse ( montre la dnition de la classe sure par la taille du tableau La donne membre membre

11

i.e. le double mot le moins signicatif est au dplacement 0). La Figure 7.16
size_
de la classe est aecte au dplacement 0 et le

Big_int12 . La taille d'un Big_int est med'unsigned utilis pour stocker les donnes.

number_

est aect au dplacement 4.

Pourquoi ? Car les oprations d'addition commenceront ainsi toujours par le dbut du tableau et avanceront. 12 Voyez le code source d'exemple pour obtenir le code complet de cet exemple. Le texte ne se rfrera qu' certaines parties du code.

11

166

CHAPITRE 7.

STRUCTURES ET C++

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

class Big_int { public : / Paramtres : size


/

taille de l ' entier exprime en nombre d'unsigned valeur initiale du Big_int sous forme d'un

initial_value

int normaux

unsigned int normal

explicit Big_int( size_t size , unsigned initial_value = 0); / Paramtres : size taille de l ' entier exprime en nombre d'unsigned int normaux initial_value valeur initiale du Big_int sous forme d'une chane contenant une reprsentation hexadcimale de la valeur . / Big_int( size_t size , const char initial_value );
Big_int( const Big_int & big_int_to_copy); ~Big_int(); // renvoie la taille du Big_int (en termes d'unsigned int) size_t size () const;

const Big_int & operator = ( const Big_int & big_int_to_copy); friend Big_int operator + ( const Big_int & op1, const Big_int & op2 ); friend Big_int operator ( const Big_int & op1, const Big_int & op2); friend bool operator == ( const Big_int & op1, const Big_int & op2 ); friend bool operator < ( const Big_int & op1, const Big_int & op2); friend ostream & operator << ( ostream & os, const Big_int & op ); private : size_t size_ ; // taille du tableau d'unsigned unsigned number_; // pointeur vers un tableau d'unsigned contenant la valeur };
Fig. 7.16  Dnition de la classe Big_int

7.2.

ASSEMBLEUR ET C++

167

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

// prototypes des routines assembleur extern "C" { int add_big_ints( Big_int & res , const Big_int & op1, const Big_int & op2); int sub_big_ints( Big_int & res , const Big_int & op1, const Big_int & op2); }

inline Big_int operator + ( const Big_int & op1, const Big_int & op2) { Big_int result (op1. size ()); int res = add_big_ints(result , op1, op2); if ( res == 1) throw Big_int::Overow(); if ( res == 2) throw Big_int::Size_mismatch(); return result ; } inline Big_int operator ( const Big_int & op1, const Big_int & op2) { Big_int result (op1. size ()); int res = sub_big_ints(result , op1, op2); if ( res == 1) throw Big_int::Overow(); if ( res == 2) throw Big_int::Size_mismatch(); return result ; }
Fig. 7.17  Code de l'Arithmtique sur la Classe Big_int

168

CHAPITRE 7.

STRUCTURES ET C++

Pour simplier l'exemple, seuls les objets ayant des tableaux de la mme taille peuvent tre additionns entre eux. La classe a trois constructeurs : le premier (ligne 9) initialise l'instance de la classe en utilisant un entier non sign normal ; le second, (ligne 19) initalise l'instance en utilisant une chane qui contient une valeur hexadcimale. Le troisime constructeur (ligne 22) est le

constructeur par copie .

Cette explication se concentre sur la faon dont fonctionnent les oprateurs d'addition et de soustraction car c'est l que l'on utilise de l'assembleur. La Figure 7.17 montre les parties du chier d'en-tte relatives ces oprateurs. Elles montrent comment les oprateurs sont paramtrs pour appeler des routines assembleur. Comme des compilateurs dirents utilisent des rgles de dcoration radicalement direntes pour les fonctions oprateur, des fonctions oprateur inline sont utilises pour initialiser les appels aux routines assembleur lies au format C. Cela les rend relativement simples porter sur des compilateurs dirents et est aussi rapide qu'un appel direct. Cette technique limine galement le besoin de soulever une exception depuis l'assembleur ! Pourquoi l'assembleur n'est-il utilis qu'ici ? Souvenez vous que pour effectuer de l'arithmtique en prcision multiple, la retenue doit tre ajoute au double mot signicatif suivant. Le C++ (et le C) ne permet pas au programmeur d'accder au drapeau de retenue du processeur. On ne pourrait eectuer l'addition qu'en recalculant indpendamment en C++ la valeur du drapeau de retenue et en l'ajoutant de faon conditionnelle au double mot suivant. Il est beaucoup plus ecace d'crire le code en assembleur partir duquel on peut accder au drapeau de retenue et utiliser l'instruction qui ajoute automatiquement le drapeau de retenue. Par concision, seule la routine assembleur

ADC

ici. Voici le code de cette routine (contenu dans

add_big_ints sera big_math.asm) :

explique

1 2 3 4 5 6 7 8 9 10 11 12 13

big_math.asm segment .text global add_big_ints, sub_big_ints %define size_offset 0 %define number_offset 4 %define EXIT_OK 0 %define EXIT_OVERFLOW 1 %define EXIT_SIZE_MISMATCH 2 ; Paramtres des routines add et sub %define res ebp+8 %define op1 ebp+12 %define op2 ebp+16

7.2.

ASSEMBLEUR ET C++

169

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55

add_big_ints: push ebp mov ebp, esp push ebx push esi push edi ; ; initialise esi pour pointer vers op1 ; edi pour pointer vers op2 ; ebx pour pointer vers res mov esi, [op1] mov edi, [op2] mov ebx, [res] ; ; s'assure que les 3 Big_int ont la mme taille ; mov eax, [esi + size_offset] cmp eax, [edi + size_offset] jne sizes_not_equal ; op1.size_ != op2.size_ cmp eax, [ebx + size_offset] jne sizes_not_equal ; op1.size_ != res.size_ mov ecx, ; ; initialise ; esi = ; edi = ; ebx = ; mov ebx, mov esi, mov edi, eax ; ecx = taille des Big_int

les registres pour qu'ils pointent vers leurs tableaux respectifs op1.number_ op2.number_ res.number_ [ebx + number_offset] [esi + number_offset] [edi + number_offset] ; met le drapeau de retenue 0 ; edx = 0

clc xor edx, edx ; ; boucle d'addition add_loop: mov eax, [edi+4*edx] adc eax, [esi+4*edx] mov [ebx + 4*edx], eax

170

CHAPITRE 7.

STRUCTURES ET C++

56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

inc loop

edx add_loop

; ne modifie pas le drapeau de retenue

jc overflow ok_done: xor eax, eax jmp done overflow: mov eax, EXIT_OVERFLOW jmp done sizes_not_equal: mov eax, EXIT_SIZE_MISMATCH done: pop edi pop esi pop ebx leave ret big_math.asm

; valeur de retour = EXIT_OK

Heureusement, la majorit de ce code devrait tre comprhensible pour le lecteur maintenant. Les lignes 25 27 stockent les pointeurs vers les objets

Big_int

passs la fonction via des registres. Souvenez vous que les rf-

rences sont des pointeurs. Les lignes 31 35 vrient que les tailles des trois objets sont les mmes (Notez que le dplacement de

size_

est ajout au

pointeur pour accder la donne membre). Les lignes 44 46 ajustent les registres pour qu'ils pointent vers les tableaux utiliss par leurs objets respectifs au lieu des objets eux-mmes (l encore, le dplacement du membre

number_

est ajout au pointeur sur l'objet).

La boucle des lignes 52 57 additionne les entiers stocks dans les tableaux en additionnant le double mot le moins signicatif en premier, puis les doubles mots suivants, pour l'arithmtique en prcision tendue (voir Section 2.1.5). La ligne 59 vrie qu'il n'y a pas de dpassement de capacit, lors d'un dpassement de capacit, le drapeau de retenue sera allum par la dernire addition du double mot le plus signicatif. Comme les doubles mots du tableau sont stocks dans l'ordre little endian, la boucle commence au dbut du tableau et avance jusqu' la n. La Figure 7.18 montre un court exemple utilisant la classe que les constantes de

etc. L'addition doit tre eectue dans cet ordre

Big_int

Big_int. Notez

doivent tre dclares explicitement comme

la ligne 16. C'est ncessaire pour deux raisons. Tout d'abord, il n'y a pas de constructeur de conversion qui convertisse un entier non sign en

Big_int.

Ensuite, seuls les

Big_int

de mme taille peuvent tr addition-

ns. Cela rend la conversion problmatique puisqu'il serait dicile de savoir

7.2.

ASSEMBLEUR ET C++

171

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

#include "big_int.hpp" #include <iostream> using namespace std; int main() { try { Big_int b(5,"8000000000000a00b"); Big_int a(5,"80000000000010230"); Big_int c = a + b; cout << a << " + " << b << " = " << c << endl; for ( int i=0; i < 2; i++ ) { c = c + a; cout << "c = " << c << endl; } cout << "c1 = " << c Big_int(5,1) << endl; Big_int d(5, "12345678"); cout << "d = " << d << endl; cout << "c == d " << (c == d) << endl; cout << "c > d " << (c > d) << endl; } catch( const char str ) { cerr << "Caught : " << str << endl; } catch( Big_int::Overow ) { cerr << "Dpassement de capacit " << endl; } catch( Big_int::Size_mismatch ) { cerr << "Non concordance de taille" << endl; } return 0; }
Fig. 7.18 

Utilisation Simple de

Big_int

172

CHAPITRE 7.

STRUCTURES ET C++

vers quelle taille convertir. Une implmentation plus sophistique de la classe permettrait d'additionner n'importe quelle taille avec n'importe quelle autre. L'auteur ne voulait pas compliquer inutilement cet exemple en l'implmentant ici (cependant, le lecteur est encourag le faire).

7.2.5 Hritage et Polymorphisme


L'

hritage
A

permet une classe d'hriter des donnes et des mthodes et

d'une autre. Par exemple, considrons le code de la Figure 7.19. Il montre deux classes,

B,

o la classe

hrite de

A.

La sortie du programme est :

Taille de a : 4 Dplacement de ad : 0 Taille de b : 8 Dplacement de ad: 0 Dplacement de bd: 4 A::m() A::m()


Notez que les membres

ad

des deux classes (B l'hrite de

A)

sont au mme

dplacement. C'est important puisque l'on peut passer la fonction un pointeur vers un objet type driv de (

i.e. qui hrite de) A. La Figure 7.20 montre le code assembleur (dit) de la fonction (gnr par gcc ).
Notez que la sortie de la mthode l'objet est cod en dur dans la fonction. Dans le cadre d'une vraie programmation oriente objet, la mthode appele devrait dpendre du type d'objet pass la fonction. On appelle cela le

A soit un pointeur vers un objet de n'importe quel

soit

m de A a t produite la fois par a et l'objet b. D'aprs l'assembleur, on peut voir que l'appel A::m()

polymorphisme. Le C++ dsactive cette fonctionnalit par dfaut. On utilise le mot-cl virtual pour l'activer. La Figure 7.21 montre comment les deux classes seraient modies. Rien dans le restet du code n'a besoin d'tre chang. Le polymorphisme peut tre implment de beaucoup de manires. Malheureusement, l'implmentation de

gcc

est en transition au moment d'crire ces lignes et devient beaucoup plus

complique que l'implmentation initiale. An de simplier cette explication, l'auteur ne couvrira que l'implmentation du polymorphisme que les compilateurs Microsoft et Borland utilisent. Cette implmentation n'a pas chang depuis des annes et ne changera probablement pas dans un futur proche. Avec ces changements, la sortie du programme change :

Size of a: 8 Dplacement de ad : 4 Size of b: 12 Dplacement de ad : 4 Dplacement de bd : 8 A::m() B::m()


Maintenant, le second appel passe un objet

B.

appelle la mthode

B::m()

car on lui

Ce n'est pas le seul changement cependant. La taille d'un

7.2.

ASSEMBLEUR ET C++

173

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

#include <cstddef> #include <iostream> using namespace std; class A { public : void __cdecl m() { cout << "A::m()" << endl; } int ad; }; class B : public A { public : void __cdecl m() { cout << "B::m()" << endl; } int bd; }; void f ( A p ) { p>ad = 5; p>m(); } int main() { A a; B b; cout << "Taille de a : " << sizeof(a) << " Dplacement de ad : " << osetof(A,ad) << endl; cout << "Taille de b : " << sizeof(b) << " Dplacement de ad : " << osetof(B,ad) << " Dplacement de bd : " << osetof(B,bd) << endl; f(&a); f(&b); return 0; }
Fig. 7.19 

Hritage Simple

174

CHAPITRE 7.

STRUCTURES ET C++

1 2 3 4 5 6 7 8 9 10 11

_f__FP1A: push mov mov mov mov push call add leave ret

ebp ebp, esp eax, [ebp+8] dword [eax], 5 eax, [ebp+8] eax _m__1A esp, 4

; nom de fonction dcor ; eax pointe sur l'objet ; utilisation du dplacement 0 pour ad ; passage de l'adresse de l'objet A::m() ; nom dcor de la mthode A::m()

Fig. 7.20  Code Assembleur pour un Hritage Simple

1 2 3 4 5 6 7 8 9 10 11

class A { public : virtual void __cdecl m() { cout << "A::m()" << endl; } int ad; }; class B : public A { public : virtual void __cdecl m() { cout << "B::m()" << endl; } int bd; };
Fig. 7.21 

Hritage Polymorphique

7.2.

ASSEMBLEUR ET C++

175

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

?f@@YAXPAVA@@@Z: push ebp mov ebp, esp mov mov mov mov mov push call add pop ret eax, [ebp+8] dword [eax+4], 5 ; p->ad = 5; ecx, [ebp + 8] edx, [ecx] eax, [ebp + 8] eax dword [edx] esp, 4 ebp ; ; ; ; ; ; ecx = p edx = pointeur sur la vtable eax = p empile le pointeur "this" appelle la premire fonction de la vtable nettoie la pile

Fig. 7.22  Code Assembleur de la Fonction

f() ad
vaut

vaut maintenant 8 (et 12 pour

B).

De plus, le dplacement de

maintenant 4, plus 0. Qu'y a-t-il au dplacement 0 ? La rponse cette question est lie la faon dont est implment le polymorphisme. Une classe C++ qui a une (ou plusieurs) mthode(s) virutelle(s) a un champ cach qui est un pointeur vers un tableau de pointeurs sur des mthodes

13 . Cette table est souvent appele la

vtable.

Pour les classes

et

ce pointeur est stock au dplacement 0. Les compilateurs Windows placent toujours ce pointeur au dbut de la classe au sommet de l'arbre d'hritage. En regardant le code assembleur (Figure 7.22) gnr pour la fonction on peut voir que l'appel la mthode

(de

la Figure 7.19) dans la version du programme avec les mthodes virtuelles,

ne se fait pas via une tiquette. La

ligne 9 trouve l'adresse de la vtable de l'objet. L'adresse de l'objet est place sur la pile ligne 11. La ligne 12 appelle la mthode virtuelle en se branchant la premire adresse dans la vtable exemple de

14 . Cet appel n'utilise pas d'tiquette, il

se branche l'adresse du code sur lequel pointe

liaison tardive (late binding). La liaison tardive repousse le choix

EDX.

Ce type d'appel est un

de la mthode appeler au moment de l'excution du code. Cela permet

13 Pour les classes sans mthode virtuelle, les compilateurs C++ rendent toujours la classe compatible avec une structure C normale qui aurait les mmes donnes membres. 14 Bien sr, la valeur est dj dans le registre ECX. Elle y a t place la ligne 8 et le ligne 10 pourrait tre supprime et la ligne suivante change de faon empiler ECX. Le code n'est pas trs ecace car il a t gnr en dsactivant les optimisations du compilateur.

176

CHAPITRE 7.

STRUCTURES ET C++

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

class A { public : virtual void __cdecl m1() { cout << "A::m1()" << endl; } virtual void __cdecl m2() { cout << "A::m2()" << endl; } int ad; }; class B : public A { // B hrite du m2() de A public : virtual void __cdecl m1() { cout << "B::m1()" << endl; } int bd; }; / ache la vtable de l ' objet fourni / void print_vtable ( A pa ) { // p voit pa comme un tableau de doubles mots unsigned p = reinterpret_cast<unsigned >(pa); // vt voit la vtable comme un tableau de pointeurs void vt = reinterpret_cast<void >(p[0]); cout << hex << "adresse de la vtable = " << vt << endl; for ( int i=0; i < 2; i++ ) cout << "dword " << i << " : " << vt[i] << endl;
// appelle les fonctions virtuelle d'une faon ABSOLUMENT non portable void (m1func_pointer)(A ); // variable pointeur de fonction m1func_pointer = reinterpret_cast<void ()(A)>(vt[0]); m1func_pointer(pa); // appelle m1 via le pointeur de fonction

void (m2func_pointer)(A ); // variable pointeur de fonction m2func_pointer = reinterpret_cast<void ()(A)>(vt[1]); m2func_pointer(pa); // appelle m2 via le pointeur de fonction

int main() { A a; B b1; B b2; cout << "a: " << endl; print_vtable(&a); cout << "b1: " << endl; print_vtable(&b1); cout << "b2: " << endl; print_vtable(&b2); return 0; }
Fig. 7.23 

Exemple plus compliqu

7.2.

ASSEMBLEUR ET C++

177

0 vtablep s 4 8 ad bd b1

0- &B::m1() 4 &A::m2() vtable


b1

Fig. 7.24  Reprsentation interne de

d'appeler la mthode correspondant l'objet. Le cas normal (Figure 7.20) code en dur un appel une certaine mthode et est appel

liaison prcoce

(early binding), car la mthode est lie au moment de la compilation. Le lecteur attentif se demandera pourquoi les mthodes de classe de la Figure 7.21 sont explicitement dclares pour utiliser la convention d'appel C en utilisant le mot-cl

__cdecl. Par dfaut, Microsoft utilise une convention ECX au

dirente de la convention C standard pour les mthodes de classe C++. Il passe le pointeur sur l'objet sur lequel agit la mthode via le registre explicites de la mthode. Le modicateur C par dfaut. Observons maintenant un exemple lgrement plus compliqu (Figure 7.23). Dans celui-l, les classes lieu d'utiliser la pile. La pile est toujours utilise pour les autres paramtres

__cdecl

demande l'utilisation de

la convention d'appel C standard. Borland C++ utilise la convention d'appel

et

ont chacune deux mthodes :

venez vous que comme la classe jet

m1

et

ne dnit pas sa propre mthode

hrite de la mthode de classe de

A.

m2. Soum2, elle

La Figure 7.24 montre comment l'ob-

apparat en mmoire. La Figure 7.25 montre la sortie du programme.

Tout d'abord, regardons l'adresse de la vtable de chaque objet. Les adresses des deux objets

sont les mmes, ils partagent donc la mme vtable. Une

vtable appartient une classe pas un objet (comme une donne membre

static).

Ensuite, regardons les adresses dans les vtables. En regardant la

sortie assembleur, on peut dterminer que le pointeur sur la mthode est au dplacement 0 (ou dword 0) et celui sur (dword 1). Les pointeurs sur la mthode des classes

et

car la classe

m2

m2

m1

est au dplacement 4

sont les mmes pour les vtables

hrite de la mthode

m2

de la classe

A.

Les lignes 25 32 montrent comment l'on pourrait appeler une fonction virtuelle en lisant son adresse depuis la vtable de l'objet

15 . L'adresse de la

15
gcc

Souvenez vous, ce code ne fonctionne qu'avec les compilateurs MS et Borland, pas

178

CHAPITRE 7.

STRUCTURES ET C++

a: Adresse de la vtable = 004120E8 dword 0: 00401320 dword 1: 00401350 A::m1() A::m2() b1: Adresse de la vtable = 004120F0 dword 0: 004013A0 dword 1: 00401350 B::m1() A::m2() b2: Adresse de la vtable = 004120F0 dword 0: 004013A0 dword 1: 00401350 B::m1() A::m2()
Fig. 7.25  Sortie du programme de la Figure 7.23

mthode est stocke dans un pointeur de fonction de type C avec un pointeur

this

explicite. D'aprs la sortie de la Figure 7.25, on peut voir comment cela

fonctionne. Cependant, s'il vous plat, n'crivez vtable.

pas

de code de ce genre ! Il

n'est utilis que pour illustrer le fait que les mthodes virtuelles utilisent la

Il y a plusieurs leons pratiques tirer de cela. Un fait important est qu'il faut tre trs attentif lorsque l'on crit ou que l'on lit des variables de type classe depuis un chier binaire. On ne peut pas utiliser simplement une lecture ou une criture binaire car cela lirait ou crirait le pointeur vtable depuis le chier ! C'est un pointeur sur l'endroit o la vtable rside dans la mmoire du programme et il varie d'un programme l'autre. La mme chose peut arriver avec les structures en C, mais en C, les structures n'ont des pointeurs que si le programmeur en dnit explicitement. Il n'y a pas de pointeurs explicites dclars dans les classes

ou

B.

Une fois encore, il est ncessaire de raliser que des compilateurs dirents implmentent les mthodes virtuelles diremment. Sous Windows, les objets de la classe COM (Component Object Model) utilisent des vtables pour implmenter les interfaces COM

16 . Seuls les compilateurs qui implmentent

16 Les classes COM utilisent galement la convention d'appel __stdcall, pas la convention C standard.

7.2.

ASSEMBLEUR ET C++

179

les vtables des mthodes virtuelles comme le fait Microsoft peuvent crer des classes COM. C'est pourquoi Borland utilise la mme implmentation que Microsoft et une des raisons pour lesquelles crer des classes COM. Le code des mthodes virtuelles est identique celui des mthodes nonvirtuelles. Seul le code d'appel est dirent. Si le compilateur peut tre absolument sr de la mthode virtuelle qui sera appele, il peut ignorer la vtable et appeler la mthode directement (

gcc ne peut pas tre utilis pour

p.e., en utilisant la liaison prcoce).

7.2.6 Autres fonctionnalits C++


Le fonctionnement des autres fonctionnalits du C++ ( le cadre de ce livre. Si le lecteur veut aller plus loin,

p.e., le RunTime

The Annotated C++ Reference Manual de Ellis et Stroustrup et The Design and Evolution of C++ de Stroustrup constituent un bon point de dpart.

Type Information, la gestion des exceptions et l'hritage multiple) dpasse

180

CHAPITRE 7.

STRUCTURES ET C++

Annexe A

Instructions 80x86
A.1 Instructions hors Virgule Flottante
Cette section liste et dcrit les actions et les formats des instructions hors virgule ottante de la famille de processeurs Intel 80x86. Les formats utilisent les abbrviations suivantes : R R8 R16 R32 SR M M8 M16 M32 I registre gnral registre 8 bits registre 16 bits registre 32 bits registre de segment mmoire octet mot double mot valeur immdiate

Elles peuvent tre combines pour les instructions plusieurs oprandes. Par exemple, le format

R,R

signie que l'instruction prend deux oprandes de

type registre. Beaucoup d'instructions deux oprandes acceptent les mmes oprandes. L'abbrviation

O2 est utilise pour reprsenter ces oprandes : R,R R,M R,I M,R M,I. Si un registre 8 bits ou un octet en mmoire peuvent tre utiliss comme oprande, l'abbrviation R/M8 est utilise.
Le tableau montre galement comment les dirents bits du registre

FLAGS sont modis par chaque instruction. Si la colonne est vide, le bit correspondant n'est pas modi du tout. Si le bit est toujours positionn une valeur particulire, un 1 ou un 0 gure dans la colonne. Si le bit est positionn une valeur qui dpend des oprandes de l'instruction, un dans la colonne. Enn, si le bit est modi de faon indnie, direction sont

C un ?

gure gure

dans la colonne. Comme les seules instructions qui changent le drapeau de

CLD

et

STD,

il ne gure pas parmi les colonnes FLAGS. 181

182

ANNEXE A.

INSTRUCTIONS 80X86

ADC ADD AND BSWAP CALL CBW CDQ CLC CLD CMC CMP CMPSB CMPSW CMPSD CWD CWDE DEC DIV ENTER IDIV IMUL

Nom

Description
Ajout avec Retenue Addition entire ET niveau bit Echange d'Octets Appel de Routine Conversion Octet-Mot Conversion QMot Eteindre la retenue Eteindre la direction Inverser la retenue Comparaison Entire Comparaison d'Octets Comparaison de Mots Comparaison DMots Conversion Mot-DMot dans DX:AX Conversion Mot-DMot dans EAX Decrmentation d'Entier Division non Signe Cration de cadre de pile Division Signe Multiplication Signe de DMot-

Formats
O2 O2 O2 R32 R M I

Flags O S Z A P C
C C 0 C C C C C C C C ? C C C C C 0

0 C O2 C C C C C C C C C C C C C C C C C C C C C C C C

R M R M I,0 R M R M R16,R/M16 R32,R/M32 R16,I R32,I


R16,R/M16,I R32,R/M32,I

C ?

C ?

C ?

C ?

C ? ?

? C

? ?

? ?

? ?

? ?

? C

INC INT JA JAE JB

Incrmentation tier

d'En-

R M I I I I

Gnrer une Interruption Saut si Au-dessus Saut si Au-Dessus ou Egal Saut si En-Dessous

A.1.

INSTRUCTIONS HORS VIRGULE FLOTTANTE

183

JBE JC JCXZ JE JG JGE JL JLE JMP JNA JNAE JNB JNBE JNC JNE JNG JNGE JNL JNLE JNO JNS JNZ JO JPE JPO JS JZ LAHF LEA

Nom
Egal

Description
Saut si En-Dessous ou Saut si Retenue Saut si CX = 0 Saut si Egal Saut si Suprieur Saut Egal Saut si Infrieur Saut Egal Saut Inconditionnel Saut si Non Au-Dessus Saut si Non Au-Dessus ou Egal Saut Saut si si Non Non EnEnI I I I I I I I I I I I I I I I dans Dessous Dessous ou Egal Saut si pas de Retenue Saut si Non Egal Saut si Non Suprieur Saut si Non Suprieur ou Egal Saut si Non Infrieur Saut si Non Infrieur ou Egal Saut si Non Overow Saut si Non Signe Saut si Non Zro Saut si Overow Saut si Pair Saut si Impair Saut si Signe Saut si Zro Charge AH Charge l'Adresse Eective FLAGS si Infrieur ou I I si Suprieur ou I I I I I I

Formats

Flags O S Z A P C

R M I I I

R32,M

184

ANNEXE A.

INSTRUCTIONS 80X86

LEAVE LODSB LODSW LODSD

Nom
Pile

Description
Quitter le Cadre de Charger un Octet Charger un Mot Charger Mot Boucler Boucler si Egal Boucler si Non Egal Dplacement nes de donun Double I I I

Formats

Flags O S Z A P C

LOOP LOOPE/LOOPZ LOOPNE/LOOPNZ MOV MOVSB MOVSW MOVSD MOVSX MOVZX MUL NEG NOP NOT OR POP POPA POPF PUSH PUSHA PUSHF RCL

O2 SR,R/M16 R/M16,SR

Dplacement d'Octet Dplacement de Mot Dplacement Double Mot Dplacement Sign R16,R/M8 R32,R/M8 R32,R/M16 Dplacement gn Multiplication Non Signe Inverser Pas d'Opration Complment 1 OU niveau bit Retirer de la pile Tout retirer de la pile Retirer FLAGS de la pile Empiler Tout Empiler Empiler FLAGS Rotation Gauche R/M,I R/M,CL C C avec Retenue R/M16 R/M32 I C C C C C C R M O2 R/M16 R/M32 0 C C ? C 0 R M C C C C C C Non SiR16,R/M8 R32,R/M8 R32,R/M16 R M C ? ? ? ? C de

A.1.

INSTRUCTIONS HORS VIRGULE FLOTTANTE

185

RCR

Nom

Description
Rotation Droite avec Retenue Rpter Rpter Si Egal Rpter Si Non Egal Retour Rotation Gauche Rotation Droite Copie FLAGS AH dans

Formats
R/M,I R/M,CL

Flags O S Z A P C
C C

REP REPE/REPZ REPNE/REPNZ RET ROL ROR SAHF SAL SBB SCASB SCASW SCASD SETA SETAE SETB SETBE SETC SETE SETG SETGE SETL SETLE SETNA SETNAE SETNB

R/M,I R/M,CL R/M,I R/M,CL

C C C C C C

C C C C

Dcalage Arithmtique Gauche Soustraire Avec Retenue Recherche d'Octet Recherche de Mot Recherche de DoubleMot Allumer Si Au-Dessus Allumer Si Au-Dessus ou Egal Allumer Si En-Dessous Allumer Si En-Dessous ou Egal Allumer Si Retenue Allumer Si Egal Allumer Si Plus Grand Allumer Si Plus Grand ou Egal Allumer Si Plus Petit Allumer Si Plus Petit ou Egal Allumer Dessus Allumer Allumer Dessous Si Si Non Non AuEnSi Non Au

R/M,I R/M, CL O2 C C C C R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 C C C C C C C C C C C C C C C C

C C C C

Dessus ou Egal

186

ANNEXE A.

INSTRUCTIONS 80X86

SETNBE SETNC SETNE SETNG SETNGE SETNL SETNLE SETNO SETNS SETNZ SETO SETPE SETPO SETS SETZ SAR SHR SHL STC STD STOSB STOSW STOSD SUB TEST XCHG

Nom

Description
Allumer Si Non EnDessous ou Egal Allumer Si Pas de Retenue Allumer Si Non Egal Allumer Si Non Plus Grand Allumer Si Non Plus Grand ou Egal Allumer Si Non Infrieur Allumer Si Non Infrieur ou Egal Allumer Si Non Overow Allumer Si Non Signe Allumer Si Non Zro Allumer Si Overow Allumer Si Parit Paire Allumer Si Parit Impaire Allumer Si Signe Allumer Si Zro Dcalage Arithmtique Droite Dcalage Droite Dcalage Gauche Allumer la Retenue Allumer le Drapeau de Direction Stocker l'Octet Stocker le Mot Stocker le Double-Mot Soustraction Comparaison Logique Echange Logique Logique

Formats
R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M,I R/M, CL R/M,I R/M, CL R/M,I R/M, CL

Flags O S Z A P C

C C C 1

O2 R/M,R R/M,I R/M,R R,R/M

C 0

C C

C C

C ?

C C

C 0

A.1.

INSTRUCTIONS HORS VIRGULE FLOTTANTE

187

XOR

Nom

Description
Ou Exclusif Niveau Bit

Formats
O2

Flags O S Z A P C
0 C C ? C 0

188

ANNEXE A.

INSTRUCTIONS 80X86

A.2

Instruction en Virgule Flottante

Dans cette section, la plupart des instructions du coprocesseur mathmatique du 80x86 son dcrites. La section description dcrit brivement l'opration eectue par l'instruction. Pour conomiser de la place, il n'est pas prcis si l'instruction dcale la pile ou non. La colonne format indique le type d'oprande pouvant tre utilis avec chaque instruction. Les abbrviations suivantes sont utilises : ST F D E I16 I32 I64

Registre du coprocesseur Nombre simple prcision en mmoire Nombre double prcision en mmoire Nombre prcision tendue en mmoire Mot entier en mmoire Double mot entier en mmoire Quadruple mot entier en mmoire

Les instructions ncessitant un Pentium Pro ou ou suprieur sont signales par une astrisque( ).

FABS FADD src FADD dest, ST0 FADDP dest [,ST0] FCHS FCOM src FCOMP src FCOMPP src FCOMI src FCOMIP src FDIV src FDIV dest, ST0 FDIVP dest [,ST0] FDIVR src FDIVR dest, ST0 FDIVRP dest [,ST0] FFREE dest FIADD src FICOM src FICOMP src FIDIV src FIDIVR src

Instruction

Description
ST0 = |ST0| ST0 += src dest += STO dest += ST0 ST0 = ST0 Compare ST0 et src Compare ST0 et src Compare ST0 et ST1
Compare dans FLAGS Compare dans FLAGS

Format
ST

n STn STn
ST ST ST

F D

n n

F D F D

ST0 /= src dest /= STO dest /= ST0 ST0 = src /ST0 dest = ST0/dest dest = ST0/dest
Marquer comme vide

ST0 += src Compare ST0 et src Compare ST0 et src STO /= src STO = src /ST0

n STn STn STn STn STn STn STn STn

F D

F D

I16 I32 I16 I32 I16 I32 I16 I32 I16 I32

A.2.

INSTRUCTION EN VIRGULE FLOTTANTE

189

FILD src FIMUL src FINIT FIST dest FISTP dest FISUB src FISUBR src FLD src FLD1 FLDCW src FLDPI FLDZ FMUL src FMUL dest, ST0 FMULP dest [,ST0] FRNDINT FSCALE FSQRT FST dest FSTP dest FSTCW dest FSTSW dest FSUB src FSUB dest, ST0 FSUBP dest [,ST0] FSUBR src FSUBR dest, ST0 FSUBP dest [,ST0] FTST FXCH dest

Instruction

Place

ST0 *= src
Initialise le Coprocesseur

src

Description
sur la Pile

Format
I16 I32 I64 I16 I32 I16 I32 I16 I32 I64 I16 I32 I16 I32 ST

ST0 Stocke ST0 ST0 -= src ST0 = src - ST0


Stocke Place

src

sur la Pile

F D E

Place 1.0 sur la Pile Charge le Registre du Mot de Contrle Place sur la Pile Place 0.0 sur la Pile I16

ST0 *= src dest *= STO dest *= ST0 Arrondir ST0 ST0 = ST0 2 ST0 = STO Stocke ST0 Stocke ST0

n n STn
ST ST

F D

ST1

ST ST

n n

F D F D E

Stocke le Registre du Mot de Contrle Stocke le Registre du Mot de Statut

I16 I16 AX

ST0 -= src dest -= STO dest -= ST0 ST0 = src -ST0 dest = ST0-dest dest = ST0-dest Compare ST0 avec 0.0 Echange ST0 et dest

n n STn STn STn STn


ST ST ST

F D

F D

Index
$, 72 dition de liens, 24 ADC, 39, 56 ADD, 13, 39 adressage indirect, 6970 tableaux, 102107 AND, 52 array1.asm, 103107 assembleur, 12 big et little endian, 6062 invert_endian, 62 binaire, 12 addition, 2 boucle do while, 4546 boucle while, 45 branchement conditionnel, 4144 BSWAP, 62 BYTE, 17 C++, 157179 dition de liens avec respect des types, 158 classes, 163179 constructeur par copie, 168 dcoration de noms, 157160 exemple Big_int, 165172 extern "C", 159160 fonctions inline, 161163 hritage, 172179 liaison prcoce, 177 liaison tardive, 175 member functions, voir methods polymorphisme, 172179 190 rfrences, 161 virtual, 172 vtable, 175179 CALL, 7374 CBW, 33 CDQ, 33 CLC, 39 CLD, 110 CMP, 40 CMPSB, 113, 114 CMPSD, 113, 114 CMPSW, 113, 114 COM, 178 commentaire, 13 compilateur, 6, 12 Borland, 23, 24 DJGPP, 23, 24 gcc, 23 __attribute__, 88, 151, 152, 155, 156 Microsoft, 23 pragma pack, 152, 155, 156 Watcom, 88 complment deux, 3032 arithmtique, 3539 compter les bits, 6367 mthode deux, 6465 mthode trois, 6667 mthode une, 63 convention d'appel, 69, 7481, 88 89 __cdecl, 89 __stdcall, 89 C, 22, 76, 8589

INDEX

191

tiquettes, 86 paramtres, 87 registres, 86 valeurs de retour, 88 Pascal, 76 registre, 89 standard call, 89 stdcall, 76, 88, 178 coprocesseur virgule ottante, 129 145 addition et soustraction, 131 133 charger et stocker des donnes, 130131 comparaisons, 134135 matriel, 129130 multiplication et division, 133 134 CWD, 33 CWDE, 33 dbogage, 1819 dcimal, 1 DEC, 14 directive, 1416 %dene, 14 D

dump_stack, 19 print_char, 18 print_int, 18 print_nl, 18 print_string, 18 read_char, 18 read_int, 18 endian, 26 entiers, 2940 bit de signe, 29, 32 comparaisons, 40 division, 3637 extension de signe, 3235 multiplication, 3536 non signs, 29, 40 prcision tendue, 39 reprsentation, 2935 complment deux, 3032 grandeur signe, 29 representation one's complement, 30 signs, 2932, 40 excution spculative, 55 FABS, 136 FADD, 132 FADDP, 132 FCHS, 136 FCOM, 134 FCOMI, 135 FCOMIP, 135, 146 FCOMP, 134 FCOMPP, 134 FDIV, 134 FDIVP, 134 FDIVR, 134 FDIVRP, 134 FFREE, 131 FIADD, 132 chier listing, 25 chier squelette, 26 FICOM, 134 FICOMP, 134

X, 15, 99

DD, 16 donnes, 1516 DQ, 16 equ, 14 extern, 82 global, 22, 82, 85 RES

X, 15, 99

TIMES, 16, 99 DIV, 36, 50 DWORD, 17 E/S, 1719 bibliothque asm_io, 1719 dump_math, 19 dump_mem, 18 dump_regs, 18

192

INDEX

FIDIV, 134 FIDIVR, 134 FILD, 130 FIST, 131 FISUB, 133 FISUBR, 133 FLD, 130 FLD1, 130 FLDCW, 131 FLDZ, 130 FMUL, 133 FMULP, 133 FSCALE, 136, 147 FSQRT, 136 FST, 131 FSTCW, 131 FSTP, 131 FSTSW, 135 FSUB, 133 FSUBP, 133 FSUBR, 133 FSUBRP, 133 FTST, 134 FXCH, 131 gas, 163 hexadcimal, 34 horloge, 6 IDIV, 37 immdiat, 13 IMUL, 3536 INC, 14 instruction if, 4445 instructions de chanes, 110120 interfaage avec le C, 8593 interruptions, 11 JC, 42 JE, 43 JG, 43 JGE, 43 JL, 43

JLE, 43 JMP, 41 JNC, 42 JNE, 43 JNG, 43 JNGE, 43 JNL, 43 JNLE, 43 JNO, 42 JNP, 42 JNS, 42 JNZ, 42 JO, 42 JP, 42 JS, 42 JZ, 42 label, 1517 LAHF, 135 lanceur C, 20 langage assembleur, 1213 langage machine, 5, 11 LEA, 8788, 107 localit, 149 LODSB, 111 LODSD, 111 LODSW, 111 LOOP, 44 LOOPE, 44 LOOPNE, 44 LOOPNZ, 44 LOOPZ, 44 mmoire, 45 pages, 10 segments, 9, 10 virtuelle, 10 mthodes, 163 math.asm, 3739 memory.asm, 116120 mnmonique, 12 mode protg 16 bits, 910

INDEX

193

32 bits, 10 mode rel, 9 mot, 8 MOV, 13 MOVSB, 112 MOVSD, 112 MOVSW, 112 MOVSX, 33 MOVZX, 33 MUL, 3536, 50, 107 NASM, 12 NEG, 37, 58 NOT, 54 octet, 4 oprations sur les bits assembleur, 5455 C, 5860 dcalages, 4952 dcalages arithmtiques, 50 51 dcalages logiques, 4950 rotations, 51 ET, 52 NOT, 53 OU, 53 XOR, 53 opcode, 12 OR, 53 pile, 7281 paramtres, 7477 variables locales, 8081, 8788 prdiction de branchement, 5556 prime.asm, 4648 prime2.asm, 141145 processeur, 57 80x86, 6 programmes multi-modules, 8285 quad.asm, 136139 quadruplet, 4 QWORD, 17

rcursivit, 9396 RCL, 51 RCR, 51 read.asm, 139141 register FLAGS SF, 40 registre, 5, 78 32 bits, 8 EDI, 111 EDX:EAX, 33, 36, 37, 39, 88 EFLAGS, 8 EIP, 8 ESI, 111 FLAGS, 8, 40 CF, 40 DF, 110 OF, 40 PF, 42 ZF, 40 index, 7 IP, 8 pointeur de base, 7, 8 pointeur de pile, 7, 8 segment, 7, 8, 112 REP, 113 REPE, 115 REPNE, 115 REPNZ, voir REPNE REPZ, voir REPE RET, 7374, 76 ROL, 51 ROR, 51 SAHF, 135 SAL, 50 SAR, 50 SBB, 39 SCASB, 113, 114 SCASD, 113, 114 SCASW, 113, 114 SCSI, 153155 segment bss, 22

194

INDEX

segment de code, 22 segment de donnes, 22 SET

reprsentation, 121126 dnormaliss, 125126 double prcision, 126 IEEE, 123126 simple prcision, 124126 un masqu, 124 WORD, 17 XCHG, 62 XOR, 53

xx, 56

SETG, 58 SHL, 49 SHR, 49 sous-programme, 7098 appel, 7381 rentrant, 9394 STD, 110 storage types volatile, 98 STOSB, 111 STOSD, 111 structures, 149157 alignement, 150152 champs de bits, 153155 osetof(), 150 SUB, 14, 39 subroutine, voir subprogram tableaux, 99120 accs, 100107 dnition, 99100 statique, 99 variable locale, 100 multidimensionnels, 107110 deux dimensions, 107108 paramtres, 110 TCP/IP, 61 TEST, 54 text segment, voir code segment TWORD, 17 types de stockage automatic, 96 global, 96 register, 96 static, 96 UNICODE, 61 virgule ottante, 121145 arithmtique, 126129