`testl` eax contra eax?

118

Estoy tratando de entender alguna asamblea.

El montaje de la siguiente manera, me interesa la testllínea:

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

Estoy tratando de entender ese punto testlentre %eaxy %eax? Creo que los detalles de lo que este código no es importante, solo estoy tratando de entender la prueba consigo misma, ¿no sería siempre cierto el valor?

maxpenguin
fuente

Respuestas:

91

Prueba si eaxes 0, superior o inferior. En este caso, el salto se realiza si eaxes 0.

Chris Jester-Young
fuente
2
Hice una edición para convertir esta respuesta popular en una mejor respuesta canónica a "de qué se trata esto TEST y en qué se diferencia de CMP", que está implícito. Vea mi propia respuesta más abajo para obtener comentarios sobre el significado semántico de los sinónimos JE y JZ. Por favor revise mi edición ya que es bastante importante y sigue siendo su respuesta.
Peter Cordes
@PeterCordes Aprecio la intención, pero voy a revertir tu edición. 1. Tu "voz" es muy diferente a la mía, y ahora se parece mucho más a tu respuesta que a la mía. 2. Más problemática es la afirmación audaz de que las banderas salen exactamente de la misma manera entre testy cmp. Sí, entiendo que esa es su creencia basada en sus comentarios a Cody. Sin embargo, ponerlo en mi publicación es un asunto diferente; no es una afirmación que esté dispuesta a mantener, simplemente porque no sé si es idéntica en todos los casos.
Chris Jester-Young
1
@PeterCordes Si encuentro algo de tiempo libre, quiero desarrollar esta respuesta para que sea más canónica. Sin embargo, lo escribiría como lo escribo, y soy bastante particular sobre cómo escribo las cosas. :-) Por ejemplo, me gustaría escribir je, jz, cmp, y test, y no JE, JZ, CMP, o TEST. Soy así de quisquilloso.
Chris Jester-Young
1
No estaba tratando de mejorar mi propia respuesta. De hecho, olvidé que había respondido a esta pregunta yo mismo cuando hice esa edición, y solo lo noté después. Solo miré esto después de que alguien lo golpeó, y lo que comenzó como una pequeña edición se convirtió en demasiado. No se ofenda que quisiera revertirlo; fue solo una sugerencia y definitivamente se lee como mi trabajo, no el tuyo. Tomaré algo de lo que escribí y lo pondré en mi propia respuesta.
Peter Cordes
2
Vaya, después de editar mi respuesta a esta pregunta para incluir lo que agregué a la tuya, me di cuenta de que había duplicado casi exactamente la mayor parte de lo que escribí en junio. ¡Ups! Lo actualicé con más razonamiento para respaldar mi afirmación test a,ay cmp $0,aestablecer banderas de manera idéntica; gracias por señalar que se trata de una afirmación no trivial. re: TEST vs test.: recientemente comencé a usar mayúsculas como los manuales de Intel. Pero cuando hablo de mnemónicos de AT&T frente a mnemónicos de Intel, utilizo testbestilo para AT&T. IDK si eso ayuda a la legibilidad.
Peter Cordes
90

El significado de testes hacer Y los argumentos juntos, y verificar que el resultado sea cero. Entonces, este código prueba si EAX es cero o no. jesaltará si es cero.

Por cierto, esto genera una instrucción más pequeña cmp eax, 0que la razón por la que los compiladores generalmente lo hacen de esta manera.

phuclv
fuente
34

La instrucción de prueba realiza una operación Y lógica entre los operandos pero no escribe el resultado en un registro. Solo se actualizan las banderas.

En su ejemplo, la prueba eax, eax establecerá el indicador cero si eax es cero, el indicador de signo si se establece el bit más alto y también algunos otros indicadores.

La instrucción Jump if Equal (je) salta si se establece el indicador de cero.

Puede traducir el código a un código más legible como este:

cmp eax, 0
je  somewhere

