Alles illegal!

Die illegalen OpCodes des C64

C64 Studio & AMCE

Da sind wir also, bei den „sagenumwobenen“ illegalen OpCodes. Eigentlich ist dies kein Thema für Einsteiger, aber ich möchte in den Grundlagen einmal alle Befehle erwähnt haben. Als erstes sollte ich aber darauf hinweisen und warnen (das mache ich hier noch häufiger), dass die hier aufgeführten Befehle teilweise nicht ganz unproblematisch sind. Ihr solltet sie also mit Bedacht einsetzen.

Wir haben jetzt alle 56 Befehle des Prozessors kennengelernt, die offiziell zur Verfügung stehen. Wie wir aber wissen, besitzt der C64 einen 8-Bit-Prozessor, somit sind theoretisch 256 unterschiedliche Befehle möglich. Bei unseren 56 bisherigen kommen wir, wenn man die unterschiedlichen Adressierungsarten mit einbezieht, auf 151 verwendete Bytes. Bleiben also noch 105 mögliche Befehle übrig. Dabei handelt es sich meistens um eine Kombination aus mehreren offiziellen Befehlen (z. B. erst ein ASL und dann ein ORA). Diese mehr oder weniger sinnvollen Befehle, werden häufig als ‚illegal‚, ‚extra‚ oder ‚undocumented‚ bezeichnet. Einige Befehle haben sogar mehrere OpCodes, obwohl es sich immer um denselben Befehl handelt, dort wird also keine andere Adressierungsart verwendet.

Hardware-Exkurs: Wieso gibt es solche OpCodes überhaupt?

Befehlsdekodierung

Ich will hier nicht wirklich in die Details gehen, es soll nur eine kurze oberflächliche Erklärung werden.
Der 6510/6502 benutzt keine MicrocodesinfoInfoDie Befehle der CPU sind nicht fest verdrahtet, wie z. B. bei RISC-Prozessoren, sondern in einem ROM gespeichert., um die Befehle zu dekodieren, wie andere CPUs. Das „Instruction-Register“ und der Zyklus aus der „Timing Generation Logic“ werden im „Decode ROM“ zusammengefasst. Diese besteht aus 130 Zeilen zu je 21 Bits. Jede Zeile enthält nun Bit-Muster, die zur Identifikation der Befehle dienen. Dabei wird festgelegt, welche Bits im Muster 0 und welche 1 sein müssen und bei welchem Zyklus das stattfindet. Trifft ein Muster zu, so liefert die Zeile eine 1. Jeder Befehl ist hier also mit einem Bit-Muster hinterlegt, verwendet man einen illegalen OpCode, dann kann dieser auch dekodiert werden. Aber durch die ungeplanten Bit-Muster ist es nun möglich, dass mehrere Zeilen eine 1 liefern und wir so die eben erwähnte Kombination von bekannten Befehlen erhalten.

 

Wie kann man die Befehle nun verwenden?

Zunächst mal ist wichtig, dass das System auf dem das Programm laufen soll, diese Befehle versteht. Bei einem echten C64 ist das der Fall, auch die meisten Emulatoren können diese Befehle umsetzen, aber das muss nicht immer so sein.
Für uns als Programmierer ist es natürlich am schönsten, wenn unser Assembler diese Befehle kennt. Auch für die illegalen-Befehle gibt es Mnemonics, da diese aber nicht offiziell sind, gibt es teilweise unterschiedlichen Namen für einen Befehl. Hier muss man ggf. schauen, welche der verwendete Assembler versteht. Sollte der Assembler mit den Befehlen nichts anfangen können, dann müsst ihr wohl oder übel die gewünschten Befehle direkt als Bytes eingeben. Je nach Assembler ist es evtl. auch noch möglich sich eigene Macros für die Befehle zu bauen. Das C64 Studio versteht einen Großteil der hier erklärten Befehle, ACME einige weniger. Beim Einsatz von ACME müsst ihr als CPU-Typ außerdem 6510 angeben, sonst gibt es Fehler bei der Assemblierung. Fügt zu Beginn einfach ein !cpu 6510 in den Quellcode ein. Beim C64 Studio könnt ihr dies zur Sicherheit auch machen.

Der Turbo Assembler kennt die Befehle nicht, dort müsst ihr mit .byte arbeiten.

