Assembler

Aus C64-Wiki
Zur Navigation springenZur Suche springen

Assembler ist eine Programmiersprache, welche eine für Menschen bessere, lesbare Repräsentation der Maschinensprache ist. Als Assembler wird nicht nur die Sprache selbst, sondern auch ihr Compiler benannt. Übersetzt der Compiler ein Assembler-Programm in die Maschinensprache, so nennt man den Vorgang "assemblieren". Den umgekehrten Vorgang nennt man "disassemblieren".

Allgemeines[Bearbeiten | Quelltext bearbeiten]

Jede Prozessorarchitektur (z.B. x86, RISC) hat einen anderen Befehlssatz und folglich auch eine andere Assembler-Sprache, dennoch weisen diese untereinander immer Ähnlichkeiten auf. Maschinenbefehle und ihre Operanden bestehen aus Zahlen, die wiederum als Binärcode im Speicher vorliegen. Da pure Zahlenreihen für den Menschen nur schwer lesbar sind, fasst man diese Opcodes in Kürzel (Mnemonics - gesprochen Niemonix) zusammen.

Ein Assembler übersetzt diese Mnemonics in Bitmuster bzw. eine Folge von Bytes, bestehend aus Opcodes und Daten.

Neben Assembler als separaten Compiler, finden sich auch einfachere Formen in Maschinensprachemonitore oder sogenannte "Inline-Assembler" bei Hochsprachen, etwa einem C-Compiler, wo mit speziellen Instruktionen Assembler-Befehle direkt in die Hochsprachensyntax eingebettet werden können. Der Assembler kann sogar als Teil der Compiler-Suite für den letzten Schritt, die Erzeugung des Maschinencodes verantwortlich sein.

Prinzipiell findet man Assembler lauffähig auf dem Rechner selbst (Native Assembler), wo der Code dann auch ablaufen soll und sogenannte Crossassembler, wo der Assemblierungsvorgang auf einem anderen, üblicherweise schnelleren und komfortableren System für die Entwicklungstätigkeit, stattfindet.

  • Native Assembler: Der Entwicklungszyklus ist recht rasch, da Entwicklung und Ausführung am selben System stattfindet, allerdings zu Lasten der Ressourcen auf dem Zielsystem, dass den Assemblercode und den auszuführenden Code aufnehmen muss. Das kann speziell bei Abstürzen des System auch dazu führen, dass das Entwicklungssystem unbrauchbar wird.
    Eine Abwandlung stellt etwa eine Master-Slave-Umgebungen dar, wo zwei C64-Systeme über den Userport gekoppelt sind, wobei ein System als Entwicklungssystem agiert und das andere den Code ausführt.[1] Auch kann mitunter eine RAM-Disk einer REU oder zusätzliche Speicherbänke (auf einer DTV-Plattform) genutzt werden, um die Platzsituation im System zu entspannen.
  • Cross Assembler: Die Entwicklung passiert auf einem separaten System, das auch dazu geeignet ist, mit umfangreichen Projekten umzugehen. Der Assemblierungsvorgang, aber auch andere Tools für die Entwicklung sind hier komfortabler einsetzbar und weniger von den Platznöten oder Einschränkungen hinsichtlich der Geschwindigkeit am Zielsystem geprägt. Der Code muss allerdings immer auf das Zielsystem übertragen werden oder in einer Emulation getestet werden.

C64-Assembler[Bearbeiten | Quelltext bearbeiten]

Syntax[Bearbeiten | Quelltext bearbeiten]

Die Syntax der Assembler-Befehle für das Erzeugen der Maschinenbefehle und der Adressierungsarten ist ziemlich einheitlich.
Abweichungen finden sich in Syntax und Umfang der Operatoren in den Ausdrücken für Parameter. Besonders Pseudobefehle und Direktiven können stark variieren. Hier gibt es keinen wirklichen Standard, auch wenn viele Assembler recht ähnliche Elemente aufweisen. Generell muss man allerdings immer davon ausgehen, dass Assembler-Sourcen für unterschiedliche Assembler jeweils anzupassen sind.

Ein Beispiel zu Assembler beim C64:

*=$C000    ; Startadresse des Programms = $C000 = 49152
LDA #$00   ; 0 in den Akku laden
STA $D020  ; Akku nach Register $20 des VIC schreiben (Farbe des Bildschirmrands)
... 

