`testl` eax przeciwko eax?

118

Próbuję zrozumieć jakiś montaż.

Montaż jak następuje, interesuje mnie testllinia:

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

Próbuję zrozumieć ten punkt testlpomiędzy %eaxi %eax? Myślę, że specyfika tego, co ten kod nie jest ważna, po prostu próbuję zrozumieć test samym sobą - czy wartość nie zawsze byłaby prawdziwa?

maxpenguin
źródło

Odpowiedzi:

91

Sprawdza, czy eaxwynosi 0, więcej lub mniej. W tym przypadku skok jest wykonywany, jeśli eaxwynosi 0.

Chris Jester-Young
źródło
2
Zrobiłem edycję, aby przekształcić tę popularną odpowiedź w lepszą kanoniczną odpowiedź na pytanie „o co chodzi w tym TESTIE i czym różni się od CMP”, co jest w pewnym sensie implikowane. Zobacz moją własną odpowiedź poniżej, aby uzyskać komentarze dotyczące semantycznego znaczenia synonimów JE i JZ. Przejrzyj moją zmianę, ponieważ jest dość poważna i nadal jest Twoją odpowiedzią.
Peter Cordes
@PeterCordes Doceniam ten zamiar, ale zamierzam cofnąć twoją edycję. 1. Twój „głos” bardzo różni się od mojego, a teraz brzmi bardziej jak twoja odpowiedź niż moja. 2. Bardziej problematyczne jest śmiałe stwierdzenie, że flagi pojawiają się dokładnie w ten sam sposób między testa cmp. Tak, rozumiem, że to twoja wiara oparta na komentarzach do Cody'ego. Jednak umieszczenie tego w moim poście to inna sprawa; nie jest to stwierdzenie, na które jestem gotów stać, po prostu dlatego , że nie wiem, czy jest identyczne we wszystkich przypadkach.
Chris Jester-Young
1
@PeterCordes Jeśli znajdę trochę wolnego czasu, chcę rozwinąć tę odpowiedź, aby była bardziej kanoniczna. Napisałbym to tak, jak piszę, i jestem bardzo szczególny, jeśli chodzi o to, jak piszę. :-) Na przykład, chciałbym napisać je, jz, cmp, i test, a nie JE, JZ, CMP lub TEST. Jestem taki wybredny.
Chris Jester-Young,
1
Nie próbowałem wzmocnić własnej odpowiedzi. Właściwie zapomniałem, że sam odpowiedziałem na to pytanie, kiedy robiłem tę edycję, i zauważyłem to dopiero później. Po prostu spojrzałem na to po tym, jak ktoś na to wpadł, i to, co zaczęło się jako mała zmiana, przerodziło się w zbyt wiele. Bez urazy, że chciałeś go cofnąć; to była tylko sugestia i zdecydowanie brzmi jak moja praca, a nie twoja. Wezmę część tego, co napisałem i umieszczę to w mojej własnej odpowiedzi.
Peter Cordes
2
Wow, po zredagowaniu mojej odpowiedzi na to pytanie, tak aby zawierała to, co dodałem do twojego, zdałem sobie sprawę, że prawie dokładnie powieliłem większość tego, co napisałem w czerwcu. Ups! Zaktualizowałem go, dodając więcej powodów, aby poprzeć moje twierdzenie test a,ai cmp $0,austawić flagi identycznie; dzięki za wskazanie, że to nietrywialne twierdzenie. re: TEST vs test.: ostatnio zacząłem używać wielkich liter, takich jak podręczniki Intela. Ale kiedy mówię o mnemonikach AT&T i mnemonikach Intela, używam testbstylu dla AT&T. IDK, jeśli to pomaga w czytelności.
Peter Cordes,
90

Znaczenie testto ORAZ argumenty razem i sprawdzić wynik na zero. Więc ten kod sprawdza, czy EAX ma wartość zero, czy nie. jeprzeskoczy jeśli zero.

Przy okazji, generuje to mniejszą instrukcję, niż cmp eax, 0jest to powód, dla którego kompilatory zwykle robią to w ten sposób.

phuclv
źródło
34

Instrukcja testowa wykonuje operację logiczną AND pomiędzy operandami, ale nie zapisuje wyniku z powrotem do rejestru. Aktualizowane są tylko flagi.

W twoim przykładzie test eax, eax ustawi flagę zero, jeśli eax jest równe zero, flagę znaku, jeśli ustawiono najwyższy bit, a także kilka innych flag.

Instrukcja Jump if Equal (je) przeskakuje, jeśli ustawiona jest flaga zero.

