assembly - book - `testl` eax contra eax?
x86 assembly (8)
Estoy tratando de entender alguna asamblea.
El montaje de la siguiente manera, estoy interesado en la línea testl
:
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 de testl
entre %eax
y %eax
? Creo que los detalles de lo que este código no es importante, solo estoy tratando de entender la prueba consigo mismo, ¿no sería el valor siempre cierto?
El significado de la test
es ANDAR los argumentos juntos, y verificar el resultado para cero. Entonces este código prueba si EAX es cero o no. je
saltará si es cero.
Por cierto, esto genera una instrucción más pequeña que cmp eax, 0
que es la razón por la cual los compiladores generalmente lo hacen de esta manera.
En algunos programas, se pueden usar para verificar un desbordamiento de búfer. En la parte superior del espacio asignado, se coloca un 0. Después de ingresar los datos en la pila, busca el 0 al principio del espacio asignado para asegurarse de que el espacio asignado no se desborde.
Se usó en el ejercicio stack0 de exploits-exercises para comprobar si se había desbordado y si no existía y había un cero allí, mostraría "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
Este fragmento de código proviene de una subrutina a la que se le dio un puntero a algo, probablemente alguna estructura u objeto. La segunda línea desreferencia ese puntero, obteniendo un valor de esa cosa, posiblemente él 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 saltean 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 humano?) Que escribió esto podría haber pensado que una prueba se ejecutaría más rápido, teniendo en cuenta todas las cosas modernas de la CPU como pipelining y register cambio de nombre Es de la misma bolsa de trucos que tiene 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.
La instrucción de prueba realiza una operación AND lógica entre los operandos pero no vuelve a escribir el resultado en un registro. Solo las banderas están actualizadas.
En su ejemplo, la prueba eax, eax establecerá el indicador de cero si eax es cero, el indicador de señal si el bit más alto se estableció y algunos otros indicadores también.
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
Eso tiene la misma funcionalidad pero requiere algunos bytes más espacio de código. Esa es la razón por la cual el compilador emitió una prueba en lugar de una comparación.
Prueba si eax
es 0, o superior, o inferior. En este caso, el salto se toma si eax
es 0.
Si eax es cero, realizará el salto condicional, de lo contrario continuará la ejecución en 319e9
podríamos ver el jg , jle If testl %edx,%edx. jle .L3
testl %edx,%edx. jle .L3
podríamos encontrar fácilmente jle is suit (SF^OF)|ZF
, si% edx es cero, ZF = 1, pero si% edx no es cero y es -1, después del testl, OF = 0, y SF = 1, entonces la bandera = verdadera, que implementa jump .sorry, mi inglés es pobre
test
es similar and
, excepto que solo escribe FLAGS, 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 ej., test al, 3
establece ZF si EAX es un múltiplo de 4 (y por lo tanto tiene sus dos bits bajos a cero).
test eax,eax
establece todas las banderas exactamente de la misma manera que cmp eax, 0
haría :
- CF y OF borrados (AND / TEST siempre hace eso, y restar cero nunca produce un acarreo)
- ZF, SF y PF según el valor en EAX. (
a = a&a = a-0
)
(Excepto por el AF obsoleto (indicador de transporte auxiliar, utilizado por las instrucciones ASCII / BCD). TEST lo deja indefinido , pero CMP lo establece "de acuerdo con el resultado" . Como restar cero no puede producir un acarreo del 4 al 5 poco, CMP siempre debe borrar AF).
TEST es más pequeño (no inmediato) y, a veces más rápido (puede fusionarse macro en un uop de comparación y rama en más CPU en más casos que CMP). Eso hace que la test
sea el idioma preferido para probar un registro para cero o no .
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 verificar un byte cero de terminación al final de un estilo C de longitud implícita cuerda).
AVX512F agrega kortestw k1, k2
y AVX512DQ / BW (Skylake pero no KNL) agregue ktestb/w/d/q k1, k2
, que operan en los registros de máscara AVX512 (k0..k7) pero siguen configurando FLAGS regulares como test
does, de la misma manera que lo hacen las instrucciones OR
o AND
.
kortestw k1,k1
es la forma idiomática de ramificar / cmovcc / setcc basada en un resultado de comparación AVX512, reemplazando SSE / AVX2 (v)pmovmskb/ps/pd
+ test
o cmp
.
El uso de jz
vs. je
puede ser confuso.
jz
y je
son 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 típicamente la salida de ASM de los compiladores) solo usarán uno, por lo que se pierde la distinción semántica.
cmp
y sub
set ZF cuando sus dos entradas son iguales (es decir, el resultado de la resta es 0). je
(jump if equal) es el sinónimo semánticamente relevante.
test %eax,%eax
/ and %eax,%eax
vuelve a establecer ZF cuando el resultado es cero, pero no hay prueba de "igualdad". ZF después de la prueba no te dice si los dos operandos eran iguales. Entonces jz
(jump if zero) es el sinónimo semánticamente relevante.