... im Speicher des C64 würde es nun so aussehen:

Adresse  Opcode + Operand(en)

C000     A9 00      ; "C000" ist die Speicheradresse, 
                    ; "A9" ist der Opcode für LDA, 
                    ; "00" ist der Operand
C002     8D 20 D0   ; "C002" ist die Speicheradresse, 
                    ; "8D" ist der Opcode für STA, 
                    ; "20" und "D0" sind die Operanden (low/high-Reihenfolge)
...

... und als Binärcode ab $C000 (die Leerzeichen dienen nur der optischen Gruppierung in 8er-Gruppen)

10101001 00000000 10001101 00100000 11010000

Makros[Bearbeiten | Quelltext bearbeiten]

Manche Assembler für den C64 (z.B. Hypra-Ass, INPUT ASS, Turbo Assembler oder Crossassembler ACME ) übersetzen nicht nur die Mnemonics, sondern bieten auch die Möglichkeit Makros anzulegen an. Diese werden dann Makro-Assembler genannt. Makros lassen sich vereinfacht als Aneinanderreihung von Befehlen ersetzt durch einen Platzhalter zusammenfassen, was insbesondere beim relativ begrenzten Befehlsumfang des MOS 6510 den Quelltext des Programms stark verkürzen kann und es auch lesbarer macht. Beispiel: Will man einen 16-Bit-Wert im Speicher kopieren, ist das ausgeschrieben z.B.

LDA $xxxx
STA $yyyy
LDA $xxxx+1
STA $yyyy+1

Diese Sequenz ist nicht sonderlich lesbar. Mit einem entsprechend definierten Makro wird daraus z.B.

move16 $xxxx,$yyyy

Zu beachten ist, dass Makros Tipparbeit einsparen, aber das fertig übersetzte Programm natürlich genauso viel Speicher braucht. Außerdem müssen bei Makros stets auf Nebenwirkungen geachtet werden. Im Beispiel zuvor muss z.B. bei der Benutzung von "move16" klar sein, dass dabei der Akku überschrieben wird, was bei den Einzelbefehlen noch offensichtlich war.
Die Verwendung von Makros verhindert aber eventuell auch das Erkennen von Umständen für besondere Optimierungen hinsichtlich Code-Ausführungsgeschwindigkeit und Code-Länge. Beispielsweise kann ein Makro damit enden, dass das Y-Register stets 0 ist und so dem anschließenden Code ermöglichen den Akku "billig" mit TYA zu löschen oder das explizite Löschen des Y-Registers unterbleiben kann.

Implementierungen[Bearbeiten | Quelltext bearbeiten]

Hier einige Vertreter ohne Anspruch auf Vollständigkeit, die auf dem C64 lauffähig sind.
Neben rein kommerziellen Varianten druckten auch Magazine oder typischerweise Assembler-Bücher nicht selten einen (wenn auch oft einfache) Assembler als Listing ab (u.U. sogar in BASIC realisiert).

Charakteristiken[Bearbeiten | Quelltext bearbeiten]

Bezogen auf C64-Assembler.

Vorteile[Bearbeiten | Quelltext bearbeiten]

  • sehr maschinennah, das heißt:
    • Eine taktgenaue Ansteuerung z.B. des VIC ist möglich (spezielle Grafikeffekte möglich).
    • Volle Kontrolle über alles Bereiche der Speicherbelegung (unter BASIC ist z.B. kein lesender Zugriff auf das RAM unter dem BASIC-ROM möglich).
    • Die Nutzung und Kontrolle von Interrupts ist möglich.
  • sehr schnell:
    • Bei etwa drei Takten pro Befehl und 1 MHz Takt werden ca. 300.000 Assembler-Befehle pro Sekunde verarbeitet - BASIC schafft nur wenige dutzend bis hunderte Befehle pro Sekunde.
  • geringer Speicherverbrauch (kompakter Programmcode und effiziente, bit-genaue Datenstrukturen)

