Amiga Assembler – Teil 4
Es wird Zeit mit dem Assembler Code auf den RAM Speicher vom Amiga zuzugreifen. Der Rechner hat neben der CPU ja auch noch jede Menge RAM – zumindest im Vergleich zu anderen Rechnern dieser Zeit. Um schlussendlich ein richtiges Programm zu schreiben müssen wir dort Werte Ablegen und zur Laufzeit verarbeiten.
Amiga Assembler – Teil 4
Beim Zugriff auf den Hauptspeicher des Amigas wird es nun erstmals wirklich interessant. Je nach Modell und Erweiterungen kann so ein Amiga ganz unterschiedlich großen RAM haben. Der Einfachheit gehen wir in den ersten Beispielen davon aus einen Stock Amiga vor uns zu haben. Beim Amiga 1200 wäre das 2 MB Chip RAM. Die Amiga Architektur hat eine fest gelegte Memory Map, das heißt der Speicher ist durchnummeriert (Adressen) die über alle Modelle gleich sind. Die ersten 2 MB sehen wie folgt aus:
$000000 – $07FFFF 512KB Chip RAM (OCS Chipset, Stock Amiga 500)
$080000 – $0FFFFF 512KB erweiterter Chip RAM (ECS Chipset, Stock Amiga 600)
$100000 – $1FFFFF 1MB Chip RAM (AGA Chipset, Stock Amiga 1200)
Verwenden wir beispielsweise nur die ersten 512KB vom Chip RAM, dann ist unser Programm abwärtskompatibel bis zum Amiga 500. Führen wir später unser Programm im Debugger aus kann man an der Adresse vom Speicher gut ablesen in welchem Speicherbereich die Werte abgelegt sind, aber dazu gleich mehr.
Hauptspeicher nutzen
Um den Hauptspeicher vom Amiga zu nutzen kennen wir in Assembler zwei Befehle:
- DC
Data constant legt einen Wert der angegebenen Größe mit dem angegebenen Wert im Speicher an. - DS
Data segment reserviert einen Bereich angegebener Größe im Speicher.
Ich habe ein kleines Testprogramm erstellt und bekomme zur Kompilierungszeit einen Fehler:
Diejenigen von euch die mit der Programmers Reference von Motorola diese Tutorials durchspielen haben vermutlich gemerkt, dass weder DC noch DS dort zu finden sind. Die beiden Befehle sind der CPU unbekannt. Viel mehr handelt es sich um Befehle die VASM beim Compilieren vom Programm benötigt um diese Werte direkt im Speicher anzulegen.
Zum Fehler: ich habe den DC Befehl genutzt um einen Wert mit der Größe eines Bytes anzulegen, beim Wert jedoch 3 Nibbles angegeben. Das passt natürlich nicht in den Bereich. Ausgebessert ergibt sich folgender Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 | START: ; creates some values in memory dc.b $5 dc.b $EE dc.w $AAFF dc.b 0 dc.b "Hello World",0 ds.w 10 ds.b 5 nop nop |
Ein kurzer Blick in die Debugumgebung zeigt uns im Memory Fenster die angelegten Werte:
Bei Programmstart wird der angegebene Speicher reserviert und mit den Werten belegt. Ich habe den Anfang jedes Speicherbereichs von jedem der 7 Befehle markiert. Was auffällt ist, dass der String „Hello World“ mit einer abschließenden 0 im Speicher steht (Null terminated string) und der Bereich mit 10 Word bzw. 5 Bytes direkt danach anschließt.
Alignment
Manchmal ist es störend, wenn Speicher so zusammengepackt wird und ein größerer Bereich nicht an einer geraden Speicheradresse beginnt (siehe dazu Punkt 6 im Screenshot). Wir können mit dem even Befehl den Compiler zwingen den Speicher anders auszurichten.
1 2 3 4 5 | ... ds.w 10 even ds.b 5 ... |
Ein zusätzliches Byte mit dem Wert 0 wurde automatisch eingebaut, der Block aus 10 Wörtern beginnt nun an einer anderen Stelle im Speicher.
Pointer
Ich nenne dieses Kapitel Pointer. Das ist ein Begriff, bei dem die meisten C Programmieranfänger den Hut werfen. Moderne Sprache verbergen diese Thematik, weil Pointer gefährlich sind (z.B. kann damit memory leaks erzeugen). In Assembler müssen wir uns allerdings Speicheradressen merken um effektiv mit dem RAM zu arbeiten. Will man beispielsweise auf die im vorangegangenen Beispiel angelegten Werte im Speicher zugreifen, müssten wir ab Adresse 00C369D0 im Hauptspeicher lesen (siehe dazu die Speicheradresse im Screenshot von MonAm). Der folgende Code legt einen Speicherbereich mit einem String an und zur Laufzeit setzen wir uns einen Pointer auf den Speicherbereich:
1 2 3 4 5 6 | START: lea MYMEMORY,a0 nop MYMEMORY: dc.b "Hello World from Amiga Memory",0 even |
Es gibt folgenden neuen Befehl:
- LEA
Load Effective Address lädt den Speicher ab dem Bereich den wir als Quelle mit einer Sprungmarke angeben in das angegebene Adressregister
Der Screenshot zeigt, dass nach Ausführung des lea Befehls in a0 die Speicheradresse 00C369D6 steht. Das ist exakt das Byte, in dem der erste Buchstabe des Strings steht.
Speicher kopieren
Ein wesentliche Funktion die wir benötigen werden ist Speicher zu kopieren. Ein Programm macht das andauernd, weshalb es in Assembler auch recht einfach funktioniert. Ein Pointer auf einen Speicherbereich ist kein konstanter Wert, wir können damit wie mit einer jeden anderen Variable rechnen. Erhöhen wir diesen Schrittweise, dann können wir Zeichen für Zeichen auf den Speicher zugreifen. Um einen Wert zu kopieren greifen wir auf den bereits bekannten move Befehl zurück.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | START: ; copy memory from SOURCE to DESTINATION lea SOURCE,a0 lea DESTINATION,a1 move.b (a0)+,(a1)+ nop SOURCE: dc.b "Hello World from Amiga Memory",0 even DESTINATION: ds.b 50 |
Der Speicher wurde angelegt und befüllt und die LEA Befehle haben die Quell- und die Zieladresse ins Adressregister geschrieben. Mit dem move Befehlt passieren jetzt mehrere Dinge…
Der move.b Befehl kopiert ein Byte von Speicheradresse a0 nach a1. Das ist in diesem Fall der Hexadezimalwert $48, beziehungsweise als Zeichen das H. Zusätzlich kann man mit dem + angeben, dass der Pointer auf die Adresse incrementiert wird. Sowohl a0 als auch a1 zeigen schon auf das nächste Byte. Man kann schon erahnen, dass so in einer Schleife ein ganzer Speicherbereich kopiert werden kann.
Speicherbereiche kopieren
Das folgende Beispiel zeigt wie man einen ganzen Bereich im Speicher kopiert. Wir verwenden dazu den zuvor schon vorgestellten Befehl für eine do/while Schleife.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | BLOCK_SIZE equ 100 START: move.l #BLOCK_SIZE-1,d7 lea SOURCE,a0 lea DESTINATION,a1 .copy: move.b (a0)+,(a1)+ beq.s .exit dbf d7,.copy .exit move.l $0,d7 rts SOURCE: dc.b "Block of some code to copy in RAM of Amiga 1200." even DESTINATION: ds.b BLOCK_SIZE even |
Das Beispiel führt einen neuen Befehl ein:
- EQU
Das ist wieder kein CPU Befehl. Mit EQU legen wir eine Konstante an. Der Compiler ersetzt im Code die benannten Konstanten mit dem angegebenen Wert. Das ist somit nur eine Hilfe effizienten Source Code zu schreiben.
In der Schleife wird so lange der Wert Byte weise vom a0 Pointer nach a1 kopiert. Wichtig zu wissen ist, dass die Zählvariable im Datenregister d7 gespeichert wird und mit der Größe – 1 initialisiert wird. Das ist deshalb nötig, da die Schleife so lange läuft, bis der Wert negativ wird, also 0 ebenfalls zählt. Mit dem BEQ Befehl beinhaltet die Schleife ebenfalls eine Abbruchsbedingung. Sobald das Zero Flag gesetzt wurde bricht die Schleife schon ab bevor die ganze Block in angegebener Größe kopiert wurde. Das ist bei einem 0 am Ende des Strings der Fall.
Fazit
In diesem ausführlichen Tutorial habe ich gezeigt wie man mit dem RAM Speicher mit Amiga Assembler arbeitet. Der Hauptspeicher bietet uns genügend Platz um für das zu entwickelnde Programm alle Ressourcen abzulegen. Den Speicher kann man sehr einfach mittels Adresszeiger (Pointer) kopieren. Pointerarithmetik funktioniert wie in höheren Sprachen (beispielsweise C).
Das Thema ist zugegebenermaßen etwas schwieriger. Man muss das Konzept von Zeigern auf Adressen verstehen und auch behirnen, dass man diese verändern kann. Man kann recht wild im Speicher herum kopieren und gleich mal das laufende System kaputt machen. Die Verwaltung ist sicher eines der schwierigsten Themen beim Programmieren. Am besten du probierst noch mit eigenen Beispielen aus, was man alles machen kann.
Wie geht es dir nach diesem Tutorial?
Alle Artikel dieser Artikelserie:
Programmieren auf dem Amiga
Programmieren auf dem Amiga – Teil 2
Amiga Assembler
Amiga Assembler – Teil 2
Amiga Assembler – Teil 3
Amiga Assembler – Teil 4
Amiga Assembler – Teil 5
Amiga Bibliotheken verwenden
Hallo, im Sourcecode zum Speicherbereiche kopieren (2) fehlt in Zeile 16 die Nullterminierung.