Copy Link
Add to Bookmark
Report

Grundlagenartikel über Assembler

atari's profile picture
Published in 
atari
 · 20 Mar 2021

Hi, hier ist Kai an den Tasten. Ich wurde soeben dazu verdonnert, einen Grundlagenartikel über Assembler zu schreiben. Hier ist er..

Als erstes ein paar Grundlagen des 68000. Der 68000 kennt Bits, Bytes, Words und Longwords, wobei ein Bit den Zustand 0 oder 1 annehmen kann:

 |--------------------------------------| 
|1 Byte : 8 Bits |
|1 Word : 16 Bits, 2 Bytes |
|1 Longword : 32 Bits, 4 Bytes, 2 Words|
|--------------------------------------|


Er hat 19 Register auf die der Programmierer zugreifen kann:

  • D0-D7 : Datenregister
  • A0-A7 : Adressregister
  • SR : Statusregister
  • CCR : Condition Code-Register
  • USP : Userstackpointer
  • PC : Programmcounter (wird indirekt bei Sprungbefehlen vom Programmierer verändert.)

Der Unterschied zwischen Datenregistern und Adressregistern ist, daß die Adressregister als Zeiger und als Datenspeicher benutzt werden können. Datenregister können nur als 32 Bit Datenspeicher benutzt werden.

Dabei nimmt das Adressregister A7 eine Sonderstellung ein: Es ist der Stackpointer(SP). Im Programm ist es dabei egal ob man A7 oder SP schreibt. Was die Funktion des Stackpointers ist, wird später erklärt.

Im Grund sind es aber nur 17 Register, da das CCR in dem SR drinsteckt. Der Aufbau sieht wie folgt aus:

 ------------------SR---------------- 
| -------CCR------|
| | |
------------------------------------
|T|-|S|-|-|I2|I1|I0|-|-|-|X|N|Z|V|C|
------------------------------------
| | | | | | | | | Carry
| | Interruptmasken | | | oVerflow
| Supervisor | | Zero
Tracebit | Negativ
eXtended

In manchen Dokumentationen wird aber auch zwischen Systembyte (SR OHNE CCR) und Userbyte(NUR CCR) unterschieden.

Hier kommen nun die einzelnen Bits in der Erklärung:

Tracebit: ist es gesetzt, wird nach jedem Befehl eine Traceroutine aufgerufen, deren Adresse an $24 stehen muß.

Supervisor: mit diesem Bit werden Zugriffe auf die Systemvariablen und die Hardwareregister erlaubt. Ist es gesetzt, darf das Programm auf sie zurückgreifen. Man spricht auf davon, daß das Programm im Supervisormodus (Bit=1) oder im Usermodus(Bit=0) ist. Ist ein Programm nicht im Supervisormodus und es greift trotzdem auf Systemvariablen zurück, gibt es einen Bombenhagel.

Interruptmasken: Diese Bits sind für Interrupts relevant, da sie angeben, welche Interrupts zugelassen sind, und welche nicht. Ein Interrupt ist eine Anforderung an den Prozessor, das aktuelle Programm kurz zu Unterbrechen und irgendetwas zu machen. Ein Beispiel ist die Uhr, die alle Sekunde hochgezählt wird. Dabei wird der Interrupt so gestellt, das er alle Sekunde aktiviert wird, und die Interruptroutine kann dann die Zeit hochaddieren. Mit den Bits könnte man eigentlich 8 Prioritäten erzeugen, da aber I0 immer auf low(0) liegt, werden nur die Prioritäten 2,4,6 verwendet. Wenn man nun die höchste Priorität anwählt, werden zwar alle Interrupts gestoppt, es werden aber auch nicht die Systeminterrupts ausgeführt, was zur folge hat, das z.B. die Uhr nicht mehr stimmt... (Wer mehr über Interrupts wissen will, sollte sich das Profi-Buch zulegen)

