Scopo dell'istruzione NOP e dell'istruzione di allineamento nell'assembly x86

13

È passato circa un anno dall'ultima volta che ho preso una lezione di assemblaggio. In quella classe, stavamo usando MASM con le librerie Irvine per semplificare la programmazione in.

Dopo aver esaminato la maggior parte delle istruzioni, ha detto che l'istruzione NOP essenzialmente non ha fatto nulla e non si preoccupa di usarlo. Ad ogni modo, era sul medio termine e ha qualche codice di esempio che non funzionerebbe correttamente, così ci ha detto di aggiungere un'istruzione NOP e ha funzionato bene. Ho chiesto di essere dopo la lezione perché e cosa effettivamente ha fatto, e ha detto che non lo sapeva.

Qualcuno lo sa?

    
posta alvonellos 15.09.2012 - 23:09
fonte

7 risposte

35

Spesso si usa NOP per allineare gli indirizzi di istruzioni. Questo di solito si verifica ad esempio quando si scrive Shellcode per sfruttare buffer overflow o vulnerabilità della stringa di formato .

Supponiamo di avere un salto relativo a 100 byte in avanti e di apportare alcune modifiche al codice. Le probabilità sono che le tue modifiche rovinino l'indirizzo del target di salto e come tale dovresti anche cambiare il salto relativo di cui sopra. Qui puoi aggiungere NOP s per far avanzare l'indirizzo di destinazione. Se hai più NOP s tra l'indirizzo di destinazione e l'istruzione di salto, puoi rimuovere NOP s per spostare l'indirizzo di destinazione all'indietro.

Questo non sarebbe un problema se si lavora con un assemblatore che supporta le etichette. Puoi semplicemente fare JXX someLabel (dove JXX è un salto condizionale) e l'assemblatore sostituirà someLabel con l'indirizzo di tale etichetta. Tuttavia, se si modifica semplicemente il codice macchina assemblato (gli opcode effettivi) a mano (come talvolta accade con lo shellcode di scrittura), è necessario modificare manualmente l'istruzione di salto. O lo modifichi, oppure sposta l'indirizzo del codice di destinazione utilizzando NOP s.

Un altro caso d'uso per l'istruzione NOP sarebbe qualcosa chiamato Slitta NOP . In sostanza, l'idea è quella di creare una serie abbastanza ampia di istruzioni che non causi effetti collaterali (come NOP o incrementare e quindi decrementare un registro) ma aumentare il puntatore dell'istruzione. Questo è utile ad esempio quando si vuole saltare a un certo pezzo di codice che non è noto l'indirizzo. Il trucco è piazzare la suddetta slitta NOP davanti al codice obiettivo e poi saltare da qualche parte verso la slitta indicata. Quello che succede è che l'esecuzione continua speranzosamente dall'array che non ha effetti collaterali e attraversa in avanti istruzioni per istruzione fino a quando non raggiunge il pezzo di codice desiderato. Questa tecnica è comunemente utilizzata negli exploit di overflow del buffer sopra menzionati e in particolare per contrastare misure di sicurezza come ASLR .

Ancora un altro uso particolare per l'istruzione NOP è quando si sta modificando il codice di qualche programma. Ad esempio, puoi sostituire parti di salti condizionali con NOP s e come tale eludere la condizione. Questo è un metodo spesso usato quando " cracking " copia la protezione del software. Nel modo più semplice si tratta solo di rimuovere il costrutto del codice assembly per if(genuineCopy) ... riga di codice e di sostituire le istruzioni con NOP s e .. Voilà! Non vengono effettuati controlli e la copia non originale funziona!

Si noti che in pratica entrambi gli esempi di shellcode e cracking fanno lo stesso; modificare il codice esistente senza aggiornare gli indirizzi relativi delle operazioni che si basano sull'indirizzamento relativo.

    
risposta data 16.09.2012 - 10:44
fonte
9

Un nop può essere utilizzato in uno slot di ritardo quando nessuna altra istruzione può essere riordinata per essere inserita lì.

lw   v0,4(v1)
jr   v0

In MIPS, questo sarebbe un bug perché al momento in cui jr stava leggendo il registro v0 il registro v0 non è stato ancora caricato con il valore dell'istruzione precedente.

Il modo per risolvere il problema sarebbe:

lw   v0,4(v1)
nop
jr   v0
nop

Questo riempie gli slot dealy dopo la parola load e le istruzioni jump register con un nop in modo che l'istruzione word load sia completata prima che venga eseguito il comando jump register.

Ulteriori letture - un po 'su SPARC riempimento di slot di ritardo . Da quel documento:

What can be put into the delay slot?

  • Some useful instruction that should be executed whether you branch or not.
  • Some instruction that does useful work only when you branch (or when you don't branch), but doesn't do any harm if executed in the other case.
  • When all else fails, a NOP instruction

What MUST NOT be put into the delay slot?

  • Anything that sets the CC that the branch decision depends on. The branch instruction makes the decision on whether to branch or not right away but it doesn't actually do the branch until after the delay instruction. (Only the branch is delayed, not the decision.)
  • Another branch instruction. (What happens if you do this is not even defined! The result is unpredictable!)
  • A "set" instruction. This is really two instructions, not one, and only half of it will be in the delay slot. (The assembler will warn you about this.)

Notare la terza opzione nel cosa inserire nello slot di ritardo. Il bug che hai visto era probabilmente qualcuno che riempiva una delle cose che non dovevano essere inserite nello slot di ritardo. Mettere un nop in quella posizione risolverebbe quindi il bug.

Nota: dopo aver riletto la domanda, questo era per x86, che non ha slot di ritardo (la ramificazione invece blocca semplicemente la pipeline). Quindi quella non sarebbe la causa / soluzione del bug. Sui sistemi RISC, quella avrebbe potuto essere la risposta.

    
risposta data 18.09.2012 - 16:45
fonte
6

almeno un motivo per usare NOP è l'allineamento. I processori x86 leggono i dati dalla memoria principale in blocchi piuttosto grandi, e l'inizio del blocco da leggere è sempre allineato, quindi se uno ha un blocco di codice, che verrà letto molto, questo blocco dovrebbe essere allineato. Ciò si tradurrà in una piccola accelerazione.

    
risposta data 16.09.2012 - 06:48
fonte
3

Uno scopo per NOP (in assembly generale, non solo x86) per introdurre ritardi temporali. Ad esempio, si desidera programmare un microcontrollore che deve emettere su alcuni LED con un ritardo di 1 s. Questo ritardo può essere implementato con NOP (e rami). Ovviamente potresti usare qualche ADD o qualcos'altro, ma questo renderebbe il codice più illeggibile; o forse hai bisogno di tutti i registri.

    
risposta data 16.09.2012 - 10:12
fonte
3

In generale su 80x86, le istruzioni NOP non sono richieste per la correttezza del programma, sebbene occasionalmente su alcune macchine i NOP posizionati strategicamente potrebbero far sì che il codice funzioni più rapidamente. Sull'8086, ad esempio, il codice verrebbe prelevato in blocchi di due byte e il processore aveva un buffer "prefetch" interno che poteva contenere tre blocchi di questo tipo. Alcune istruzioni potrebbero essere eseguite più velocemente di quanto potrebbero essere scaricate, mentre altre istruzioni richiederebbero un po 'di tempo per essere eseguite. Durante le istruzioni lente, il processore tenterebbe di riempire il buffer di prefetch, in modo che se le istruzioni successive fossero veloci, potrebbero essere eseguite rapidamente. Se l'istruzione che segue l'istruzione lenta inizia su un confine di parole pari, i prossimi sei byte di istruzioni saranno precaricati; se inizia su un limite di byte dispari, verranno prefetchati solo cinque byte. Se ricordo, un NOP ha richiesto tre cicli e un recupero di memoria ne ha richiesti quattro, quindi se il prefetch del byte in più salverebbe un ciclo di memoria, aggiungendo un "NOP" per far sì che l'istruzione dopo l'avvio lento su un confine di parola pari potesse a volte salva un ciclo.

Tali problemi di allineamento della memoria possono influire sulla velocità del programma, ma generalmente non influiscono sulla correttezza. D'altra parte, ci sono alcuni problemi relativi al precetch su quei vecchi processori in cui un NOP potrebbe influenzare la correttezza. Se un'istruzione altera un byte di codice che è già stato precaricato, l'8086 (e penso che l'80286 e l'80386) eseguirà l'istruzione prefetched anche se non corrisponde più a ciò che è in memoria. Aggiungendo un NOP o due tra l'istruzione che altera la memoria e il byte di codice che è stato modificato, è possibile impedire il recupero del byte di codice fino a quando non è stato scritto. Si noti, tra l'altro, che molti schemi di protezione dalla copia hanno sfruttato questo tipo di comportamento; nota anche, tuttavia, che questo comportamento non è garantito. Diverse varianti del processore possono gestire il precaricamento in modo diverso, alcuni possono invalidare i byte precaricati se la memoria da cui sono stati letti viene modificata e gli interrupt generalmente invalidano il buffer di prefetch; il codice verrà recuperato quando gli interrupt ritornano.

    
risposta data 18.09.2012 - 18:54
fonte
3

C'è un caso specifico x86 che non è ancora descritto in altre risposte: gestione degli interrupt. Per alcuni stili, ci possono essere sezioni di codice quando gli interrupt sono disabilitati perché il codice principale funziona con alcuni dati condivisi con gestori di interrupt, ma è ragionevole consentire interruzioni tra tali sezioni. Se uno scrive in modo ingenuo


    STI
    CLI

questo non elaborerà gli interrupt in sospeso perché, citando Intel:

After the IF flag is set, the processor begins responding to external, maskable interrupts after the next instruction is executed.

quindi questo deve essere riscritto almeno come:


    STI
    NOP
    CLI

Nella seconda variante, tutti gli interrupt in attesa verranno elaborati solo tra NOP e CLI. (Naturalmente, ci possono essere molte varianti alternative, come il raddoppio dell'istruzione STI, ma il NOP esplicito è più ovvio, almeno per me.)

    
risposta data 24.12.2013 - 20:54
fonte
-2

NOP significa Nessuna operazione

Generalmente viene utilizzato per inserire o eliminare il codice della macchina o per ritardare l'esecuzione di un particolare codice.

Usato anche da cracker e debugger per impostare i breakpoint.

Quindi probabilmente fare qualcosa come: XCHG BX, BX darà anche lo stesso risultato.

Mi sembra che ci fossero poche operazioni che erano ancora in corso e quindi hanno causato un errore.

Se hai familiarità con VB, posso darti un esempio:

Se crei un sistema di login in vb e carichi 3 pagine insieme - facebook, youtube e twitter in 3 diverse schede.

E usa 1 pulsante di accesso per tutti. Potrebbe dare un errore se la tua connessione internet è lenta. Il che significa che una delle pagine non è ancora stata caricata. Quindi inseriamo Application.DoEvents per superare questo problema. È possibile utilizzare lo stesso modo nel montaggio NOP.

    
risposta data 16.09.2012 - 10:19
fonte

Leggi altre domande sui tag