Objetivo da instrução NOP e instrução de alinhamento na montagem x86


15

Faz mais ou menos um ano desde a última vez que participei de uma aula de montagem. Nessa aula, estávamos usando o MASM com as bibliotecas Irvine para facilitar a programação.

Depois de examinarmos a maioria das instruções, ele disse que a instrução NOP não fazia nada e não se preocupava em usá-la. De qualquer forma, foi no meio do semestre e ele tem algum código de exemplo que não funcionaria corretamente, então ele nos disse para adicionar uma instrução NOP e funcionou bem. Perguntei que estava depois da aula por que e o que realmente fazia, e ele disse que não sabia.

Alguém sabe?


O NOP não faz nada, mas consome ciclos. Acho que sua pergunta não pode ser respondida, sem o código que podemos adivinhar. Bem, o meu palpite seria um deslizamento NOP ...
yannis

11
NOP realmente faz alguma coisa. Incrementa o ponteiro de instruções.
EricSchaefer

Respostas:


37

Muitas vezes, o tempo NOPé usado para alinhar os endereços das instruções. Isso geralmente é encontrado, por exemplo, ao escrever o Shellcode para explorar o estouro de buffer ou formatar a vulnerabilidade de cadeia de caracteres .

Digamos que você tenha um salto relativo para 100 bytes adiante e faça algumas modificações no código. As chances são de que suas modificações atrapalhem o endereço do alvo do salto e, como tal, você também precisará alterar o salto relativo acima mencionado. Aqui, você pode adicionar NOPs para enviar o endereço de destino. Se você tiver vários NOPs entre o endereço de destino e a instrução de salto, poderá remover os NOPs para puxar o endereço de destino para trás.

Isso não seria um problema se você estiver trabalhando com um montador que suporta etiquetas. Você pode simplesmente fazer JXX someLabel(onde JXX é algum salto condicional) e o montador substituirá o someLabelpelo endereço desse rótulo. No entanto, se você simplesmente modificar o código da máquina montado (os opcodes reais) manualmente (como às vezes acontece com a gravação do código do shell), também será necessário alterar a instrução de salto manualmente. Você o modifica ou move o endereço do código de destino usando NOPs.

Outro caso de uso para NOPinstruções seria algo chamado trenó NOP . Em essência, a idéia é criar uma série de instruções suficientemente grande que não cause efeitos colaterais (comoNOPou incrementar e depois decrementar um registro), mas aumente o ponteiro da instrução. Isso é útil, por exemplo, quando se deseja pular para um determinado pedaço de código cujo endereço não é conhecido. O truque é colocar o referido trenó NOP na frente do código de destino e depois pular em algum lugar para o referido trenó. O que acontece é que a execução continua, esperançosamente, a partir da matriz que não tem efeitos colaterais e percorre a instrução por instrução até atingir o trecho de código desejado. Essa técnica é comumente usada em explorações de buffer overflow mencionadas anteriormente e, especialmente, para combater medidas de segurança, como o ASLR .

Ainda outro uso específico para a NOPinstrução é quando alguém está modificando o código de algum programa. Por exemplo, você pode substituir partes de saltos condicionais por NOPs e, como tal, contornar a condição. Esse é um método frequentemente usado ao " quebrar " a proteção contra cópias do software. No mais simples, trata-se de remover a construção do código de montagem da if(genuineCopy) ...linha de código e substituir as instruções por NOPs e .. Voilà! Nenhuma verificação é feita e a cópia não original funciona!

Observe que, em essência, os dois exemplos de código de shell e cracking fazem o mesmo; modificar o código existente sem atualizar os endereços relativos das operações que dependem do endereço relativo.


2
Esta foi uma resposta maravilhosa, obrigado por dedicar um tempo para explicar isso! Eu finalmente entendi!
alvonellos 16/09/12

