`testl` eax contra eax?

118

Estou tentando entender alguma montagem.

A montagem da seguinte forma, estou interessado na testllinha:

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

Estou tentando entender esse ponto testlentre %eaxe %eax? Acho que as especificações desse código não são importantes, estou apenas tentando entender o teste por si mesmo - o valor não seria sempre verdadeiro?

maxpinguim
fonte

Respostas:

91

Ele testa se eaxé 0, ou acima ou abaixo. Neste caso, o salto é realizado se eaxfor 0.

Chris Jester-Young
fonte
2
Fiz uma edição para transformar essa resposta popular em uma resposta canônica melhor para "o que é essa coisa de TESTE e como é diferente do CMP", o que está meio que implícito. Veja minha própria resposta mais abaixo para comentários sobre o significado semântico dos sinônimos JE e JZ. Por favor, revise minha edição, pois é muito importante, e ainda é sua resposta.
Peter Cordes
@PeterCordes Agradeço a intenção, mas vou reverter sua edição. 1. Sua "voz" é muito diferente da minha e, no momento, parece muito mais com a sua resposta do que com a minha. 2. Mais problemática é a afirmação ousada de que as bandeiras saem exatamente da mesma maneira entre teste cmp. Sim, entendo que essa é sua crença com base em seus comentários para Cody. No entanto, colocá-lo na minha postagem é uma questão diferente; não é uma afirmação que eu esteja disposto a apoiar, simplesmente porque não sei se é idêntica em todos os casos.
Chris Jester-Young
1
@PeterCordes Se eu encontrar algum tempo livre, quero dar corpo a essa resposta para ser mais canônica. Eu escreveria como eu escrevo, entretanto, e sou bastante particular sobre como escrevo as coisas. :-) Por exemplo, eu ia escrever je, jz, cmp, e test, e não JE, JZ, CMP, ou TEST. Eu sou exigente assim.
Chris Jester-Young
1
Eu não estava tentando impulsionar minha própria resposta. Na verdade, esqueci que tinha respondido a essa pergunta quando fiz aquela edição e só percebi depois. Eu só olhei para isso depois que alguém bateu nele, e o que começou como uma pequena edição virou uma bola de neve demais. Sem querer ofender-se por querer revertê-lo; foi apenas uma sugestão e definitivamente parece meu trabalho, não seu. Vou pegar um pouco do que escrevi e colocá-lo em minha própria resposta.
Peter Cordes
2
Uau, depois de editar minha resposta a esta pergunta para incluir o que adicionei à sua, percebi que tinha quase exatamente duplicado a maior parte do que escrevi em junho. Ops! Eu o atualizei com mais raciocínio para apoiar minha afirmação test a,ae cmp $0,adefinir sinalizadores de forma idêntica; obrigado por apontar que essa é uma afirmação não trivial. re: TESTE vs test.: recentemente comecei a usar letras maiúsculas como os manuais da Intel. Mas quando estou falando sobre mnemônicos da AT&T vs. mnemônicos da Intel, uso testbestilo para a AT&T. IDK se isso ajuda na legibilidade.
Peter Cordes
90

O significado de testé para AND os argumentos juntos e verifique o resultado para zero. Portanto, este código testa se EAX é zero ou não. jeirá pular se for zero.

BTW, isso gera uma instrução menor do cmp eax, 0que a razão pela qual os compiladores geralmente fazem isso dessa maneira.

phuclv
fonte
34

A instrução de teste faz uma operação lógica AND entre os operandos, mas não escreve o resultado de volta em um registrador. Apenas os sinalizadores são atualizados.

Em seu exemplo, o teste eax, eax definirá o sinalizador zero se eax for zero, o sinalizador se for o bit mais alto definido e alguns outros sinalizadores também.

A instrução Jump if Equal (je) salta se o sinalizador zero estiver definido.

Você pode traduzir o código para um código mais legível como este:

