9
votes

Provoquant une erreur de dépassement de division (X86)

J'ai quelques questions sur Diviser des erreurs de dépassement sur l'architecture X86 ou X86_64. Dernièrement, j'ai lu sur les débordements entier. Habituellement, lorsqu'une opération arithmétique entraîne un débordement entier, le bit de transport ou le débit de débordement dans le registre des drapeaux est défini. Mais apparemment, selon Cet article , déborder des opérations de division Don ' t Définissez le débit de débordement, mais plutôt déclencher une exception matérielle, semblable à la division de zéro.

Maintenant, les débordements entier résultant de la division sont beaucoup plus rares que de dire la multiplication. Il n'y a que quelques façons de déclencher un débordement de la division. Une solution serait de faire quelque chose comme: xxx

Dans ce cas, en raison de la représentation du complément des deux entiers signés, vous ne pouvez pas représenter positif 32768 dans un 16 Bit Integer, de sorte que l'opération de division déborde, entraînant la valeur erronée de -32768.

Quelques questions:

1) contrairement à ce que cet article dit, ce qui précède n'a pas causé une exception matérielle. J'utilise une machine X86_64 exécutant Linux et lorsque je divisez par zéro, le programme se termine par une exception de point flottante . Mais lorsque je provoque un débordement de la division, le programme continue comme d'habitude, ignorant silencieusement le quotient erroné. Alors, pourquoi cela ne provoque-t-il pas une exception matérielle?

2) Pourquoi les erreurs de division sont-elles traitées si sévèrement par le matériel, par opposition à d'autres débordements arithmétiques? Pourquoi une dépassement de multiplication (qui est beaucoup plus susceptible de se produire accidentellement) soit silencieusement ignorée par le matériel, mais un débordement de la division est censé déclencher une interruption fatale?

== ========= edit ==============

D'accord, merci à tout le monde pour les réponses. J'ai eu des réponses en disant essentiellement que la division entière de 16 bits ci-dessus ne devrait pas causer de faute matérielle car le quotient est toujours inférieur à la taille du registre. Je ne comprends pas ça. Dans ce cas, le registre stockant le quotient est de 16 bits - qui est trop petit pour stocker signé positif 32768. Alors pourquoi une exception matérielle n'est-elle pas soulevée?

Ok, Faisons-le directement dans l'assemblage en ligne GCC et voyons ce qui se passe: xxx

ceci simplifie simplement une valeur erronée: -32768 . Toujours pas d'exception matérielle, même si le registre stocke le quotient (AX) est trop petit pour s'adapter au quotient. Donc, je ne comprends pas pourquoi aucune faute matérielle n'est élevée ici.


4 commentaires

Vous parlez de comportement spécifique à un quincaillerie, mais vous le testez avec Code C. Cela n'a pas de sens. Pourquoi essayez-vous d'appliquer une spécification d'instructions de rassemblement à l'opérateur de division de langage C? Quelque chose vous fait penser que votre division C génère réellement l'instruction de division dont vous avez citée la spécification? Avez-vous vérifié que c'est le cas?


@Andreyt, d'accord bon point - j'ai mis à jour la question à essayer cela directement dans la langue d'assemblage


Vous avez représenté -32768 dans le dx: hache paire de manière incorrecte. En fait, vous divisez +32768 par -1 , c'est pourquoi obtenir -32768 . Voir ma réponse.


Sur une implémentation avec int 32 bits, votre exemple n'entraîne pas un débordement de division . Il en résulte un int parfaitement représentable, 32768, qui est ensuite converti en int16_t de manière définie par la mise en œuvre lorsque vous effectuez la mission. (Astuce: promotions par défaut de recherche)


7 Réponses :


2
votes

Lorsque vous obtenez un débordement entier avec le complément d'entier 2 Ajouter / Soustrayer / Multipliez, vous avez toujours un résultat valide - il manque simplement quelques bits d'ordre élevé. Ce comportement est souvent utile, il ne serait donc pas approprié de générer une exception pour cela.

Avec la division entière, cependant, le résultat d'une fracture par zéro est inutile (car, contrairement à un point flottant, les entiers complémentaires de 2 n'ont aucune représentation INF).


0 commentaires

1
votes

Contrairement à ce que cet article dit, ce qui précède n'a pas provoqué une exception matérielle

L'article n'a pas dit cela. Est dit

... Ils génèrent une erreur de division si l'opérande source (diviseur) est zéro ou si le quotient est trop grand pour le registre désigné

La taille du registre est définitivement supérieure à 16 bits (32 || 64)


6 commentaires

Mais dans ce cas, le registre stocker le quotient n'est que de 16 bits, alors pourquoi une exception matérielle n'est-elle pas soulevée? Voir ma question modifiée pour plus de précision.


@ Channel72, le type de stockage du quotient n'est que de 16 bits. Le registre lui-même est de 32 bits.


@MSN: En X86, la taille du registre est variable. AX est un registre 16 bits même sur des machines de 64 bits, et c'est le seul moyen d'expliquer comment le résultat est tronqué ici.


@Msn, je suis confus. Le registre AX stocke le quotient et le registre AX est 16 bits


@Potatoswatter, Ah, eh bien, l'assemblée dans la poste éditée est fausse (voir le commentaire de Andreyt) mais je doute sincèrement que GCC utiliserait des opérations de 16 bits pour faire une fracture INT16_T. Et la norme ne dicte pas la manière dont l'opération de division est réellement mise en œuvre, seuls ses résultats. Quoi qu'il en soit, vous pouvez toujours obtenir le débordement entier pour la division, mais vous ne devriez pas essayer avec des types plus petits que int (puisque vous ne pouvez le faire qu'avec int_min / -1 de toute façon).


