Hallo ihr
,
angeregt durch die verschiedenen BF-Interpreter im
Brainfuck-Thread, wollte ich versuchen, einen kleinen JIT-Compiler zu schreiben.
Bisher habe ich nur die Rechenoperationen (und Schleifen) implementiert, und soweit scheint der generierte Binärcode - wenn auch nicht optimal - zumindest korrekt zu sein, denn wenn ihn mir in der CPU-Ansicht von Delphi in disassemblierter Form ansehe, kommt das Erwartete heraus.
Leider kann ich den Code dennoch nicht ausführen - denn schon bei der ersten Instruktion (MOV) erscheint schon eine Zugriffsverletzung:
Code:
---------------------------
Bfcompiler
---------------------------
Zugriffsverletzung bei Adresse 015f043c. Schreiben von Adresse 015f043c.
---------------------------
OK
---------------------------
$015f043c ist die Adresse an der der reservierte Speicher für das Brainfuck-Programm anfängt.
Der relevante Code ist folgender:
Delphi-Quellcode:
TBrainFuckCompiler =
class(TBrainfuckParser)
protected
// In diesen Stream wird der Programmcode geschrieben
FMemStream: TMemoryStream;
procedure DoCompile;
virtual;
{ ... }
public
procedure Compile;
{ ... }
end;
TBrainFuckRunner =
class(TBrainfuckCompiler)
protected
// In diesem Array ist der Speicher für das Programm reserviert:
FMemory:
packed array[0..$10000]
of Byte;
procedure DoCompile;
override;
public
procedure Run;
end;
{ TBrainFuckRunner }
procedure TBrainFuckRunner.DoCompile;
var
B: Byte;
DW: Longint;
begin
// Speicher-Adresse in ECX übergeben
B := $B8 + 1;
// MOV to ECX
FMemStream.
Write(B, 1);
// Speicheradresse schreiben
DW := LongInt(@(FMemory[0]));
FMemStream.
Write(DW, 4);
// Programmcode kompilieren
inherited;
// RETurn (for execution)
B := $C3;
// RET
FMemStream.
Write(B, 1);
end;
procedure TBrainFuckRunner.Run;
var
address: pointer;
begin
address := FMemStream.Memory;
asm
call address;
end;
end;
Die eigentliche Kompilierung findet in TBrainfuckCompiler.DoCompile statt - dabei habe ich es so gelöst, dass aktuelle "Zelle" des Brainfuckprogramms (Pointer auf die Speicheradresse) immer im Register ECX gehalten wird. Wenn dann eine Zelle weitergerückt wird, wird einfach ECX inkrementiert usw.
Natürlich muss ECX dafür zuerst mit der richtigen Adresse initialisiert werden, und das geschieht vor der eigentlichen Kompilierung in TBrainFuckRunner.DoCompile.
Die generierten Instruktionen sehen dann (laut Delphis Disassembler) für das Brainfuck-Programm "++>-<-->+>+++" z.B. so aus:
Code:
// $015f043c = Basis-Speicheradresse für das Brainfuck-Programm
01600460 B93C045F01 mov ecx,$015f043c // Und hier kratzt er dann auch schon ab...
01600465 8B01 mov eax,[ecx]
01600467 40 inc eax
01600468 8BC8 mov ecx,eax
0160046A 8B01 mov eax,[ecx]
0160046C 40 inc eax
0160046D 8BC8 mov ecx,eax
0160046F 41 inc ecx
01600470 8B01 mov eax,[ecx]
01600472 48
dec eax
01600473 8BC8 mov ecx,eax
01600475 49
dec ecx
01600476 8B01 mov eax,[ecx]
01600478 48
dec eax
01600479 8BC8 mov ecx,eax
0160047B 8B01 mov eax,[ecx]
0160047D 48
dec eax
0160047E 8BC8 mov ecx,eax
01600480 41 inc ecx
01600481 8B01 mov eax,[ecx]
01600483 40 inc eax
01600484 8BC8 mov ecx,eax
01600486 41 inc ecx
01600487 8B01 mov eax,[ecx]
01600489 40 inc eax
0160048A 8BC8 mov ecx,eax
0160048C 8B01 mov eax,[ecx]
0160048E 40 inc eax
0160048F 8BC8 mov ecx,eax
01600491 8B01 mov eax,[ecx]
01600493 40 inc eax
01600494 8BC8 mov ecx,eax
01600496 C3 ret
(Nicht perfekt, ich weiß - aber es ist auch mein erstes Projekt dieser Art und viel Erfahrung mit Assembler habe ich auch nicht
)
Wie schon beschrieben, kratzt der bei der ersten Instruktion mit oben gezeigter Fehlermeldung ab. Was mich vor allem verwundert, ist, dass er sich wegen eines Schreibzugriffs beschwert - ich schreibe doch gar nicht an die Adresse, oder? Eigentlich soll nur dieser Wert ins Register ECX geschoben werden.
Kann mich jemand mit mehr Assembler-Erfahrung vielleicht erleuchten?
Vielen Dank schon mal!