Académique Documents
Professionnel Documents
Culture Documents
Pcasm Book French
Pcasm Book French
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.
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
1.5
Fichier Squelette
. . . . . . . . . . . . . . . . . . . . . . . . .
26
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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
49
49 49 50 50 51 51 52 52 53 53 53 54 54 55 58 58 59 60 61 63 63 64 66
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
TEST
Eviter les Branchements Conditionnels . . . . . . . . . . . . . Manipuler les bits en C 3.4.1 3.4.2 Les oprateurs niveau bit du 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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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
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
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
REP
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
iv
Multiplication et division
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
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
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 :
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
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
vii
Page de l'auteur Page NASM sur SourceForge DJGPP Assembleur Linux The Art of Assembly USENET Documentation Intel
Ractions
L'auteur accepte toute raction sur ce travailThe author welcomes any feedback on this work.
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 :
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
Decimal 8 9 10 11 12 13 14 15
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
carry, retenue)
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 :
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
25 2 = 12 r 1 11001 10 = 1100 r 1
Donc
2510 = 110012
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 :
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
24D16
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
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-
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
Units Mmoire
Toutes les donnes en mmoire sont numriques. Les caractres sont stocks en utilisant un
code caractre
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.
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
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
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
correct. Le nombre de battements (ou, comme on les appelle couramment du processeur. Le nombre de cycles dpend de l'instruction.
8088,8086:
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
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
bits.
mode protg 16
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:
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
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.
eXentions) au Pentium. Ces instructions peuvent acclrer des oprations graphiques courantes. C'est un processeur Pentium Pro avec les instructions MMX
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.
mot
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
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
1.2.
ORGANISATION DE L'ORDINATEUR
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
slecteur.
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.
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
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
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.
pages .
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
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.
vec-
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.,
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
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
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
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
beaucoup plus
mnmonique oprande(s)
Un
assembleur
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.
registre :
du processeur.
mmoire :
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,
src
dest .
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) :
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
sub sub
Les instructions
DEC
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
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
sont des constantes nommes qui peuvent tre utilises dans le programme assembleur. Le format est le suivant :
pas
La directive %dene
Cette directive est semblable la directive
1.3.
LANGAGE ASSEMBLEUR
15
Unit
octet mot double mot quadruple mot dix octets
Lettre
B W D Q T
RESX
et
DX
Le code ci-dessus dnit une macro appele dans une instruction nombres constants.
MOV.
SIZE
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
de l'objet (ou des objets) qui sera stock. Le Tableau 1.3 montre les valeurs
DX.
Les lettres
Il est trs courant de marquer les emplacements mmoire avec des cements mmoire dans le code. Voici quelques exemples :
labels
L1 L2 L3 L4 L5 L6 L7 L8
db dw db db db dd resb db
; ; ; ; ; ; ; ;
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
DD
DQ ne peut tre utilise que pour dnir des constantes virgule ottante en
double prcision. Pour les grandes squences, la directive de NASM
Cette directive rpte son oprande un certain nombre de fois. Par exemple :
L12 L13
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
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
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
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
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
L6.
TWORD6 .
%include.
%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
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
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
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
stdout (i.e.
ZF
prend en argument un entier qui est ach galement. Cela peut aider distinguer la sortie de direntes commandes
dump_regs.
dump_mem
(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
1.4.
CRER UN PROGRAMME
19
l'adresse. La mmoire ache commencera au premier multiple de paragraphe avant l'adresse demande.
dump_stack
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).
EBP
aprs
dump_math
avant
EBP.
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.
20
CHAPITRE 1.
INTRODUCTION
1 2 3 4 5 6
driver.c
nomme
asm_main.
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.
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
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
et
Les donnes non initialises doivent tre dclares dans le segment bss (appel
.bss
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
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 (
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).
La directive
global
dfaut. Cela signie que seul le code du mme module peut utiliser le label.
global
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.
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
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 :
-f win32
9 10
obj.
http://www.delorie.com/djgpp
24
CHAPITRE 1.
INTRODUCTION
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).
gcc -c driver.c
L'option
marrage
beaucoup
code de d-
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 :
first.exe
(ou juste
first
sous Linux).
first.exe.
Il est possible de combiner les tapes de compilation et d'dition de liens. Par exemple :
gcc
compilera
driver.c
puis liera.
1.4.
CRER UN PROGRAMME
25
-l fichier-listing
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
456E7465722061206E756D6265723A2000 456E74657220616E6F74686572206E756D6265723A2000
prompt1 db prompt2 db
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
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 :
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
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
input2
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.
big endian
et
little endian.
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 )
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
111000,
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
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).
30
CHAPITRE 2.
l'arithmtique pour le processeur. De plus, l'arithmtique gnrale est galement complique. Si l'on ajoute 10
56,
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
le complment un de
11000111
est la reprsenta-
56.
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
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
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.
2.1.
31
Reprsentation Hexa 00 01 7F 80 81 FE FF
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
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.
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.
1 sign ou
+255
entiers signs et non signs. Cela permet au compilateur C de dterminer les instructions correctes utiliser avec les donnes.
mov mov
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 !
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 !
tendre
que les nouveaux bits deviennent des copies du bit de signe. Comme le bit
2.1.
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
ah, 0
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 :
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 :
; ; ; ;
ax al al ax
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
Octet en Mot) tend le signe du registre AL dans AX. Les oprandes sont
CWD
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
Quad word, Convertir un Double mot en Quadruple mot) tend le signe de EAX dans EDX:EAX (64 bits !) Enn, l'instruction
MOVZX
34
CHAPITRE 2.
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
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
fgetc()est
int
puis-
int
char
une valeur qu'elle peut retourner qui n'est pas un caractre, macro habituellement dnie comme valant un
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).
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,
Le C tronquera alors
FF
000000FF et FF. Donc, la boucle while ne peut pas char est ch est compar avec EOF. Comme
Ce que fait exactement le code dans ce cas change selon que le sign ou non. Pourquoi ? Parce que ligne 2,
2.1.
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
FFFFFFFF.
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
char.
ch
comme un
int,
ch doit
sub
add
carry ag
overow
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
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
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.
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
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
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
MUL,
imul imul
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.
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.
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
deux. L'oprande peut tre n'importe quel registre ou emplacement mmoire sur 8 bits, 16 bits, or 32 bits.
%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
38
CHAPITRE 2.
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.
39
70 71 72 73 74 75
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
L'instruction
1 2
add adc
; additionne les 32 bits de poids faible ; additionne les 32 bits de poids fort et la retenue
1 2
sub sbb
; soustrait les 32 bits de poids faible ; soustrait les 32 bits de poids fort et la retenue
vraiment
Section 2.2). Pour une boucle de somme, il serait pratique d'utiliser l'ins-
ADC
CLC
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.
40
CHAPITRE 2.
2.2
Structures de Contrle
Les langages de haut niveau fournissent des structures de contrle de haut niveau (
et
langage assembleur ne fournit pas de structures de contrle aussi complexes. Il utilise la place l'infme
goto
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.
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
La dirence
i.e.
1) et CF est teint (
i.e.
CMP
vaut 0, 0). Si
SF
OF
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
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 )
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
SHORT
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
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
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.
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
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
bits dans les 8 bits de poids faible du rsultat est pair ou impair).
1 2 3 4 5 6 7
; ; ; ;
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 :
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
Non Sign
JE JNE JB, JNAE JBE, JNA JA, JNBE JAE, JNB saute si saute si saute si saute si saute si saute si
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
Le code ci-dessus est trs confus. Heureusement, le 80x86 fournit des instructions de branchement additionnelles pour rendre ce type de test
coup
beau-
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
above
En utilisant ces nouvelles instructions de branchement, le pseudo-code ci-dessus peut tre traduit en assembleur plus facilement.
44
CHAPITRE 2.
1 2 3 4 5 6 7
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
LOOPNE, LOOPNZ
et ZF = 0, saute
Les deux dernires instructions de boucle sont utiles pour les boucles de recherche squentielle. Le pseudo-code suivant :
1 2 3 4 5
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.
1 2 3 4 5 6 7
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
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:
1 2 3 4 5 6
pour positionner FLAGS selon la condition endwhile ; choisir xx pour sauter si la condition est fausse of loop while
do while
46
CHAPITRE 2.
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
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
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.4.
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
; 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.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
div cmp je
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
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).
SHR
SHL
et
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.
1 2 3 4
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.
5 6 7 8
0C123H 2 3 cl
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-
MUL
et
DIV
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 !
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
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
0C123H 1 1 2
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
0C123H 1 1 1 2 1
; ; ; ; ;
ax ax ax ax ax
= = = = =
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
RCL
RCR.
1 2 3 4 5 6 7
Voici un extrait de code qui compte le nombre de bits qui sont allums
1 2
52
CHAPITRE 3.
X
0 0 1 1
Y
0 1 0 1
ET 0 0 0 1
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
3 4 5 6 7 8
rol eax, 1.
3.2
table de vrit
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
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 = 8022H
3.2.
53
X
0 0 1 1
Y
0 1 0 1
OU 0 1 1 1
X
0 0 1 1
Y
0 1 0 1
XOR 0 1 1 0
3.2.2 L'opration OU
Le
OU
rsultat vaut 1 comme le montre la table de vrit du Tableau 3.2. Voici un exemple de code :
1 2
mov or
; ax = E933H
OU
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 = 2912H
NOT
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.
X
0 1
NOT 1 0
Allumer le bit
i i i
OU ET
2i
(qui
XOR
masque
2i
Notez que le
NOT
FLAGS.
TEST
AND
1 2 3 4 5 6 7 8
; ; ; ; ; ; ;
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.
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
ET
1 2 3
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
; tout d'abord, construire le nombre pour le OU ; dcalage gauche cl fois ; allume le bit
1 2 3 4 5
; 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
Cette instruction est utilise car son code machine est plus petit que l'ins-
MOV
correspondante.
3.3
Les processeurs modernes utilisent des techniques trs sophistiques pour excuter le code le plus rapidement possible. Une technique rpandue est appele
56
CHAPITRE 3.
1 2 3 4 5 6
; 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
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
grande. Le programme exemple ci dessous montre comment le maximum peut tre trouv sans utiliser aucun branchement.
1 2
3.3.
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 :
; affichage du rsultat
58
CHAPITRE 3.
45 46 47
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
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
|. L'opration XOR est reprsente par l'oprateur binaire ^ NOT est reprsente par l'oprateur unaire ~ .
naires
&1 . L'opration OU
ET
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
/ 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.
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
5 6 7 8 9
x *= 2.
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
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
missions sur un chier. Cette fonction prend deux paramtres, une chaine avec le nom du chier modier et un entier
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.
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.
stat
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 (
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.
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
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.
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 ;
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.
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 :
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
bswap
edx XCHG
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
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.
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;
3.6
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.
data.
data
data
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
partir du 1 le plus droite, les bits seront les complments des bits originaux
o les x sont les mmes pour les deux nombres. Lorsque l'on applique un sur
data - 1,
data
ET
64
CHAPITRE 3.
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]];
4 milliards
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.
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;
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]
data
signicatif selon que le matriel est little ou big endian, respectivement). Bien sr, on peut utiliser une instruction comme :
for
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
loop unrolling
66
CHAPITRE 3.
data.
La
1) & 0x55)
0x55 vaut 01010101 en binaire. Dans ET sur data avec elle, les impaires sont supprims. La seconde oprande ((data
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.
101100112 ,
+
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 :
011000102 ):
+
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 :
3.6.
67
data
gale
001100102 ):
Maintenant,
une implmentation de cette mthode qui compte les bits dans un double mot. Elle utilise une boucle
de drouler la boucle ; cependant, la boucle rend plus claire la faon dont la mthode se gnralise direntes tailles de donnes.
68
CHAPITRE 3.
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.
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
; 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
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.
d'un registre pour dterminer o sauter (donc, le registre agit plus comme
pointeur de fonction
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.
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
Le sous-programme
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
$ + 7
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
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 (
est une zone de la mmoire qui est organise de cette faon. L'instruction
PUSH
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
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
[ESP]
[ESP].
POP
lit le double
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.
73
1 2 3 4 5 6
; ; ; ; ; ;
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
des registres EAX, EBX, ECX, EDX, ESI, EDI et EBP (pas dans cet ordre).
POPA
4.4
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
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 :
et en changeant le sous-programme
74
CHAPITRE 4.
SOUS-PROGRAMMES
get_int
appelle
adresse. la n du code de l'adresse de retour et saute RET de vers LIFO. Souvenez vous, il est
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
1 2 3 4 5
read_int [ebx], eax eax ; dpile la valeur de EAX, pas l'adresse de retour !!
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.
RET.
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.
tre appel depuis n'importe quel endroit du programme en toute scurit (mme depuis le sous-programme lui-mme).
valeur.
adresse
4.5.
CONVENTIONS D'APPEL
75
ESP + 4 ESP
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
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
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.
[EBP + 8]
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
stdcall,
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 (
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
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
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
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
; 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
; 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;
automatiques
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
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
; 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
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
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
n sump
Adresse de retour EBP sauv
sum
1 2 3 4 5
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
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
qu'il s'agit d'tiquettes qui peuvent tre utilises dans ce module mais sont
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
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.
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
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
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
sub4.asm
4.7.
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
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
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.
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
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
printf x
Valeur de
printf
register
peut
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.
aucun
doit
tre tiquete
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.
87
inverse
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
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
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
stdarg.h
x!
qui
tre
utilises
pour l'eectuer de faon portable. Voyez n'importe quel bon livre sur le C pour plus de dtails.
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)
8 sur la pile, on ne
mov
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
pas
vrai. L'instruction
LEA
ne lit
jamais
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 (
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).
trs
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__
int,
standard call
. La fonction
cdecl par stdcall. La dirence entre stdcall et cdecl est que stdcall
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.
89
printf
et
scanf).
i.e.
celles
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:
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
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.
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
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
Pourquoi la ligne 22 de
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
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
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.
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 ...
scanf
depuis l'assembleur
32 33 34 35 36 37
eax, [ebp-4]
; eax = sum
sub6.asm
scanf pour
lire un entier depuis le clavier ? La Figure 4.14 montre comment le faire. Une
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
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).
une
copie du code se
Link Libraries,
principe.
Dynamic
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
directe
soit
programme, disons
foo, foo
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.
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.
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
cadre n=3
n(3)
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.
global
bss)
data
ou
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,
i.e.
seules les
en termes as-
static
data
bss),
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
requte.
Le compi-
4.8.
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:
98
CHAPITRE 4.
SOUS-PROGRAMMES
lateur n'a
pas
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
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.
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
x x
z z
Chapitre 5
Tableaux
5.1
Un
Introduction
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.
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
toutes
ESP (soit directement, soit en utilisant l'instruction tableau de 50 lments d'un mot, il faudrait
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.
[ ]
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
5.1.
INTRODUCTION
101
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
1 2 3 4 5 6 7
al, [array1] al, [array1 + [array1 + 3], ax, [array2] ax, [array2 + [array2 + 6], ax, [array2 +
1] al 2] ax 1]
; ; ; ; ; ; ;
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
ADD
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:
1 2 3 4 5 6 7 8 9 10
lp:
; ebx = adresse de array1 ; dx contiendra la somme ; dl += *ebx ; si pas de retenue goto next ; incrmente dh ; bx++
next:
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.
reg de base
ou EDI.
est un des registres EAX, EBX, ECX, EDX, EBP, ESP, ESI
facteur
et 7.
5.1.
INTRODUCTION
103
1 2 3 4 5 6 7 8
lp:
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.
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
; 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
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
; 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
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 /
5.1.
INTRODUCTION
107
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
Il stocke la valeur de
plus simple et plus rapide que d'utiliser du fait que l'expression entre crochets par 6 rapidement.
doit
Donc, par exemple, cette instruction ne peut pas tre utilise pour multiplier
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]
a[0][0]
a[0][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
44]
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 ]
au niveau ligne
comme cela qu'un compilateur C/C++ reprsenterait le tableau. Comment le compilateur dtermine o et
a[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
pas
du nombre de lignes.
gcc
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
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
int b [4][3][2];
Ce tableau serait stock comme s'il tait compos de trois tableaux deux dimensions, chacun de taille
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 .
dimensions de dimension
D1
Dn ,
i1
in
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 ,
que
n j =1
j 1 k=1
Dk ij Dn ,
qui n'apparat pas dans la
110
CHAPITRE 5.
TABLEAUX
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 :
La famille des processeurs 80x86 fournit plusieurs instructions conues pour travailler avec les tableaux. Ces instructions sont appeles
de chanes.
instructions
une opration puis incrmentent ou dcrmentent automatiquement l'un des registres d'index ou les deux. Le
drapeau de direction
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
AL = [DS:ESI] ESI = ESI 1 AX = [DS:ESI] ESI = ESI 2 EAX = [DS:ESI] ESI = ESI 4
[ES:EDI] = AL EDI = EDI 1 [ES:EDI] = AX EDI = EDI 2 [ES:EDI] = EAX EDI = EDI 4
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 !
Une erreur
trs
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.
Index
112
CHAPITRE 5.
TABLEAUX
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
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 !
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
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
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
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
rsultat. La seule dirence serait que le registre EAX ne serait pas utilis du tout dans la boucle.
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
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:
CMP.
Les instructions
CMPSx
SCASx
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).
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
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:
REPE
et
le sont
REPZ sont simplement des synonymes pour le mme REPNE et REPNZ). Si l'instruction de comparaison de
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 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
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
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
%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
; ; ; ;
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
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 :
a, b, c, . . .
0, abcdef . . .
Multipliez le nombre par deux. La reprsentation du nouveau nombre sera :
a, bcdef . . .
121
122
CHAPITRE 6.
VIRGULE FLOTTANTE
= 1 = 0 = 0 = 1
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
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 ),
6.1.
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,
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
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
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.
double.
cision plus leve appele
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.
est
1, sssssssss).
Cela
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 .
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
reprsentent
6.1.
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).
NaN
(Not
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
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.
Nombres dnormaliss
Les nombres dnormaliss peuvent tre utiliss pour reprsenter des nombres avec des grandeurs trop petites normaliser ( Par exemple, considrons le nombre
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
dcimal). La reprsentation de
126
CHAPITRE 6.
VIRGULE FLOTTANTE
63 s
62 e
52
51 f
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
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.
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
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, 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;
6.2.2 Soustraction
La soustraction fonctionne d'une faon similaire l'addition et soure des mmes problmes. Par exemple, considrons
1, 0000110 24 1, 1111111 23
128
CHAPITRE 6.
VIRGULE FLOTTANTE
Dcaler
1, 1111111 23
1, 0000000 24
f(x)
qui essaie de trouver la racine de la fonction . On peut tre tent d'utiliser l'expression suivante pour vrier si
if ( f(x) == 0.0 )
Mais, que se passe-t-il si ment que
l'galit ne sera pas vrie. Il n'y a peut tre aucune valeur en virgule ottante IEEE de dans
f(x).
6.3.
LE COPROCESSEUR ARITHMTIQUE
129
1 1010 ).
EPS
est une macro dnissant une trs petite valeur positive (du genre Cette expression est vraie ds que
f(x)
En gnral, pour comparer une valeur en virgule ottante (disons autre ( ), on utilise :
x)
une
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
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.
pas
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.
Lit un
entier
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.
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
destination
peut tre un nombre en simple ou double prcision ou un Stocke le sommet de la pile en mmoire, exactement comme
destination
FIST dest
destination
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
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
FISTP dest
Identique mot.
supprim et la
destination
Il y a deux autres instructions qui peuvent placer ou retirer des donnes de la pile.
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
et d'un
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
FADD source FADD dest, ST0 FADDP dest ou FADDP dest, STO FIADD source
ST0 += source .
La
source
nation
seur.
destiLa
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.
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
source
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
destination
La
processeur.
source
moire.
source
ST0
d'un
en mmoire.
Multiplication et division
Les instructions de multiplication sont totalement analogues celles d'addition.
FMUL source
ST0 *= source .
La
source
FMUL dest, ST0 FMULP dest ou FMULP dest, ST0 FIMUL source
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
source
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
destination
La
processeur.
ST0 = (float) source / ST0. Divise un entier par ST0. La source 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
un registre du
ST0
source ,
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
source
(float)source ,
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:
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.
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.
compare compare
ST0 ST0
et et
source . source ,
La
source
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.
FICOMP).
Instructions diverses
Cette section traite de diverses autres instructions fournies par le coprocesseur.
136
CHAPITRE 6.
VIRGULE FLOTTANTE
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.
ax2 + bx + c = 0
La formule donne deux solutions pour
x: x1 et x2 . b b2 4ac x1 , x2 = 2a
2
4ac)
est appele le
dterminant. Sa
valeur est utile pour dterminer laquelle des trois possibilits suivantes est
b2 4ac = 0
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
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]
segment .text global _quadratic _quadratic: push ebp mov ebp, esp sub esp, 16 push ebx fild
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, -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
no_real_solutions: mov eax, 0 quit: pop mov pop ebx esp, ebp ebp
6.3.
LE COPROCESSEUR ARITHMTIQUE
139
76
ret
quad.asm
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 .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
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
; ; 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
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;
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
; ; ; ; ;
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
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
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
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
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
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 :
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.
offsetof().
est le nom du Figure 7.1.
type
offsetof
en
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
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
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
l'alignement. Le compilateur permet de spcier l'alignement de n'importe quel type en utilisant une syntaxe spciale. Par exemple, la ligne suivante :
unaligned_int
sances de deux pour spcier d'autres alignements (2 pour s'aligner sur les
unaligned_int, gcc
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
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 /
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 (
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
: 3; : 10; : 11; : 8;
unsigned int
ou un
int
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
limites d'octets. Si l'on inverse tous les octets, les morceaux des champs
f3
f2.
L'tiquette
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
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
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'
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
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
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
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
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
1 2 3 4 5 6 7
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++.
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
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
_f__Fiid
pas
et Borland en
@f$qiid.
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
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
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 :
moyen pour que le C++ puisse appeler du code C. C'est trs important car
norme
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
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 :
160
CHAPITRE 7.
STRUCTURES ET C++
1 2 3 4 5 6 7 8 9 10
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
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 :
__cplusplus
(avec
deux
carac-
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
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
adresse
de
y.
6 :
et
b.
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,
fonctions inline
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 :
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
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,
supposant que
est l'adresse
ebp-8
et
en
ebp-4):
1 2 3 4
1 2 3
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
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
tous
n'im-
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
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.
struct
drons la classe simple dnie dans la Figure 7.13. Une variable de type
Simple
ressemblerait une
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
int.
cach
set_data
de la classe
Simple
de
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
gcc
-S
du compilateur
DJGPP
et
gcc
.s
et utilise malheureusement la syntaxe du langage assembleur AT&T qui est assez dirente des syntaxes NASM et MASM
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 / }
int Simple :: get_data() const { return data; } void Simple :: set_data( int x ) { data = x; }
Fig. 7.13 Une classe C++ simple
DJGPP
commentaires supplmentaires pour clarier le but des instructions. Sur la toute premire ligne, notez que la mthode
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
doivent
frentes. Les paramtres sont encods an que la classe puisse surcharger la mthode
set_data
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
EAX.
Ce n'est
pas
le
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
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
on agit. La ligne 6 stocke le paramtre dans le double mot sur lequel pointe
Simple
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_
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
taille de l ' entier exprime en nombre d'unsigned valeur initiale du Big_int sous forme d'un
initial_value
int normaux
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
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
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
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
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_
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
Big_int
Big_int. Notez
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.
Big_int
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).
hritage
A
d'une autre. Par exemple, considrons le code de la Figure 7.19. Il montre deux classes,
B,
o la classe
hrite de
A.
ad
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
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
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 :
B.
appelle la mthode
B::m()
car on lui
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()
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
f() ad
vaut
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
vtable.
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
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
EDX.
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
7.2.
ASSEMBLEUR ET C++
177
0 vtablep s 4 8 ad bd b1
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
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
et
m1
et
A.
Tout d'abord, regardons l'adresse de la vtable de chaque objet. Les adresses des deux objets
vtable appartient une classe pas un objet (comme une donne membre
static).
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
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
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
this
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 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 (
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.
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
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
CLD
et
STD,
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
C ?
C ?
C ?
C ?
C ? ?
? C
? ?
? ?
? ?
? ?
? C
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.
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
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.
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
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
C 0
C C
C C
C ?
C C
C 0
A.1.
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
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
F D
F D
I16 I32 I16 I32 I16 I32 I16 I32 I16 I32
A.2.
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
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
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
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
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