`testl` eax против eax?

118

Пытаюсь разобраться в какой-то сборке.

Сборка следующая, интересует testlлинейка:

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

Я пытаюсь понять точку testlмежду %eaxи %eax? Я думаю, что особенности этого кода не важны, я просто пытаюсь разобраться в тесте с самим собой - не всегда ли значение будет истинным?

maxpenguin
источник

Ответы:

91

Он проверяет, eaxравен ли 0, больше или меньше. В этом случае прыжок выполняется, если он eaxравен 0.

Крис Джестер-Янг
источник
2
Я внес правку, чтобы превратить этот популярный ответ в лучший канонический ответ на вопрос «Что это за ТЕСТ и чем он отличается от CMP», что вроде как подразумевается. См. Мой собственный ответ ниже для комментариев о семантическом значении синонимов JE и JZ. Пожалуйста, просмотрите мои изменения, так как они довольно серьезные, и это все еще ваш ответ.
Питер Кордес
@PeterCordes Я ценю намерение, но я собираюсь отменить ваше изменение. 1. Ваш "голос" очень отличается от моего, и сейчас он гораздо больше похож на ваш ответ, чем на мой. 2. Более проблематичным является смелое утверждение о том, что флаги появляются точно так же между testи cmp. Да, я понимаю, что это ваше мнение, основанное на ваших комментариях Коди. Однако поместить это в свой пост - другое дело; это не утверждение, с которым я согласен, просто потому, что я не знаю , одинаково ли оно во всех случаях.
Крис Джестер-Янг,
1
@PeterCordes Если у меня появится свободное время, я хочу придать этому ответу более канонический характер. Я бы написал это так, как пишу, и я очень разборчив в том, как я пишу. :-) Например, я пишу je, jz, cmp, и test, а не JE, JZ, CMP или TEST. Я такой придирчивый.
Крис Джестер-Янг,
1
Я не пытался усилить свой ответ. На самом деле я забыл, что сам ответил на этот вопрос, когда делал это редактирование, и заметил только потом. Я просто посмотрел на это после того, как кто-то наткнулся на это, и то, что началось как небольшое правка, превратилось в слишком много. Без обид, что ты хотел его откатить; это было просто предложение, и оно определенно похоже на мою работу, а не на вашу. Я возьму кое-что из написанного и вставлю в свой ответ.
Питер Кордес
2
Вау, отредактировав свой ответ на этот вопрос, включив в него то, что я добавил к вашему, я понял, что почти полностью скопировал большую часть того, что написал в июне. К сожалению! Я обновил его, добавив больше аргументов, чтобы подтвердить свое утверждение, test a,aи cmp $0,aидентично установил флаги; спасибо, что указали, что это нетривиальное утверждение. re: TEST vs test.: недавно я начал использовать заглавные буквы, как в руководствах Intel. Но когда я говорю о мнемонике AT&T и мнемонике Intel, я использую testbстиль для AT&T. IDK, если это помогает читаемости.
Питер Кордес
90

Смысл в testтом, чтобы объединить аргументы И и проверить результат на ноль. Таким образом, этот код проверяет, равен ли EAX нулю. jeпрыгнет, если ноль.

Кстати, это генерирует меньшую инструкцию, чем cmp eax, 0по этой причине компиляторы обычно делают это таким образом.

phuclv
источник
34

Команда тестирования выполняет логическую операцию И между операндами, но не записывает результат обратно в регистр. Обновляются только флаги.

В вашем примере тестовый eax, eax установит нулевой флаг, если eax равен нулю, знак-флаг, если установлен самый высокий бит, а также некоторые другие флаги.

Команда Перейти, если равно (je) перескакивает, если установлен нулевой флаг.

Вы можете перевести код в более читаемый код следующим образом:

cmp eax, 0
je  somewhere

Это имеет ту же функциональность, но требует на несколько байтов больше места для кода. Вот почему компилятор выдал тест вместо сравнения.

Нильс Пипенбринк
источник
3
На самом деле cmp может там не работать. То есть, он работает для конкретного представленного случая, но cmp влияет на флаги иначе, чем test, потому что он является внутренним вложением вместо и. Что нужно иметь в виду.
Cody Brocious
4
для теста против нуля это совершенно верно.
Нильс Пипенбринк,
3
Но вы не знаете, что еще смотрит на флаги позже. Эффекты на флаги очень разные, поэтому это может быть проблемой, и это происходит очень часто.
Cody Brocious
2
Нет, единственные флаги, которые устанавливаются другим / методом /, - это перенос и переполнение, оба из которых имеют значение 0. / values ​​/ других флагов будут отличаться, потому что cmp использует вспомогательные и тестовые использует и.
Cody Brocious
2
@CodyBrocious: test eax, eaxи cmp eax, 0оба устанавливают все флаги и устанавливают для них одинаковые значения. Обе инструкции устанавливают все флаги «согласно результату». Вычитание 0никогда не может привести к переносу или переполнению. Ваш аргумент верен для любого немедленного действия, кроме 0, но не для 0.
Питер Кордес
22

