`testl` eax gegen eax?


118

Ich versuche eine Versammlung zu verstehen.

Die Montage wie folgt interessiert mich an der testlLinie:

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

Ich versuche diesen Punkt testlzwischen %eaxund zu verstehen %eax? Ich denke, die Einzelheiten dieses Codes sind nicht wichtig. Ich versuche nur, den Test mit sich selbst zu verstehen. Wäre der Wert nicht immer wahr?

Antworten:


91

Es wird geprüft, ob eax0 oder höher oder niedriger ist. In diesem Fall wird der Sprung ausgeführt, wenn eax0 ist.


2
Ich habe eine Bearbeitung vorgenommen, um diese beliebte Antwort in eine bessere kanonische Antwort auf "Worum geht es bei diesem TEST und wie unterscheidet es sich von CMP" zu verwandeln, was irgendwie impliziert ist. Siehe meine eigene Antwort weiter unten für Kommentare zur semantischen Bedeutung von JE und JZ. Bitte überprüfen Sie meine Bearbeitung, da sie ziemlich umfangreich ist und immer noch Ihre Antwort ist.
Peter Cordes

@PeterCordes Ich schätze die Absicht, aber ich werde Ihre Bearbeitung zurücksetzen. 1. Ihre "Stimme" unterscheidet sich sehr von meiner und liest sich im Moment viel mehr wie Ihre Antwort als meine. 2. Problematischer ist die kühne Behauptung, dass die Flags zwischen testund genau gleich herauskommen cmp. Ja, ich verstehe, dass dies Ihre Überzeugung ist, basierend auf Ihren Kommentaren zu Cody. Es ist jedoch eine andere Sache, es in meinen Beitrag aufzunehmen. Es ist keine Behauptung, zu der ich bereit bin, einfach weil ich nicht weiß, ob sie in allen Fällen identisch ist.
Chris Jester-Young

1
@PeterCordes Wenn ich etwas Freizeit finde, möchte ich diese Antwort kanonischer ausarbeiten. Ich würde es jedoch so schreiben, wie ich es schreibe, und ich bin ziemlich genau darüber, wie ich Dinge schreibe. :-) Zum Beispiel würde ich schreiben je, jz, cmp, und test, und nicht JE, JZ, CMP oder TEST. Ich bin so wählerisch.
Chris Jester-Young

1
Ich habe nicht versucht, meine eigene Antwort zu verbessern. Ich habe tatsächlich vergessen, dass ich diese Frage selbst beantwortet habe, als ich diese Bearbeitung vorgenommen habe, und habe es erst danach bemerkt. Ich habe mir das nur angesehen, nachdem jemand darauf gestoßen war, und was als kleiner Schnitt begann, ist zu viel geworden. Keine Beleidigung, dass Sie es zurückrollen wollten; Es war nur ein Vorschlag und es liest sich definitiv wie meine Arbeit, nicht wie deine. Ich werde etwas von dem, was ich geschrieben habe, in meine eigene Antwort aufnehmen.
Peter Cordes

2
Wow, nachdem ich meine Antwort auf diese Frage so bearbeitet hatte, dass sie das enthielt, was ich zu Ihrer hinzugefügt hatte, stellte ich fest, dass ich das meiste, was ich im Juni geschrieben hatte, fast genau dupliziert hatte. Hoppla! Ich habe es mit mehr Überlegungen aktualisiert, um meine Behauptung zu untermauern test a,aund cmp $0,aFlags identisch zu setzen. Vielen Dank für den Hinweis, dass dies eine nicht triviale Behauptung ist. re: TEST vs.: Vor testkurzem habe ich angefangen, All-Caps wie Intels Handbücher zu verwenden. Aber wenn ich über AT & T-Mnemonik im Vergleich zu Intel-Mnemonik spreche, verwende ich testbStil für AT & T. IDK, wenn dies die Lesbarkeit verbessert.
Peter Cordes

90

Die Bedeutung von testist, die Argumente zusammen UND zu verknüpfen und das Ergebnis auf Null zu überprüfen. Dieser Code testet also, ob EAX Null ist oder nicht. jewird springen, wenn Null.

Übrigens erzeugt dies einen kleineren Befehl als cmp eax, 0der Grund, warum Compiler dies im Allgemeinen auf diese Weise tun.


34

Der Testbefehl führt eine logische UND-Verknüpfung zwischen den Operanden durch, schreibt das Ergebnis jedoch nicht zurück in ein Register. Nur die Flags werden aktualisiert.

In Ihrem Beispiel setzt der Test eax, eax das Null-Flag, wenn eax Null ist, das Vorzeichen-Flag, wenn das höchste Bit gesetzt ist, und einige andere Flags.

Der Befehl Jump if Equal (je) springt, wenn das Null-Flag gesetzt ist.

Sie können den Code in einen besser lesbaren Code wie folgt übersetzen:

cmp eax, 0
je  somewhere

Das hat die gleiche Funktionalität, erfordert aber einige Bytes mehr Code-Speicherplatz. Aus diesem Grund hat der Compiler einen Test anstelle eines Vergleichs ausgegeben.


3
Tatsächlich funktioniert cmp dort möglicherweise nicht. Das heißt, es funktioniert für den dargestellten speziellen Fall, aber cmp wirkt sich anders auf Flags aus als Test, da es ein internes Sub anstelle von und ist. Etwas zu beachten.
Cody Brocious

4
für einen Test gegen Null ist es vollkommen gültig.
Nils Pipenbrinck

3
Aber Sie wissen nicht, was später noch auf die Flaggen schaut. Die Auswirkungen auf Flags sind sehr unterschiedlich, daher kann dies ein Problem sein und ist es sehr häufig.
Cody Brocious

2
Nein, die einzigen Flags, die von einer anderen / method / gesetzt werden, sind Carry und Overflow, die beide auf 0 gesetzt sind. Die / values ​​/ der anderen Flags unterscheiden sich, da cmp Sub- und Test-Verwendungen und verwendet.
Cody Brocious

2
@CodyBrocious: test eax, eaxund cmp eax, 0beide setzen alle Flags und setzen sie auf identische Werte. Beide Anweisungen setzen alle Flags "entsprechend dem Ergebnis". Das Subtrahieren 0kann niemals zu einem Übertrag oder Überlauf führen. Ihr Argument ist korrekt für alle anderen als 0, aber nicht für 0.
Peter Cordes

22

testist wie and, außer dass es nur FLAGS schreibt und beide Eingaben unverändert lässt. Mit zwei verschiedenen Eingängen ist es nützlich zu testen, ob einige Bits alle Null sind oder ob mindestens eines gesetzt ist. (z. B. test al, 3setzt ZF, wenn EAX ein Vielfaches von 4 ist (und somit beide niedrigen 2 Bits auf Null gesetzt sind).


test eax,eaxsetzt alle Flags genau die gleiche Art und Weise, cmp eax, 0würde :

  • CF und OF gelöscht (AND / TEST macht das immer; das Subtrahieren von Null erzeugt niemals einen Übertrag)
  • ZF, SF und PF gemäß dem Wert in EAX. ( a = a&a = a-0).
    (PF wird wie gewohnt nur auf die niedrigen 8 Bits eingestellt )

Mit Ausnahme des veralteten AF (Auxiliary-Carry-Flag, das von ASCII / BCD-Anweisungen verwendet wird). TEST lässt es undefiniert , aber CMP setzt es "entsprechend dem Ergebnis" . Da das Subtrahieren von Null keinen Übertrag vom 4. zum 5. Bit erzeugen kann, sollte CMP immer AF löschen.


TEST ist kleiner (kein sofortiger) und manchmal schneller (kann in mehr Fällen als CMP auf mehr CPUs zu einem Vergleichs- und Verzweigungs-UOP verschmelzen). Das macht testdie bevorzugte Redewendung zum Vergleichen eines Registers mit Null . Es ist eine Gucklochoptimierung cmp reg,0, die Sie unabhängig von der semantischen Bedeutung verwenden können.

Der einzige häufige Grund für die Verwendung von CMP mit einer unmittelbaren 0 ist, wenn Sie mit einem Speicheroperanden vergleichen möchten. Zum Beispiel, cmpb $0, (%esi)um am Ende einer Zeichenfolge im C-Stil mit impliziter Länge nach einem abschließenden Null-Byte zu suchen.


AVX512F fügt hinzukortestw k1, k2 und AVX512DQ / BW (Skylake-X, aber nicht KNL) fügt hinzu ktestb/w/d/q k1, k2, die mit AVX512-Maskenregistern (k0..k7) arbeiten, aber weiterhin reguläre FLAGS setzen, wie testdies bei Ganzzahlen ORoder ANDAnweisungen der Fall ist . (Ähnlich wie SSE4 ptestoder SSE ucomiss: Eingaben in die SIMD-Domäne und Ergebnis in ganzzahligen FLAGS.)

kortestw k1,k1ist die idiomatische Methode zum Verzweigen von / cmovcc / setcc basierend auf einem AVX512-Vergleichsergebnis, wobei SSE / AVX2 (v)pmovmskb/ps/pd+ testoder ersetzt werden cmp.


Die Verwendung von jzvs. jekann verwirrend sein.

jzund jesind buchstäblich die gleiche Anweisung , dh der gleiche Opcode im Maschinencode. Sie tun dasselbe, haben aber für den Menschen eine andere semantische Bedeutung . Disassembler (und normalerweise Asm-Ausgaben von Compilern) verwenden immer nur einen, sodass die semantische Unterscheidung verloren geht.

cmpund subsetze ZF, wenn ihre zwei Eingänge gleich sind (dh das Subtraktionsergebnis ist 0). je(Sprung wenn gleich) ist das semantisch relevante Synonym.

test %eax,%eax/ and %eax,%eaxsetzt erneut ZF, wenn das Ergebnis Null ist, aber es gibt keinen "Gleichheitstest". ZF nach dem Test sagt Ihnen nicht, ob die beiden Operanden gleich waren. Also jz(Sprung wenn Null) ist das semantisch relevante Synonym.


Ich würde in Betracht ziehen, die grundlegenden Informationen über testden bitweisen andBetrieb hinzuzufügen. Dies ist möglicherweise nicht offensichtlich für Leute, die nur das Zusammenbauen lernen (und faul sind / nicht wissen, dass sie alle 60 Sekunden die Referenzanleitung überprüfen müssen;) :)).
Ped7g

