`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?

Respostas:


91

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


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.


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.


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.


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.


3

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


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

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 jg,jle 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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.