testподобен and, за исключением того, что он записывает только FLAGS, оставляя оба входа неизмененными. С двумя разными входами это полезно для проверки, все ли некоторые биты равны нулю или установлен хотя бы один. (например, test al, 3устанавливает ZF, если EAX кратно 4 (и, таким образом, оба младших бита обнулены).


test eax,eaxустанавливает все флаги точно так же, как если cmp eax, 0бы :

За исключением устаревшего AF (флаг вспомогательного переноса, используемый инструкциями ASCII / BCD). TEST оставляет его неопределенным , но CMP устанавливает его «в соответствии с результатом» . Поскольку вычитание нуля не может привести к переносу с 4-го на 5-й бит, CMP всегда должен очищать AF.


ТЕСТ меньше (не сразу), а иногда и быстрее (может макрослиться в муп сравнения и ветвления на большем количестве процессоров в большем количестве случаев, чем CMP). Это делает testпредпочтительную идиому для сравнения регистра с нулем . Это оптимизация на глазок cmp reg,0, которую можно использовать независимо от семантического значения.

Единственная распространенная причина использования CMP с немедленным 0 - это когда вы хотите сравнить с операндом памяти. Например, cmpb $0, (%esi)чтобы проверить завершающий нулевой байт в конце строки C-стиля неявной длины.


AVX512F добавляетkortestw k1, k2 и добавляет AVX512DQ / BW (Skylake-X, но не KNL) ktestb/w/d/q k1, k2, которые работают с регистрами маски AVX512 (k0..k7), но по-прежнему устанавливают обычные ФЛАГИ, как это testделается, точно так же, как это делают целые числа ORили ANDинструкции. (Что-то вроде SSE4 ptestили SSE ucomiss: входы в домен SIMD и результат целочисленные флаги.)

kortestw k1,k1- это идиоматический способ ветвления / cmovcc / setcc на основе результата сравнения AVX512, заменяющий SSE / AVX2 (v)pmovmskb/ps/pd+ testили cmp.


Использование jzvs. jeможет сбивать с толку.

jzи jeпредставляют собой буквально одну и ту же инструкцию , то есть один и тот же код операции в машинном коде. Они делают то же самое, но имеют разное смысловое значение для людей . Дизассемблеры (и обычно вывод asm из компиляторов) будут всегда использовать только один, поэтому семантическое различие теряется.

cmpи subустановите ZF, когда их два входа равны (т.е. результат вычитания равен 0). je(переход, если равно) - семантически значимый синоним.

test %eax,%eax/ and %eax,%eaxснова устанавливает ZF, когда результат равен нулю, но нет проверки на «равенство». ZF после теста не сообщает вам, равны ли два операнда. Итак jz(переход, если ноль) - это семантически значимый синоним.

Питер Кордес
источник
Я бы подумал о добавлении базовой информации о testпобитовой andоперации, может быть неочевидно для людей, только изучающих сборку (и ленивых / не знающих, что проверять справочное руководство каждые 60 секунд;) :)).
Ped7g
1
@ Ped7g: честно, я думаю, не повредит поместить все в этот ответ, вместо того, чтобы оставить эту часть другим ответам. Добавил AVX512 kortest*и ktest*пока я был на нем.
Питер Кордес
Кстати, это в основном то же самое, что и мой ответ на другую версию того же вопроса , но я сказал больше о производительности там, например, возможно, избегая остановок чтения регистров на старых процессорах семейства P6, таких как Nehalem, путем перезаписи регистра с тем же значением.
Питер Кордес
@PeterCordes Это должен быть принятый ответ: исчерпывающий и технический. В отличие от принятого поста, это утоляет любопытство и жажду знаний. Так держать, сэр.
программисты
Следует отметить, что PF установлен на четность младших 8 бит, которые в данном случае являются AL.
ecm 01
5

Этот фрагмент кода взят из подпрограммы, которой был дан указатель на что-то, возможно, на какую-то структуру или объект. Вторая строка разыменовывает этот указатель, извлекая значение из этой вещи - возможно, это указатель или, может быть, просто int, хранящийся как его второй член (смещение +4). Третья и четвертая строки проверяют это значение на ноль (NULL, если это указатель) и пропускают следующие несколько операций (не показаны), если оно равно нулю.

Тест на ноль иногда кодируется как сравнение с непосредственным буквальным нулевым значением, но компилятор (или человек?), Который написал это, мог подумать, что testl op будет работать быстрее - принимая во внимание все современные вещи процессора, такие как конвейерная обработка и регистр переименование. Это из того же набора уловок, в котором заключена идея очистки реестра с помощью XOR EAX, EAX (который я видел на чьем-то номерном знаке в Колорадо!), А не очевидного, но, возможно, более медленного MOV EAX, # 0 (я использую более старую нотацию. ).

В asm, как и в perl, TMTOWTDI.

DarenW
источник
3

Если eax равен нулю, он выполнит условный переход, в противном случае он продолжит выполнение на 319e9.

Майк Томпсон
источник
0

В некоторых программах их можно использовать для проверки переполнения буфера. В самом верху выделенного места ставится 0. После ввода данных в стек он ищет 0 в самом начале выделенного пространства, чтобы убедиться, что выделенное пространство не переполнено.

Он использовался в упражнении stack0 для эксплойтов-упражнений, чтобы проверить, не было ли он переполнен, и если его нет и там был ноль, он отобразил бы "Попробуйте снова"

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
источник
Я не понимаю, что этот конкретный случай проверки регистра на ненулевое значение добавляет к этим вопросам и ответам. Особенно, когда cmp DWORD PTR [esp+0x5c], 0/ jz 0x8048427 <main+51>было бы более эффективным, чем отдельная загрузка MOV, а затем ТЕСТ. Вряд ли это обычный вариант использования проверки нуля.
Питер Кордес
-4

мы могли бы увидеть JG , JLE Если testl %edx,%edx. jle .L3мы могли бы легко найти JLE костюм (SF^OF)|ZF, если% EDX равен нулю, ZF = 1, но если% EDX не равен нулю и -1, после testl, то OF = 0 и SF = 1, поэтому флаг = true, который реализует прыжок. Извините, мой английский плохой

cbei_you
источник