Certos sistemas em tempo real (PLCs vêm à mente) permitem "corrigir" uma nova lógica em um programa existente enquanto ele estiver em execução. Esses sistemas deixam os NOPs antes de cada pequeno pedaço de lógica, para que você possa sobrescrever o NOP com um salto para a nova lógica que está inserindo. No final da nova lógica, ela pulará para o final da lógica original que você está substituindo. A nova lógica também terá um NOP na frente para que você possa substituir a nova lógica também.
Scott Whitlock

10

Um nop pode ser usado em um slot de atraso quando nenhuma outra instrução pode ser reordenada para ser colocada nele.

lw   v0,4(v1)
jr   v0

No MIPS, isso seria um bug, porque no momento em que o jr estava lendo o registro v0, o registro v0 ainda não havia sido carregado com o valor da instrução anterior.

A maneira de corrigir isso seria:

lw   v0,4(v1)
nop
jr   v0
nop

Isso preenche os slots dealy após as instruções load word e jump register com um nop, para que a instrução load word seja concluída antes que o comando jump register seja executado.

Leitura adicional - um pouco sobre o preenchimento SPARC de slots de atraso . A partir desse documento:

O que pode ser colocado no slot de atraso?

  • Algumas instruções úteis que devem ser executadas, independentemente de você ramificar ou não.
  • Algumas instruções úteis apenas funcionam quando você ramifica (ou quando não ramifica), mas não causa nenhum dano se executado no outro caso.
  • Quando tudo mais falhar, uma instrução NOP

O que NÃO DEVE ser colocado no slot de atraso?

  • Qualquer coisa que defina o CC do qual depende a decisão da filial. A instrução de ramificação toma a decisão de ramificar ou não imediatamente, mas na verdade não faz a ramificação até depois da instrução de atraso. (Apenas a ramificação está atrasada, não a decisão.)
  • Outra instrução de ramificação. (O que acontece se você fizer isso nem está definido! O resultado é imprevisível!)
  • Uma instrução "set". Essas são realmente duas instruções, não uma, e apenas metade delas estará no slot de atraso. (O montador irá avisá-lo sobre isso.)

Observe a terceira opção no o que colocar no slot de atraso. O bug que você viu foi provavelmente alguém preenchendo uma das coisas que não devem ser colocadas no slot de atraso. Colocar um nop nesse local corrigia o bug.

Nota: após reler a pergunta, era para x86, que não possui slots de atraso (ramificação, em vez disso, apenas paralisa o pipeline). Portanto, essa não seria a causa / solução do bug. Nos sistemas RISC, essa poderia ter sido a resposta.


4
Observe que a pergunta está marcada como x86 e x86 não possui slots de atraso. Também nunca será, pois é uma mudança radical.
MSalters

6

pelo menos um motivo para usar o NOP é o alinhamento. Os processadores x86 lêem dados da memória principal em blocos bastante grandes e o início do bloco para leitura é sempre alinhado; portanto, se houver um bloco de código, isso será lido muito, esse bloco deverá ser alinhado. Isso resultará em pouca aceleração.


Não é exatamente o que o bloco precisa ser alinhado, é que você não precisa buscar os últimos dois bytes do bloco anterior. Portanto, é bom pular para 0x1002, porque ainda existem 14 bytes de instruções no bloco alinhado de 16B que contém o endereço de destino, mas não é bom pular para 0x099D.
Peter Cordes

3

Um objetivo do NOP (em assembléia geral, não apenas x86) é introduzir atrasos. Por exemplo, você deseja programar um microcontrolador que precisa emitir alguns LEDs com um atraso de 1 s. Esse atraso pode ser implementado com NOP (e ramificações). Claro que você poderia usar ADD ou algo mais, mas isso tornaria o código mais ilegível; ou talvez você precise de todos os registros.


1
Geralmente, por longos períodos de tempo, como 1 segundo, são usados ​​temporizadores. Os NOPS são usados ​​para épocas dentro de uma ordem de magnitude do relógio - nano e microssegundos.
mattnz