Możesz przetłumaczyć kod na bardziej czytelny kod w następujący sposób:

cmp eax, 0
je  somewhere

Ma tę samą funkcjonalność, ale wymaga o kilka bajtów więcej miejsca na kod. To jest powód, dla którego kompilator wyemitował test zamiast porównania.

Nils Pipenbrinck
źródło
3
Właściwie cmp może tam nie działać. Oznacza to, że działa w konkretnym przedstawionym przypadku, ale cmp wpływa na flagi inaczej niż test, ponieważ jest wewnętrzną podrzędną zamiast i. Coś, o czym warto pamiętać.
Cody Brocious,
4
dla testu względem zera jest to całkowicie poprawne.
Nils Pipenbrinck
3
Ale nie wiesz, co jeszcze patrzy na flagi później. Efekty na flagach są bardzo różne, więc może to stanowić problem i bardzo często tak jest.
Cody Brocious,
2
Nie, jedynymi flagami ustawianymi przez inną / metodę / są carry i overflow, z których obie mają wartość 0. / values ​​/ innych flag będą się różnić, ponieważ cmp używa sub i testowych zastosowań i.
Cody Brocious,
2
@CodyBrocious: test eax, eaxi cmp eax, 0obaj ustawiają wszystkie flagi i ustawiają je na identyczne wartości. Obie instrukcje ustawiają wszystkie flagi „zgodnie z wynikiem”. Odejmowanie 0nigdy nie może spowodować przeniesienia ani przepełnienia. Twój argument jest poprawny dla każdego natychmiastowego innego niż 0, ale nie dla 0.
Peter Cordes,
22

testjest jak and, z wyjątkiem tego, że pisze tylko FLAGI, pozostawiając oba wejścia niezmienione. Dzięki dwóm różnym wejściom przydaje się do testowania, czy niektóre bity mają wartość zero lub czy przynajmniej jeden jest ustawiony. (np. test al, 3ustawia ZF, jeśli EAX jest wielokrotnością 4 (a zatem ma wyzerowane oba niskie 2 bity).


test eax,eaxustawia wszystkie flagi dokładnie w taki sam sposób, w cmp eax, 0jaki :

Z wyjątkiem przestarzałego AF (pomocniczy znacznik przenoszenia, używany w instrukcjach ASCII / BCD). TEST pozostawia go niezdefiniowanym , ale CMP ustawia go „zgodnie z wynikiem” . Ponieważ odejmowanie zera nie może dać przeniesienia z 4 do 5 bitu, CMP powinien zawsze wyczyścić AF.


TEST jest mniejszy (nie jest natychmiastowy) i czasami szybszy (może łączyć się w makro w celu porównania i rozgałęzienia na większej liczbie procesorów w większej liczbie przypadków niż CMP). To jest testpreferowanym idiomem do porównywania rejestru z zerem . Jest to optymalizacja wizjera cmp reg,0, której można używać niezależnie od znaczenia semantycznego.

Jedynym częstym powodem używania CMP z natychmiastowym 0 jest to, że chcesz porównać z operandem pamięci. Na przykład, cmpb $0, (%esi)aby sprawdzić kończący bajt zerowy na końcu niejawnej długości ciągu w stylu C.


AVX512F dodajekortestw k1, k2 i AVX512DQ / BW (Skylake-X, ale nie KNL) ktestb/w/d/q k1, k2, które działają na rejestrach maski AVX512 (k0..k7), ale nadal ustawiają regularne FLAGI, tak jak testrobi to liczba całkowita ORlub ANDinstrukcje. (Coś w rodzaju SSE4 ptestlub SSE ucomiss: dane wejściowe w domenie SIMD i dają w wyniku FLAGI całkowite).

kortestw k1,k1jest idiomatycznym sposobem rozgałęzienia / cmovcc / setcc na podstawie wyniku porównania AVX512, zastępując SSE / AVX2 (v)pmovmskb/ps/pd+ testlub cmp.


Użycie jzvs. jemoże być mylące.

jzi jesą dosłownie tą samą instrukcją , tj. tym samym kodem operacyjnym w kodzie maszynowym. Robią to samo, ale mają inne znaczenie semantyczne dla ludzi . Dezasemblery (i zazwyczaj dane wyjściowe asm z kompilatorów) będą zawsze używać tylko jednego, więc rozróżnienie semantyczne zostaje utracone.

cmpi subustawia ZF, gdy ich dwa wejścia są równe (tj. wynik odejmowania wynosi 0). je(skok, jeśli równe) jest semantycznie istotnym synonimem.

test %eax,%eax/ and %eax,%eaxponownie ustawia ZF, gdy wynik jest równy zero, ale nie ma testu „równości”. ZF po teście nie mówi ci, czy dwa operandy były równe. Zatem jz(skok jeśli zero) jest semantycznie istotnym synonimem.

Peter Cordes
źródło
Zastanowiłbym się nad dodaniem podstawowych informacji o operacjach testbitowych and, może nie być oczywiste dla osób dopiero uczących się asemblera (i leniwych / nieświadomych sprawdzania instrukcji obsługi co 60 sekund;) :)).
Ped7g
1
@ Ped7g: wystarczy, myślę, że nie zaszkodzi umieścić wszystko w tej odpowiedzi, zamiast pozostawić tę część innym odpowiedziom. Dodano AVX512 kortest*i ktest*kiedy byłem na tym.
Peter Cordes
Przy okazji, to w zasadzie to samo, co moja odpowiedź na inną wersję tego samego pytania , ale powiedziałem tam więcej rzeczy na temat wydajności, np. Unikanie blokad odczytu rejestrów na starych procesorach z rodziny P6, takich jak Nehalem, przez przepisanie rejestru z tą samą wartością.
Peter Cordes
@PeterCordes To powinna być akceptowana odpowiedź: wyczerpująca i techniczna. W przeciwieństwie do przyjętego postu, to gasi ciekawość i pragnienie wiedzy. Tak trzymaj Sir.
programmersn
Należy zauważyć, że PF jest ustawiony na parzystość z dolnych 8 bitów, co w tym przypadku jest AL.
ecm
5