Carry: Dieses Flag wird bei einem öberlauf gesetzt, z.B. bei einer Addition, Subtraktion oder Bitverschiebung.

Extended: Dieses Bit entspricht dem Carry-Bit, mit dem Unterschied, das es bei einem Vergleichs-Befehl ( CMP ) nicht verändert wird.

Zero: Wenn dieses Bit 1 ist, ist das Ergebnis des letzten Befehls 0.

Negativ: Dieses Flag signalisiert ein negatives Ergebnis des letzten Befehls. Diesem Bit liegt immer die Interpretation des Ergebnisses als Zweierkomplementzahl zu Grunde.

Overflow: Dieses Bit markiert ein öberschreiten des Rechenbereichs in Zweierkomplement-Arithmetik. Werden z.B. die positiven Zahlen 32000 und 768 addiert, ergibt sich als Summe die Zahl 32768. Wurde nur mit Worten gearbeitet, so ist das Ergebnis negativ. Der Rechenbereich der vorzeichenbehafteten Zahl ist bei dieser Operation überschritten worden. Das Overflow-Flag ist dann gesetzt. Das C-Bit dagegen ist gelöscht, da kein öbertrag in vorzeichenloser Arithemik aufgetreten ist.

Nun ist es soweit, die ersten Befehle werden erklärt:

  • MOVE - Kopiert eine Adresse in eine andere. Es können auch Datenregister und Adressregister sowie SR benutzt werden.
  • CLR - Löschen eines Registers
  • ADD - Addiert zwei werte
  • SUB - Subtraktion zweier Werte
  • MUL - Multiplikation zweier Werte
  • DIV - Division zweier Werte
  • NEG - Vorzeichenwechsel eines Wertes
  • CMP - Vergleich zweier Werte
  • NOT - Bitweise NICHT-Operation aller Bits eines Wertes
  • AND - Bitweise UND-Verknüpfung zweier Werte
  • EOR - Bitweise Exklusiv-Oder-Verknüpfung zweier Werte
  • OR - Bitweise ODER-Verknüpfung zweier Werte
  • BSR - Aufruf eines Unterprogramms(wie in Basic GOSUB)
  • RTS - Rückkehr aus einem Unterprogramm(Basic: RETURN)
  • Bcc - Bedingte Verzweigung
  • EXT - Umwandlung eines Bytes in Word oder Word->Longword. Dabei wird Vorzeichenrichtig erweitert.
  • BTST - Bit testen
  • BSET - Bit testen und setzen
  • BCLR - Bit testen und löschen
  • BCHG - Bit testen und verändern
  • ROL - Bitweises Rollen eines Wertes. Das Bit, das links herausgeschoben wird, wird rechts wieder reingeschoben.
  • ROR - Bitweises Rollen eines Wertes nach rechts. Das Bit das Rechts herausgeschoben wird, wird links wieder hereingeschoben.

Es gibt noch einige Befehle, aber das sind die wichtigsten. Am besten kauft man sich ein Buch, in dem alle Befehle drinstehen. In ihm steht auch z.b. welche Bits im CCR von den Befehlen verändert werden. (Zur übersicht aller Befehle habe ich noch an das Ende des Artikels eine Tabelle mit allen Assembler-Befehlen gehängt.)

Nun zu etwas, das in Assembler auch sehr wichtig ist; die Adressierungsarten:

Register direkt
Eines der Register wird bearbeitet
CLR D0 (Löscht D0)

Adressregister indirekt
Der Inhalt des Adressregisters wird als Adresse angesehen, und die Speicherzelle, auf die diese Adresse zeigt, wird berührt.
MOVE (A0),D0 (Angenommen a0=$1234, wird der Wert der in $1234 steht nach d0 transportiert.)