Isso faz sentido em um microcontrolador, não em um x86 moderno. A maioria dos códigos x86 não satura a largura do pipeline das modernas CPUs superscalares fora de ordem, portanto, adicionar um NOP entre todas as instruções na maioria dos códigos teria apenas um pequeno impacto (eu acho que o número do código "médio" pode ser 5 a 20% para dobrar o número de instruções, com alguns códigos mostrando não abrandar, mas alguns loops estreitos mostrando quase um 2x mais lento.) De qualquer forma, o código x86 antigo e tradicional usava tradicionalmente a loopinstrução para loops de atraso , não NOPs.
Peter Cordes

3

Em geral, no 80x86, as instruções NOP não são necessárias para a correção do programa, embora, ocasionalmente, em algumas máquinas, os NOPs estrategicamente colocados possam fazer com que o código seja executado mais rapidamente. No 8086, por exemplo, o código seria buscado em blocos de dois bytes e o processador possuía um buffer interno de "pré-busca" que podia conter três desses blocos. Algumas instruções seriam executadas mais rapidamente do que poderiam ser buscadas, enquanto outras instruções levariam algum tempo para serem executadas. Durante as instruções lentas, o processador tentaria preencher o buffer de pré-busca, para que, se as próximas instruções fossem rápidas, pudessem ser executadas rapidamente. Se a instrução que segue a instrução lenta iniciar em um limite de palavras pares, os próximos seis bytes de instruções serão pré-buscados; se iniciar em um limite de bytes ímpares, apenas cinco bytes serão pré-buscados.

Esses problemas de alinhamento de memória podem afetar a velocidade do programa, mas geralmente não afetam a correção. Por outro lado, existem alguns problemas relacionados à pré-busca nos processadores mais antigos em que um NOP pode afetar a correção. Se uma instrução alterar um byte de código que já foi pré-buscado, o 8086 (e acho que o 80286 e o ​​80386) executará a instrução pré-buscada, mesmo que não corresponda mais ao que está na memória. Adicionar um NOP ou dois entre a instrução que altera a memória e o byte de código alterado pode impedir que o byte de código seja buscado até depois de ter sido gravado. Observe, a propósito, que muitos esquemas de proteção contra cópias exploravam esse tipo de comportamento; Observe também, no entanto, que esse comportamento não é garantido. Diferentes variações do processador podem lidar com a pré-busca de maneira diferente, alguns podem invalidar bytes pré-buscados se a memória da qual eles foram lidos for modificada e as interrupções geralmente invalidam o buffer de pré-busca; o código será buscado novamente quando as interrupções retornarem.


3

Há um caso específico do x86 ainda não descrito em outras respostas: tratamento de interrupção. Para alguns estilos, pode haver seções de código quando as interrupções são desativadas porque o código principal funciona com alguns dados compartilhados com os manipuladores de interrupção, mas é razoável permitir interrupções entre essas seções. Se alguém escreve ingenuamente


    STI
    CLI

isso não processa interrupções pendentes porque, citando a Intel:

Depois que o sinalizador IF é definido, o processador começa a responder a interrupções externas mascaradas após a execução da próxima instrução.

portanto, isso deve ser reescrito pelo menos como:


    STI
    NOP
    CLI

Na segunda variante, todas as interrupções pendentes serão processadas apenas entre o NOP e a CLI. (Obviamente, pode haver muitas variantes alternativas, como duplicar a instrução STI. Mas o NOP explícito é mais óbvio, pelo menos para mim.)


-2

NOP significa Sem Operação

Geralmente é usado para inserir ou excluir código de máquina ou para atrasar a execução de um código específico.

Também usado por crackers e depuradores para definir pontos de interrupção.

Então provavelmente fazendo algo como: XCHG BX, BX também resultará no mesmo.

Parece-me como se houvesse poucas operações que ainda estavam em processo e, portanto, causou um erro.

Se você conhece o VB, posso dar um exemplo:

Se você criar um sistema de login em vb e carregar 3 páginas juntas - facebook, youtube e twitter em 3 abas diferentes.

E use 1 botão de login para todos. Pode ocorrer um erro se sua conexão à Internet estiver lenta. O que significa que uma das páginas ainda não foi carregada. Então, colocamos no Application.DoEvents para superar isso. Da mesma forma na montagem, o NOP pode ser usado.

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.