cmp eax, 0
je  somewhere

Isso tem a mesma funcionalidade, mas requer alguns bytes a mais espaço de código. Essa é a razão pela qual o compilador emitiu um teste em vez de uma comparação.

Nils Pipenbrinck
fonte
3
Na verdade, cmp pode não funcionar lá. Ou seja, ele funciona para o caso específico apresentado, mas cmp afeta os sinalizadores de forma diferente do que o teste, devido a ser um sub interno em vez de e. Algo para se manter em mente.
Cody Brocious,
4
para um teste contra zero é perfeitamente válido.
Nils Pipenbrinck
3
Mas você não sabe o que mais olhará para as bandeiras depois. Os efeitos nos sinalizadores são muito diferentes, então isso pode ser um problema, e com frequência é.
Cody Brocious,
2
Não, os únicos sinalizadores definidos por um / método / diferente são carry e overflow, ambos definidos como 0. Os / valores / dos outros sinalizadores serão diferentes porque cmp usa sub e teste usa e.
Cody Brocious,
2
@CodyBrocious: test eax, eaxe cmp eax, 0ambos definem todos os sinalizadores e os definem com valores idênticos. Ambas as instruções definem todos os sinalizadores "de acordo com o resultado". A subtração 0nunca pode produzir transporte ou transbordamento. Seu argumento está correto para qualquer imediato diferente de 0, mas não para 0.
Peter Cordes
22

testé semelhante and, exceto que apenas grava FLAGS, deixando ambas as entradas inalteradas. Com duas entradas diferentes , é útil para testar se alguns bits são todos zero ou se pelo menos um está definido. (por exemplo:test al, 3 define ZF se EAX é um múltiplo de 4 (e, portanto, tem ambos os 2 bits inferiores zerados).


test eax,eax define todos os sinalizadores exatamente da mesma maneira que cmp eax, 0 faria :

Exceto para o AF obsoleto (sinalizador de transporte auxiliar, usado pelas instruções ASCII / BCD). TEST deixa indefinido , mas CMP o configura "de acordo com o resultado" . Uma vez que subtrair zero não pode produzir um transporte do 4º para o 5º bit, o CMP deve sempre limpar o AF.


TEST é menor (não imediato) e às vezes mais rápido (pode fundir macro em um uop de comparação e ramificação em mais CPUs em mais casos do que CMP). Isso torna testo idioma preferido para comparar um registro com zero . É uma otimização de olho mágico paracmp reg,0 que você pode usar independentemente do significado semântico.

O único motivo comum para usar CMP com um 0 imediato é quando você deseja comparar com um operando de memória. Por exemplo, cmpb $0, (%esi)para verificar se há um byte zero de terminação no final de uma string estilo C de comprimento implícito.


AVX512F adicionakortestw k1, k2 e AVX512DQ / BW (Skylake-X mas não KNL) adiciona ktestb/w/d/q k1, k2, que opera em registradores de máscara AVX512 (k0..k7), mas ainda define FLAGS regulares como testfaz, da mesma forma que inteiros ORou ANDinstruções fazem. (Mais ptestou menos como SSE4 ou SSEucomiss : entradas no domínio SIMD e resultam em FLAGS inteiros.)

kortestw k1,k1é a maneira idiomática de ramificar / cmovcc / setcc com base em um resultado de comparação AVX512, substituindo SSE / AVX2 (v)pmovmskb/ps/pd+ testou cmp.


O uso de jzvs. jepode ser confuso.

jze jesão literalmente a mesma instrução , ou seja, o mesmo opcode no código de máquina. Eles fazem a mesma coisa, mas têm significados semânticos diferentes para humanos . Os desmontadores (e normalmente a saída do conjunto de compiladores) usarão apenas um, portanto, a distinção semântica é perdida.

cmpe subdefina ZF quando suas duas entradas forem iguais (ou seja, o resultado da subtração é 0). je(pule se for igual) é o sinônimo semanticamente relevante.

test %eax,%eax/ and %eax,%eaxnovamente define ZF quando o resultado é zero, mas não há teste de "igualdade". ZF após o teste não informa se os dois operandos eram iguais. Portanto, jz(pule se zero) é o sinônimo semanticamente relevante.

Peter Cordes
fonte
Eu consideraria adicionar as informações básicas sobre a operação testbit a bit and, pode não ser óbvio para pessoas que estão aprendendo a montagem (e sendo preguiçoso / desatento para verificar o guia de referência de instrução a cada 60 segundos;) :)).
Ped7g
1
@ Ped7g: justo, acho que não custa colocar tudo nesta resposta, em vez de deixar essa parte para as outras respostas. Adicionado AVX512 kortest*e ktest*enquanto eu estava nisso.
Peter Cordes
BTW, isso é basicamente o mesmo que minha resposta a outra versão da mesma pergunta , mas eu disse mais coisas sobre desempenho lá, por exemplo, possivelmente evitando paralisações de leitura de registro em CPUs da família P6 antigas como Nehalem reescrevendo o registro com o mesmo valor.
Peter Cordes,
@PeterCordes Esta deve ser a resposta aceita: exaustiva e técnica. Ao contrário da postagem aceita, isso sacia a curiosidade e a sede de conhecimento. Continue assim, senhor.
programadores em
Deve-se notar que PF é definido para a paridade dos 8 bits baixos, que neste caso é AL.
ecm
5

