mardi 28 octobre 2008

Integer Overflow

Hello :),
je me suis souvent posé la question du pourquoi du comment j'obtenais parfois des résultats ambigus avec mes entiers. Par exemple une somme de deux entiers positifs qui donnait un résultat négatif...
Ceci est en fait du à ce qu'on appelle "Integer Overflow" ou débordement d'entier.
Tout d'abord essayons de comprendre comment tout ceci fonctionne.

Les Entiers



Tout d'abord nous savons tous qu'il existe différents types de données (BYTE, WORD, DWORD, ...). Prenons quelques exemples de type de données :

-Le type BYTE/SBYTE (en C c'est notre cher unsigned/signed char) : il est codé sur 1 octet donc 8 bits. Sa plage de valeur est donc comprise entre 0x00 et 0xFF.

-Le type WORD/SWORD (en C unsigned/signed short) : il est codé sur 2 octets donc 16 bits. Sa plage de valeur est comprise entre entre 0x0000 et 0xFFFF.

-De même le type DWORD/SDWORD (notre fameux unsigned/signed int) : codé sur 4 octets soit 32bits. Sa plage va de 0x00000000 à 0xFFFFFFFF;

De plus nous savons qu'un entier est soit positif, non-signé(unsigned), soit relatif, signé(signed). Pour les entiers positifs, il n'y a pas de complication. Pour un BYTE par exemple la plage de valeur est de 0 à 255.
Cependant voyons maintenant les entiers signés ou relatifs. Et oui ils peuvent être soit positifs soit négatifs d'où notre (petit) problème. Ce n'est en fait pas très compliqué. Prenons l'exemple d'un SBYTE (codé sur 1 octet et donc 8 bits). Il va en fait être partagé en deux parties, la première constituant le bit de poids fort et la seconde les 7 autres bits. Ce qui donne :



Si on parle binaire, les nombres compris entre 00000000 et 011111111 sont positifs et les nombres compris entre 10000000 et 11111111 sont négatifs. En hexa cela nous donne positif de 0x00 à 0x7F et négatif de 0x80 à 0xFF avec 0x80 = -128 < 0xff =" -1.">Débordement d'entier
Pour commencer qu'est-ce qu'un débordement d'entier ? Et bien c'est tout simplement lorsque un entier prend une valeur qui n'est pas comprise dans sa plage. Par exemple un BYTE ne peut contenir la valeur 395 puisque supérieure à 255. Tout de même si vous tentez l'expérience vous verrez que votre variable prend une valeur (139 pour être précis). Tiens c'est bizarre tout ça.
En fait, lorsqu'on va tenter de mettre 395 dans notre variable c'est le résultat de 395 % 255 = 139 qui va être placé dans la variable.
Il se fera de même pour les autres types de données et les entiers relatifs.
Voici un code qui illustre tout ça :



Maintenant vous allez me dire, mais que se passe-t-il d'autre lors d'un débordement d'entier, une exception n'est pas levée ? Et bien à part dans le cas d'une division non. On verras plus tard pour ce cas particulier. Il y a parcontre des changements au niveau des flags notament le Carry Flag et le Overflow Flag. Le CF lui est armé à chaque fois qu'une opération déborde de la plage de valeur du type de donnée. Pour le OF c'est un peu plus compliqué. Il est mis à un lorsque le résultat d'une opération est de signe logiquement contraire à celui qu'il aurait du être, ceci étant du à un int overflow. Quelques exemples pour illustrer ceci :

-L'addition de deux nombres positifs qui donne un nombre négatif ou inversement. Ce qui est intéressant c'est si l'entier est signé ou non-signé le flag OF prend les mêmes valeurs. Voyez le code si dessous vous comprendrez peutêtre mieux:




Pour le premier résultat il n'y a pas de problème, on constate bien qu'il est négatif à cause de l'int overflow.
Pour le second c'est un peu plus surprenant. Le résultat 254 est positif cependant si on le converti en hexa 0xFE ou en binaire 11111110 on voit bien que si c'était un entier signé il serait négatif. Et bien c'est ce qui est pris en compte pour notre OF.

-La soustraction d'un nombre positif avec un nombre négatif qui donne un résultat négatif ou inversement.

-La multiplication de deux nombres positifs qui donne un résultat négatif, de même pour la division.

Tout à l'heure je parlais de l'instruction DIV mais aussi IDIV qui génèrent une exception lorsque le résultat ou quotient est trop grand. Voici un morceau de code qui génère l'exception :

MOV AX, 1000h
MOV BL, 10h
DIV BL

Et on obtient un joli message d'erreur:




A noter que l'exception générée est Integer Overflow, on s'en serait douté. Cette exception correspond à l'interruption 04h et comme nous le dis le manuel intel le handler de cette interruption ne va pas modifier grand chose au programme et celui-ci va planter. On nous dis aussi que l'instruction INTO (INTerruption on Overflow) génère l'exception Integer Overflow lorsque le OF est à 1. On va donc pouvoir « catcher » ou détecter une partie des Int Overflow là où on pense qu'ils peuvent avoir lieu. Je dis une partie en effet car l'OF n'est pas set à chaque fois comme dis précédemment. Il suffit de mettre un INTO après notre calcul puis de poser un handler et le tour est joué. Voici ce que ça donne :




Et voilà on arrive à détecter un Int overflow qui agit sur l'OF avant qu'il ne puisse faire mal.
Pour ce qui est des débordements d'entiers qui agissent seulement sur le CF il faut soit vérifier son état soit utiliser une instruction comme ADC (ADd with Carry)par exemple qui ajoute à l'opérande destination l'oprérande source et le contenu de CF.

C'est à peu près tout ce que je voulais expliquer. Je vous laisse donc avec un petit divertissement qui a fais peur à Overclok :p : MiniCrackme :)

Les codes et binaires sont disponibles ici.

Voilà à bientôt.