Adressregister indirekt mit Postinkrement
Macht dasselbe wie Adressregister indirekt, nur wird nachdem der Wert aus der Speicherstelle geholt wurde der Wert im Adressregister um die Breite erhöht.
MOVE.B (A0)+,D0 Hole Wert nach D0, dann A0=A0+1
MOVE.W (A0)+,D0 Hole Wert nach D0, dann A0=A0+2
MOVE.L (A0)+,D0 Hole Wert nach D0, dann A0=A0+4

Adressregister indirekt mit Predekrement
Wie bei Adressregister ind. m. Potink., nur wird das Adressregister DAVOR ERNIEDRIGT.
MOVE.W -(A0),D0 A0=A0-2, dann hole Wert nach D0

Adressregister indirekt mit Adressdistanz
Wie bei Adressregister indirekt, nur wird noch zur Adresse des Adressregisters die Adressdistanz hinzuaddiert oder subtrahiert.
Dabei darf die Adressdistanz nur zwischen -32768 und 32767 sein.
MOVE.W -10(A0),D0 Angenommen A0=100, dann wird der Inhalt von Adresse 100-10=90 nach D0 kopiert.

Adressregister indirekt mit Adressdistanz und Index
Wei bei Adressregister indirekt mit Adressdistanz, nur das nur noch der Index mitgerechnet wird.
MOVE.W -10(A0,D0),D1 Wie vorher ist A0=100 und D0=50, dann wird der Inhalt von 100-10+50=140 nach D1 geholt. Dabei darf der Index auf andere Extensionen haben(MOVE.B -10(A0,D0.L),D1).

Absolute Adressierung SEHR einfach:
MOVE.L $1234,$2223 Hier wird der Inhalt von Adresse $1234 nach Adresse $2223 kopiert. Es kann natürlich auch heißen: MOVE.L $1234,D0; Dann wird nur der Inhalt der Adresse $1234 nach D0 kopiert.
Dabei unterscheidet die CPU auch noch zwischen Short(64 Kb) und Long(dem gesamten Speicher). Der Programmierer merkt nicht den Unterschied, da es der Assembler unter umschtänden selber auf Short bringt. Short hat den Vorteil, daß es nicht so viele Bytes im Speicher verbraucht und nicht so eine lange Ausführungszeit hat als Long. Short kann man z.B. bei den Systemvariablen benutzen. MOVE.L $44e.W,A0 Holt aus Adresse $44e den Wert und schreibt ihn in A0. Manche Assembler akzeptieren aber das .W nicht(z.B. der GFA-Assembler). Bei denen kann man aber meistens bei den Optimierungen einstellen, daß er sie in Short-Adressen wandelt.

Konstanten-Adressierung:
Auch sehr einfach. Um eine Konstante zu bewegen, muß nur einfach ein "#" davor.
MOVE.W #12,D0 Danach steht in D0 der WERT 12.

PC-Relative Adressierung:
Diese Adressierung benötigt man wenn man seine Programme optimieren will, oder wenn man sie lageunabhängig programmieren will.
Im Programm steht z.B. MOVE.L 100(PC),D0 Dann wird der Wert, der vom Programmcounter +100 Bytes entfernt ist nach D0 kopiert. Wobei hier der Wert auch auf -32768...32767 begrenzt ist.

PC-Relativ mit Adressdistanz und Index
Hier gilt sinngemäß dasselbe wie bei Adressdistanz mit Adressdestanz und Index, nur das hier die Basisadresse hier PC+2 ist.
MOVE.L 100(PC,A0.W),D0 Hier wird der Inhalt der Adresse, die PC+100+A0.W Bytes entfernt ist, nach D0 kopiert.

Jetzt folgen ein paar Tips zum Optimieren und zum besseren Verständnis:

MOVE.W #65,D0 ist gleich wie MOVE.W #"A",D0, da der ASCII-Code vom Buchstaben "A" den Wert 65 hat. Hingegen wird beim Befehl MOVE.W 65,D0 der Inhalt der Adresse 65 nach D0 kopiert.

MOVE.L #0,D0 macht dasselbe CLR.L D0 (am schnellsten ist aber MOVEQ #0,D0)