Die Befehle sind zum Teil echt strange, ich versuche erst gar nicht sinnvolle Beispiele anzubieten. Meiner Meinung nach sollte man diese Befehle nur im äußersten Notfall einsetzen, z. B. wenn sie dringend benötigten Platz sparen oder in einer zeitkritischen Routine einen Vorteil bringen.

Diese Liste wurde anhand der Infos auf verschiedenen Internetseiten und mit dem Buch „The Complete Commodore Inner Space Anthology“ erstellt. Ich bin trotzdem nicht ganz sicher, ob alles stimmt (z. B. habe ich bei den Taktzyklen nicht immer einen Hinweis darauf gefunden, ob und wie sich eine Überschreitung der Page-Grenze auswirkt).

Los gehts…

Genug der Vorrede, werfen wir endlich einen Blick auf die letzten Befehle des C64:

SLO bzw. ASO

SLO: ASL+ORA (alternatives Mnemonic: ASO)
SLO absolut ($0F, 3B, 6T, NZC)
SLO kombiniert ein ASL mit einem ORA. Dabei wird das, über die Adressierung zufindene Byte zunächst per ASL bitweise nach links verschoben und anschließend mit dem Akku ODER-Verknüpft.

Man könnte also auch schreiben:

Wenn ihr jetzt mal in die Mnemonics schaut, werdet ihr feststellen, dass diese beiden Befehle zusammen 6Bytes und 10TZ benötigen. Wir können mit SLO also, 3Byte Speicher und 4TZ sparen, das ist schonmal nicht schlecht. Außerdem können wir Daten über Adressierungsarten shiften, die es beim ASL nicht gibt (z. B. absolut,Y).

Übersicht der Adressierungsarten:

Sollte euer Assembler diesen Befehl nicht verstehen, könnt ihr ihn z. B. durch die angabe der Bytes !byte $0f, $2d, $08 (entspricht slo $082d) trotzdem verwenden.

RLA

RLA: ROL+AND
RLA absolut ($2F, 3B, 6T, NZC)
RLA kombiniert ein ROL mit einem AND. Dabei wird das über die Adressierung zufindene Byte zunächst per ROL bitweise nach links verschoben (von rechts wird das C-Flag eingeschoben) und anschließend mit dem Akku UND-Verknüpft.

Man könnte also auch schreiben:

Übersicht der Adressierungsarten:

SRE bzw. LSE

SRE: LSR+EOR (alternatives Mnemonic: LSE)
SRE absolut ($4F, 3B, 6T, NZC)
SRE kombiniert ein LSR mit einem EOR. Dabei wird das über die Adressierung zufindene Byte zunächst per LSR bitweise nach rechts verschoben und anschließend mit dem Akku exklusiv-ODER-Verknüpft.

Man könnte also auch schreiben:

Übersicht der Adressierungsarten:

RRA

RRA: ROR+ADC
RRA absolut ($6F, 3B, 6T, NZ)
RRA kombiniert ein ROR mit einem ADC. Dabei wird das über die Adressierung zufindene Byte zunächst per ROR bitweise nach rechts verschoben (das herausfallende Bit landet im C-Flag) und wird anschließend durch ADC mit dem Akku addiert (dabei wird natürlich auch das Carry-Flag vom ROR beachtet).

Man könnte also auch schreiben:

Da ADC nach dem ROR ausgeführt wird, ist das C-Flag also abhängig von der Addition, wir bekommen nach dem RRA also kein Carry-Flag für das mittels ROR hinausgeschobene Bit!!

Übersicht der Adressierungsarten:

SAX bzw. AXS

SAX: Store Akku AND X-Register to memory (alternatives Mnemonic: AXS)
SAX absolut ($8F, 3B, 4T, <keine>)
SAX funktioniert so: Die Inhalte von Akku und X-Register werden UND-Verknüpft, aber OHNE eines der beiden Register zu ändern! Das Ergbnis wird dann an der angegebenen Adresse abgelegt. Die Flags im Statusregister (SR) bleiben ebenfalls unverändert!

Wollte man das mit normalen Befehlen nachbilden, dann bräuchte man eine ganze Menge davon:

Übersicht der Adressierungsarten:

LAX

LAX: LDA+LDX
LAX absolut ($AF, 3B, 4T, NZ)
LAX lädt das Byte, von der angegebenen Adresse, gleichzeitig in den Akku und ins X-Register.

Mit regulären Befehlen sehe das so aus:

Übersicht der Adressierungsarten:

Hier wird bei zwei Adressierungsarten ein extra Taktzyklus benötigt, falls die Page-Grenze überschritten wird.

DCP bzw. DCM

DCP: DEC+CMP (alternatives Mnemonic: DCM)
DCP absolut ($CF, 3B, 4T, NZC)
DCP verringert das Byte, an der angegebenen Speicherstelle, mit DEC und vergleicht dieses dann mittels CMP mit dem Akku.

Mit regulären Befehlen sehe das so aus:

Übersicht der Adressierungsarten:

ISC bzw. INS

ISC: INC+SBC (alternatives Mnemonic: INS)
ISC absolut ($EF, 3B, 6T, NZC)
ISC erhöht das Byte an der angegebenen Speicherstelle, mit INC und subtrahiert es dann mit SBC vom Akku.

Mit regulären Befehlen sehe das so aus:

Übersicht der Adressierungsarten:

ALR

ALR: AND+LSR
ALR unmittelbar ($4B, 2B, 2T, NZC)
ALR führt per AND eine UND-Verknüpfung mit dem angegebenen Wert und dem Akku durch. Abschließend wird per LSR der Akku bitweise nach rechts verschoben.

Mit regulären Befehlen sehe das so aus:

Hier gibt es nur die eben gezeigte Adressierungsart.

ACME kennt diesen Befehl nicht!

ARR

ARR: AND+ROR
ARR unmittelbar ($6B, 2B, 2T, NZC)
ARR führt per AND eine UND-Verknüpfung mit dem angegebenen Wert und dem Akku durch. Abschließend wird per ROR der Akku bitweise nach rechts rotiert.

Mit regulären Befehlen sehe das so aus:

Hier gibt es ebenfalls nur die eben gezeigte Adressierungsart.

XAA

XAA: TXA+AND
XAA unmittelbar ($8B, 2B, 2T, NZ)
XAA kopiert den Inhalt des X-Register mit TAX in den Akku und führt dann eine UND-Verknüpfung mit dem angegebenen Wert und dem Akku durch.

Mit regulären Befehlen sehe das so aus:

Wieder gibt es nur die eben gezeigte Adressierungsart.

ACME kennt diesen Befehl nicht!

Nochmal LAX, aber eigentlich OAL

LAX (eigentlich OAL ORA+AND+LDX )
LAX unmittelbar ($AB, 2B, 2T, NZ)
Jetzt wird es langsam echt verrückt. 😉
Diese Version von LAX führt zuerst per ORA eine ODER-Verknüpfung mit dem Akku und dem Wert #$ee durch! Dann wird der Akku durch AND mit dem angegebenen Wert UND-Verknüpft und anschließend mittels TAX ins X-Register kopiert.

Mit regulären Befehlen sehe das so aus:

Wieder gibt es nur die eben gezeigte Adressierungsart. Das C64 Studio kennt diesen Befehl, als LAX. Andere Assembler kennen OAL, was eigentlich auch besser ist, da sich das Verhalten von den anderen LAX-Befehlen unterscheidet.

ACME kennt übrigens keinen der beiden Befehle!

AXS

AXS: AND+TAX+Subtract (oder SAX)
AXS unmittelbar ($CB, 2B, 2T, NZ)
Hier wird zuerst eine UND-Verknüfung zwischen dem Akku und dem X-Register vorgenommen. Dann wird vom Ergebnis der UND-Verknüpfung der angegebenen Wert abgezogen (ohne das Carry-Flag zu beachten!) und das Ergebnis im X-Register gespeichert. Das C-Flag kann aber durch die Subtraktion wiederum gesetzt werden, das OVerflow-Flag bleibt allerdings, ebenso wie der Akku, unverändert!

Ein Nachbau ist nur mit Hilfe einer Speicheradresse möglich, nehmen wir $fb auf der Zero-Page ( AXS kommt natürlich ohne $fb aus):

Wieder gibt es nur die eben gezeigte Adressierungsart.

ACME kennt auch diesen Befehl nicht!

SKB

SKB: SKip next Byte (nächstes Byte überspringen)
SKB implizit ($80, 1B, 2-4T, <keine>)
Der Befehl SKB überspringt einfach das nächste Byte. Ein Nachbau würde nur einen Sprungbefehl (z. B. JMP) verwenden. Der Befehl kann auch noch mit weiteren OpCodes ausgelöst werden:
$82, $c2, $e2, $04, $14, $34, $44, $54, $64, $74, $d4, $f4
Eine genaue Angabe der Taktzyklen konnte ich leider nicht finden, nur das sie zwischen 2 und 4 liegen.

Weder das C64 Studio noch ACME kennen diesen Befehl!

SKW

SKW: SKip next Word (nächstes Word [zwei Byte] überspringen)
SKW implizit ($0C, 1B, 4-5T, <keine>)
Der Befehl SKW überspringt ein sog. Word (das wiederum zwei Byte entspricht). Ein Nachbau könnte wie beim SKB mit einem Sprungbefehl (z. B. JMP) erfolgen oder mit dem BIT-OpCode (wobei der das Statusregister verändert!). SKW kann auch noch mit weiteren OpCodes ausgelöst werden: $1c, $3c, $5c, $7c, $dc, $fc
Intern ist SKW eine Leseoperation, nur dass das Ergebnis nirgends gespeichert wird. Beim OpCode $0c wird eine absolute Adressierung verwendet, bei den anderen wird die absolute X-Indizierte Adressierung verwendet, dort wird dann beim Überschreiten der Page-Grenze ein weiterer Taktzyklus benötigt.

Auch diesen Befehl kennen weder ACME noch das C64 Studio.

NOP

Zum Schluß der relativ sicher verwendbaren OpCodes, bleibt noch der uns bereits bekannte NOP-Befehl. Dieser wird nicht nur beim offiziellen OpCode $ea ausgeführt, sondern auch bei ( $1a, $3a, $5a, $7a, $da, $fa).

Jetzt wird es noch illegaler 😉

Die nun folgenden OpCodes sind im Buch „The Complete Commodore Inner Space Anthology“ nicht mehr beschrieben. Ich habe sie nur im Netz gefunden, allerdings ist ihr verhalten teilweise nicht vorhersagbar. Da VICE diese Befehle kennt und verarbeitet, führe ich sie der Vollständigkeit halber auf.
Die kommen Befehle werden jetzt immer verrückter und problematischer, ein sinnvoller Einsatz ist daher äußerst fragwürdig!

HLT

HLT: HaLT (Absturz verursachen)
HLT implizit ($02, 1B, ?T, <keine>)
Tja, was soll man dazu sagen?
Triff die CPU auf einen dieser OpCodes $02, $12, $22, $32, $42, $52, $62, $72, $92, $b2, $d2, $f2, dann „hängt“ sie sich auf! Nur ein Reset bzw. Neustart hilft dann weiter. Taktzyklen sind hier nicht auffindbar und wären auch vollkommen irrelevant, der Rechner steht dann eh!

Weder ACME, noch das C64 Studio kennen diesen Befehl.

TAS

TAS: TXS+AND+STA
TAS absolut Y-indiziert ($9B, 3B, 5T, <keine>)
Zunächst werden der Akku und das X-Register per UND-Verknüpft, aber ohne das sich eines der Register ändert! Das Ergebnis wird in den Stackpointer (SP / nicht auf den Stack!!!) geschrieben. Dann wird das Ergebnis mit dem (Wert des MSB)+1 der angegebenen Adresse UND-Verknüpft und im Speicher an der angegebenen Adresse+Y-Register abgelegt. Wenn ihr das ausprobieren wollt, achtet auf den Stackpointer. Da der sehr wahrscheinlich geändert wurde (das sollte schon ein ungeheurer Zufall sein, wenn die erste UND-Verknüpfung den selben Wert wie der aktuelle SP ergibt) solltet ihr ihn mit TSX / TXS in einer Speicherstelle sichern und wiederherstellen. Die Flags bleiben dabei unverändert.

Auch das läßt sich nur unter Zuhilfenahme weiterer Speicherstellen (wir nehmen $fb & $fc auf der Zero-Page) nachbauen:

Soviel Aufwand für nichts, hat jemand einen sinnvollen Einsatz hierfür???
Weitere Adressierungsarten gibt es übrigens nicht.

SAY

SAY: Store value AND Y-Register
SAY absolut X-indiziert ($9C, 3B, 5T, <keine>)
Es wird das Y-Register mit dem Wert des MSB+1 UND-Verknüpft und anschließend an der angegebenen Adresse gespeichert. Die Flags bleiben dabei unverändert.

Nachbauen läßt sich das ganz einfach:

Auch hier gibt es keine anderen Adressierungsarten.

Weder ACME, noch das C64 Studio kennen diesen Befehl.

XAS

XAS: X-Reg. AND value Stored to memory
XAS absolut Y-indiziert ($9E, 3B, 5T, <keine>)
Es wird das X-Register mit dem Wert des MSB+1 UND-Verknüpft und anschließend an der angegebenen Adresse gespeichert. Die Flags bleiben dabei unverändert.

Nachbauen läßt sich das ganz einfach:

Auch hier gibt es keine anderen Adressierungsarten.

Weder ACME, noch das C64 Studio kennen diesen Befehl.

AXA

AXA: Akku and X-Reg. STA
AXA absolut Y-indiziert ($9F, 3B, 5T, <keine>)
Es wird der Akku mit dem X-Register und anschließend mit dem Wert des MSB+1 UND-Verknüpft. Anschließend wird das Ergebnis an der angegebenen Adresse gespeichert. Die Flags bleiben dabei unverändert.

Zum Nachbauen benötigen wir wieder eine Speicherstelle, die der Befehl natürlich nicht verwendet, nehmen wir wieder $fb auf der Zero-Page:

Übersicht der Adressierungsarten:

Weder ACME, noch das C64 Studio kennen diesen Befehl.

ANC

ANC: Akku AND value influence Carry
ANC unmittelbar ($2B, 2B, 2T, NZC)
Der Akku wird mit dem angegebenen Wert UND-Verknüpft. Das Ergebnis steht dann auch wieder im Akku. Er sieht also zunächst wie ein normaler AND-Befehl aus, aber hier wird das N-Flag ins Carry-Flag kopiert.

Der OpCode $0b führt zum selben Befehl, wie der eben erwähnte $2b .

Ein Nachbau könnte so aussehen:

LAS

LAS: Hierzu fällt mir nur: Lade Alles mit Schrott ein 😉
LAS absolut Y-indiziert ($BB, 3B, 2T, NZ)
Der Befehl führt eine UND-Verknüpfung mit dem Byte das an der angegebenen Speicherstelle zufinden ist und dem Stackpointer (SP) durch. Das Ergebnis wird dann im Akku, X-Register und SP abgelegt.

Zum letzten Mal ein Nachbau:


Zwei letzte OpCodes, für bekannte Befehle soll es noch geben, aber auch die sind nicht abgesichert:

$89 soll ebenfalls ein NOP ausführen.

$eb soll einem einem SBC #value entsprechen.


Wer ein Wenig sucht, findet seitenweise Diskussionen über diese Befehle. Wie bereits erwähnt, gibt es teilweise ein unterschiedliches Verhalten auf verschiedenen Rechnern und teilweise bei verschiedenen Ausgangssituationen auf dem selben Computer.
Daher nochmal die Warnung mit diesen Befehlen sparsam umzugehen!
Nichts ist frustrierender als stundenlang einen Fehler zu suchen und dann festzustellen, dass der illigale OpCode sich diesmal anders verhält nur weil der Bildschirmspeicher andere Daten enthält.


So, jetzt haben wir aber wirklich alle Befehle einmal gesehen. Als nächstes gibt es noch einen ‚Abschluß‚-Beitrag und dann sind wir mit dem Assembler-Grundlagen-Tutorial durch. Danach geht es mit dem Programmieren endlich richtig los.


Schrott!!Naja...Geht so...Ganz gut...SUPER! (12 Bewertungen | Ø 4,75 von 5 | 95,00%)

Loading...


ZurückWeiter

Ein Gedanke zu „Alles illegal!“

  1. Für mich macht der HLT Befehl dahingehend Sinn, das man damit eine Codeanipulationssicherung / Kopierschutz bauen könnte (Reines Gedankenspiel ohne jetzt eine konkrete Umsetzung im Hinterkopf zu haben)
    Bei Fehlverhalten würde der Rechner mittels HLT eingefroren. Somit würde kein Warmstart oder Softwarereset helfen also der Speicher nicht reproduzierbar sein. Oder täusche ich mich ?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Protected by WP Anti Spam