Adok's Way to Assembler
Folge 1
Hier ist mein Kurs für alle, die Assembler lernen wollen! Wir fangen praktisch
bei Null an, so daß jeder anfangen kann, egal ob er Einsteiger oder
Programmier-Profi ist. Voraussetzung für diesen Kurs ist es, daß ihr einen
Assembler habt. Im Kurs selbst beziehe ich mich auf den Turbo Assembler
(TASM), der den meisten Borland-Programmiersprachen kostenlos beiliegt. Falls
ihr den Turbo Assembler nicht habt, könnt ihr auch den kompatiblen Microsoft
Makroassembler (MASM) verwenden, bei dem allerdings die Kommandozeilenaufrufe
zum Compilieren und Linken anders aussehen.
+++ Unser Komm-Puter +++
So, beginnen wir einmal mit einem kurzen Überblick über das Innere des Compis.
Das Herz des Computers ist der Prozessor, auch CPU genannt. Hier werden die
Assemblerbefehle verarbeitet. Das funktioniert, einmal ganz grob gesagt,
folgendermaßen: Der Prozessor ist in zwei Teile geteilt. Der eine Teil holt
sich die Befehle aus dem Arbeitsspeicher und gibt sie in die Warteschlange des
Prozessors. Der zweite Teil des Prozessors holt sich dann der Reihe nach die
Befehle aus der Warteschlange, übersetzt und verarbeitet sie. Dabei spricht
er, je nach Befehl, den Arbeitsspeicher an, rechnet einfache arithmetische
Operationen aus oder gibt den Befehl an die Peripheriegeräte weiter.
Das ist im Moment alles, was wir über den Prozessor wissen müssen. Weiter
geht's mit dem Arbeitsspeicher, dem RAM. Wie wohl jeder weiß, kann man diesen
Speicher frei beschreiben und lesen, nur gehen die Informationen nach dem
Ausschalten wieder verloren. Jede Speicherstelle im RAM wird Adresse genannt.
+++ DOS' Speicheradressierung +++
Bei dieser Gelegenheit können wir gleich die Speicheradressierung von DOS
besprechen: Da in DOS 1 MB Speicher adressiert werden kann, im Real Mode (dem
Prozessormodus, in welchem DOS normalweise arbeitet) die Adreßleitung jedoch
nur 16 Bit breit ist (für 1 MB Speicher bräuchte man 20 Bit), muß man ein
bißchen tricksen. Der Speicher wird in DOS nicht linear, sondern über ein
Segment und einen Offset adressiert. Das Segment wird mit 16 multipliziert und
der Offset dazuaddiert - dann hat man die "physikalische" Adresse.
Ein Segment ist 64 KByte groß. Folglich kann die Offsetadresse zwischen 0h und
FFFFh (eine Hexadezimalzahl, welche der Dezimalzahl 65535 entspricht) groß
sein. Das Segment kann ebenfalls 0h bis FFFFh sein. Wenn man mal nachrechnet,
ergibt sich, daß manche Segment-Offsetkombinationen einander überschneiden.
Aber gehen wir vorläufig nicht darauf ein, damit es nicht zu kompliziert wird.
Jau, und die Adresse wird immer in der Form xxxx:xxxx geschrieben, und zwar in
hexadezimalen Zahlen, da diese die Basis 16 haben und man somit leichter die
physikalische Adresse berechnen kann! Falls jemand nur Bahnhof versteht: In
unserem dezimalen Zahlensystem haben wir die Basis 10. Wir haben auch zehn
Ziffern: von 0 bis 9. Die Dezimalzahl 10 ist, wie wohl jedes Baby weiß, zehn.
Beim Hexadezimalsystem mit der Basis 16 haben wir auch 16 Ziffern. Man
verwendet hierbei nach der 9 die Buchstaben A bis F, also geht das Hex-System
von 0 bis F. Nach F folgt dann 10, und diese Zahl ist im Hexadezimalsystem
nicht zehn, sondern sechzehn! Zur besseren Unterscheidung schreibt man deshalb
nach jeder Hexadezimalzahl ein kleines h. Alles klar? Wenn nicht, schaut in
einem Mathe-Lexikon nach!
+++ E/A-Ports +++
Weiters finden wir im PC die Peripheriegeräte (Drucker, Monitor, Tastatur &
Co.) und Zusatzkarten. (Nicht Asse, Piks und Konsorten, sondern Soundkarte,
Grafikkarte und so weiter! ;) ) Diese Teile werden alle über die E/A-Ports
angesprochen.
So, das wäre einmal alles, was wir über das Innere des Compis wissen müssen!
Bevor wir das erste Assembler-Programm schreiben können, müssen wir jedoch
noch etwas Theorie lernen...
+++ Register +++
Register - genauer gesagt, Prozessorregister, denn auch die Grafikkarte und
andere Peripherie kann eigene Register haben - sind eigentlich 16-Bit-breite
Integer-Variablen, die Werte von 0 bis 65535 annehmen können, nur sind sie
schneller als normale Variablen (die ja im Hauptspeicher abgelegt werden),
weil die Register ein Bestandteil des Prozessors sind. Es gibt mehrere Arten
von Registern:
- Allgemeine Register: Diese sind frei verwendbar, allerdings müssen bei
manchen ASM-Befehlen in ihnen bestimmte Werte stehen. Folgende Register
werden bei folgenden Befehlen zu folgendem Zweck benutzt:
AX = Meistbenutztes allgemeines Reg. Bei Interrupt-Aufrufen (Erklärung folgt
später) steht hier meistens die Funktionsnummer. Außerdem wird AX für
manche Rechenoperationen benötigt.
BX = Parameter für Interrupts, indirekte Adressierung (z.B. beim MOV-Befehl,
den wir noch SPÄTER besprechen werden).
CX = Zählreg für Schleifen.
DX = Wird von Befehlen, die die E/A-Ports ansprechen, verwendet. Außerdem
wird das DX-Register auch von einigen Rechenoperationen verwendet, wenn
AX "nicht mehr reicht".
Jedes allgemeine Reg läßt sich in zwei 8-Bit-Regs teilen, die dann nicht mit
X, sondern mit H bzw. L enden. Genauer gesagt: Ein 16-Bit-Reg wie AX läßt
sich als vierstellige Hexadezimalzahl darstellen. Die oberen zwei Stellen
(Hi-Byte) sind AH, die unteren (Lo-Byte) AL. Somit dürfte auch geklärt sein,
wozu Hexadezimalzahlen gut sind: 8 Bit (1 Byte) lassen sich durch zwei
Hex-Stellen, 16 Bit (1 Word) durch vier Hex-Stellen darstellen. Noch ein
Beispiel zur Vertiefung:
AX = 1234h => AH = 12h, AL = 34h.
- Segmentregister: Was die Segmente sind, haben wir ja schon bei der
Adressierung des Arbeitsspeichers in DOS geklärt. In diesen Segmentregistern
wird nun die Segmentadresse des jeweiligen Segments (schon wieder so ein
Wortspiel) angegeben. Dabei gibt es mehrere Segmente, die für verschiedene
Aufgaben GEDACHT sind. Ich sage 'gedacht', weil man sie auch für andere
Zwecke mißbrauchen kann! So lassen sich mit einem einfachen Trick Variablen
auch im Codesegment anlegen. Dann kann man gleich ein Segment einsparen, was
für COM-Dateien essentiell ist - siehe unten. Doch nun zur Übersicht, damit
jeder versteht, wovon ich quassle.
CS = Zeigt auf das Codesegment, in dem (normalerweise, hehe) die ASM-Befehle
eines Proggys (also das eigentliche Proggy) steh[en/t].
DS = Zeigt auf das Datensegment, in dem die Variablen des Proggys stehen.
ES = Kann auf ein beliebiges Segment im RAM zeigen, das frei verwendet
werden darf!
SS = Zeigt auf das Stacksegment, in dem meistens Daten temporär
zwischengespeichert werden.
WICHTIG: COM-Dateien dürfen nur ein Segment haben, nämlich das Codesegment!
Deshalb muß man etwas tricksen, wenn man Variablen in COMs verwenden will.
Das werden wir aber noch später besprechen.
- Weitere Register, die man meistens frei verwenden kann, sind BP, SI und DI.
BP wird in Hochsprachen zum Adressieren von Variablen verwendet, SI und DI
finden vor allem bei den Stringbefehlen ihre Verwendung. Abzuraten ist
jedoch von IP (zeigt auf die nächste Anweisung im Programm) und SP (zeigt
auf die aktuelle Position im Stack)! Wer diese verändert, muß mindestens mit
einem Absturz rechnen, wenn nicht etwas noch schlimmeren!
So, nun zu den...
+++ Interrupts +++
Interrupts sind, mal oberflächlich betrachtet, stinknormale Unterprogramme.
Doch es gibt zwei Arten von Interrupts:
- Hardware-Interrupts und
- (tatatatamm) Software-Interrupts!
Hardware-Interrupts werden immer automatisch durch ein bestimmtes Ereignis
ausgelöst. Zum Beispiel wird beim Drücken einer Taste der Tastaturinterrupt
ausgelöst, der dann herausfindet, welche Taste gedrückt wurde, und sie in den
Tastaturpuffer speichert. Das geht so schnell, daß ein normaler User gar nix
davon merkt!
Wenn man das Ganze genauer betrachtet, gibt's zwei Arten von Hardware-
Interrupts: Während die wenigen nicht-maskierbaren Interrupts IMMER ausgelöst
werden, kann man die maskierbaren Interrupts mit dem Assembler-Befehl CLI
abschalten und mit STI wieder einschalten.
Das zweite sind die Software-Interrupts, die keine Interrupts in dem Sinn
sind, sondern wirklich nur Unterroutinen! Sie werden vom BIOS und vom DOS
bereitgestellt und dienen hauptsächlich dazu, dem Coder bei der ohnehin harten
Arbeit ein wenig zu unterstützen. So beinhaltet der Interrupt 33h viele
Funktionen, mit denen man ohne große Schwierigkeiten Maussteuerung
programmieren kann.
Die Software-Interrupts werden in Assembler mit dem Befehl INT aufgerufen.
Danach folgt die Nummer des Interrupts. Viele Interrupts haben mehrere
Funktionen. So kann der BIOS-Interrupt 10h den Bildschirmmodus wechseln, den
Bildschirmmodus abfragen, Text ausgeben, den Cursor positionieren und vieles
mehr. Deshalb benötigt man eine Funktionsnummer. Sie muß meistens im AH-
Register (Erinnert ihr euch? AH ist das Hi-Byte von AX!) übergeben werden, nur
beim Interrupt 33h im "großen" AX. Die Parameter für die Interrupts werden in
den anderen Registern übergeben. Welche das sind, ist von Interrupt zu
Interrupt verschieden. Außerdem haben einige Interrupts sogar Unterfunktionen
und Unter-Unterfunktionen und Unter-Unter-Unter... Diese werden meistens in AL
übergeben.
So, das war die Theorie! Mit diesem Wissen werden wir das Beispielprogramm,
das in der nächsten Folge kommt, viel besser verstehen. Bis dahin noch viiiiel
Spaß wünscht euch Adok!