Was ist der Zweck des CIL-NOP-Opcodes?

82

Ich gehe durch MSIL und stelle fest, dass die MSIL viele NOP- Anweisungen enthält.

Der MSDN-Artikel besagt, dass sie keine Maßnahmen ergreifen und verwendet werden, um Speicherplatz zu füllen, wenn der Opcode gepatcht ist. Sie werden in Debug-Builds viel häufiger verwendet als in Release-Builds.

Ich weiß, dass diese Art von Anweisungen in Assemblersprachen verwendet werden, um spätere Anweisungen auszurichten, aber warum werden MSIL-Nops in MSIL benötigt?

(Anmerkung des Herausgebers: Die akzeptierte Antwort bezieht sich auf Maschinencode-NOPs, nicht auf MSIL / CIL-NOPs, nach denen die Frage ursprünglich gestellt wurde.)

Dan Goldstein
quelle
19
In diesen Antworten besteht eine große Verwirrung zwischen dem MSIL-NOP-Befehl, der vom Sprachcompiler in die Assembly ausgegeben wird, und den x86-NOP-Anweisungen (auf dieser Plattform), die vom JIT-Compiler ausgegeben werden, wenn die Assembly ausgeführt wird. [Tatsächlich handelt es sich bei der akzeptierten Antwort um x86-Nops und hat keine Beziehung zu MSIL.] Idealerweise sollte diese Frage in zwei verschiedene Fragen aufgeteilt werden: Zweck von MSIL :: nop? und Zweck der nativen Plattform nop?
Steve Steiner

Antworten:

107

NOPs dienen mehreren Zwecken:

  • Sie ermöglichen es dem Debugger, einen Haltepunkt in einer Zeile zu platzieren, selbst wenn dieser im generierten Code mit anderen kombiniert wird.
  • Dadurch kann der Lader einen Sprung mit einem Zielversatz unterschiedlicher Größe patchen.
  • Damit kann ein Codeblock an einer bestimmten Grenze ausgerichtet werden, was für das Caching gut sein kann.
  • Es ermöglicht eine inkrementelle Verknüpfung zum Überschreiben von Codeabschnitten mit einem Aufruf eines neuen Abschnitts, ohne sich um die Änderung der Größe der Gesamtfunktion kümmern zu müssen.
Anthony Williams
quelle
Aus Wikipedia : "Ein NOP wird am häufigsten zu Zeitsteuerungszwecken verwendet, um die Speicherausrichtung zu erzwingen, Gefahren zu vermeiden, einen Verzweigungsverzögerungsschlitz zu belegen, eine vorhandene Anweisung wie einen Sprung ungültig zu machen oder einen Platzhalter zu ersetzen durch aktive Anweisungen später in der Programmentwicklung (oder das Entfernen entfernter Anweisungen, wenn das Refactoring problematisch oder zeitaufwändig wäre). In einigen Fällen kann ein NOP geringfügige Nebenwirkungen haben, z. B. bei der Motorola 68000-Prozessorserie, dem NOP Opcode bewirkt eine Synchronisation der Pipeline. "
mbomb007
10

Hier erfahren Sie , wie MSIL / CIL- Nops ( kein x86-Maschinencode)nop ) beim Debuggen verwendet:

Nops werden von Sprachcompilern (C #, VB usw.) verwendet, um implizite Sequenzpunkte zu definieren. Diese teilen dem JIT-Compiler mit, wo sichergestellt werden soll, dass Maschinenanweisungen wieder IL-Anweisungen zugeordnet werden können.

In Rick Byers Blogeintrag zu DebuggingModes.IgnoreSymbolStoreSequencePoints werden einige Details erläutert.

C # platziert Nops auch nach Anrufanweisungen, sodass der Ort der Rückgabestelle in der Quelle eher der Anruf als die Leitung nach dem Anruf ist.

Steve Steiner
quelle
C # platziert Nops auch nach Anrufanweisungen, sodass der Ort der Rückgabestelle in der Quelle eher der Anruf als die Leitung nach dem Anruf ist. Ich bin mir nicht sicher, ob ich das bekomme. Haben Sie eine Referenz zur Verfügung?
Benutzer492238
8

Es bietet die Möglichkeit für zeilenbasierte Markierungen (z. B. Haltepunkte) im Code, bei denen ein Release-Build keine ausgeben würde.

Harpo
quelle
Müssen Haltepunkte auf einem NOP liegen? Warum nicht einfach einen Haltepunkt auf den regulären Opcode setzen?
Dan Goldstein
4
Viele reguläre Opcodes werden in Release-Builds optimiert. Das würde Ihre Haltepunkte durcheinander bringen, es sei denn, es gibt einen Platzhalter, auf den die Haltepunkte immer noch verweisen würden
Jimmy
1
In Debug-Builds werden sie auch verwendet, um eine Anweisung zum Unterbrechen bereitzustellen, wenn der Code keine Anweisung enthält. Zum Beispiel Klammern öffnen.
Greg D.
1
Möglicherweise möchten Sie einen Verweis auf blogs.msdn.com/oldnewthing/archive/2007/08/17/4422794.aspx als Quelle hinzufügen .
Greg D.
1
Nun, danke Greg und Jimmy, dass sie daraus eine tatsächliche Antwort gemacht haben.
Harpo
6

Bei der Optimierung für bestimmte Prozessoren oder Architekturen kann Code auch schneller ausgeführt werden:

Prozessoren verwenden lange Zeit mehrere Pipelines, die ungefähr parallel arbeiten, sodass zwei unabhängige Anweisungen gleichzeitig ausgeführt werden können. Auf einem einfachen Prozessor mit zwei Pipelines unterstützt der erste möglicherweise alle Anweisungen, während der zweite nur eine Teilmenge unterstützt. Außerdem gibt es zwischen den Pipelines einige Verzögerungen, wenn auf das Ergebnis einer vorherigen Anweisung gewartet werden muss, die noch nicht abgeschlossen ist.

Unter diesen Umständen kann ein dedizierter NOP den nächsten Befehl in eine bestimmte Pipeline zwingen (den ersten oder nicht den ersten) und die Paarung der folgenden Befehle verbessern, so dass die Kosten des NOP mehr als amortisiert werden.

Peterchen
quelle
5

Kumpel! No-op ist großartig! Es ist eine Anweisung, die nichts anderes tut, als Zeit zu verbrauchen. In trüben dunklen Zeiten würden Sie es verwenden, um Mikroanpassungen im Timing in kritischen Schleifen vorzunehmen oder, was noch wichtiger ist, als Füllstoff für selbstmodifizierenden Code.

Sockel
quelle
Ich verstehe seine Verwendung, wenn es direkt auf der Hardware ausgeführt wird, aber MSIL ist JITed.
Dan Goldstein
MSIL ist nur auf Systemen mit JIT JITed - MSIL erfordert kein JIT.
Sockel
5

In einem Prozessor, für den ich kürzlich (vier Jahre lang) gearbeitet habe, wurde NOP verwendet, um sicherzustellen, dass der vorherige Vorgang beendet wurde, bevor der nächste Vorgang gestartet wurde. Zum Beispiel:

Ladewert zum Registrieren (dauert 8 Zyklen) nop 8 addiere 1 zum Registrieren

Dadurch wurde sichergestellt, dass das Register vor dem Hinzufügen den richtigen Wert hatte.

Eine andere Verwendung bestand darin, Ausführungseinheiten auszufüllen, beispielsweise die Interrupt-Vektoren, die eine bestimmte Größe (32 Bytes) haben mussten, da die Adresse für Vektor 0 beispielsweise 0 für Vektor 1 0x20 usw. war, sodass der Compiler dort NOPs einfügte, wenn erforderlich.

Makis
quelle
4

Sie könnten sie verwenden, um das Bearbeiten und Fortfahren beim Debuggen zu unterstützen. Es bietet dem Debugger Raum zum Arbeiten, um den alten Code durch neuen zu ersetzen, ohne Offsets usw. zu ändern.

Rob Walker
quelle
4
Ich habe die Debugger-Unterstützung in VS for Edit implementiert und fortgefahren (ich habe weder die CLR- noch die Compiler-Teile implementiert). Nops sind Teil der Geschichte, um korrekte Zuordnungen von der alten zur neuen Version einer Methode sicherzustellen (insbesondere bei Sprüngen aus dem Ausnahmebehandlungscode). Das Ersetzen der MSIL erfolgt jedoch auf der Basis einer ganzen Funktion. Für CLR-verwalteten Code ist es nicht erforderlich, im msil Platz zu lassen, um diesen Teil auszuführen. Was Sie hier sagen, ist in Bezug auf native Bearbeiten korrekt und fahren Sie fort.
Steve Steiner
4

Eine etwas unorthodoxe Verwendung sind NOP-Slides , die in Pufferüberlauf-Exploits verwendet werden.

mdm
quelle
suchte danach :)
Suraj Jain
Korrigieren Sie Ihren Link, es funktioniert nicht, bitte stellen Sie immer sicher, dass Sie einen Webarchiv-Link eingereicht haben, da diese 404 nicht gefunden werden.
Suraj Jain
Derzeit arbeitet an Shellcode und wollte mehr Kontext auf NOP. Musste ein bisschen zurückgehen, aber hier ist ein Archiv: web.archive.org/web/20110124015428/http://www.phreedom.org:80/…
Saniboy
4

50 Jahre zu spät, aber hey.

Nops sind nützlich, wenn Sie Assembler-Code von Hand eingeben. Wenn Sie Code entfernen müssten, könnten Sie die alten Opcodes nicht verwenden.

Ebenso können Sie neuen Code einfügen, indem Sie einen Opcode überschreiben und woanders springen. Dort setzen Sie die überschriebenen Opcodes ein und fügen Ihren neuen Code ein. Wenn Sie fertig sind, springen Sie zurück.

Manchmal musste man die verfügbaren Werkzeuge verwenden. In einigen Fällen war dies nur ein sehr einfacher Maschinencode-Editor.

Heutzutage machen die Techniken bei Compilern überhaupt keinen Sinn mehr.

Kröte
quelle
3

Eine klassische Verwendung für sie ist, dass Ihr Debugger einer IL-Anweisung immer eine Quellcodezeile zuordnen kann.

Larry OBrien
quelle
Da msil beim Ausführen JIT-kompiliert wird, kann diese Zuordnung verloren gehen (z. B. hat der native Befehl keinen eindeutigen MSIL-Befehl). NOPs werden als Kommunikationsmechanismus vom Sprachcompiler zum JIT-Compiler verwendet, um eine solche Zuordnung beizubehalten.
Steve Steiner
3

In der Software-Cracking-Szene besteht eine klassische Methode zum Entsperren einer Anwendung darin, die Zeile, die nach dem Schlüssel oder der Registrierung oder dem Zeitraum sucht, mit einem NOP zu patchen, oder so, damit nichts unternommen wird und die Anwendung einfach weiter gestartet wird, als ob sie registriert wäre .

shoosh
quelle
5
Ich bin mir ziemlich sicher, dass keine No-Op-Anweisungen erfunden wurden, um Menschen bei der Piraten-Software zu helfen :-)
Simon Howard
3

Ich habe auch NOPs in Code gesehen, der sich selbst modifiziert, um zu verschleiern, was er als Platzhalter tut (sehr alter Kopierschutz).

TheMarko
quelle
3

Wie ddaa sagte, können Sie mit nops die Varianz im Stapel berücksichtigen, sodass beim Überschreiben der Rücksprungadresse zum nop-Schlitten (viele nops hintereinander) gesprungen wird und dann der ausführbare Code korrekt getroffen wird, anstatt zu einigen zu springen Byte in der Anweisung, die nicht der Anfang ist.

Alex Gartrell
quelle
1

Sie ermöglichen es dem Linker, einen längeren Befehl (normalerweise Weitsprung) durch einen kürzeren Befehl (Kurzsprung) zu ersetzen. Das NOP benötigt zusätzlichen Platz - der Code konnte nicht verschoben werden, da andere Sprünge nicht mehr funktionieren würden. Dies geschieht zur Verbindungszeit, sodass der Compiler nicht wissen kann, ob ein langer oder kurzer Sprung angemessen wäre.

Zumindest ist das eine ihrer traditionellen Anwendungen.

MarkR
quelle
1

Dies ist keine Antwort auf Ihre spezielle Frage, aber früher konnten Sie einen NOP verwenden, um einen Zweigverzögerungsschlitz zu füllen , wenn Sie es nicht schaffen, ihn mit einer ansonsten nützlichen Anweisung zu füllen.

Chris Conway
quelle
1

Richten die .NET-Compiler die MSIL-Ausgabe aus? Ich würde mir vorstellen, dass es nützlich sein könnte, um den Zugriff auf die IL zu beschleunigen ... Meines Wissens nach ist es auch portabel und auf einigen anderen Hardwareplattformen sind ausgerichtete Zugriffe erforderlich.

Brian Knoblauch
quelle
1

Die erste Assembly, die ich gelernt habe, war SPARC, daher bin ich mit dem Verzweigungsverzögerungsschlitz vertraut. Wenn Sie ihn nicht mit einer anderen Anweisung füllen können, normalerweise der Anweisung, die Sie über die Verzweigungsanweisung setzen oder einen Zähler in Schleifen erhöhen möchten, die Sie verwenden ein NOP.

Ich bin nicht mit Cracken vertraut, aber ich denke, es ist üblich, den Stapel mit NOP zu überschreiben, sodass Sie nicht genau berechnen müssen, wo Ihre böswillige Funktion beginnt.

Sonntag
quelle
1

Ich habe NOPs verwendet, um die nach Eingabe eines ISR akkumulierte Latenz automatisch anzupassen. Sehr praktisch, um das Timing auf den Punkt zu bringen.


quelle
-1

nopwird bei der Speicherbeschädigung nützlich sein, um Nutzdaten auszunutzen. Natürlich nopist ähnlich wiexchg eax, eax

Nicht Star
quelle
Eine andere Antwort erwähnte bereits NOP-Folien für Code-Injection-Exploits. So auch eine andere Antwort auf dieselbe Frage. Diese Antwort erwähnt nicht, wie nützlich sie sind, und es ist nicht offensichtlich, wenn Sie es noch nicht wissen.
Peter Cordes