Nachteile[Bearbeiten | Quelltext bearbeiten]

  • schwer zu erlernen:
    • Die Bedeutung der einzelnen Opcodes ist einfach, aber jeder einzelne Befehl bewirkt noch nicht viel.
    • Eine Aufgabe kann auf viele verschiedene Arten formuliert werden. Oft gebrauchte Codesequenzen und effiziente Codierungstechniken (z.B. 16-Bit-Operationen) müssen als Erfahrung aufgebaut werden.
    • Ein recht tiefes Wissen über die Hardware und residenter Systemsoftware (BASIC-ROM und KERNAL-ROM) ist sinnvoll.
    • Die Entwicklung und das Testen von Programmen ist mühsam und fehlerträchtig, da bereits kleinste logische Fehler im Programmablauf fatale Abstürze hervorrufen können. Bei Nutzung von Emulatoren relativiert sich das allerdings etwas.
  • hoher Programmieraufwand:
    • Aus Sicht des MOS 6510 gibt es nur Speicherstellen und Bytes.
      • Komplexere Datentypen, wie Strings und Fließkommazahlen sind der CPU fremd und müssen irgendwie in Bytes dargestellt und verarbeitet werden.
      • Ein-/Ausgabe-Konzepte wie "Bildschirminhalt" oder "Diskettenlaufwerk" kennt die CPU nicht; diese Dinge "verstecken" sich hinter (Byte-)Registern einzelner Chips des C64, die wiederum an bestimmten Speicherstellen eingeblendet sind. Ohne Nutzung von BASIC-/KERNAL-Routinen wird in dem Bereich alles zu einer Tortur.
    • Selbst einfache Rechenoperationen sind teilweise nicht trivial, z.B. gibt es beim MOS 6510 keinen Multiplikations- oder Divisions-Opcode. Man muss diese Operationen also aus Addition bzw. Subtraktion und Schiebeoperationen nachbilden oder BASIC-/KERNAL-Routinen dazu benutzen.
    • Schon die Ausgabe einer einzigen Zahl auf dem Bildschirm benötigt viele Einzelbefehle: In BASIC ist es einfach ein PRINT (Zahl), in Assembler muss man entweder die Zahl in Dezimalstellen zerlegen (mangels Division nicht ganz einfach), die einzelnen Ziffern in Bildschirmcode umwandeln und dann in den Bildschirmspeicher schreiben oder die Zahl an die richtige Stelle im Speicher schreiben und eine BASIC-/KERNAL-Routine aufrufen, die vorige Schritte dann ausführt.
  • nicht portabel:
    • Ausführbarkeit: Einfache BASIC-Programme mögen z.B. auf dem C64 und dem VIC-20 laufen, aber kaum ein Assembler-Programm läuft auf beiden Rechnern oder anderen der gleichen Prozessorarchitektur.
    • Wiederverwendbarkeit: Algorithmen in einem Assembler-Dialekt lassen sich nur schwer (erfordert entsprechende Erfahrung) auf Rechnern einer anderen Plattform bzw. eines anderen CPU-Typs übertragen.

Nutzung von Assembler heute[Bearbeiten | Quelltext bearbeiten]

Assembler hat einen hohen Lehrwert und man erlangt nebenbei - zumindest beim C64, bei neueren Rechnern und Betriebssystemen aber nur noch bedingt - tieferes Verständnis für die Arbeitsweise eines Computers. Auch heute wird noch auf bestimmten Plattformen (z.B. im Zusammenhang mit Microcontrollern) in Assembler programmiert, da sich zum Beispiel zeitkritische Probleme so besser lösen lassen als in Hochsprachen. Für die allermeisten Probleme wird man aber bei aktuellen Rechnern auf Hochsprachen zurückgreifen, da Compiler inzwischen sehr gut optimieren und selbst interpretierte Sprachen dank Just-in-time-Kompilierung und anderen Techniken ähnliche Geschwindigkeiten wie reiner Assembler erreichen. Abgesehen vom Geschwindigkeitsaspekt, spielt auch das Erreichen gewisser CPU-Eigenschaften, wie das Ansprechen besonderer Register und Modi eine wesentliche Rolle, was aus einer Hochsprache heraus kaum vorgesehen ist.

Beispiele[Bearbeiten | Quelltext bearbeiten]

Bildschirm komplett schwarz färben ... (SYS 49152)

       *=$C000    ; Startadresse des Programms = $C000 = 49152
       LDA #$00   ; 0 in den Akku laden
       STA $D020  ; Akku nach Register $20 des VIC schreiben (Farbe des Bildschirmrands)
       STA $D021  ; Akku nach Register $21 des VIC schreiben (Farbe des Bildschirmhintergrunds)
       RTS        ; zurück zur aufrufenden Routine

Weblinks & Literatur[Bearbeiten | Quelltext bearbeiten]


Quellen