Können Sie sich legitime (intelligente) Verwendungen für die Änderung des Laufzeitcodes vorstellen (Programm, das zur Laufzeit seinen eigenen Code ändert)?
Moderne Betriebssysteme scheinen Programme, die dies tun, zu missbilligen, da diese Technik von Viren verwendet wurde, um eine Erkennung zu vermeiden.
Ich kann mir nur eine Art Laufzeitoptimierung vorstellen, bei der Code entfernt oder hinzugefügt wird, indem zur Laufzeit etwas bekannt wird, das zur Kompilierungszeit nicht bekannt ist.
Antworten:
Es gibt viele gültige Fälle für Codeänderungen. Das Generieren von Code zur Laufzeit kann nützlich sein für:
Manchmal wird Code zur Laufzeit in Code übersetzt (dies wird als dynamische binäre Übersetzung bezeichnet ):
Die Codeänderung kann verwendet werden, um Einschränkungen des Befehlssatzes zu umgehen:
Weitere Fälle von Codeänderungen:
quelle
Dies wurde in Computergrafiken durchgeführt, insbesondere in Software-Renderern zu Optimierungszwecken. Zur Laufzeit wird der Status vieler Parameter untersucht und eine optimierte Version des Rasterizer-Codes generiert (wodurch möglicherweise viele Bedingungen beseitigt werden), mit der Grafikprimitive, z. B. Dreiecke, viel schneller gerendert werden können.
quelle
Ein triftiger Grund ist, dass dem asm-Befehlssatz einige notwendige Befehle fehlen, die Sie selbst erstellen können. Beispiel: Auf x86 gibt es keine Möglichkeit, einen Interrupt für eine Variable in einem Register zu erstellen (z. B. Interrupt mit Interrupt-Nummer in ax). Es waren nur im Opcode codierte Konstantennummern zulässig. Mit selbstmodifizierendem Code könnte man dieses Verhalten emulieren.
quelle
Einige Compiler verwendeten es für die Initialisierung statischer Variablen, um die Kosten einer Bedingung für nachfolgende Zugriffe zu vermeiden. Mit anderen Worten, sie implementieren "Diesen Code nur einmal ausführen", indem sie diesen Code bei der ersten Ausführung mit No-Ops überschreiben.
quelle
Es gibt viele Fälle:
Die Sicherheitsmodelle einiger Betriebssysteme bedeuten, dass selbstmodifizierender Code nicht ohne Root- / Administratorrechte ausgeführt werden kann, was ihn für den allgemeinen Gebrauch unpraktisch macht.
Aus Wikipedia:
Unter solchen Betriebssystemen benötigen sogar Programme wie die Java-VM Root- / Administratorrechte, um ihren JIT-Code auszuführen. ( Weitere Informationen finden Sie unter http://en.wikipedia.org/wiki/W%5EX. )
quelle
Das Synthesis-Betriebssystem hat Ihr Programm im Wesentlichen teilweise in Bezug auf API-Aufrufe ausgewertet und den Betriebssystemcode durch die Ergebnisse ersetzt. Der Hauptvorteil ist, dass viele Fehlerprüfungen weg waren (denn wenn Ihr Programm das Betriebssystem nicht auffordert, etwas Dummes zu tun, muss es nicht überprüft werden).
Ja, das ist ein Beispiel für die Laufzeitoptimierung.
quelle
Vor vielen Jahren habe ich einen Morgen damit verbracht, selbstmodifizierenden Code zu debuggen. Eine Anweisung hat die Zieladresse der folgenden Anweisung geändert, dh ich habe eine Zweigadresse berechnet. Es wurde in Assemblersprache geschrieben und funktionierte perfekt, als ich das Programm einzeln durchging. Aber als ich das Programm ausführte, schlug es fehl. Schließlich stellte ich fest, dass die Maschine 2 Anweisungen aus dem Speicher abrief und (da die Anweisungen im Speicher angeordnet waren) die Anweisung, die ich änderte, bereits abgerufen worden war und die Maschine daher die unveränderte (falsche) Version der Anweisung ausführte. Natürlich habe ich beim Debuggen immer nur eine Anweisung gleichzeitig ausgeführt.
Mein Punkt, selbstmodifizierender Code kann beim Testen / Debuggen extrem unangenehm sein und hat oft versteckte Annahmen über das Verhalten der Maschine (sei es Hardware oder virtuell). Darüber hinaus könnte das System niemals Codepages zwischen den verschiedenen Threads / Prozessen teilen, die auf den (jetzt) Multi-Core-Maschinen ausgeführt werden. Dies beeinträchtigt viele der Vorteile des virtuellen Speichers usw. Es würde auch Zweigoptimierungen auf Hardwareebene ungültig machen.
(Hinweis - Ich habe JIT nicht in die Kategorie des selbstmodifizierenden Codes aufgenommen. JIT übersetzt von einer Darstellung des Codes in eine alternative Darstellung, es ändert den Code nicht.)
Alles in allem ist es nur eine schlechte Idee - wirklich ordentlich, wirklich dunkel, aber wirklich schlecht.
Natürlich - wenn Sie nur 8080 und ~ 512 Byte Speicher haben, müssen Sie möglicherweise auf solche Praktiken zurückgreifen.
quelle
Aus Sicht eines Betriebssystemkerns führt jede Just In Time Compiler- und Linker-Laufzeit eine Selbständerung des Programmtextes durch. Prominentes Beispiel wäre Googles V8 ECMA Script Interpreter.
quelle
Ein weiterer Grund für selbstmodifizierenden Code (eigentlich ein "selbstgenerierender" Code) ist die Implementierung eines Just-In-Time-Kompilierungsmechanismus für die Leistung. Beispielsweise kann ein Programm, das einen algebrischen Ausdruck liest und ihn anhand einer Reihe von Eingabeparametern berechnet, den Ausdruck in Maschinencode konvertieren, bevor die Berechnung angegeben wird.
quelle
Sie kennen die alte Kastanie, dass es keinen logischen Unterschied zwischen Hardware und Software gibt ... man kann auch sagen, dass es keinen logischen Unterschied zwischen Code und Daten gibt.
Was ist selbstmodifizierender Code? Code, der Werte in den Ausführungsstrom einfügt, damit er nicht als Daten, sondern als Befehl interpretiert werden kann. Sicher gibt es den theoretischen Standpunkt in funktionalen Sprachen, dass es wirklich keinen Unterschied gibt. Ich sage zu e kann dies auf einfache Weise in imperativen Sprachen und Compilern / Interpreten tun, ohne die Annahme eines gleichen Status.
Ich beziehe mich auf den praktischen Sinne, dass Daten Programmausführungspfade verändern können (in gewissem Sinne ist dies äußerst offensichtlich). Ich denke an so etwas wie einen Compiler-Compiler, der eine Tabelle (ein Array von Daten) erstellt, die man beim Parsen durchläuft, von Status zu Status wechselt (und auch andere Variablen ändert), genau wie ein Programm von Befehl zu Befehl wechselt , Variablen im Prozess ändern.
Selbst in dem üblichen Fall, in dem ein Compiler Codebereich erstellt und auf einen vollständig separaten Datenbereich (den Heap) verweist, können die Daten dennoch geändert werden, um den Ausführungspfad explizit zu ändern.
quelle
Ich habe ein Programm implementiert, das Evolution verwendet, um den besten Algorithmus zu erstellen. Es wurde selbstmodifizierender Code verwendet, um den DNA-Entwurf zu modifizieren.
quelle
Ein Anwendungsfall ist die EICAR-Testdatei , eine legitime ausführbare DOS-COM-Datei zum Testen von Antivirenprogrammen.
Es muss eine Selbstcodemodifikation verwendet werden, da die ausführbare Datei nur druckbare / typisierbare ASCII-Zeichen im Bereich [21h-60h, 7Bh-7Dh] enthalten darf, wodurch die Anzahl der codierbaren Anweisungen erheblich begrenzt wird
Die Details werden hier erklärt
Es wird auch für das Versenden von Gleitkommaoperationen unter DOS verwendet
Einige Compiler senden
CD xx
mit xx im Bereich von 0x34-0x3B anstelle von x87-Gleitkommaanweisungen. Da diesCD
der Opcode für denint
Befehl ist, springt er in den Interrupt 34h-3Bh und emuliert diesen Befehl in der Software, wenn der x87-Coprozessor nicht verfügbar ist. Andernfalls ersetzt der Interrupt-Handler diese 2 Bytes durch,9B Dx
sodass spätere Ausführungen direkt von x87 ohne Emulation verarbeitet werden.Was ist das Protokoll für die x87-Gleitkommaemulation unter MS-DOS?
quelle
Der Linux-Kernel verfügt über ladbare Kernel-Module, die genau das tun.
Emacs hat auch diese Fähigkeit und ich benutze sie die ganze Zeit.
Alles, was eine dynamische Plugin-Architektur unterstützt, ändert im Wesentlichen den Code zur Laufzeit.
quelle
Ich führe statistische Analysen für eine ständig aktualisierte Datenbank durch. Mein statistisches Modell wird jedes Mal geschrieben und neu geschrieben, wenn der Code ausgeführt wird, um neue Daten aufzunehmen, die verfügbar werden.
quelle
Das Szenario, in dem dies verwendet werden kann, ist ein Lernprogramm. In Reaktion auf Benutzereingaben lernt das Programm einen neuen Algorithmus:
Es gibt eine Frage, wie das in Java gemacht werden kann: Welche Möglichkeiten gibt es, um Java-Code selbst zu ändern?
quelle
Die beste Version davon sind möglicherweise Lisp-Makros. Im Gegensatz zu C-Makros, die nur ein Präprozessor sind, können Sie mit Lisp jederzeit auf die gesamte Programmiersprache zugreifen. Dies ist ungefähr die mächtigste Funktion in Lisp und existiert in keiner anderen Sprache.
Ich bin kein Experte, aber bringen Sie einen der Lisp-Typen dazu, darüber zu sprechen! Es gibt einen Grund, warum sie sagen, dass Lisp die mächtigste Sprache ist und die klugen Leute nein, dass sie wahrscheinlich Recht haben.
quelle