PEA str (oder PEA str(pc)) hat dieselbe funktion wie MOVE.L #str,-(sp) Hier wird eine Adresse auf den Stackpointer abgelegt.

1 Nibble = 1 Halbbyte

 -----------------   D1          D2 
VORHER: ABCD ABCD
nach ASL #8,D1 CDxx ABCD
nach ASR #8,D2 CDxx xxAB
nach MOVE.B D1,D2 CDAB xxAB

Nun die Beschreibung des Stacks(wird bei Systemaufrufen gebraucht). Der Stack ist ein Bereich auf den man Daten ablegen kann, wobei die Regel gilt: First In, First Out. Ein (paar) Betriebssystemaufruf sieht z.B. so aus:

 1:     PEA MESSAGE(PC)    ; Adresse von MESSAGE auf Stack 
2: MOVE.W #9,-(SP) ; und Funktionsnummer(PRINTLINE) auf Stack
3: TRAP #1 ; Betriebssystem aufrufen(hier GEMDOS)
4: ADDQ.l #6,SP ; Stack berichtigen(alle Parameter wieder
5: ; ungültig machen)
6: CLR.W -(SP) ; und Programm beenden(PTERM)
7: TRAP #1 ; UND TSCHöSS
8:MESSAGE: DC.B "HELLO WORLD!",0 ; Hier der String, der auf dem
;Bildschirm ausgegeben wird(MUß mit Null-Byte aufhören).

Hier wird die Nachricht "HELLO WORLD!" auf dem Bildschirm ausgegeben

Nun zur Beschreibung:
Im allgemeinen sieht der Assembler den Strichpunkt als Trennung zwischen Befehlen und Kommentaren. Manche brauchen auch anstatt dem ; ein *.
In Zeile 1 wird die Adresse von MESSAGE auf den Stack gebracht.
Man hätte auch MOVE.L #MESSAGE,-(sp) schreiben können, aber der PEA-Befehl ist schneller(außerdem geht beim MOVE-Bef. die PC- Relative Adressierung nicht.)

In Zeile 2 wird jetzt noch die Funtionsnummer auf den Stack abgelegt. Sie ist IMMER ein Word.(Für Beschreibung der Funktionen sollte man sich auch ein Buch(am besten das Profi-Buch) zulegen, da es einige Betriebssystem-Funktionen gibt, die einem einiges abnehmen.(z.B. auch die File-Funktionen)).

Nun kommt in Zeile 3 der Aufruf an das Betriebssystem aktiv zu werden. Der Befehl löst eine Exception aus (dabei steht für 1 das GEMDOS; 13 = BIOS; und 14 = XBIOS)

In Zeile 4 wird nun der Stack wieder berichtigt. Dabei MUß man in Bytes rechnen((1 Longword)4 +(1 Word)2 = 6(gesamter Betrag))

In Zeile 6 wird nun die Funktionsnummer 0 auf den Stack gelegt. Mit ihr kann man Programme beenden. Nun wird noch das GEMDOS aufgerufen(in Zeile 7) und das Programm ist beendet.

Jetzt wird nur noch die Variable MESSAGE deffiniert. Dabei steht der Opcode DC.B dafür, das der Assembler die Nachfolgenden Werte direkt in das Programm schreibt. Würde man DC.L $100 schreiben, würde nachher im Programm an der Stelle $00000100 stehen. Eigentlich hätte ich die Variable MESSAGE in den DATA- Bereich des Programms legen sollen (zur besseren öbersicht), aber wenn sie im DATA-Bereich liegt, dann kann man nicht mehr PC- Relativ darauf zurückgreifen.

Jetzt fällt auf, das der Text auf den Bildschirm ausgegeben wird, man aber keine Zeit hat, den Text zu lesen. Deshalb wird das Programm so erweitert, das es noch auf einen Tastendruck wartet.
Dazu muß man in die Zeile 5 noch 3 Befehle einfügen.

 MOVE.W #7,-(SP)    ; Funktionsnummer (Crawin) 