Ten fragment kodu pochodzi z podprogramu, któremu nadano wskaźnik do czegoś, prawdopodobnie jakiejś struktury lub obiektu. Druga linia wyłuskuje ten wskaźnik, pobierając wartość z tego elementu - prawdopodobnie sam wskaźnik lub po prostu int, przechowywany jako drugi element członkowski (przesunięcie +4). Trzeci i czwarty wiersz testują tę wartość na zero (NULL, jeśli jest wskaźnikiem) i pomijają następujące kilka operacji (nie pokazano), jeśli wynosi zero.

Test na zero jest czasami kodowany jako porównanie z bezpośrednią wartością dosłownego zera, ale kompilator (lub człowiek?), Który to napisał, mógł pomyśleć, że operacja testowa będzie działać szybciej - biorąc pod uwagę wszystkie nowoczesne rzeczy związane z procesorem, takie jak potokowanie i rejestrowanie zmiana nazwy. To z tego samego worka sztuczek, który zawiera pomysł wyczyszczenia rejestru za pomocą XOR EAX, EAX (które widziałem na czyjejś tablicy rejestracyjnej w Kolorado!), A nie oczywistego, ale może wolniejszego MOV EAX, # 0 (używam starszej notacji ).

W asm, podobnie jak perl, TMTOWTDI.

DarenW
źródło
3

Jeśli eax ma wartość zero, wykona skok warunkowy, w przeciwnym razie będzie kontynuował wykonywanie na 319e9

Mike Thompson
źródło
0

W niektórych programach można ich użyć do sprawdzenia przepełnienia bufora. Na samej górze przydzielonego miejsca jest umieszczane 0. Po wprowadzeniu danych do stosu szuka 0 na samym początku przydzielonego miejsca, aby upewnić się, że przydzielone miejsce nie jest przepełnione.

Został użyty w ćwiczeniu stack0 w ćwiczeniach exploitów, aby sprawdzić, czy jest przepełniony, a jeśli go nie było i było tam zero, wyświetlał „Spróbuj ponownie”

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
user7259278
źródło
Nie rozumiem, co ten konkretny przypadek sprawdzania rejestru pod kątem wartości niezerowej dodaje do tego pytania i odpowiedzi. Zwłaszcza gdy cmp DWORD PTR [esp+0x5c], 0/ jz 0x8048427 <main+51>byłby bardziej wydajny niż oddzielne obciążenie MOV, a następnie TEST. Nie jest to typowy przypadek użycia do sprawdzania zera.
Peter Cordes
-4

moglibyśmy zobaczyć jgjle Jeśli testl %edx,%edx. jle .L3moglibyśmy łatwo znaleźć jle jest odpowiednie (SF^OF)|ZF, jeśli% edx jest równe zero, ZF = 1, ale jeśli% edx nie jest zerem i jest równe -1, po testl, OF = 0 i SF = 1, więc flaga = prawda, ten skok narzędzia. Przepraszam, mój angielski jest słaby

cbei_you
źródło