Selbstmodifizierender Code

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche
Dieser Artikel oder Abschnitt bedarf einer Überarbeitung. Näheres ist auf der Diskussionsseite angegeben. Hilf mit, ihn zu verbessern, und entferne anschließend diese Markierung.

Mit der Bezeichnung Selbstmodifizierender Code (engl: Self Modifying Code) wird ein Abschnitt eines Computerprogramms bezeichnet, das zur Lösung der Programmaufgabe Teile des eigenen Programmcodes während der Ausführung gezielt verändert. Unter der Bezeichnung „freier Rechenplan“ hatte schon Konrad Zuse selbstmodifizierenden Code als Möglichkeit in die von ihm entworfene Programmiersprache Plankalkül aufgenommen.

Das Programm muss dabei in der Lage sein, im Maschinencode bestimmte Befehle durch sinnvolle andere Maschinenbefehle zu ersetzen. Bei höheren Programmiersprachen (z. B. APL) manipuliert das Programm den Quellcode als Zeichenkette (text string).

Selbstmodifizierender Code kann da verwendet werden, wo es möglich ist, mehrere, nur an wenigen Stellen unterschiedliche Programmteile zu einem einzigen zusammenzufassen.

Die Methode, Code sich selbst modifizieren zu lassen, stammt hauptsächlich aus einer Zeit, in der Ressourcen (CPU-Zeit, Speicher) noch sehr knappe Güter waren – es wurde also oftmals eine Optimierung des Laufzeitverhaltens oder Speicherverbrauchs angestrebt. Beides ist mittlerweile nur noch äußerst selten notwendig (z. B. beim „Retro computing“, wenn also auf sehr alten Systemen programmiert wird). Ein anderer Grund zur Selbstmodifikation waren Kopierschutzverfahren. In Anbetracht der historischen Motivationen zum Schreiben von selbstmodifizierendem Code sollte das Vorhandensein von solchem Code nicht alleine nach modernen Maßstäben zur Bemessung von Codequalität bewertet werden, sondern auch immer die (historischen und/oder technischen) Umstände berücksichtigt werden.

Die Veränderung des Programmcodes ist nur in einer Von-Neumann-Architektur möglich, wo Programm und Daten denselben Adressraum teilen. In Prozessoren mit Harvard-Architektur ist das Modifizieren von Programmcode während der Laufzeit nicht vorgesehen, und da ein normales Programm keinen Compiler enthält, muss die Modifikation direkt in Maschinensprache ausgeführt werden. Bei höheren Programmiersprachen sind in der Regel interpretierende (also nicht compilierende) Systeme notwendig. Aus diesen beiden Gründen ist eine Portierung von selbstmodifizierendem Code auf einen beliebigen Prozessor fast nicht möglich.

Der selbstmodifizierende Code eines Programms hat nichts mit Lernen oder der Verbesserung eines Programmes zu tun. Selbstmodifizierende Programme, die die Hochsprache des Programms modifizieren, sind in der Zukunft möglicherweise hilfreich, die Maschinenintelligenz zu steigern.

Beispiele[Bearbeiten]

Videospiel[Bearbeiten]

In einem Videotennis-Spiel kann im Programmteil, das den Ball steuert, ein Inkrement-Befehl durch einen Dekrement-Befehl ersetzt werden, wenn er an die Wand prallt, dadurch wird die Bewegungsrichtung umgekehrt.

Die Bytes, die die Koordinaten des Balles beinhalten, können so im Speicher abgelegt werden, dass sie gleichzeitig als direkte Parameter eines Kommandos interpretiert werden. Man stelle sich beispielsweise einen Befehl vor, der dazu führt, dass der Ball an einer bestimmten Stelle angezeigt wird. Statt nun die beiden Argumente „X-Position“ und „Y-Position“ indirekt als Variablen anzusprechen, können sie direkt so im Speicher abgelegt sein, dass sie Teil des Befehls „Stelle Ball dar“ sind.

Kombination der beiden Beispiele als Pseudo-Programm:

  • wenn Ball an vertikale Wand geprallt ist und im Programmcode „inkrementiere x-Koordinate“ steht, dann schreibe an die entsprechende Speicherstelle den Befehl für „dekrementiere x-Koordinate“ und überspringe den nächsten Befehl
  • wenn Ball an vertikale Wand geprallt ist und im Programmcode „dekrementiere x-Koordinate“ steht, dann schreibe an die entsprechende Speicherstelle den Befehl für „inkrementiere x-Koordinate“
  • wenn Ball an horizontale Wand geprallt ist und im Programmcode „inkrementiere y-Koordinate“ steht, dann schreibe an die entsprechende Speicherstelle den Befehl für „dekrementiere y-Koordinate“ und überspringe den nächsten Befehl
  • wenn Ball an horizontale Wand geprallt ist und im Programmcode „dekrementiere y-Koordinate“ steht, dann schreibe an die entsprechende Speicherstelle den Befehl für „inkrementiere y-Koordinate“
  • inkrementiere x-Koordinate des Balldarstellungsbefehls
  • inkrementiere y-Koordinate des Balldarstellungsbefehls
  • Stelle den Ball dar an Position 1, 1 und fange von vorne an

Sowohl die beiden Befehle zum Inkrementieren als auch die Koordinaten „1,1“ stellen in diesem Beispiel lediglich Anfangswerte dar, die vom Programm selbst modifiziert werden.

Mathematikprogramm[Bearbeiten]

In Microsoft BASIC auf Commodore Computern (z.B. PET , VC 20 , C64)) war es über ein kurzzeitiges Anhalten eines Programms effektiv möglich, eine über den INPUT Befehl im Programm abgefragte Benutzerfunktion (z. B. "SIN(X)") an den Programmeditor zu übergeben, der eine Zeile im BASIC-Programm entsprechend veränderte, worauf das Programm ohne Verlust der Variableninformation (mittels GOTO-Befehl) wieder fortgesetzt wurde und die neue Zeile für Berechnungen nutzen konnte. Dies geschah durch Ausdruck der gewünschten neuen Programmzeile in der obersten Bildschirmzeile (unter Benutzung des Microsoft BASIC Ausdrucks "DEF FN") und Ausgabe des Befehls "GOTO xxx" zum Rücksprung ins Programm in der zweiten Bildschirmzeile. Füllen des Tastaturpuffers mit den Zeichen HOME und mehreren Steuerzeichen für Wagenrücklauf sorgte dafür, dass nach dem STOP-Befehl der systemeigene Programmeditor die zuvor ausgegebene Programmzeile bearbeitete und bei Erreichen des GOTO-Befehls (ausgelöst durch die Wagenrücklauf-Zeichen) das BASIC-Programm wieder ausführte.

Kopierroutinen (6502 CPU)[Bearbeiten]

Eine solches Unterprogramm bekam Startadresse, Zieladresse und Größe in Byte oder Speicherseiten (je 256 Byte) übergeben. Die normale Art und Weise zu kopieren bestand darin, die Adressen in zwei Zeigern innerhalb der Zeropage zu speichern, und dann indirekt-zeropage adressierbare Lade- und Speicherbefehle mit Indexzugriff zu verwenden. Diese brauchen aber auf der 6502 CPU zwei Taktzyklen mehr als die absolut adressierbaren. Der Trick zur Steigerung der Geschwindigkeit besteht darin, absolut adressierbare Befehle zu verwenden. Bei dieser Art des selbstmodifizierenden Codes werden nicht das Indexregister und die Zeigeradressen hochgezählt sondern die Adressen im Programmcode hinter dem Opcode der absolut adressierbaren Lade- und Speicherbefehle. Damit lassen sich Kopierroutinen deutlich beschleunigen.

Vorteile[Bearbeiten]

  1. Bei bestimmten Aufgabenstellungen kann ein sehr kompaktes Programm konstruiert werden.
  2. Die gefundene Programmlösung kann elegant erscheinen.
  3. Das Programm kann vor Reverse Engineering besser geschützt werden.

Nachteile[Bearbeiten]

  1. Die Erstellung von selbstmodifizierendem Code wird von Compilern nicht unterstützt.
  2. Der Programmcode ist schwierig oder gar nicht portierbar.
  3. Der Maschinencode ist schwierig nachzuvollziehen.
  4. Der CPU-Entwurf wird deutlich komplizierter; mitunter kommt es bei anderen CPU-Versionen zu Fehlern.[Anmerkung 1]

Anmerkungen[Bearbeiten]

  1. Selbstmodifizierender Code wurde z.B. verwendet, um den Intel 8088 vom Intel 8086 zu unterscheiden, da einer eine längere Befehlspipeline besaß: Der Prozessor mit der kurzen Pipeline folgte der Änderung, der Prozessor mit der längeren Pipeline führte jedoch weiterhin den „alten“ Befehl aus, da dieser bereits in der Pipeline gespeichert war.