TRAP #1 ; GEMDOS anklingeln
ADDQ.L #2,SP ; Stack wieder berichtigen

Setzt mal die Befehle ein, assembliert die paar Zeilen und startet es auch noch. Nun wartet das Programm brav auf eine Taste, bevor es wieder zum Desktop geht. Wie der Assembleriervorgang gestartet wird, muß jeder selber in der Anleitung des Assemblers nachlesen, da es bei jedem Assembler verschieden ist.

Der Stack sieht beim Programm wie folgt aus:

 Am Anfang:       |----| <- Zeiger von Stack 
|----|

Nach Zeile 1 |1234| Angenommen die Adr. von MESSAGE liegt bei $12345678
|5678| (in 2 Worten(Adr.=Longword)
|----| <- Zeiger von Stack

Nach Zeile 2 |12345678| =Adr. von Message
|0009| = Funktionsnummer
|----| <- Zeiger von Stack

Nach Zeile 3 hat der Stack denselben Aufbau wie nach Zeile 2.
Nach Zeile 4 |----| <- Zeiger von Stack
|----|

Nach Zeile 4 hat der Stack wieder denselben Wert wie am Anfang.

Bei den Befehlen für den Tastendruck sieht der Stack so aus:

 Am Anfang                |----| <- Zeiger von Stack 

Nach MOVE.W #7,-(sp) |0007|
|----| <- Zeiger von Stack

Nach ADDQ.L #2,SP |----| <- Zeiger von Stack
|----|

Nun, wenn man jetzt nur Bahnhof verstanden hat, ist es auch nicht weiter schlimm. Mir ging es am Anfang auch so. Jetzt kann ich jedem nur noch raten andere Programme anzuschauen und zu erforschen, wie die Programmierer ein best. Problem gelöst haben. Und noch ein Tip: besorgt euch das Profi-Buch. Es ist für jeden Programmierer eine große Hilfe, da in ihm die gesamten Betriebssystem-Aufrufe dokumentiert sind (mit Beispielen) und die einzelnen Hardwareteile und Hardwareregister dokumentiert sind.

Also Leute, versucht soviel dokumentierte Listings zu bekommen und zieht sie euch rein, denn anders lernt ihr es nicht...
Viel Spaß...


P.S.: Hier noch die versprochene Tabelle...

Befehlsreferenz(aus ST-Magazin):
der Aufbau ist:

  
Befehl Präfix Quelle, Ziel Erklärung
MOVE B,W,L <ea>,<ea> öbertrage Operand von Quelle nach Ziel
MOVEA W,L <ea>,An öbertrage eine Adresse
MOVEM W,L Reg-liste,<ea> öbertrage mehrere Register
MOVEP W,L Dx,d(An) öbertrage Daten von/zur Peripherie
MOVEQ B,W,L #Konstante,Dn öbertrage SCHNELL eine Konstante
CLR B,W,L <ea> Löschen eines Operanden
LEA <ea>,An Lade eine effektive Adresse
PEA <ea> Lege eine Adresse auf den Stack ab
EXG Rx,Ry Austauschen von Registerinhalten
SWAP Dn Vertausche zwei Registerhälften
ABCD B Dx,Dy Addition zweier BCD-Zahlen
ADD B,W,L <ea>,Dn Binäre Addition
ADDA W,L <ea>,An Binäre Addition von Adressen
ADDI B,W,L #Konst.,<ea> Addition einer Konstante
ADDQ B,W,L #Konst.,<ea> Schnelle Addition einer wobei Konst.
zwischen 0..8 sein muß
ADDX B,W,L Dy,Dx Addition mit Extendbit
DIVS <ea>,Dn Division mit Vorzeichen
DIVU <ea>,Dn Division ohne Vorzeichen
MULS <ea>,Dn Multiplikation mit Vorzeichen
MULU <ea>,Dn Multiplikation ohne Vorzeichen
NBCD B <ea> Negation einer BCD-Zahl(Neunerkomplement)
NEG B,W,L <ea> Negation eines Operanden(Zweierkompl.)
NEGX B,W,L <ea> Negation mit Extendbit
SBCD B Dx,Dy Subtraktion zweier BCD-Zahlen
SUB B,W,L <ea>,Dn Binäre Subtraktion
SUBA W,L <ea>,An Binäre Subtraktion von Adressen
SUBI B,W,L #Konst.,<ea> Subtraktion einer Konst.
SUBQ B,W,L #Konst.,<ea> Schnelle Subtraktion einer Konst.
(Beschränkung siehe ADDQ)
SUBX B,W,L Dy,Dx Subtraktion mit Extendbit
BRA B,W >Marke< Verzweigung unbedingt
Bcc >Marke< Verzweigung bedingt(siehe unten)
DBcc W Dn,>Marke< Prüfe Bedingung, Dekrement und Verzweige
JMP <ea> Springe an Adresse
Scc B <ea> Setze Bits abhängig von Bedingung sonst
alle Bits = 0
BCHG Dn,<ea> Verändere ein bestimmtes Bit
BCLR Dn,<ea> Lösche ein bestimmtes Bit
BSET Dn,<ea> Setze ein bestimmtes Bit
BTST Dn,<ea> Prüfe ein bestimmtes Bit
CHK <ea>,Dn Prüfe ein Datenreg. gegen Grenze
CMP B,W,L <ea>,Dn Vergleiche zweier Operanden
CMPA W,L <ea>,An Vergleichen zweier Adressen
CMPI B,W,L #Konst.,<ea> Vergleichen mit einer Konstante
CMPM B,W,L (Ay)+,(Ax)+ Vergleichen zweier Speicheroperanden
TAS <ea> Prüfe und setze ein Byte
TST B,W,L <ea> Teste ob Operand null ist
AND B,W,L <ea>,Dn Logisches UND
ANDI B,W,L #Konst.,<ea> Logisches UND mit Konstante
EOR B,W,L <ea>,Dn Exklusiv-ODER
EORI B,W,L #Konst.,<ea> Exklusiv-ODER mit Konstante
NOT B,W,L <ea> Einer-Komplement(Invertieren)
OR B,W,L <ea>,Dn Logisches ODER
ORI B,W,L #Konst.,<ea> Logisches ODER mit Konstante
ASL B,W,L <ea> Arithmetische Verschiebung links
ASR B,W,L <ea> Arithmetische Verschiebung rechts
LSL B,W,L <ea> Logische Verschiebung links
LSR B,W,L <ea> Logische Verschiebung rechts
ROL B,W,L <ea> Rotation nach links
ROR B,W,L <ea> Rotation nach rechts
ROXL B,W,L <ea> Rotation mit Extendbit nach links
ROXR B,W,L <ea> Rotation mit Extendbit nach rechts
BSR B,W >Marke< Verzweige in Unterprogramm
JSR <ea> Springe an ein Unterprogramm
RTR Rückkehr mit Laden der Flags
RTS Rückkehr aus einem Unterprogramm
EXT Dn Vorzeichenrichtige Erweiterung
LINK W,L An,>offset< Baue Stackbereich auf
NOP Keine Operation
RESET Zurücksetzen der Peripherie
RTE Rückkehr aus einer Exception
STOP Halte in der Verarbeitung an
TRAP #Vektornr. Gehe in Exception
TRAPV Prüfe ob Flag gesetzt, evtl, Excep.
UNLK An Baue Stackbereich ab

P.P.S.: Falls ihr noch Fragen habt, oder ihr nicht wisst, wie man ein Problem programmieren könnte, dann schreibt einfach an die Redaktion. Ich glaube die sind so nett, und leiten es an mich weiter... Also, keep hacking...

Kai

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT