`testl` eax contre eax?


118

J'essaie de comprendre un assemblage.

Le montage comme suit, je suis intéressé par la testlligne:

000319df  8b4508        movl   0x08(%ebp), %eax  
000319e2  8b4004        movl   0x04(%eax), %eax  
000319e5  85c0          testl  %eax, %eax  
000319e7  7407          je     0x000319f0  

J'essaie de comprendre ce point testlentre %eaxet %eax? Je pense que les spécificités de ce code ne sont pas importantes, j'essaie simplement de comprendre le test avec lui-même - la valeur ne serait-elle pas toujours vraie?

Réponses:


91

Il teste si la valeur eaxest 0, supérieure ou inférieure. Dans ce cas, le saut est effectué si eaxvaut 0.


2
J'ai fait une modification pour transformer cette réponse populaire en une meilleure réponse canonique à "de quoi s'agit-il de TEST et en quoi est-ce différent de CMP", ce qui est en quelque sorte implicite. Voir ma propre réponse plus bas pour des commentaires sur la signification sémantique des synonymes JE et JZ. Veuillez revoir ma modification car elle est assez importante, et c'est toujours votre réponse.
Peter Cordes

@PeterCordes J'apprécie l'intention, mais je vais revenir sur votre modification. 1. Votre «voix» est très différente de la mienne, et en ce moment elle ressemble beaucoup plus à votre réponse qu'à la mienne. 2. Plus problématique est l'affirmation audacieuse que les drapeaux sortent exactement de la même manière entre testet cmp. Oui, je comprends que c'est votre opinion sur la base de vos commentaires à Cody. Cependant, le mettre dans mon message est une autre affaire; ce n'est pas une affirmation que je suis prêt à maintenir, simplement parce que je ne sais pas si c'est identique dans tous les cas.
Chris Jester-Young

1
@PeterCordes Si je trouve du temps libre, je veux étoffer cette réponse pour qu'elle soit plus canonique. Je l'écrirais comme je l'écris, cependant, et je suis assez particulier sur la façon dont j'écris les choses. :-) Par exemple, j'écrire je, jz, cmpet test, et non JE, JZ, CMP ou TEST. Je suis difficile comme ça.
Chris Jester-Young

1
Je n'essayais pas de renforcer ma propre réponse. J'ai en fait oublié que j'avais répondu à cette question moi-même lorsque j'ai fait cette modification, et je ne l'ai remarqué qu'après. Je viens de regarder cela après que quelqu'un l'ait heurté, et ce qui a commencé comme un petit montage a fait boule de neige en trop. Aucune offense prise que vous vouliez le faire reculer; c'était juste une suggestion et cela ressemble vraiment à mon travail, pas au vôtre. Je vais prendre un peu de ce que j'ai écrit et le mettre dans ma propre réponse.
Peter Cordes

2
Wow, après avoir modifié ma réponse à cette question pour inclure ce que j'ai ajouté à la vôtre, j'ai réalisé que j'avais presque exactement reproduit la plupart de ce que j'avais écrit en juin. Oups! Je l'ai mis à jour avec plus de raisonnement pour étayer mon affirmation test a,aet cmp $0,adéfinir des indicateurs de manière identique; merci de souligner que c'est une affirmation non triviale. re: TEST vs test.: récemment, j'ai commencé à utiliser des majuscules comme les manuels d'Intel. Mais quand je parle des mnémoniques AT&T et des mnémoniques Intel, j'utilise le testbstyle pour AT&T. IDK si cela aide à la lisibilité.
Peter Cordes

90

La signification de testest de ET les arguments ensemble, et vérifiez le résultat pour zéro. Donc, ce code teste si EAX est égal à zéro ou non. jesautera si zéro.

BTW, cela génère une instruction plus petite que cmp eax, 0ce qui est la raison pour laquelle les compilateurs le feront généralement de cette façon.


34

L'instruction de test effectue une opération ET logique entre les opérandes mais ne réécrit pas le résultat dans un registre. Seuls les drapeaux sont mis à jour.

Dans votre exemple, le test eax, eax définira l'indicateur zéro si eax vaut zéro, l'indicateur de signe si le bit le plus élevé est défini et quelques autres indicateurs également.

L'instruction Jump if Equal (je) saute si l'indicateur zéro est défini.

Vous pouvez traduire le code en un code plus lisible comme celui-ci:

cmp eax, 0
je  somewhere

Cela a la même fonctionnalité mais nécessite quelques octets de plus d'espace de code. C'est la raison pour laquelle le compilateur a émis un test au lieu d'une comparaison.


3
En fait, cmp pourrait ne pas fonctionner là-bas. Autrement dit, cela fonctionne pour le cas spécifique présenté, mais cmp affecte les indicateurs différemment de test, car il s'agit d'un sous-système interne au lieu de et. Quelque chose à garder à l'esprit.
Cody Brocious

