IFT-17584 Semaine 03 Programmation système @Pierre Marchand et Martin Dubois, 2002 59 Plan de la rencontre • • • • • • • • • Retour sur la semaine précédente Types de données Rangement des données Modes d’adressage Gestion de la pile Variables locales Passage des arguments Interface avec les langages de haut niveau Récursivité, réentrance et translatabilité @Pierre Marchand et Martin Dubois, 2002 60 La semaine précédente • • • • • • • • • Compilateur Assembleur L’éditeur de liens « Listing files » Assembleur « inline » Déverminage Masm32 Programme complètement en assembleur Programme mixte @Pierre Marchand et Martin Dubois, 2002 61 Types de données entières 7 0 Octet (byte) 15 0 Mot (word) 31 Mot double (doubleword) 0 7 43 0 BCD 3 03 0 BCD compacté (packed BCD) Near Pointer Far Pointer (Logical Address) 47 31 0 32 31 0 Champ de bits (bit field) Field length @Pierre Marchand et Martin Dubois, 2002 62 Types de données virgule flottante 31 8 Simple précision Double précision 0 63 0 11 52 79 0 15 Précision étendue 79 23 64 0 Décimal compacté @Pierre Marchand et Martin Dubois, 2002 63 float Simple précision 31 0 s e = 8 bits f = 23 bits N = (-1)s 1,f 2e-127 Nombres normalisés e = 1 à 254 e = 0 réservé pour 0 et les nombres dénormalisés e = 255 réservé pour les infinis et les NaNs @Pierre Marchand et Martin Dubois, 2002 64 Normalisé vs dénormalisé Nombres normalisés (e = 1 à 254) Nombre maximum normalisé: 1,11111111111111111111111 2127 = 3,402823 1038 Nombre minimum normalisé: 1,00000000000000000000000 2-126 = 1,175494 10-38 Nombres dénormalisés (e = 0 et f ≠ 0) e = 0 et f ≠ 0. Alors: N = (-1)s 0,f 2-126 Nombre dénormalisé minimum: 0.00000000000000000000001 2-126 = 1,401298 10-45 @Pierre Marchand et Martin Dubois, 2002 65 Zéro, infini et NaN Zéro (e = 0 et f = 0) Zéro est représenté par 1,0000000000000000000000 x 2-127 = 00000000000000000000000000000000 Infini (e = 255 et f = 0) L’infini est représenté par 1,0000000000000000000000 x 2128 = 01111111100000000000000000000000 = 7F800000 Nan (e = 255 et f ≠ 0) @Pierre Marchand et Martin Dubois, 2002 66 double Double précision 63 0 s e = 11 bits f = 52 bits N = (-1)s 1,f 2e-1023 Nombres normalisés: e = 1 à 2046 e = 0 réservé pour 0 et les nombres dénormalisés e = 2047 réservé pour les infinis et les NaNs @Pierre Marchand et Martin Dubois, 2002 67 Limites des doubles Nombres normalisés (e entre 1 et 2026) Nombre maximum normalisé: 1,111111111111111… 21023 = 1,79769313486 10308 Nombre minimum normalisé: 1,000000000000000… 2-1022 = 2,22507385851 10-308 Nombres dénormalisés (e = 0 et f ≠ 0) e = 0 et f ≠ 0. Alors: N = (-1)s 0,f 2-1022 Nombre dénormalisé minimum: 0,00000000…000001 2-1022 = 4,94065645842 10-324 @Pierre Marchand et Martin Dubois, 2002 68 Zéro, Infini et NaN Zéro (e = 0 et f = 0) Zéro est représenté par 1,00000… x 2-1023 = 0000000000000000 Infini (e = 2047 et f = 0) L’infini est représenté par 1,00000000 x 21024 = 011111111111000000000…. = 7FF0 0000 0000 0000 Nan (e = 2047 et f ≠ 0) @Pierre Marchand et Martin Dubois, 2002 69 Précision étendue Précision étendue 79 62 0 s e = 15 bits f = 63 bits d =1 ou 0 N = (-1)s d,f 2e-16383 Nombres normalisés: e = 1 à 32766 e = 0 réservé pour 0 et les nombres dénormalisés e = 32767 réservé pour les infinis et les NaNs @Pierre Marchand et Martin Dubois, 2002 70 Limites Nombres normalisés (e entre 1 et 16383) Nombre maximum normalisé : 1,11111111111111111… 216383 = 6 104931 Nombre minimum normalisé : 1,000000……0000000... 2-16383 = 8 10-4933 Nombres dénormalisés e = 0 et f ≠ 0. Alors: N = (-1)s 0,f 2-16383 Nombre dénormalisé minimum: 0,00000000…000001 2-16383 = 9 2-4952 @Pierre Marchand et Martin Dubois, 2002 71 Format Packed Decimal Format Packed Decimal 79 72 0 s 18 digits BCD Représentation : -1018 + 1 à 1018 - 1 @Pierre Marchand et Martin Dubois, 2002 72 Les NaN Les NaN ne représentent pas des nombres, on ne peut donc pas les comparer avec des nombres. Ce sont des quantités considérées comme non ordonnées. • Pour la même raison, le signe des NaN est ignoré, donc un NaN n’est ni positif ni négatif. • La fraction d’un QNaN (Quiet NaN)est toujours de la forme : 0,1xxxxx…xxxxxxx tandis que celle d’un SNaN (Signaling NaN) est de la forme : 0,0xxxxx…xxxxxxx @Pierre Marchand et Martin Dubois, 2002 73 Les NaN (la suite) Les Nan • Les SNaN signalent des exceptions lorsqu’ils sont fournis comme opérandes arithmétiques. Ils ne sont jamais générés par une opération. On peut toutefois les créer manuellement en mémoire. • Les QNaN représentent le résultat de certaines opérations invalides telles que des opérations arithmétiques invalides sur des infinis ou sur des NaN. • Un QNaN est généré dans les conditions suivantes: Une opération invalide se produit alors que le masque d’opération invalide de FPSCR est 1. Par exemple ∞ - ∞, ∞ ÷ ∞, 0 ÷ 0, ∞ 0, opération sur un SNaN, comparaison ordonnée avec un NaN. La conversion en entier d’un infini ou d’un NaN. @Pierre Marchand et Martin Dubois, 2002 74 Les NaN (la suite) Les Nan • Les QNaN se propagent à travers toutes les opérations, sauf les comparaisons ordonnées et la conversion en entier, sans signaler d’exception. • Le code d’un QNaN donné peut servir d’information de diagnostic pour permettre d’identifier les résultats d’opérations invalides. • Si l’un ou l’autre des opérandes d’une opération est un NaN, le résultat de l’opération est ce NaN. • Chacune des conditions pouvant provoquer un NaN peut générer une exception, c’est-à-dire une interruption logicielle, si une telle exception est autorisée dans le registre FPCR. @Pierre Marchand et Martin Dubois, 2002 75 Type de données MMX Octets compactés Mots compactés Double mots compactés Quadruples mots @Pierre Marchand et Martin Dubois, 2002 63 0 63 0 63 0 63 0 76 Types de données SIMD Doubles compactés 127 63 0 127 63 0 Quadruples mots 127 95 63 31 0 127 95 63 31 0 Simples compactés Doubles mots compactés 127 111 95 79 63 47 31 127 111 95 79 63 47 31 15 0 Mots compactés 23 15 7 0 Octets compactés @Pierre Marchand et Martin Dubois, 2002 77 Rangement des données Little Endian vs Big Endian 31 24 23 16 15 87 octet 3 0 mot 0 mot 1 octet 2 octet 1 octet 0 Registre mot 0 Mot double mot 1 octet 0 octet 1 octet 2 octet 3 Adresse du mot (double) Adresse + 1 Adresse + 2 Adresse + 3 Mémoire Chez Intel, l’octet de poids faible est stocké à l’adresse la plus basse (Little Endian). @Pierre Marchand et Martin Dubois, 2002 78 Modes d’adressage • • • • • • Registre Immédiat Direct Implicite Indirection registre Indirection registre avec offset registre @Pierre Marchand et Martin Dubois, 2002 79 Adressage de registre mov ebx, eax • • • • ; (ebx) = (eax) Le plus utilisé Très rapide Utilisé pour tout les calcule interne au processeur La largeur de la données dépends de la largeur du registre AL AX EAX 8 bits 16 bits 32 bits @Pierre Marchand et Martin Dubois, 2002 80 Adressage immédiat mov eax, 0122Bh ; ou mov eax, 0x0122B mov eax, -1 ; eax = 0xFFFFFFFF mov bx, 3 ; bx = 0x0003 • Utilisé lors de calcule impliquant des constantes • La données et encodé à même l’instruction @Pierre Marchand et Martin Dubois, 2002 81 Adressage direct mov eax, variable variable est traduite par l’assembleur en offset par rapport au début du segment (habituellement le data segment). Cet offset est l’adresse à laquelle la variable a été implantée. • Utilisé pour lire ou écrire des variables de type simple @Pierre Marchand et Martin Dubois, 2002 82 Adressage implicite Certaines instructions n’ont pas d’opérande explicite et la description de l’adresse est contenue dans l’instruction elle-même ou dans des registres prédéfinis ret xlat mul div ; dépile l’adresse de retour ; utilise EBX et AL ; utilise edx et eax ; utilise edx et eax @Pierre Marchand et Martin Dubois, 2002 83 Exemple 01 ; Constantes ; //////////////////////////////////////////////////////// .const X dw 2 ; Variables ; //////////////////////////////////////////////////////// .data sZ dw ? ; Debut du segment de code ; //////////////////////////////////////////////////////// @Pierre Marchand et Martin Dubois, 2002 84 Exemple 01 (suite) .code ; ===== Start ======================================= ; Le point d'entree du programme start: mov ax, X add ax, 2 mov sZ, ax push 0 call ExitProcess end start @Pierre Marchand et Martin Dubois, 2002 85 Indirection registre mov edx, [ebx] ; adresse dans ebx Le registre entre crochets est appelé registre de base. Les registres suivants peuvent servir de registre de base : eax edi ebx esi ecx ebp edx esp Indirection registre avec offset mov eax, [ebx + 8] @Pierre Marchand et Martin Dubois, 2002 ; adresse = ebx + 8 86 Exemple 02 Disponible sur le site du cours @Pierre Marchand et Martin Dubois, 2002 87 Instruction lea lea Load Effective Address Calcule l’adresse efficace de l’opérande source et place le résultat dans le registre destination. oszapc = oszapc lea reg, mem 0,5 Équivalent : mov reg, offset mem 0,5 Quand doit-on utiliser lea au lieu de mov ? Pour les paramètres d'une fonction, on utilise toujours mov. Pour les variables, on utilise lea lorsqu'il s'agit d'une variable de type tableau, mais on utilise mov lorsqu'il s'agit d'un pointeur ou d'un type simple. @Pierre Marchand et Martin Dubois, 2002 88 Indirection registre avec offset registre L’offset registre est aussi appelé index mov [ebx + edi * k],eax ; adresse = ebx + edi * k ; k = 1, 2, 4 ou 8 seulement Indirection registre avec index + offset mov ax,[ebx + esi * k + 16] ; adresse= ebx + esi * k + 16 Les registres suivants peuvent servir d’index : eax edi ebx esi ecx ebp edx @Pierre Marchand et Martin Dubois, 2002 89 Utilisation des index Dans les modes d’adressage précédents, la constante k permet d’ajuster l’instruction à la taille de l’opérande, de façon à accéder directement au ième élément d’un tableau. Pour des octets (char, byte, boolean), k = 1, pour des mots (short), k = 2, pour des mots doubles (int, long, float), k = 4, pour des mots quadruples (long long, double), k = 8. .data tableau dw 50, 75, 342, -9, … .code lea ebx, tableau mov esi, i mov ax, [ebx + esi * 2] ; ax = tableau[ i ] ... @Pierre Marchand et Martin Dubois, 2002 90 Exemple 03 Disponible sur le site du cours @Pierre Marchand et Martin Dubois, 2002 91 Entrées-sorties en mode console Dans les exemples précédents, nous aurions pu afficher les résultats en utilisant la méthode démontré dans l’exemple 04 A ne surtout pas oublier ! Il faut compiler au moyen de Console Assemble & Link. @Pierre Marchand et Martin Dubois, 2002 92 Programmation Windows On n’utilise plus la console dans les applications contemporaines, mais plutôt une fenêtre avec menus, boutons, bandes de défilement, case de fermeture, etc. Par exemple : Pour réaliser de telles applications, il faut faire appel aux libraires de Windows (API). La programmation Windows dépasse le cadre de ce cours, mais vous trouverez des exemples dans les travaux pratiques et sur le site du cours. @Pierre Marchand et Martin Dubois, 2002 93 Instruction mov mov Move Data Copie l’opérande source dans l’opérande destination oszapc = oszapc mov reg, reg mov dx, bx 0,5 mem, reg mov table[edi], ebx reg, mem mov ebx, a reg, immed mov cx, 256 mem, immed mov word ptr [ebx], 15 @Pierre Marchand et Martin Dubois, 2002 94 Instructions movzx et movsx Variantes de mov : movzx et movsx Supposons que bl contient 0x94 movzx ax, bl -> ax = 0094 = + 148 sur 16 bits movzx eax, bl -> eax = 00000094 = + 148 sur 32 bits movsx ax, bl movsx eax, bl -> ax = FF94 = –108 sur 16 bits -> eax = FFFFFF94 = – 108 sur 32 bits Ces instructions permettent de changer le type d’une variable sx = Signe extend zx = Zero extend @Pierre Marchand et Martin Dubois, 2002 95 Instructions xchg, xlat et xlatb xchg Exchange Échange les deux opérandes oszapc = oszapc xchg reg, reg mem, reg reg, mem xlat, xlatb xchg xchg xchg cx, dx [ebx], ax ebx, pointer Translate Remplace un indice contenu dans al par le contenu correspondant d’une table dont l’adresse est dans ebx. oszapc = oszapc xlat mem xlatb @Pierre Marchand et Martin Dubois, 2002 96 Pile d’exécution • • • • À quoi sert-elle ? Placer l’adresse de retour lors d’un appel de fonction Sauvegarder le contexte d’exécution lorsqu’une interruption doit être traité Placer les variables locales Passer les arguments à une fonction @Pierre Marchand et Martin Dubois, 2002 97 Comment le Pentium IV manipule la pile ? Le Pentium IV a trois registres spécialement conçu pour la gestion de la pile • SS Stack Segment • ESP Stack Pointer • EBP Base Pointer ESP pointe sur la dernière donnée empiler et la première à être dépilé. @Pierre Marchand et Martin Dubois, 2002 98 Instruction call Exemples: call Fonction call sVariable Déroulement: • Ajouter la grandeur de l’adresse de retour à ESP • Placer l’adresse de retour dans l’espace mémoire pointé par ESP • Placer l’opérande dans IP ou EIP @Pierre Marchand et Martin Dubois, 2002 99 Instruction ret Exemples: ret ret 12 Déroulement: • Lire l’adresse de retour de l’espace mémoire pointé par ESP et la placer dans IP ou EIP • Ajouter la grandeur de l’adresse de retour à ESP • Ajoute l’opérande à ESP Spécialement conçu pour qu’une fonction puisse enlever elle même ses arguments de la pile @Pierre Marchand et Martin Dubois, 2002 100 Exemple 05 Disponible sur le site du cours @Pierre Marchand et Martin Dubois, 2002 101 La pile et les interruptions Lors d’un interruption certaines informations supplémentaires sont placé sur la pile comparativement à un appel de fonction normal cs eflags Code Segment Registre de flags Dans certains cas, d’autres informations sont aussi sauvegardé Le retour d’un interruption se fait à l’aide de l’instruction iret et non ret @Pierre Marchand et Martin Dubois, 2002 102 Instruction push Exemples: push push push eax sVariable 64 Déroulement: • Soustrait la grandeur de l’opérande à ESP • Place l’opérande dans l’espace mémoire pointé par ESP Dans le mode 32 bit, tout les items poussés sur la pile occupent 4 octets. @Pierre Marchand et Martin Dubois, 2002 103 Instruction pop Exemples: pop eax pop sVariable Déroulement: • Lit la valeur pointée par ESP et la place dans l’opérande • Ajoute la grandeur de l’opérande à ESP @Pierre Marchand et Martin Dubois, 2002 104 pusha – pushad – popa – popad Ces quatre instructions manipule l’ensemble des registre généraux d’un seul coup Sans le D final, les registres suivants sont affectés AX, CX, DX, BX, SP, BP, SI, DI Avec le D final, les registres suivants sont affectés EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI Très utile pour sauvegarder le contexte d’exécution lors du traitement d’un interruption @Pierre Marchand et Martin Dubois, 2002 105 pushf – pushfd – popf – popfd Cas particulier pour le traitement du registre de flag Si le processeur est dans un mode d’exécution qui ne permet pas le changement de certains bits du registre, ces bits ne change pas et aucune erreur ne se produit Vous pouvez utiliser ces deux instructions pour conserver, sur la pile, le résultat d’une comparaison @Pierre Marchand et Martin Dubois, 2002 106 Variables locales • Création sub esp, 12 • Utilisation mov eax, [esp] inc [esp + 4] add eax, [esp + 8] ; Variable 3 ; Variable 2 ; Variable 1 • Destruction add esp, 12 @Pierre Marchand et Martin Dubois, 2002 107 « Stack Frame » Espace de rangement pour les variables locales … Adresse de retour EBP précédent Variable locale Variable locale • Empiler EBP • Copier ESP dans EBP • Ajouter la grandeur des variables locales à ESP @Pierre Marchand et Martin Dubois, 2002 108 Adressage des variables locales Habituellement les variables locales sont adressées en utilisant un adressage indirect basé sur le registre EBP Supposons que la première variable locale soit un mot double, nous pourrions lui ajouter un de la manière suivante inc [ebp+4] @Pierre Marchand et Martin Dubois, 2002 109 Instruction enter Exemple: enter 12,0 Description: Création d’un « Stack Frame » avec N bytes de variables locales @Pierre Marchand et Martin Dubois, 2002 110 Instruction leave Exemple: leave Description: Retirer un « Stack Frame » de la pile @Pierre Marchand et Martin Dubois, 2002 111 Passage des arguments • • • • • Par variables globales Par registres cdecl stdcall fastcall @Pierre Marchand et Martin Dubois, 2002 112 Passage par variables globales • Le moyen le plus simple de tous ! • L’appelant place les arguments dans des variables globales connues de tous • L’appelé lit les arguments de ces même variables globales • La valeur de retour peut suivre le même chemin • Voir l’exemple 06 Que faire pour les fonctions récursives ? Que faire pour les fonctions utilisés par plusieurs threads ? @Pierre Marchand et Martin Dubois, 2002 113 Passage par registres • • • • La méthode plus rapide de tous Très simple Permet les fonctions réentrantes Il faut cependant oublier plusieurs des registres: cs, ds, es, ss, esp,… • Il reste donc un nombre limité de registres • La valeur de retour peut prendre le même chemin • Oblige à sauvegarder les arguments en mémoire pour retrouver l’usage des registres nécessaires aux opérations @Pierre Marchand et Martin Dubois, 2002 114 cdecl • Les arguments sont empilés dans l’ordre inverse de leur déclaration • L’appelant a la responsabilité de dépiler les arguments • Les arguments plus petit qu’un mot double sont tout de même empilés sous la forme de mots doubles pour préserver l’alignement de la pile @Pierre Marchand et Martin Dubois, 2002 115 La valeur de retour • Si la valeur de retour est un mot double ou qu’elle est plus petite qu’un mot double, elle est placé dans eax • Si c’est un mot quadruple, elle est placé dans la paire de registres edx:eax • Si c’est une valeur virgule flottante, elle est placé dans st(0) @Pierre Marchand et Martin Dubois, 2002 116 stdcall • Les arguments sont empilés dans l’ordre inverse de leur déclaration • La fonction appelée à la responsabilité de dépiler les arguments • Si la fonction a un nombre variable d’argumente, c’est l’appelant qui a la responsabilité de dépiler les arguments • Les arguments plus petit qu’un mot double sont tout de même empilés sous la forme de mots doubles pour préserver l’alignement de la pile @Pierre Marchand et Martin Dubois, 2002 117 Fastcall • Le premier argument est passé dans le registre ecx • Le second argument est passé dans le registre edx • Les autres sont empilés sur la pile dans l’ordre inverse de leur déclaration • Les arguments plus petit qu’un mot double sont tout de même empilés sous la forme de mots doubles pour préserver l’alignement de la pile @Pierre Marchand et Martin Dubois, 2002 118 Le “Stack Frame” et les arguments [ebp + 16] [ebp + 12] [ebp + 8] [ebp + 4] [ebp] [ebp – 4] [ebp – 8] @Pierre Marchand et Martin Dubois, 2002 Argument 3 Argument 2 Argument 1 Adresse de retour ebp précédent Variable Local 1 Variable Local 2 119 Récursivité Un programme récursif est un programme qui s’appelle luimême. Un exemple bien connu est celui de la fonction factorielle, même si ce n’est pas une façon très efficace d’évaluer cette fonction: unsigned long factorial(unsigned long n) { if (n == 1) return 1; else return n * factorial(n - 1); } @Pierre Marchand et Martin Dubois, 2002 120 Récursivité (la suite) En assembleur du Pentium, on aura: factorial: mov cmp jne mov jmp suite: dec push call add mov mul fin: ret eax, [esp+4] eax, 1 suite eax, 1 fin eax eax factorial esp, 4 ebx, n ebx @Pierre Marchand et Martin Dubois, 2002 // if (n == 1) // return 1 // factorial (n - 1) // factorial (n-1) dans eax // n * factorial (n - 1) dans eax 121 Réentrance Définition Un programme dit est réentrant s’il peut être interrompu et que le programme interrupteur peut l’appeler et obtenir un résultat correct, et qu’après l’interruption, le programme interrompu donne lui aussi un résultat correct; Conditions Avoir du code pur, c.-à-d. qui ne se modifie pas lui-même: aucune information interne au code ne peut être modifiée par le code; Ne jamais utiliser de stockage temporaire interne au code; Le programme doit être interruptible. Un programme récursif doit être rentrant (s’il n’interdit pas les interruptions), mais un programme réentrant n’est pas nécessairement récursif. @Pierre Marchand et Martin Dubois, 2002 122 Translatabilité Un sous-programme est dit translatable ou indépendant de la position s'il fonctionne correctement où qu'il soit placé en mémoire. Un programme indépendant de la position ne nécessite pas un chargeur relocalisant car toutes les adresses sont exprimées par rapport à la valeur courante du compteur ordinal (EIP). Il peut ainsi être utilisé avec n'importe quelle combinaison d'autres programmes. Un programme indépendant de la position est donc presque toujours préférable à une programme qui ne l'est pas, même s'il est de 5 à 10% moins rapide. @Pierre Marchand et Martin Dubois, 2002 123 Comment faire Pour écrire du code indépendant de la position, il faut: a.Éviter les branchements absolus (Ex. jmp 0x1000) b.Référer aux variables au moyen des modes d'adressage indexés qui utilisent un offset par rapport à un registre comme ebp ou esp. c. Utiliser la pile système pour du stockage temporaire (allocation dynamique). On peut par exemple réserver 32 octets dans la pile en soustrayant 32 du pointeur de pile avec l'instruction sub esp, 32 On réfère ensuite à cet espace mémoire avec des offsets indexés par rapport à esp ou ebp. @Pierre Marchand et Martin Dubois, 2002 124 Déterminer l’adresse d’exécution Si nécessaire, on peut toujours déterminer la valeur courante du compteur ordinal au moyen d'une instruction comme : ici: lea eax, ici Le programme peut donc calculer son adresse réelle en mémoire. @Pierre Marchand et Martin Dubois, 2002 125 Translatabilité et adresses absolues Il existe habituellement des adresses absolues dans une machine, par exemple, celles des vecteurs d ’interruption. On peut accéder à de telles adresses de façon absolue sans nuire à la translatabilité du programme, si elles ne se trouvent pas dans la zone d'implantation du programme. @Pierre Marchand et Martin Dubois, 2002 126