Es ist ungefähr ein Jahr her, seit ich das letzte Mal eine Montageklasse besucht habe. In dieser Klasse verwendeten wir MASM mit den Irvine-Bibliotheken, um das Programmieren zu vereinfachen.
Nachdem wir die meisten Anweisungen durchgearbeitet hatten, sagte er, dass die NOP-Anweisung im Wesentlichen nichts zu tun habe und sich keine Sorgen darüber zu machen, sie zu verwenden. Wie auch immer, es ging um die Halbzeit, und er hat einen Beispielcode, der nicht richtig ausgeführt werden kann. Er sagte uns, wir sollten eine NOP-Anweisung hinzufügen, und es funktionierte einwandfrei. Ich fragte mich nach dem Unterricht, warum und was es tatsächlich tue und er sagte, er wisse es nicht.
Weiß jemand Bescheid?
Antworten:
Oft
NOP
wird verwendet, um Befehlsadressen auszurichten. Dies tritt normalerweise auf, wenn beispielsweise Shellcode geschrieben wird , um die Sicherheitsanfälligkeit bezüglich Pufferüberlaufs oder Formatstrings auszunutzen .Angenommen, Sie haben einen relativen Sprung zu 100 Byte vorwärts und nehmen einige Änderungen am Code vor. Es besteht die Möglichkeit, dass Ihre Änderungen die Adresse des Sprungziels durcheinander bringen und Sie daher auch den oben genannten relativen Sprung ändern müssen. Hier können Sie
NOP
s hinzufügen , um die Zieladresse nach vorne zu verschieben. Wenn Sie mehrereNOP
s zwischen der Zieladresse und der Sprunganweisung haben, können Sie dieNOP
s entfernen , um die Zieladresse nach hinten zu ziehen.Dies ist kein Problem, wenn Sie mit einem Assembler arbeiten, der Labels unterstützt. Sie können dies einfach tun
JXX someLabel
(wobei JXX ein bedingter Sprung ist), und der Assembler ersetzt ihn durchsomeLabel
die Adresse dieses Etiketts. Wenn Sie jedoch einfach den zusammengesetzten Maschinencode (die tatsächlichen Opcodes) von Hand ändern (wie es manchmal beim Schreiben von Shellcode vorkommt), müssen Sie die Sprunganweisung auch manuell ändern. Entweder Sie ändern es, oder Sie verschieben die Zielcodeadresse mitNOP
s.Ein anderer Anwendungsfall für den
NOP
Unterricht wäre ein sogenannter NOP-Schlitten . Im Wesentlichen besteht die Idee darin, eine ausreichend große Reihe von Anweisungen zu erstellen, die keine Nebenwirkungen verursachen (wie zNOP
oder Inkrementieren und dann Dekrementieren eines Registers), aber den Befehlszeiger erhöhen. Dies ist zum Beispiel nützlich, wenn man zu einem bestimmten Code springen möchte, dessen Adresse nicht bekannt ist. Der Trick besteht darin, den besagten NOP-Schlitten vor dem Zielcode zu platzieren und dann irgendwo zum besagten Schlitten zu springen. Was passiert, ist, dass die Ausführung hoffentlich von dem Array aus fortgesetzt wird, das keine Nebenwirkungen hat, und dass es Befehl für Befehl weiterleitet, bis es das gewünschte Stück Code trifft. Diese Technik wird häufig bei den oben genannten Pufferüberlauf-Exploits verwendet und insbesondere, um Sicherheitsmaßnahmen wie ASLR entgegenzuwirken .Eine weitere spezielle Verwendung für den
NOP
Befehl besteht darin, den Code eines Programms zu ändern. Beispielsweise können Sie Teile von bedingten Sprüngen durchNOP
s ersetzen und so die Bedingung umgehen. Dies ist eine häufig verwendete Methode, um den Kopierschutz von Software zu " knacken ". Im einfachsten Fall geht es nur darum, das Assembly-Code-Konstrukt für zu entfernenif(genuineCopy) ...
und die Anweisungen durchNOP
s und .. Voilà! Es werden keine Schecks ausgestellt und es werden keine Originalkopien erstellt!Beachten Sie, dass im Wesentlichen beide Beispiele für Shellcode und Cracking dasselbe tun. Ändern Sie vorhandenen Code, ohne die relativen Adressen von Operationen zu aktualisieren, die auf relativer Adressierung basieren.
quelle
Ein NOP kann in einem Verzögerungsschlitz verwendet werden, wenn keine andere Anweisung neu angeordnet werden kann, um dort abgelegt zu werden.
In MIPS wäre dies ein Fehler, da zu dem Zeitpunkt, als jr das Register v0 las, das Register v0 noch nicht mit dem Wert aus dem vorherigen Befehl geladen wurde.
Der Weg, dies zu beheben, wäre:
Dies füllt die toten Schlitze nach den Ladewort- und Sprungregisteranweisungen mit einem Nein, so dass die Ladewortanweisung abgeschlossen ist, bevor der Sprungregisterbefehl ausgeführt wird.
Lesen Sie weiter - ein bisschen über das SPARC- Füllen von Delay Slots . Aus diesem Dokument:
Beachten Sie die dritte Option im Feld, was in den Delay-Slot eingefügt werden soll. Der Fehler, den Sie gesehen haben, war wahrscheinlich jemand, der eines der Dinge füllte, die nicht in den Delay-Slot gestellt werden dürfen. Wenn Sie an dieser Stelle ein Nein setzen, wird der Fehler behoben.
Hinweis: Nach dem erneuten Lesen der Frage war dies für x86, das keine Verzögerungszeiträume hat (Verzweigung blockiert stattdessen nur die Pipeline). Das wäre also nicht die Ursache / Lösung für den Fehler. Auf RISC-Systemen hätte dies die Antwort sein können.
quelle
Mindestens ein Grund für die Verwendung von NOP ist die Ausrichtung. x86-Prozessoren lesen Daten aus dem Hauptspeicher in recht großen Blöcken, und der Beginn des zu lesenden Blocks ist immer ausgerichtet. Wenn also ein Codeblock vorhanden ist, der häufig gelesen wird, sollte dieser Block ausgerichtet werden. Dies führt zu einer geringen Beschleunigung.
quelle
0x1002
, da der ausgerichtete Block von 16B immer noch 14 Byte Anweisungen enthält, die die Zieladresse enthalten, aber nicht in Ordnung zu springen0x099D
.Ein Zweck für NOP (in der Generalversammlung, nicht nur x86) ist es, Zeitverzögerungen einzuführen. Sie möchten beispielsweise einen Mikrocontroller programmieren, der mit einer Verzögerung von 1 s auf einige LEDs ausgeben muss. Diese Verzögerung kann mit NOP (und Zweigen) implementiert werden. Natürlich könnten Sie ADD oder etwas anderes verwenden, aber das würde den Code unleserlicher machen. oder vielleicht brauchen Sie alle Register.
quelle
loop
Befehl traditionell für Verzögerungsschleifen verwendet , nicht für NOPs.In der Regel sind auf 80x86-Computern keine NOP-Anweisungen für die Programmkorrektheit erforderlich. Auf einigen Computern können jedoch strategisch platzierte NOPs dazu führen, dass Code schneller ausgeführt wird. Auf dem 8086 würde beispielsweise Code in Zwei-Byte-Blöcken abgerufen, und der Prozessor hatte einen internen "Prefetch" -Puffer, der drei solcher Blöcke aufnehmen könnte. Einige Befehle würden schneller ausgeführt, als sie abgerufen werden könnten, während die Ausführung anderer Befehle eine Weile dauern würde. Während langsamer Befehle versuchte der Prozessor, den Vorabrufpuffer zu füllen, so dass die nächsten Befehle schnell ausgeführt werden konnten, wenn sie schnell waren. Wenn der Befehl, der auf den langsamen Befehl folgt, an einer geraden Wortgrenze beginnt, werden die Befehle im Wert von sechs Bytes vorab abgerufen. Beginnt es an einer ungeraden Bytegrenze, werden nur fünf Bytes vorabgerufen.
Solche Speicherausrichtungsprobleme können die Programmgeschwindigkeit beeinflussen, haben jedoch im Allgemeinen keinen Einfluss auf die Korrektheit. Andererseits gibt es einige Prefetch-bezogene Probleme bei älteren Prozessoren, bei denen ein NOP die Korrektheit beeinträchtigen könnte. Wenn ein Befehl ein Code-Byte ändert, das bereits vorabgerufen wurde, führt der 8086 (und ich denke der 80286 und der 80386) den vorabgerufenen Befehl aus, obwohl er nicht mehr mit dem übereinstimmt, was sich im Speicher befindet. Das Hinzufügen von ein oder zwei NOPs zwischen dem Befehl, der den Speicher ändert, und dem Code-Byte, das geändert wird, kann verhindern, dass das Code-Byte abgerufen wird, bis es geschrieben wurde. Übrigens, viele Kopierschutzsysteme haben diese Art von Verhalten ausgenutzt. Beachten Sie jedoch auch, dass dieses Verhalten nicht garantiert ist. Verschiedene Prozessorvarianten können Prefetch unterschiedlich behandeln. Einige machen vorabgerufene Bytes ungültig, wenn der Speicher, aus dem sie gelesen wurden, geändert wird, und Interrupts machen den Vorabrufpuffer im Allgemeinen ungültig. Der Code wird erneut abgerufen, wenn die Interrupts zurückkehren.
quelle
Es gibt einen x86-spezifischen Fall, der in anderen Antworten noch nicht beschrieben ist: die Interrupt-Behandlung. Für einige Stile kann es Codeabschnitte geben, wenn Interrupts deaktiviert sind, da der Hauptcode mit einigen Daten zusammenarbeitet, die mit Interrupt-Handlern geteilt werden. Es ist jedoch sinnvoll, Interrupts zwischen solchen Abschnitten zuzulassen. Wenn man naiv schreibt
Dies wird ausstehende Interrupts nicht verarbeiten, da unter Berufung auf Intel:
das muss also mindestens so umgeschrieben werden:
In der zweiten Variante werden alle anstehenden Interrupts nur zwischen NOP und CLI verarbeitet. (Natürlich kann es viele alternative Varianten geben, die den STI-Befehl verdoppeln. Aber ein explizites NOP ist zumindest für mich offensichtlicher.)
quelle
NOP bedeutet keine Operation
Es wird im Allgemeinen zum Einfügen oder Löschen von Maschinencode oder zum Verzögern der Ausführung eines bestimmten Codes verwendet.
Wird auch von Crackern und Debuggern zum Setzen von Haltepunkten verwendet.
Also wahrscheinlich etwas machen wie: XCHG BX, BX wird auch dasselbe ergeben.
Hört sich für mich so an, als ob es nur wenige Operationen gibt, die noch in Bearbeitung sind, und daher einen Fehler verursacht.
Wenn Sie mit VB vertraut sind, kann ich Ihnen ein Beispiel geben:
Wenn Sie ein Anmeldesystem in vb erstellen und 3 Seiten zusammen laden - Facebook, YouTube und Twitter in 3 verschiedenen Registerkarten.
Und verwenden Sie 1 Login-Button für alle. Wenn Ihre Internetverbindung langsam ist, kann dies zu einer Fehlermeldung führen. Was bedeutet, dass eine der Seiten noch nicht geladen wurde. Also haben wir Application.DoEvents eingeführt, um dies zu überwinden. Genauso kann bei der Montage NOP verwendet werden.
quelle