Tiene la misma funcionalidad pero requiere algunos bytes más de espacio de código. Esa es la razón por la que el compilador emitió una prueba en lugar de una comparación.

Nils Pipenbrinck
fuente
3
De hecho, es posible que cmp no funcione allí. Es decir, funciona para el caso específico presentado, pero cmp afecta a las banderas de manera diferente que prueba, debido a que es un sub interno en lugar de y. Algo para tener en cuenta.
Cody Brocious
4
para una prueba contra cero es perfectamente válido.
Nils Pipenbrinck
3
Pero no sabes qué más mira las banderas más tarde. Los efectos en las banderas son muy diferentes, por lo que esto puede ser un problema y con mucha frecuencia lo es.
Cody Brocious
2
No, los únicos indicadores que se establecen mediante un / método / diferente son carry y overflow, los cuales se establecen en 0. Los / valores / de los otros indicadores serán diferentes porque cmp usa usos secundarios y de prueba y.
Cody Brocious
2
@CodyBrocious: test eax, eaxy cmp eax, 0ambos establecen todos los indicadores y los establecen en valores idénticos. Ambas instrucciones establecen todas las banderas "según el resultado". Restar 0nunca puede producir acarreo o desbordamiento. Su argumento es correcto para cualquier inmediato distinto de 0, pero no para 0.
Peter Cordes
22

testes como and, excepto que solo escribe BANDERAS, dejando ambas entradas sin modificar. Con dos entradas diferentes , es útil para probar si algunos bits son todos cero, o si al menos uno está configurado. (por ejemplo, test al, 3establece ZF si EAX es un múltiplo de 4 (y por lo tanto tiene ambos bits bajos de 2 en cero).


test eax,eaxestablece todas las banderas de la misma manera que cmp eax, 0lo haría :

Excepto por el AF obsoleto (indicador de transporte auxiliar, utilizado por las instrucciones ASCII / BCD). TEST lo deja sin definir , pero CMP lo establece "según el resultado" . Dado que restar cero no puede producir un acarreo del cuarto al quinto bit, CMP siempre debe borrar AF.


TEST es más pequeño (no inmediato) y, a veces, más rápido (puede macro-fusionarse en un uop de comparación y ramificación en más CPU en más casos que CMP). Eso lo convierte en testel idioma preferido para comparar un registro con cero . Es una optimización de mirilla cmp reg,0que puede utilizar independientemente del significado semántico.

La única razón común para usar CMP con un 0 inmediato es cuando desea comparar con un operando de memoria. Por ejemplo, cmpb $0, (%esi)para comprobar si hay un byte de terminación cero al final de una cadena de estilo C de longitud implícita.


AVX512F agregakortestw k1, k2 y AVX512DQ / BW (Skylake-X pero no KNL) agrega ktestb/w/d/q k1, k2, que operan en registros de máscara AVX512 (k0..k7) pero aún establecen BANDERAS regulares como lo testhacen, de la misma manera que lo hacen los números enteros ORo las ANDinstrucciones. (Algo así como SSE4 ptesto SSE ucomiss: entradas en el dominio SIMD y dan como resultado BANDERAS enteras).

kortestw k1,k1es la forma idiomática de bifurcar / cmovcc / setcc basada en un resultado de comparación AVX512, reemplazando SSE / AVX2 (v)pmovmskb/ps/pd+ testo cmp.


El uso de jzvs. jepuede resultar confuso.

jzy jeson literalmente la misma instrucción , es decir, el mismo código de operación en el código de máquina. Hacen lo mismo, pero tienen un significado semántico diferente para los humanos . Los desensambladores (y generalmente la salida de asm de los compiladores) solo usarán uno, por lo que se pierde la distinción semántica.

cmpy subestablecer ZF cuando sus dos entradas son iguales (es decir, el resultado de la resta es 0). je(saltar si es igual) es el sinónimo semánticamente relevante.

test %eax,%eax/ and %eax,%eaxnuevamente establece ZF cuando el resultado es cero, pero no hay una prueba de "igualdad". ZF después de la prueba no le dice si los dos operandos eran iguales. Entonces jz(saltar si cero) es el sinónimo semánticamente relevante.

Peter Cordes
fuente
Consideraría agregar la información básica sobre la operación testbit a bit and, puede no ser obvio para las personas que solo están aprendiendo a ensamblar (y son perezosos / inconscientes para verificar la guía de referencia de instrucciones cada 60 segundos;) :)).
Ped7g
1
@ Ped7g: bastante justo, supongo que no está de más poner todo en esta respuesta, en lugar de dejar esa parte a las otras respuestas. Agregué AVX512 kortest*y ktest*mientras estaba en eso.
Peter Cordes
Por cierto, esto es básicamente lo mismo que mi respuesta a otra versión de la misma pregunta , pero dije más cosas sobre el rendimiento allí, por ejemplo, posiblemente evitando las paradas de lectura de registros en CPU antiguas de la familia P6 como Nehalem reescribiendo el registro con el mismo valor.
Peter Cordes
@PeterCordes Esta debería ser la respuesta aceptada: exhaustiva y técnica. A diferencia del puesto aceptado, esto apaga la curiosidad y la sed de conocimiento. Sigue así, señor.
programadores el
Cabe señalar que PF se establece en la paridad de los 8 bits bajos, que en este caso es AL.
ecm
5