J'ai essayé le code C et génère en fait idiodi eax, ecx dans Visual Studio ...




17
votes

Dans les opérations arithmétiques du langage C ne sont jamais effectuées dans les types plus petits que int code>. Chaque fois que vous essayez d'arithmétiques sur des opérandes plus petits, ils sont d'abord soumis à des promotions intégrées qui les convertissent en int code>. Si sur votre plate-forme int code> est, disons, 32 bits larges, alors il n'y a aucun moyen de forcer un programme C pour effectuer une division 16 bits. Le compilateur générera à la place de la division 32 bits. C'est probablement pourquoi votre expérience C ne produit pas le débordement attendu sur la division. Si votre plate-forme a en effet 32 ​​bits int code>, votre meilleur pari serait d'essayer la même chose avec des opérandes 32 bits (c.-à-d. Diviser int_min code> par - 1 code>). Je suis presque sûr que vous pourrez éventuellement reproduire l'exception de trop-plein, même en code C.


dans votre code d'assemblage que vous utilisez une division 16 bits, car vous avez spécifié bx code> comme opérande pour idiovis code>. La division 16 bits sur X86 divise le dividende 32 bits stocké dans dx: hache code> paire par le idiovis code> opérande. C'est ce que vous faites dans votre code. La paire DX: AX CODE> est interprétée comme un enregistrement composite 32 bits, ce qui signifie que le bit de panneau de cette paire est maintenant en réalité le bit de commande le plus élevé de dx code>. Le bit de commande le plus élevé de hache code> n'est plus un piqué de signe. P>

et ce que vous avez fait avec dx code>? Vous l'avez simplement effacé. Vous le définissez à 0. mais avec dx code> défini sur 0, votre dividende est interprété comme positif em>! Du point de vue de la machine, une telle paire dx: hache code> représente une valeur em> em> +32768 code>. C'est à dire. Dans votre expérience de montage en langage, vous divisez +32768 code> par -1 code>. Et le résultat est -32768 code>, comme cela devrait être. Rien d'inhabituel ici. P>

Si vous souhaitez représenter -32768 code> dans la paire DX: AX CODE>, vous devez la signer, c'est-à-dire que vous avez Pour remplir dx code> avec un motif de bits tout-un, au lieu de zéros. Au lieu de faire XOR DX, DX CODE> Vous devez avoir initialisé AX code> avec votre -32768 code>, puis effectué CWD code>. Cela aurait Signé-étendu Ax code> dans dx code>. P>

Par exemple, dans mon expérience (pas GCC) Ce code P>

__asm  {
  mov AX, -32768
  cwd
  mov BX, -1
  idiv BX
}


2 commentaires

D'accord, je vois. Merci. Cela a déclenché la défaillance du matériel attendu.


Belle prise sur l'exemple de langue de montage!



0
votes
  1. La raison Votre exemple n'a pas généré une exception matérielle est due aux règles de promotion entier de C. Les opérandes plus petites que int sont automatiquement favorisées à intens avant que l'opération soit effectuée.

  2. Quoi que Différents types de débordements sont traités différemment, considérons qu'au niveau de la machine X86, il n'y a vraiment aucune telle chose qu'un débordement de multiplication. Lorsque vous multipliez la hache par un autre registre, le résultat va dans la paire DX: AX, donc il y a toujours de la place pour le résultat, et donc aucune occasion de signaler une exception de trop-plein. Toutefois, en C et dans d'autres langues, le produit de deux Ints est censé correspondre à un int , donc il y a une telle chose que le débordement du niveau C. Le X86 est parfois défini sur (drapeau de débordement) sur mul s, mais cela signifie simplement que la partie haute du résultat n'est pas nulle.


1 commentaires

Les instructions multiples multiples 80386 utilisent un registre de destination unique qui a la même taille que la source. Ils fixent des drapeaux en cas de débordement, quel code peut ignorer ou non ignorer.



0
votes

sur une implémentation avec 32 bits int , votre exemple n'entraîne pas de débordement de division . Il en résulte un int parfaitement représentable, 32768, qui est ensuite converti en int16_t de manière définie par la mise en œuvre lorsque vous effectuez la mission. Ceci est dû aux promotions par défaut spécifiées par le langage C et, par conséquent, une implémentation qui soulevait une exception ici serait non conforme.

Si vous souhaitez essayer de causer une exception (qui peut encore ou non On se produit effectivement, c'est à la mise en œuvre), essayez: xxx

Vous devrez peut-être faire quelques astuces pour empêcher le compilateur de l'optimiser au moment de la compilation.


0 commentaires

0
votes

Je vais conjecturer cela sur certains anciens ordinateurs, essayant de diviser par zéro entraînerait des problèmes graves (par exemple, placer le matériel dans un cycle sans fin d'essayer de soustraire suffisamment que le reste serait inférieur au dividende jusqu'à ce qu'un opérateur soit arrivé. Pour réparer les choses), et cela a commencé une tradition de diviser les débordements considérés comme des défauts plus graves que des débordements entier.

À partir d'un point de vue de la programmation, il n'y a aucune raison qu'un débordement de division inattendu devrait être plus ou moins grave qu'un débordement inattendu intégré (signé ou non signé). Compte tenu du coût de la division, le coût marginal de la vérification d'un drapeau de débordement serait assez léger. La tradition est la seule raison pour laquelle je puisse voir pour avoir un piège au matériel.


0 commentaires