Das ist meine Spielwiese, hier entwerfe ich Seiten für das Wiki und stelle sie erst ein,wenn alles wunschgemäß funktioniert. Darum hier bitte nichts ändern! Außerdem brauche ich hier keine Kommentare, denn mir ist bekannt, das das was hier steht noch nicht komplett ist.
Eine Darstellung der Mnemonics in einer Tabelle ist zunächst einmal sinnvoll, wenn man einen Disassembler schreiben möchte. Ohne eine solche Tabelle müsste man für Mnemonics, Adressierungsarten, Datenlänge und Prozessortakte jeweils einen Block von 256 Bytes Belegen, um darin die entsprechenden Werte abzulegen. Durch die Tabelle kann man Gemeinsamkeiten von Mnemonic-Gruppen erkennen und so die Mnemonics durch logische Verknüpfungen filtern.
In dieser ersten Tabelle im Format 16x16 fällt bereits auf, das die Spalten x3,x7,xB und xF nicht mit Mnemonics belegt sind, also alle Mnemonics, in denen die Bits 0+1 =11 sind. Durch nur 2 Assemblerbefehle (AND #03 und CMP #03) könnte man also schon 64 illegale Codes ausfiltern.
Diese Auffälligkeit bringt mich auf die Idee, 3 weitere Tabellen zu erstellen, wo je Tabelle auch die Bits 0+1 gleich sind:
Als erstes fällt hier auf, das sich alle Branch-Befehle in einer Spalte versammelt haben, diese können mit AND #1F und CMP #10 ausgefiltert werden. Dann kann man auch gleich mit AND #0F und CMP #08 zusammen 16 (2x8) der 1-Byte-Befehle ausfiltern. Danach verbleibt in der unteren Hälfte pro Zeille nur noch 1 Mnemonic (STY, LDY, CPY, CPX), wobei sich spaltenweise immer die gleiche Adressierungsart versammelt hat. Oben hingegen haben sich alle Befehle versammelt, die Einfluß auf den Programmcounter oder den Stack haben.
Diese Gruppe zeigt (mit einer Ausnahme) eine Regelmäßigkeit, für die alleine sich schon der Aufwand mit den Tabellen gelohnt hat. Nur Befehle, die ausschließlich den Accumulator betreffen haben sich hier versammelt. Außerdem liegt in jeder Zeile ausschließlich ein Befehl und in jeder Spalte ausschließlich eine Adressierungsart
Mit 3 Ausnahmen ($91, $99 und $9D) sind auch die Prozessortakte der Befehle in den Spalten immer gleich.
In den Spalten 0A und 1A haben sich die restlichen 1-Byte-Befehle versammelt. Wenn diese ausgefiltert sind, enthält wieder jede Zeile einen Befehl und jede Spalte eine Adressierungsart.
Die vierte Tabelle braucht nicht mehr erstellt zu werden, da sie keine gültigen Befehle mehr enthält.
Ein Disassembler muss zunächst Mnemonic von illegalen Codes trennen und dann die Adressierungsarten richtig zuordnen. Das Trennen der Mnemonics von illegalen Codes macht man am besten gruppenweise, was durch die erstellten Tabellen vereinfacht wird. Wenn man die Gruppen immer mit AND auf einen Wert bringt, um sie mit CMP auszufiltern, kann man später Tabellen für die AND- und CMP-Werte erstellen und daraus die Werte mit indirekter Adressierungauslesen. Doch für die Tabelle brauche ich noch einen weiteren Wert, um festzulegen, wie ein erkannter Code weiter verarbeitet werden soll.
;X soll als Zeiger für die Tabellen dienen, da Y gebraucht wird um den Code indirect-y-indiziert zu holen
LDX #Tab1Len
NotFound DEX
BEQ Ende ;oder ein anderes RTS anspringen
LDA (ptrDisass),y
AND Tab_AND-1,X
CMP Tab_CMP-1,X
BNE NotFound
X enthält jetzt einen Wert, mit dem man bestimmen kann, wie jetzt weiter verfahren wird. Eine Möglichkeit wäre, einen Einsprungpunkt so auf den Stack zu legen, als wäre es eine Rücksprungadresse von JSR. Mit RTS würde man auf dem Weg einen "JMP Absolut,X simulieren.
LDA Hi_Einsprung-1,X ;Einen manipulierten "JMP" mit RTS vorbereiten
PHA
LDA Lo_Einsprung-1,X
PHA
Ende RTS
Eine andere Möglichkeit wäre, den X-Wert weiter zu verarbeiten um anhand einer neuen Tabelle zu entscheiden, was weiter geschehen soll:
TXA
LDX #Tab2Len
CMP Entscheidung,X
Das Disassemblieren soll soll über das Ausfiltern 2 Werte ermitteln, für die 2 Speicheradressen benötigt werden. Ich benenne diese einmal provisorisch mit CodeMne und CodeAdr. Über die genau verwendeten Adressen kann man sich später Gedanken machen. Da CodeMne maximal 55=$37 enthalten kann, weil es nur 56 verschiedene Mnemonics gibt (0-55 während -1=$FF kennzeichnet das kein Mnemonic erkannt wurde), kann man relativ einfach den Wert mit 3 multiplizieren, um die 3 Buchstaben des Mnemonics zu holen.
;Diese Routine soll nur aufgerufen werde, wenn CodeMne nicht $FF enthält
LDA CodeMne
ROL ;*2 (dadurch wird auch Carry gelöscht)
ADC CodeMne
TAX
LDA MneListe,X
JSR Ausgeben
LDA MneListe+1,X
JSR Ausgeben
LDA MneListe+2,X
JSR Ausgeben
LDA #20 ;Space
JMP Ausgeben
Diese Routine ist geringfügig (4 Byte CodeMne in ZP, sonst 5 Byte) länger, als wenn man mit 3 Listen arbeitet, die jeweils einen der 3 Buchstaben enthalten (TabChar1, TabChar2, TabChar3)
LDX CodeMne
LDA TabChar1,X
JSR Ausgeben
LDA TabChar2,X
JSR Ausgeben
LDA TabChar3,X
JSR Ausgeben
LDA #20 ;Space
JMP Ausgeben
Wenn keine Adresse für CodeMne verwendet wird, sondern der MneCode in X übergeben wird, ist diese Variante auch kürzer (10 Byte) als folgender Code:
TXA
PHA
TSX
INX
ROL
ADC 0100,X
TXS
TAX
LDA MneListe,X
JSR Ausgeben
LDA MneListe+1,X
JSR Ausgeben
LDA MneListe+2,X
JSR Ausgeben
LDA #20 ;Space
JMP Ausgeben
Man fängt am besten damit an, die Werte auszufiltern, die der Regelmäßigkeit einer anderen Gruppe wiedersprechen. Als Beispiel nehme ich einmal die beiden BIT- und die beiden JMP-Befehle, die als einzige Befehle wiedersprechen, dass mit AND #87 / CMP #04 nur illegale Codes ausgefiltert werden:
- AND #F7 und CMP #24 filtert BIT aus
- AND #DF und CMP #4C filtert JMP aus
Jetzt sind alle verbliebenen Befehle mit dem Bitmuster 0xxxx100 illegal (AND #87 / CMP #04). Weiter gehts ...
- AND #9F und CMP #0A filtert die impliziten Adressierungen von ASL, ROL, LSR und ROR aus
Jetzt sind alle verbliebenen Befehle mit dem Bitmuster 0xxxx010 illegal (AND #87 / CMP #02).