Este fragmento de código es de una subrutina a la que se le dio un puntero a algo, probablemente alguna estructura u objeto. La segunda línea elimina la referencia de ese puntero, obteniendo un valor de esa cosa, posiblemente en sí mismo un puntero o tal vez solo un int, almacenado como su segundo miembro (desplazamiento +4). Las líneas 3 y 4 prueban este valor para cero (NULL si es un puntero) y omiten las siguientes operaciones (no se muestran) si es cero.

La prueba de cero a veces se codifica como una comparación con un valor cero literal inmediato, pero el compilador (¿o el humano?) Que escribió esto podría haber pensado que una operación de prueba se ejecutaría más rápido, teniendo en cuenta todas las cosas modernas de la CPU como la canalización y el registro. renombrar. Es de la misma bolsa de trucos que sostiene la idea de borrar un registro con XOR EAX, EAX (¡que vi en la matrícula de alguien en Colorado!) En lugar del obvio pero quizás más lento MOV EAX, # 0 (uso una notación anterior ).

En asm, como perl, TMTOWTDI.

DarenW
fuente
3

Si eax es cero, realizará el salto condicional, de lo contrario continuará la ejecución en 319e9

Mike Thompson
fuente
0

En algunos programas, se pueden utilizar para comprobar si hay un desbordamiento del búfer. En la parte superior del espacio asignado se coloca un 0. Después de ingresar datos en la pila, busca el 0 al comienzo del espacio asignado para asegurarse de que el espacio asignado no se desborde.

Se usó en el ejercicio stack0 de exploits-ejercicios para verificar si estaba desbordado y si no lo había y había un cero allí, mostraría "Intentar de nuevo"

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
usuario7259278
fuente
No veo qué agrega este caso específico de verificación de un registro en busca de valores distintos de cero a esta pregunta y respuesta. Especialmente cuando cmp DWORD PTR [esp+0x5c], 0/ jz 0x8048427 <main+51>hubiera sido más eficiente que una carga MOV separada y luego TEST. Este no es un caso de uso común para verificar un cero.
Peter Cordes
-4

podríamos ver jg , jle Si testl %edx,%edx. jle .L3pudiéramos encontrar fácilmente jle es adecuado (SF^OF)|ZF, si% edx es cero, ZF = 1, pero si% edx no es cero y es -1, después de la prueba, OF = 0 y SF = 1, entonces la bandera = verdadera, que implementa salto. Lo siento, mi inglés es pobre

cbei_you
fuente