Hallo Leute, ich bin zur Zeit dran einen z80 Emulator/Disassembler zu schreiben. Über den Sinn und Unsinn möchte ich mich hier an der Stelle nicht unterhalten. Ich weiß, das es zuhauf alternativen gibt, aber das soll hier nicht weiter von belang sein. Es ist ein reines Spassprojekt. Jedoch stoße ich dabei an Probleme, die auch in der Gegenwart nicht unerheblich sein werden. Es geht dabei, den Opcode zu parsen, und das ist schwieriger, als zunächst anzunehmen ist. Die Grundlagen, wie Parsing funktioniert und auch von Sprachen sind mir bekannt. Aber der Opcode lässt sich damit nicht wirklich gut parsen. Um einfach Beispiele zu nennen. Die 8Bit Register (8 Stück an der Zahl) werden durchnummeriert. Das heißt, es werden 3 Bit benötigt, womit wiederum 9 Kombinationen möglich sind. Die 9. Kombination wird natürlich auch verwendet, was die Arbeit mit Bitmasken erschwert. Um konkret zu werden. Opcodes können die Form 0 1 r r r r' r' r' 0 1 r r r 1 1 0 0 1 1 1 0 r r r 0 1 1 1 0 1 1 0 besitzen, wobei 1 1 0 die 9. Kombination ist, die nicht für die Identifikation von Registern benutzt wird. Diesen kann man noch halbwegs mit Bitmasken parsen, aber es fängt schon dadurch an, schmutzig zu werden. Aber dann gibt es auch noch andere Opcodes in der Form 0 0 r r r x x x 0 0 q q x x x x 0 0 x x x x x x 1 0 x x x r r r 1 0 x x x x x x Da fang ich mir nun schon richtig einen an, abzubrechen. Ich hab keine Idee, wie ich das intelligent parsen kann, ohne das der Code vollkommen dreckig wird. Ich kann nicht wirklich mit Bitmasken arbeiten, da 1. Ich die drei Bit, wo die Register liegen, nicht einfach mit einer Maske ausblenden kann, da es eben eine Kombination gibt, die kein Register identifiziert (1 1 0) 2. Ich kann bei manchen keine Maske verwenden, da manchmal der variable Anteil in den Opcodes mal zwei und mal drei Bits sind 3. Der variable Anteil ist mal in der Mitte und mal am Ende Egal was ich mache. Es wird einfach schmutzig. Ich kann nicht immer nach dem gleichen Schema vorgehen, sondern muss mir immer Sonderfälle überlegen. Das verlangsamt zudem auch die Geschwindigkeit von dem Parser. Ich habe mir natürlich auch den Sourcecode von den anderen Projekten mal angesehen. Die bauen wohl alle eine Lookuptabelle auf, und schauen einfach in dieser nach, um was es sich handelt. Der Z80pack Simulatorsourcecode ist sogar so Strange, das zum Beispiel für jede Permutation für LD r, r' eine eigene Funktion geschrieben wurde. Zudem mehrmals durch eine Tabelle mit 256 Einträgen zu iterieren, stell ich mir auch alles andere als schnell vor. Gibt es denn keine Möglichkeit den Binärcode besser und effizienter zu parsen? Ich hoffe, man kann in etwa verstehen, auf was ich hinaus will. MfG Tobias
Tobias B. schrieb: > Die 8Bit Register (8 Stück an der Zahl) werden durchnummeriert. B, C, D, E, H, L (HL), A. > Das heißt, es werden 3 Bit benötigt, womit wiederum 9 > Kombinationen möglich sind. Nö. 3 Bit sind 8 Kombinationen. - Richtig ist, dass (HL) bei vielen Befehlen durch irgendwas anderes ersetzt wird; das ist wohl das Bitmuster 110. > Die 9. Kombination wird natürlich auch verwendet, was die > Arbeit mit Bitmasken erschwert. Die Ausnahmen musst Du zuerst abprüfen; wenn keine Ausnahme vorliegt, kannst Du mit der Maske weiterarbeiten. > Um konkret zu werden. Opcodes können die Form > 0 1 r r r r' r' r' Ja. "LD r, r'" > 0 1 r r r 1 1 0 Ja. "LD r, M" bzw. gleichwertig: "LD r, (HL)". > 0 1 1 1 0 r r r Ja. "LD (HL), r" bzw. "LD M, r". > 0 1 1 1 0 1 1 0 Das ist 0x76, also "HALT". Das ist die einzige Ausnahme, die Du halt vorher abtesten musst. Alles andere geht nach Schema F, sofern Du "(HL)" als Pseudo-Register behandelst. > besitzen, wobei 1 1 0 die 9. Kombination ist, die nicht > für die Identifikation von Registern benutzt wird. Siehe oben. Erstens ist es die ACHTE Kombination, und zweitens kann man "(HL)" als 8-bit-Pseudoregister auffassen. > Diesen kann man noch halbwegs mit Bitmasken parsen, aber > es fängt schon dadurch an, schmutzig zu werden. Aber dann > gibt es auch noch andere Opcodes in der Form > 0 0 r r r x x x > 0 0 q q x x x x > 0 0 x x x x x x > 1 0 x x x r r r > 1 0 x x x x x x Ja, richtig. > Da fang ich mir nun schon richtig einen an, abzubrechen. Ich > hab keine Idee, wie ich das intelligent parsen kann, ohne das > der Code vollkommen dreckig wird. Ich kann nicht wirklich mit > Bitmasken arbeiten, Quark. > da 1. Ich die drei Bit, wo die Register liegen, nicht einfach > mit einer Maske ausblenden kann, da es eben eine Kombination > gibt, die kein Register identifiziert (1 1 0) Siehe oben: Die eine Ausnahme testet man vorher ab und behandelt sie entsprechend. Was übrig bleibt, geht nach Schema F. > Egal was ich mache. Es wird einfach schmutzig. Quark. > Die bauen wohl alle eine Lookuptabelle auf, Genau. Unter anderem mein eigener (nie fertiggestellter) Disassembler macht das so. :) > Zudem mehrmals durch eine Tabelle mit 256 Einträgen zu > iterieren, stell ich mir auch alles andere als schnell vor. Kann es sein, dass Du das Prinzip der Lookup-Tabelle nicht ganz verstanden hast? Der OpCode ist der Tabellenindex ; Du benötigst exakt EINE Leseoperation der Art "BefehlsTyp := Tabelle[OpCode]". Das ist rasend schnell; schneller geht's kaum. Anschließend machst Du eine Fallunterscheidung nach Befehlstyp.
Tobias B. schrieb: > Zudem > mehrmals durch eine Tabelle mit 256 Einträgen zu iterieren, stell ich > mir auch alles andere als schnell vor. Glaubst du im Ernst, dass der Z80 das bei der Befehlsausführung so macht? Was so ein Uraltprozessor leistet, sollte ein Programmierer Jahrzehnte später doch auch effektiv hinkriegen. Tobias B. schrieb: > Die Grundlagen, wie Parsing funktioniert und auch von Sprachen sind mir > bekannt. Tobias B. schrieb: > Das heißt, es werden 3 Bit benötigt, womit > wiederum 9 Kombinationen möglich sind. Aber die Grundlagen der Programmierung überhaupt bzw. der Kombinatorik wohl eher nicht. Da solltest du zuerst mal ansetzen. Georg
Tobias B. schrieb: > Gibt es denn keine Möglichkeit den Binärcode besser und effizienter zu > parsen? Bau eine 256 Einträge Tabelle, die sagt, WELCHE Operation aufgerufen wird, und jede dieser Funktionen verwendet gewisse Bits des OpAmps als Parameter für Register und andere. Definiere pc als pointer auf so viele gepackten Bitfeld-Strukturen, wie es opcode-Typen gibt.
1 | typedef union |
2 | {
|
3 | struct regreg {unsigned char reg1:3, unsigned chasr reg2:3, unsigned char op:2};
|
4 | struct reg{unsigned char reg:3; unsigned char op:5};
|
5 | struct wordreg{unsigned char:op; unsignedc hsar reg:2; unsigned char op2;};
|
6 | } |
7 | INSTRUCTION; |
8 | INSTRUCTION *pc; |
9 | |
10 | enum {LD,LXI,STAX,STA,NOP,ADD,ADC,SUB,..} opcode[256]={NOP,LXI,STAX,..,ADD,ADD,ADD,ADD,ADD,ADD,ADD,ADD,ADC,ADC,ADC,ADC,ADC,ADC,ADC,ADC,SUB,SUB,SUB,SUB,SUB,SUB,SUB,SUB,...};
|
11 | // sogar 5 Tabellen, je nach dem ob ein BC, CD, ED, FD, vorweg kam |
12 | |
13 | switch(opcode[*pc]) |
14 | {
|
15 | case NOP: |
16 | break; |
17 | case LXI: |
18 | lxi(op->wordreg.reg); |
19 | break; |
20 | case STAX: |
21 | stax(op->wordreg.reg); |
22 | break; |
23 | case LD: // 0 1 r r r r' r' r' |
24 | ld(op->regreg.reg1,op->regreg->reg2); |
25 | break; |
26 | case ADD: // 0 1 1 1 0 r r r |
27 | add(op->reg.reg); |
28 | break; |
29 | case ADC: // 0 1 1 1 0 r r r |
30 | carry=add(op->reg.reg,carry); |
31 | break; |
32 | case SUB: // 0 1 1 1 0 r r r |
33 | sub(op->reg.reg); |
34 | break; |
35 | : |
36 | } |
Da du alle möglichen 256 opcodes auf andere Funktionen (mit ihren Parametern) auflösen kannst, kannst du problemlos solche unterbringen, bei 22 und 32 brauchst du kein STAX eintragen sondern kannst SHLD und STA eintragen.
Hi >Bau eine 256 Einträge Tabelle, die sagt, WELCHE Operation aufgerufen >wird, und jede dieser Funktionen verwendet gewisse Bits des OpAmps als >Parameter für Register und andere. Unnötig. Ein Disassembler ließ sich mit mit ca. 3..4 Schreibmaschinenseiten Assemblercode bewerkstelligen. Ein interessanter Ansatz ist z.B. hier http://www.z80.info/decoding.htm zu finden. MfG Spess
spess53 schrieb: > Unnötig. Ein Disassembler ließ sich mit mit ca. 3..4 > Schreibmaschinenseiten Assemblercode bewerkstelligen. Aha. Es ging auch um einen Emulator. Diese trimmt man oft auf Geschwindigkeit. Bei nicht zu großen LUTs sind diese jeglichen anderen Lösung meist überlegen.
Erinnere ich mich da eigentlich falsch, oder hatte nicht gerade der Z80 noch einen SEHR grossen Berg an undokumentierten Opcodes die man auch verwenden konnte? Olaf
Tobias B. schrieb: > ... > Jedoch stoße ich dabei an Probleme, die auch in der Gegenwart nicht > unerheblich sein werden. ... Ah ja. > ... Das heißt, es werden 3 Bit benötigt, womit > wiederum 9 Kombinationen möglich sind. ... Wenn ich das so lese, sehe ich ganz genau, auf welche Probleme du stößt: 1. Großen Mangel an Grundlagen. 2. Geringes Vorstellungsvermögen. Ich 'ne ganz schlechte Kombination. ;-)
Hi >Aha. Es ging auch um einen Emulator. Nicht unbedingt. Z.B. hatte der MC80 (http://www.robotrontechnik.de/index.htm?/html/computer/mc80.htm) einen Quellcodeassembler. Das heißt es existierte kein Quelltext, nur der Code und eine Markentabelle. Zur Bildschirmausgabe musste der Code also direkt wieder rückübersetzt werden. MfG Spess
Ich hab mal im letzten Jahrhundert einen ROM-Debugger für 8080/8085 geschrieben und der lief auf auch auf einem 8085 zusätzlich zur Anwendung aber Heute muss das ja mindestens ein PC oder Raspi sein ... Die meisten Befehle bestehen doch, soweit ich mich erinnere, aus 2 Bit Code und jeweils 3 Bit für Quell und Zielregister, das ist mit ein bissel Bitpopelei doch schnell gemacht. Und das was noch übrigbleibt und mehr als 1 Byte benötigt ist doch auch nicht die Welt.
Hans-Georg L. schrieb: > Die meisten Befehle bestehen doch, soweit ich mich erinnere, aus 2 Bit > Code und jeweils 3 Bit für Quell und Zielregister, Nur ein Viertel vom 8080 Opcode Bereich, nämlich 40-7F für MOV. Nicht ohne Ausnahme natürlich, denn der Opcode für MOV M,M ist etwas gänzlich anderes. Ausserdem wären da beim Z80 noch die Präfixe. Ist aber kinderleicht verglichen mit der Codierung des 68000, mit den Ausnahmen von der Ausnahme von der Regel.
A. K. schrieb: > Ist aber kinderleicht verglichen mit der Codierung des 68000, mit den > Ausnahmen von der Ausnahme von der Regel. Den 68k empfand ich immer als relativ einfach. Da konnte ich zu meiner aktiven Zeit auf dem Teil den Code direkt in Hex lesen, weil es so einfach war. An Ausnahmen kann ich mich da nicht erinnern. Wenn, dann waren es nur wenige. Gruß Jobst
Jobst M. schrieb: > Den 68k empfand ich immer als relativ einfach. Hier gehts grad nicht um die Programmierung von Programmen in Assembler, sondern um einen Disassembler oder Emulator dafür. Hast du mal dafür einen Disassembler geschrieben? Der genau nur die gültigen Befehle darstellt bzw. ausführt? Da gibt es die regulär codierten Befehle mit den Adressierungsarten der Operanden. Bei denen je nach Befehl aber nicht alle Adressierungsarten zugelassen oder sinnvoll sind. So ist PC-relativ als Zieladresse aus "politischen" Gründen nicht zulässig. In den Codes solcher Ausnahmen stecken dann oft andere Befehle wie: MOVEP = Einzelbitbefehl auf Adressregister SWAP = PEA vom Datenregister EOR = CMP reg => mem CMPM = EOR mit Adressregister etc Daraus erklärt sich auch, weshalb der EOR Befehl merkwürdigerweise nur in einer Variante vom Register zum Speicher hin existiert, nicht aber mit Speicher zu Register. Im CMP Befehl ist diese Richtung redundant, also versteckte man den EOR Befehl darin. Und in einer Ausnahme dieser Ausnahme dann den CMPM Befehl. Das wars dann aber immer noch nicht, denn die Codierungen von <op> reg,reg existieren von der Systematik her zweimal, in reg = reg <op> r/m und r/m = r/m <op> reg wobei die zweite davon oft anderweitig verwendet wird (SUBX statt SUB, SBCD statt OR), im von der Systematik her analogen EOR Befehl mit Datenregister aber zulässig ist. Aus Sicht des Programmierers ist 68000 leidlich orthogonal. Aus Sicht der Codierung aber nicht. > Da konnte ich zu meiner > aktiven Zeit auf dem Teil den Code direkt in Hex lesen, Respekt. Der ist nämlich nur oktal wirklich leicht zu lesen, weil sich der Opcode meist als Codierung aus 4.3.3.3.3 Bits darstellt. Also nur die obersten 4 Bits in Hex gut abbildbar sind. NOP kann ich allerdings auch heute noch auswendig in Hex. ;-)
Die Schwierigkeiten eines Disassemblers liegen ganz anderswo. Naemlich :wo liegt der Code. Ich musste mal 6809 code disassembleren. 1) am dem Resetvektor. Man beginnt mit dem Reset Vektor und hangelt sich dem Code entlang. Irgendwann kommt ein PC relativer Jump, eine Switch anweisung, die als PC Relativ implemetiert wird. Da beginnt man mal mit dem Code grad danach, bis der wieder wegspringt. Dann kommt moeglicherweise die naechste Switch-Moeglichkeit. Moeglicherweise wurde der Bereich der Switch-Moeglichkeiten vor dem PC relativen Jump abgetestet. Das muss man dann genau anschauen. und alle Faelle durchgehen. Wenn ein ungueltiger OpCode kommt, hat man vorher einen Fehler gemacht. Dann geht man dem code aller Interruptvektoren nach. Dann gibt es Datenstrukturen im Codesegment, die Konstanten, als DB, DW, DS. Die muss man auch rausziehen.
A. K. schrieb: > Hast du mal dafür einen Disassembler geschrieben? Nein, war ja nicht notwendig :-) A. K. schrieb: > Der ist nämlich nur oktal wirklich leicht zu lesen, weil sich > der Opcode meist als Codierung aus 4.3.3.3.3 Bits darstellt. Also nur > die obersten 4 Bits in Hex gut abbildbar sind. Okay, Hex2Bin passiert im Auge schon :-D Das ist auch immer noch so. > NOP kann ich allerdings auch heute noch auswendig in Hex. ;-) Sicher bin ich mir noch bei RET: 4E75 Für alles Andere müsste ich nun auch wieder nachsehen ... Gruß Jobst
@ Tobias Bla Ich sehe das Problem im Moment eher darin, dass Du in bestimmten Fällen, die Du ja in Deinem Eröffnungspost geschildert hast, den nötigen Code als "dreckig" oder "schmutzig" bezeichnest. Du führst da eine Wertung ein, die Dich an dem Punkt an dem Du nun bist, innehalten und hier fragen lässt. Mein Rat wird Dich vielleicht nicht wirklich befriedigen, aber ich möchte ihn dennoch geben: Trenne Dich innerlich von solchen Wertungen. Du erschwerst Dir nur selbst die Arbeit in dem Du innerliche (psychologische ) Hemmnisse erzeugst. Betrachte Die Tatsachen möglichst nüchtern und liste einfach der Reihe nach die Fälle auf. Ich habe das mal für einen AVR gemacht. Der Fall ist leider dem Umfang nach nicht wirklich gleich, aber die Systematik ist die selbe. Ich habe einfach alle Opcodes binär aufgelistet und Gruppen gebildet. Als Hilfsmittel habe ich Ternärvektoren verwendet. Daraus ergeben sich leicht Systematiken und eben auch ein paar Sonderfälle. Meist ist es aber so, dass sogar die Sonderfälle für sich eine oder mehrere Systematiken bilden. Das dauert alles ein paar Stunden oder auch Tage, aber es funktioniert. Das ist jetzt nur grob angedeutet, aber vielleicht reicht Dir das ja schon. Wenn Du willst, poste ich mal die CSV-Datei. In Summe also: 1. Vermeide unsachliche wertende Vorwegnahmen. 2. Wende eine Methode an und führe die zuende. 3. Dann erst werte das, aber nach objektiven Kriterien. Z.B. wieviele verschiedene Bedingungen hast Du um die Sonderfälle zu erfassen. Viel Erfolg.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.