Zweck der NOP-Anweisung und der Align-Anweisung in der x86-Assembly


14

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?


NOP macht nichts, verbraucht aber Zyklen. Ich glaube nicht, dass Ihre Frage beantwortet werden kann, ohne den Code können wir nur raten. Nun, meine Vermutung wäre eine NOP-Folie ...
Yannis

11
NOP macht tatsächlich etwas. Es erhöht den Anweisungszeiger.
EricSchaefer

Antworten:


37

Oft NOPwird 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 NOPs hinzufügen , um die Zieladresse nach vorne zu verschieben. Wenn Sie mehrere NOPs zwischen der Zieladresse und der Sprunganweisung haben, können Sie die NOPs 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 durch someLabeldie 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 mit NOPs.

Ein anderer Anwendungsfall für den NOPUnterricht 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 zNOPoder 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 NOPBefehl besteht darin, den Code eines Programms zu ändern. Beispielsweise können Sie Teile von bedingten Sprüngen durch NOPs 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 durch NOPs 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.


2
Dies war eine wundervolle Antwort. Vielen Dank, dass Sie sich die Zeit genommen haben, dies zu erklären. Ich verstehe endlich!
Alvonellos

Bestimmte Echtzeitsysteme (SPS) ermöglichen es Ihnen, eine neue Logik in ein vorhandenes Programm zu "patchen", während es ausgeführt wird. Diese Systeme hinterlassen NOPs vor jedem kleinen Teil der Logik, sodass Sie die NOP mit einem Sprung zu der neuen Logik, die Sie einfügen, überschreiben können. Am Ende der neuen Logik wird zum Ende der ursprünglichen Logik gesprungen, die Sie ersetzen. Die neue Logik hat auch eine NOP vor sich, so dass Sie auch die neue Logik ersetzen können.
Scott Whitlock

10

Ein NOP kann in einem Verzögerungsschlitz verwendet werden, wenn keine andere Anweisung neu angeordnet werden kann, um dort abgelegt zu werden.

lw   v0,4(v1)
jr   v0

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:

lw   v0,4(v1)
nop
jr   v0
nop

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:

Was kann in den Delay Slot gestellt werden?

  • Einige nützliche Anweisungen, die ausgeführt werden sollten, unabhängig davon, ob Sie verzweigen oder nicht.
  • Einige Befehle, die nützlich sind, funktionieren nur beim Verzweigen (oder wenn Sie nicht verzweigen), schaden jedoch nicht, wenn sie im anderen Fall ausgeführt werden.
  • Wenn alles andere fehlschlägt, ein NOP-Befehl

Was DARF NICHT in den Delay Slot gestellt werden?

  • Alles, was den CC festlegt, von dem die Entscheidung für den Zweig abhängt. Der Verzweigungsbefehl entscheidet, ob sofort verzweigt werden soll oder nicht, aber er führt die Verzweigung erst nach dem Verzögerungsbefehl aus. (Nur die Filiale wird verzögert, nicht die Entscheidung.)
  • Eine weitere Verzweigungsanweisung. (Was passiert, wenn Sie dies nicht einmal definiert! Das Ergebnis ist unvorhersehbar!)
  • Eine "Set" -Anweisung. Dies sind wirklich zwei Befehle, nicht einer, und nur die Hälfte davon befindet sich im Delay-Slot. (Der Assembler warnt Sie davor.)

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.


4
Beachten Sie, dass die Frage mit x86 gekennzeichnet ist und x86 keine Verzögerungsslots hat. Wird es auch nie, da es sich um eine brechende Veränderung handelt.
MSalters

6

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.


Es ist nicht genau so, dass der Block ausgerichtet werden muss, sondern dass Sie nicht die letzten paar Bytes des vorherigen Blocks abrufen müssen. Es ist also in Ordnung zu springen 0x1002, da der ausgerichtete Block von 16B immer noch 14 Byte Anweisungen enthält, die die Zieladresse enthalten, aber nicht in Ordnung zu springen 0x099D.
Peter Cordes

3

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.


1
In der Regel werden für lange Zeiträume, z. B. 1 Sekunde, Timer verwendet. NOPS werden für Epochen innerhalb einer Größenordnung der Uhr verwendet - Nanosekunden und Mikrosekunden.
Mattnz

Dies ist nur auf einem Mikrocontroller sinnvoll, nicht auf einem modernen x86. Der meiste x86-Code sättigt die Pipeline-Breite moderner superskalarer, nicht in der richtigen Reihenfolge angeordneter CPUs nicht, sodass das Hinzufügen einer NOP zwischen den Befehlen in den meisten Codes nur eine geringe Auswirkung hat (ich würde die Zahl für "durchschnittlichen" Code als wahrscheinlich erachten) 5 bis 20% für die Verdoppelung der Anzahl von Befehlen, wobei ein Teil des Codes keine Verlangsamung zeigt, aber ein paar enge Schleifen fast eine zweifache Verlangsamung anzeigen.) Wie auch immer, bei verkrustetem alten x86-Code wurde der loopBefehl traditionell für Verzögerungsschleifen verwendet , nicht für NOPs.
Peter Cordes

3

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.


3

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


    STI
    CLI

Dies wird ausstehende Interrupts nicht verarbeiten, da unter Berufung auf Intel:

Nachdem das IF-Flag gesetzt ist, beginnt der Prozessor auf externe, maskierbare Interrupts zu reagieren, nachdem der nächste Befehl ausgeführt wurde.

das muss also mindestens so umgeschrieben werden:


    STI
    NOP
    CLI

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.)


-2

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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.