Este trecho de código é de uma sub-rotina que recebeu um ponteiro para algo, provavelmente alguma estrutura ou objeto. A segunda linha desreferencia esse ponteiro, obtendo um valor daquela coisa - possivelmente um ponteiro ou apenas um int, armazenado como seu segundo membro (deslocamento +4). A 3ª e 4ª linhas testam este valor para zero (NULL se for um ponteiro) e pule as seguintes operações (não mostradas) se for zero.

O teste para zero às vezes é codificado como uma comparação com um valor zero literal imediato, mas o compilador (ou humano?) Que escreveu isso pode ter pensado que uma operação de teste seria executada mais rápido - levando em consideração todas as coisas da CPU moderna, como pipelining e registro renomeando. É o mesmo pacote de truques que contém a ideia de limpar um registro com XOR EAX, EAX (que eu vi na placa de alguém no Colorado!) Em vez do óbvio, mas talvez mais lento MOV EAX, # 0 (eu uso uma notação mais antiga )

Em asm, como perl, TMTOWTDI.

DarenW
fonte
3

Se eax for zero, ele executará o salto condicional, caso contrário, continuará a execução em 319e9

Mike Thompson
fonte
0

Em alguns programas, eles podem ser usados ​​para verificar se há estouro de buffer. No topo do espaço alocado, um 0 é colocado. Depois de inserir os dados na pilha, ele procura o 0 no início do espaço alocado para garantir que o espaço alocado não seja estourado.

Foi usado no exercício stack0 de exercícios de exploração para verificar se estava estourado e se não havia e havia um zero ali, ele exibia "Tente novamente"

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
fonte
Não vejo o que este caso específico de verificação de um registro diferente de zero adiciona a este Q&A. Especialmente quando cmp DWORD PTR [esp+0x5c], 0/ jz 0x8048427 <main+51>teria sido mais eficiente do que uma carga MOV separada e, em seguida, TESTE. Este não é um caso de uso comum para verificar um zero.
Peter Cordes
-4

poderíamos ver o jgjle Se testl %edx,%edx. jle .L3pudéssemos encontrar facilmente jle é o terno(SF^OF)|ZF , se% edx for zero, ZF = 1, mas se% edx não for zero e for -1, após o teste, o OF = 0 e o SF = 1, então o flag = true, que implementa o salto. Desculpe, meu inglês é ruim

cbei_you
fonte