4
pour un test contre zéro c'est parfaitement valable.
Nils Pipenbrinck

3
Mais vous ne savez pas quoi d'autre regarde les drapeaux plus tard. Les effets sur les drapeaux sont très différents, donc cela peut être un problème et très fréquemment.
Cody Brocious

2
Non, les seuls indicateurs définis par un / method / différent sont carry et overflow, qui sont tous deux définis sur 0. Les / values ​​/ des autres indicateurs seront différents car cmp utilise sub et test utilise et.
Cody Brocious

2
@CodyBrocious: test eax, eaxet cmp eax, 0tous les deux définissent tous les indicateurs et les définissent sur des valeurs identiques. Les deux instructions définissent tous les indicateurs "en fonction du résultat". La soustraction 0ne peut jamais produire de report ou de débordement. Votre argument est correct pour tout immédiat autre que 0, mais pas pour 0.
Peter Cordes

22

testest comme and, sauf qu'il n'écrit que des FLAGS, laissant ses deux entrées inchangées. Avec deux entrées différentes , il est utile pour tester si certains bits sont tous à zéro ou si au moins un est défini. (par exemple, test al, 3définit ZF si EAX est un multiple de 4 (et a donc tous les deux ses deux bits inférieurs à zéro).


test eax,eaxdéfinit tous les drapeaux de la même manière que le cmp eax, 0ferait :

  • CF et OF effacés (AND / TEST fait toujours cela; soustraire zéro ne produit jamais de report)
  • ZF, SF et PF selon la valeur en EAX. ( a = a&a = a-0).
    (PF comme d'habitude n'est défini qu'en fonction des 8 bits bas )

Sauf pour l'AF obsolète (indicateur de report auxiliaire, utilisé par les instructions ASCII / BCD). TEST le laisse indéfini , mais CMP le définit "en fonction du résultat" . Étant donné que la soustraction de zéro ne peut pas produire de report du 4ème au 5ème bit, CMP doit toujours effacer AF.


TEST est plus petit (pas immédiat) et parfois plus rapide (peut macro-fusionner en un uop de comparaison et de branchement sur plus de processeurs dans plus de cas que CMP). Cela fait de testl'idiome préféré pour comparer un registre à zéro . C'est une optimisation de judas cmp reg,0que vous pouvez utiliser quelle que soit la signification sémantique.

La seule raison courante d'utiliser CMP avec un 0 immédiat est lorsque vous souhaitez effectuer une comparaison avec un opérande mémoire. Par exemple, cmpb $0, (%esi)pour rechercher un octet de fin de zéro à la fin d'une chaîne de style C de longueur implicite.


AVX512F ajoutekortestw k1, k2 et AVX512DQ / BW (Skylake-X mais pas KNL) ajoute ktestb/w/d/q k1, k2, qui fonctionnent sur les registres de masque AVX512 (k0..k7) mais définissent toujours des FLAGs réguliers comme le testfont, de la même manière que les entiers ORou les ANDinstructions. (Un peu comme SSE4 ptestou SSE ucomiss: les entrées dans le domaine SIMD et aboutissent à des FLAGS entiers.)

kortestw k1,k1est la manière idiomatique de créer un branchement / cmovcc / setcc basé sur un résultat de comparaison AVX512, en remplaçant SSE / AVX2 (v)pmovmskb/ps/pd+ testou cmp.


L'utilisation de jzvs. jepeut être déroutante.

jzet jesont littéralement la même instruction , c'est-à-dire le même opcode dans le code machine. Ils font la même chose, mais ont une signification sémantique différente pour les humains . Les désassembleurs (et généralement la sortie asm des compilateurs) n'en utiliseront qu'un seul, donc la distinction sémantique est perdue.

cmpet subdéfinissez ZF lorsque leurs deux entrées sont égales (c'est-à-dire que le résultat de la soustraction est 0). je(jump if equal) est le synonyme sémantiquement pertinent.

test %eax,%eax/ and %eax,%eaxdéfinit à nouveau ZF lorsque le résultat est nul, mais il n'y a pas de test "d'égalité". ZF après le test ne vous dit pas si les deux opérandes étaient égaux. Donc jz(sauter si zéro) est le synonyme sémantiquement pertinent.


J'envisagerais d'ajouter les informations de base sur l' opération au niveau du testbit and, ce n'est peut-être pas évident pour les gens qui apprennent simplement l'assemblage (et qui sont paresseux / inconscients pour vérifier le guide de référence des instructions toutes les 60 secondes;) :)).
Ped7g

1
@ Ped7g: c'est juste, je suppose que ça ne fait pas de mal de tout mettre dans cette réponse, au lieu de laisser cette partie aux autres réponses. Ajout de AVX512 kortest*et ktest*pendant que j'y étais.
Peter Cordes

BTW, c'est fondamentalement la même chose que ma réponse à une autre version de la même question , mais j'en ai dit plus sur les performances, par exemple en évitant éventuellement les blocages de lecture de registre sur les anciens processeurs de la famille P6 comme Nehalem en réécrivant le registre avec la même valeur.
Peter Cordes

@PeterCordes Cela devrait être la réponse acceptée: exhaustive et technique. Contrairement au post accepté, cela étouffe la curiosité et la soif de savoir. Continuez comme ça monsieur.
programmeursn

Il convient de noter que PF est mis à la parité des 8 bits bas, qui dans ce cas est AL.
ecm

5

Cet extrait de code provient d'une sous-routine qui a reçu un pointeur vers quelque chose, probablement une structure ou un objet. La 2ème ligne déréférence ce pointeur, récupérant une valeur de cette chose - peut-être lui-même un pointeur ou peut-être juste un int, stocké comme son 2ème membre (offset +4). Les 3e et 4e lignes testent cette valeur pour zéro (NULL s'il s'agit d'un pointeur) et sautent les quelques opérations suivantes (non illustrées) s'il est égal à zéro.

Le test pour zéro est parfois codé comme une comparaison avec une valeur zéro littérale immédiate, mais le compilateur (ou l'humain?) Qui a écrit cela aurait pu penser qu'une opération de test fonctionnerait plus rapidement - en tenant compte de tous les éléments du processeur modernes tels que le pipelining et le registre renommer. C'est à partir du même sac d'astuces qui contient l'idée d'effacer un registre avec XOR EAX, EAX (que j'ai vu sur la plaque d'immatriculation de quelqu'un dans le Colorado!) Plutôt que l'évident mais peut-être plus lent MOV EAX, # 0 (j'utilise une notation plus ancienne ).

Dans asm, comme perl, TMTOWTDI.


3

Si eax vaut zéro, il effectuera le saut conditionnel, sinon il continuera l'exécution à 319e9


0

Dans certains programmes, ils peuvent être utilisés pour vérifier un dépassement de mémoire tampon. Tout en haut de l'espace alloué, un 0 est placé. Après avoir entré des données dans la pile, il recherche le 0 au tout début de l'espace alloué pour s'assurer que l'espace alloué n'est pas débordé.

Il a été utilisé dans l'exercice stack0 des exploits-exercices pour vérifier s'il avait débordé et s'il n'y en avait pas et qu'il y avait un zéro, il afficherait "Try again"

0x080483f4 <main+0>:    push   ebp
0x080483f5 <main+1>:    mov    ebp,esp
0x080483f7 <main+3>:    and    esp,0xfffffff0
0x080483fa <main+6>:    sub    esp,0x60                     
0x080483fd <main+9>:    mov    DWORD PTR [esp+0x5c],0x0 ;puts a zero on stack
0x08048405 <main+17>:   lea    eax,[esp+0x1c]
0x08048409 <main+21>:   mov    DWORD PTR [esp],eax
0x0804840c <main+24>:   call   0x804830c <gets@plt>
0x08048411 <main+29>:   mov    eax,DWORD PTR [esp+0x5c] 
0x08048415 <main+33>:   test   eax,eax                  ; checks if its zero
0x08048417 <main+35>:   je     0x8048427 <main+51>
0x08048419 <main+37>:   mov    DWORD PTR [esp],0x8048500 
0x08048420 <main+44>:   call   0x804832c <puts@plt>
0x08048425 <main+49>:   jmp    0x8048433 <main+63>
0x08048427 <main+51>:   mov    DWORD PTR [esp],0x8048529
0x0804842e <main+58>:   call   0x804832c <puts@plt>
0x08048433 <main+63>:   leave
0x08048434 <main+64>:   ret

Je ne vois pas ce que ce cas spécifique de vérification d'un registre non nul ajoute à ce Q&R. Surtout quand cmp DWORD PTR [esp+0x5c], 0/ jz 0x8048427 <main+51>aurait été plus efficace qu'une charge MOV séparée et ensuite TEST. Ce n'est guère un cas d'utilisation courant pour rechercher un zéro.
Peter Cordes

-4

nous pourrions voir le jgjle Si testl %edx,%edx. jle .L3nous pouvions trouver facilement jle est suit (SF^OF)|ZF, si% edx est nul, ZF = 1, mais si% edx n'est pas nul et vaut -1, après le testl, l'OF = 0, et le SF = 1, donc le drapeau = true, qui implémente le saut. Désolé, mon anglais est médiocre

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.