1
@ Ped7g: Fair genug, ich denke, es tut nicht weh, alles in diese Antwort zu setzen, anstatt diesen Teil den anderen Antworten zu überlassen. AVX512 hinzugefügt kortest*und ktest*während ich dabei war.
Peter Cordes

Übrigens ist dies im Grunde das Gleiche wie meine Antwort auf eine andere Version derselben Frage , aber ich habe dort mehr über die Leistung gesagt, z. B. das Vermeiden von Registerleseverzögerungen auf alten CPUs der P6-Familie wie Nehalem, indem ich das Register mit demselben Wert neu schreibe.
Peter Cordes

@PeterCordes Dies sollte die akzeptierte Antwort sein: erschöpfend und technisch. Im Gegensatz zum angenommenen Beitrag löscht dies die Neugier und den Wissensdurst. Weiter so Sir.
Programmierer

Es ist zu beachten, dass PF auf die Parität der niedrigen 8 Bits eingestellt ist, die in diesem Fall AL ist.
ecm

5

Dieser Codeausschnitt stammt aus einer Unterroutine, die einen Zeiger auf etwas erhalten hat, wahrscheinlich auf eine Struktur oder ein Objekt. Die 2. Zeile dereferenziert diesen Zeiger und ruft einen Wert von diesem Ding ab - möglicherweise selbst einen Zeiger oder nur ein Int, der als 2. Element gespeichert ist (Offset +4). Die 3. und 4. Zeile testen diesen Wert auf Null (NULL, wenn es sich um einen Zeiger handelt) und überspringen die folgenden wenigen Operationen (nicht gezeigt), wenn er Null ist.

Der Test für Null wird manchmal als Vergleich mit einem unmittelbaren wörtlichen Nullwert codiert, aber der Compiler (oder Mensch?), Der dies geschrieben hat, könnte gedacht haben, dass ein Testlop schneller laufen würde - unter Berücksichtigung aller modernen CPU-Dinge wie Pipelining und Register Umbenennung. Es ist aus der gleichen Trickkiste, die die Idee enthält, ein Register mit XOR EAX, EAX (das ich auf einem Nummernschild in Colorado gesehen habe!) Zu löschen, anstatt mit dem offensichtlichen, aber vielleicht langsameren MOV EAX, # 0 (ich verwende eine ältere Notation) ).

In asm, wie Perl, TMTOWTDI.


3

Wenn eax Null ist, wird der bedingte Sprung ausgeführt, andernfalls wird die Ausführung bei 319e9 fortgesetzt


0

In einigen Programmen können sie verwendet werden, um nach einem Pufferüberlauf zu suchen. Ganz oben auf dem zugewiesenen Platz wird eine 0 platziert. Nach der Eingabe von Daten in den Stapel wird ganz am Anfang des zugewiesenen Speicherplatzes nach der 0 gesucht, um sicherzustellen, dass der zugewiesene Speicherplatz nicht überläuft.

Es wurde in der Stack0-Übung von Exploits-Übungen verwendet, um zu überprüfen, ob es übergelaufen ist und wenn es keine Null gibt und dort eine Null angezeigt wird, wird "Erneut versuchen" angezeigt.

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

Ich verstehe nicht, was dieser spezielle Fall der Überprüfung eines Registers auf Nicht-Null zu diesen Fragen und Antworten beiträgt. Besonders wenn cmp DWORD PTR [esp+0x5c], 0/ jz 0x8048427 <main+51>wäre effizienter gewesen als eine separate MOV-Last und dann TEST. Dies ist kaum ein üblicher Anwendungsfall für die Überprüfung auf eine Null.
Peter Cordes

-4

wir könnten das jgjle sehen Wenn testl %edx,%edx. jle .L3wir leicht herausfinden könnten, dass jle passt (SF^OF)|ZF, wenn% edx Null ist, ist ZF = 1, aber wenn% edx nicht Null ist und -1 ist, nach dem Test, dem OF = 0 und dem SF = 1, also das Flag = true, das den Sprung implementiert. Tut mir leid, mein Englisch ist schlecht

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.