Dec 22, 2023
Eine kompetente Assemblersprache
In einer aktuellen Ausgabe von [Babbages] The Chip Letter wird die Unklarheit der Assemblersprache erörtert. Er weist darauf hin, und ich denke richtig, dass Assemblersprache häufiger gelesen als geschrieben wird, und doch fast
In einer aktuellen Ausgabe von [Babbages] The Chip Letter wird die Unklarheit der Assemblersprache erörtert. Er weist darauf hin, und ich glaube richtig, dass Assembler häufiger gelesen als geschrieben wird, doch fast alle von ihnen werden durch Unklarheiten behindert, die aus der Zeit stammen, als Lochkarten 80 Spalten hatten und ein Sechs-Buchstaben-Symbol alles war, was man hinbekam im begrenzten Speicherplatz des Computers. Was macht beispielsweise der ARM-Befehl FJCVTZS, ohne nachzuschlagen? Der vollständige Name der Anweisung lautet Floating-point Javascript Convert to Signed Fixed-point Rounding Towards Zero. Nicht besonders hilfreich.
Aber mir ist aufgefallen, dass Sie nichts davon abhält, einen kompetenten Assembler zu schreiben, der leichter lesbar ist. Erstens akzeptieren die meisten C-Compiler eine Art ASM-Anweisung, und das könnten Sie wahrscheinlich mit String-Konstruktion und Makros zur Kompilierungszeit erreichen. Ich denke jedoch, dass es eine bessere Möglichkeit gibt.
Da ich manchmal neue CPU-Architekturen entwickle, habe ich einen universellen Cross-Assembler, der ehrlich gesagt ein hässlicher Hack ist, aber ganz gut funktioniert. Ich habe bereits darüber gesprochen, aber wenn Sie nicht den ganzen Beitrag darüber lesen möchten: Es verwendet einige einfache Tricks, um standardmäßig aussehende Assemblersprachenformate in C-Code zu konvertieren, der dann kompiliert wird. Durch die Ausführung des resultierenden Programms wird die gewünschte Maschinensprache in ein gewünschtes Dateiformat ausgegeben. Es ist sehr einfach einzurichten und in der Mitte gibt es ein nettes C-Programm, das Maschinencode ausgibt. Es ist nicht viel besser lesbar als die Rohbaugruppe, aber Sie sollten es nicht sehen müssen. Aber was wäre, wenn wir den Prozess dort beginnen und das Format lesbar machen würden?
Das Herzstück des Systems ist ein C-Programm, das in soloasm.c gespeichert ist. Es verwaltet Befehlszeilenoptionen und die Generierung von Ausgabedateien. Es ruft eine externe Funktion auf, genasm mit einem einzelnen ganzzahligen Argument. Wenn dieses Argument auf 1 gesetzt ist, bedeutet dies, dass sich der Assembler im ersten Durchgang befindet und Sie nur Beschriftungswerte mit reellen Zahlen eingeben müssen. Wenn der Durchlauf eine 2 ist, bedeutet dies, dass das Array, das den Code enthält, tatsächlich ausgefüllt wird.
Dieses Array ist in der Anweisung __solo_info (soloasm.h) definiert. Es umfasst die Größe des Speichers, einen Zeiger auf den Code, die Wortgröße des Prozessors, die Anfangs- und Endadressen sowie ein Fehlerflag. Normalerweise wandelt das System Ihre Assembler-Eingabe in eine Reihe von Funktionsaufrufen um, die es in die Genasm-Funktion schreibt. Aber in diesem Fall möchte ich soloasm.c wiederverwenden, um eine kompetente Assemblersprache zu erstellen.
Ich habe das alles schon vor langer Zeit geschrieben, aber ich wollte, dass die Erstellung einer Literate-Assembly einfacher wird, also habe ich mich für eine Konvertierung mit geringem Aufwand nach C++ entschieden. Dadurch können Sie beispielsweise schöne Datenstrukturen für die Symboltabelle verwenden. Allerdings habe ich aus Zeitgründen nicht alle C++-Funktionen genutzt, die mir zur Verfügung standen.
Die Basisklasse ist einigermaßen prozessorunabhängig, und als Beispiel habe ich einen kompetenten RCA 1802-Assembler bereitgestellt. Nur ein Proof of Concept, daher könnte ich die Anweisungen wahrscheinlich etwas konsistenter benennen, und es gibt viel Raum für andere Verbesserungen, aber es bringt meinen Standpunkt klar zum Ausdruck.
Hier ist ein Auszug aus einem Blinklichtprogramm, das für den 1802 mit der Standard-Assembler-Syntax geschrieben wurde:
Hier ist nun genau das Gleiche, das für den gebildeten Assembler geschrieben wurde:
Nun, zugegebenermaßen gibt es Kommentare und Symbole, aber trotzdem. Sie können beide Dateien herunterladen, wenn Sie sie vergleichen möchten. Das gesamte Projekt finden Sie auch online.
Die Idee ist einfach. Jede Funktion füllt einfach ein Array mit dem oder den benötigten Bytes. Zugegebenermaßen ist der 1802 ziemlich einfach. Bei einem modernen Prozessor mit vielen Anweisungen und komplexen Modi wäre dies schwieriger zu bewerkstelligen. Aber nicht unmöglich.
Sie können viele Dinge tun, um sich das Leben zu erleichtern, sowohl beim Programmieren als auch beim Einrichten von Anweisungen. Wenn Sie beispielsweise 100 NOP-Anweisungen benötigen, könnten Sie schreiben:
for (int i = 0 ; i < 100 ; i++) NOP();
Andererseits verfügt NOP über ein optionales Argument, das dies für Sie erledigt. Sie können den C++-Compiler und den Makro-Präprozessor frei verwenden, um Ihnen das Leben zu erleichtern. Eine häufige Aufgabe beim 1802 besteht beispielsweise darin, einen konstanten Wert wie eine Beschriftung in ein Register einzufügen. Die Datei lit1802.h verfügt über ein Makro, um dies zu vereinfachen:
Natürlich können Sie die Namen entsprechend ändern oder so viele Aliase verwenden, wie Sie möchten. Vergessen Sie nicht, dass der Funktionsaufruf-Overhead, wie z. B. der Aufruf von Load_R_Label, zur Kompilierungszeit anfällt. Am Ende erhalten Sie in beiden Fällen den gleichen Maschinencode.
Der Assembler ist zweidurchgängig. Der erste Durchgang definiert nur Labels. Der zweite Durchgang generiert echten Code. Dies würde es beispielsweise schwierig machen, eine Smart-Jump-Anweisung zu erstellen, die eine Verzweigung verwendet, wenn das Ziel in der Nähe ist, und einen Weitsprung, wenn es weit entfernt ist, es sei denn, es macht Ihnen nichts aus, die Verzweigung mit einem NOP aufzufüllen, was keinen Platz sparen würde könnte aber Ausführungszeit sparen.
Für einen modernen Prozessor gäbe es noch andere Komplikationen. Versuchen Sie beispielsweise nicht, den gesamten Speicherplatz zuzuweisen oder eine verschiebbare Ausgabe zu generieren. Aber das ist wirklich ein Proof-of-Concept. Nichts davon ist unmöglich, es ist einfach nur mehr Arbeit.
Ich habe jahrelang Dutzende Assemblersprachen geschrieben und gelesen, daher bin ich mit dem Status Quo ziemlich zufrieden und werde wahrscheinlich selbst keinen Litasmus verwenden. Ich fand jedoch, dass [Babbages] Standpunkt gut dargelegt war. Wenn Sie die Assembly besser lesbar machen möchten, gibt es Vorteile, und dies zeigt, dass dies nicht so schwierig sein muss. Sie könnten auch einen Litasm-Disassembler schreiben, um Objektcode in ein solches Format zu konvertieren.
Möchten Sie mehr über den Universal Assembler erfahren? Wenn Sie sich lieber mit der praktischen x86-64-Montage befassen möchten, kennen wir einen guten Ausgangspunkt.