Vorwort und Umfang dieses Dokuments

Vor einiger Zeit fragte mich ein Freund nach einer Mausunterstützung für ein Turbo Pascal Programm. Ich empfahl ihm die Mausunit von Matin. Da tauchten dann aber eine große Menge von Befehlen wie "mov", "int" usw. auf, deren Funktionsweise er sich nicht erklären konnte.

In einer umfangreichen Email erklärte ich ihm die Grundlagen. Auf Basis dieser Email ist das vorliegende Dokument entstanden. Ich habe den ursprünglichen Text erweitert und systematisiert.

Das Dokument soll den Leser befähigen kleinere in Assembler geschriebene Programme zu verstehen und eventuell selbst zu entwerfen. Dabei geht es speziell um den Einsatz von Assembler als Unterstützung und Erweiterung der Hochsprache Turbo Pascal. Es ist nicht Sinn des Dokumentes ausgefeilte Grafikengines zu entwerfen.

In meinen Ausführungen beziehe ich mich hauptsächlich auf zum 8086 kompatible CPU's, da ich nur mit diesen nähere Erfahrungen habe. Das Grundprinzip ist aber bei allen Prozessoren gleich!

Zum Anfang dieser Seite...

Was ist Assembler?

Ein Assembler ist ein Übersetzer für Programmcode, der sich aus Maschinenbefehlen zusammensetzt. Diese Befehle können sich je nach verwendeten Prozessor stark unterscheiden. Im normalen PC werden Prozessoren verwendet, die zu dem 8086 von Intel kompatibel sind. Der Befehlssatz wurde über die Jahre erweitert, wobei die Kompatibilität zum "Urmodell" stets gewahrt wurde01.

In der Art der verwendeten Befehle besteht der wesentliche Unterschied zu allen anderen Programmiersprachen. Während sich Befehle bei den Hochsprachen02 in der Übersetzung aus mehreren Anweisungen im endgültigen Code zusammensetzen, wird der Assemblerbefehl durch den Assembler lediglich in die entsprechende Binärform03 übersetzt. Weiterhin ersetzt der Assembler Variablen durch die entsprechenden Speicheradressen.

Die Hochsprachen stellen eine Vielzahl von Funktion wie z. B. clrscr, writeln( ) usw. zur Verfügung. Deren Realisierung würde in Assembler mehrere Zeilen in Anspruch nehmen. Bei solchen Standardfunktionen sollte man immer auf die Funktionen der Hochsprache zurückgreifen.

Assembler-Programmierung wird auch oftmals als Systemprogrammierung bezeichnet. Dieses Wort weist auf den ganz speziellen Charakter hin. Assemblerprogramme sind meist nur auf einer Prozessorplattform lauffähig und können nur unter großem Aufwand auf ein anderes System portiert werden04. So kann man bei einem Assembler nicht einstellen einmal 8086 Befehle und für das nächste System RISC-Befehle zu nutzen, da die verwendeten Befehle und deren Einsatz sich stark unterscheiden.

Zum Anfang dieser Seite...

Grundkenntnisse über die CPU

Bevor man sich in die Systemprogrammierung stürzt, sollte man zunächst einige grundlegende Sachverhalte über den Aufbau des Computers in das Gedächtnis zurückrufen. Der Computer besteht aus einer Vielzahl einzelner Bauteile wie Speicher, Festplatte, Grafik- und Soundkarte und der Peripherie wie Tastatur, Monitor und Drucker. Im Mittelpunkt steht der Prozessor - die CPU. Sie muss die ganzen Geräte miteinander koordinieren. Zur Kommunikation mit den einzelnen Teilen steht ihr das Bussystem zur Verfügung.

Die CPU unterteilt man nochmals in das Rechenwerk, das Steuerwerk und den internen Speicher in Form von Registern05. Für den Programmierer sind dabei die Register von besonderem Interesse. In diesen werden dem Prozessor die Befehle und deren Parameter übergeben. Rückgabewerte nach der Ausführung eines Befehls werden ebenfalls in den Registern abgelegt. Bestimmte immer wiederkehrende Informationen werden in den Flags dargestellt, die aber dem Prinzip nach auch Register sind.

!Die folgenden Ausführungen beziehen sich nur auf zum 8086 kompatible Prozessoren!

Der 8086 besaß Register mit der Größe von 16bit06. Dies war zum einem durch die technischen Möglichkeiten und auf der anderen Seite durch die gemäßigten Ansprüche an die Hardware begründet. Diese Register bekamen nach ihrer Aufgabe bestimmte Namen, aus denen sich die Kürzel ableiten lassen, zugewiesen.

Klasse Kürzel Bedeutung weitere Hinweise
Allgemeine Register AX Akkumulator Teilung in hohes Byte (AH) und niedriges Byte (AL)
BX Basis-Register Teilung in hohes Byte (BH) und niedriges Byte (BL)
CX Count-Register Teilung in hohes Byte (CH) und niedriges Byte (CL)
DX Daten-Register Teilung in hohes Byte (DH) und niedriges Byte (DL)
Pointer-Register SP Stack-Pointer zur Adressierung des Stacks verwendet
BP Base-Pointer zur Adressierung des Stacks verwendet
IP Instruction-Pointer Offset des nächsten Befehls
Index-Register SI Source-Index Unterstützung von Adressierungen
DI Destination-Index Unterstützung von Adressierungen
Segment-Register CS Code-Segment zeigt auf aktuelles Codesegment
DS Daten-Segment zeigt auf aktuelles Datensegment
SS Stack-Segment zeigt auf aktuelles Stapelsegment
ES Extra-Segment zeigt auf weiteres Datensegment

Man sollte am Anfang die Finger von den Pointer-, Index- und Segment-Registern lassen. Diese zeigen auf die Speicherbereiche mit dem Programmcode und den Stack.

In der Übersicht kann man erkennen, dass die Allgemeinen Register jeweils in einen hohen und einen niedrigen Abschnitt unterteilt sind. Bei manchen Operationen reicht ein "halbes" Register aus. Deshalb kann man diese Abschnitte einzeln ansprechen.

Wie bereits erwähnt wurde gibt es neben den Registern noch die Flags. Das sind einzelne Speicherzellen, die jeweils den Wert 0 oder 1 haben. Diese Flags (Flagge, Schalter) haben ebenfalls bestimmte Bezeichnung, aus denen sich ihre Bedeutung und Aufgabe ableiten läßt. Dabei muss man zwischen vom Prozessor zu setzende (Statusflags) und vom Programm setzbare (Kontrollflags) Flags unterscheiden.

Klasse Kürzel Name Bedeutung Gesetzt durch
Statusflags CF Carry-Flag Übertragflag Prozessor
AF Auxiliary Carry-Flag Hilfsübertragflag Prozessor
ZF Zero-Flag Nullflag Prozessor
SF Sign-Flag Vorzeichenflag Prozessor
PF Parity-Flag Paritätsflag Prozessor
OF Overflow-Flag Überlaufflag Prozessor
Kontrollflags TF Trap-Flag Einzelschrittflag Programm
IF Interrupt Enable-Flag Interruptflag Programm
DF Direction-Flag Richtungsflag Programm

Wenn das Ergebnis einer Operation 0 ist, wird das Zero-Flag gesetzt. Das bedeutet es hat den Wert 1. Dies kann man in seinem Programm überprüfen und nutzen.

Mit jeder neuen Prozessorgeneration kommen neue Flags hinzu. Diese sind aber oftmals nicht dokumentiert und für die einfache Assembler-Programmierung nicht von Bedeutung.

Weiterhin steht der CPU und dem Programmierer der Stack (Stapel) zur Verfügung. Dies ist ein gesonderter Bereich im RAM07, der zur kurzzeitigen Zwischenspeicherung genutzt werden kann. So übergibt Turbo Pascal intern Werte an Funktionen mit Hilfe des Stack. Bei 8086-Systemen werden die als letztes auf den Stapel "gelegten" Daten zuerst wieder herausgegeben. Dies nennt man das LIFO-Prinzip08. Man kann sich dabei den Stack wie einen Stapel Teller vorstellen, bei dem ein neuer Teller immer oben draufgelegt wird. Wird nun ein Teller benötigt nimmt man zuerst den obersten Teller wieder weg.

Zum Anfang dieser Seite...

Aus dem Leben einer CPU

Der Leser sollte sich in folgende Situation versetzen: Das auszuführende Programm befindet sich im Hauptspeicher, die Register sind richtig gesetzt und zeigen auf den nächsten Befehl und den Stack. Die CPU lädt jetzt den nächsten Befehl in die Register und analysiert ihn. Wenn Parameter benötigt werden, dann werden die ebenfalls in die entsprechenden Register geladen. Danach wird der Befehl ausgeführt und eventuelle Rückgabewerte in den Registern abgelegt. Diese können dann vom nächsten Befehl genutzt werden. Die CPU würde jetzt den nächsten Befehl laden und sich auf diese Weise durch den gesamten Programmcode arbeiten.

Während der Ausführung können aber unerwartete Ereignisse auftreten, auf die die CPU reagieren muss. Dann wird die Arbeit an der momentanen Stelle unterbrochen und die nötigen Handlungen ausgeführt. Ein solcher Interrupt könnte die Betätigung der Tastatur durch den Benutzer sein. Um die gedrückte Taste auf den Bildschirm auszugeben muss ein anderer Teil des Programms ausgeführt werden. Deshalb verzweigt die CPU an diese Stelle. Danach kehrt sie zu der Stelle, an der sie unterbrochen wurde, zurück und setzt die Ausführung des Programms fort.

Der Programmierer kann solche Interrupts für seine Zwecke ausnutzen. So kann er den Timerinterrupt auf eine eigene Routine verbiegen. Dadurch wird diese immer nach einer bestimmten Zeit aufgerufen. Weiterhin gibt es eine Vielzahl von vordefinierten Interrupts. Diese kann man mit Funktionen der Hochsprachen vergleichen. In den Allgemeinen Registern werden die Parameter übergeben und dann der entsprechende Interrupt aufgerufen. Dadurch kann man z. B. den Maustreiber oder die Grafikkarte ansprechen oder das System booten.

Zum Anfang dieser Seite...

Schreiten wir zur Tat!

Die Verwendung von Assembler soll an einem kleinen Turbo Pascal Programm (1226 Bytes) gezeigt werden, wie man den Mauszeiger sichtbar macht, die aktuelle Position der Maus bestimmt und den Tastenstatus der Maus ermittelt. Damit das Programm funktioniert muss ein Maustreiber installiert sein09.

Am Anfang des Programms wird der Mauszeiger mit Hilfe der Prozedur "maus_an" auf dem Bildschirm sichtbar gemacht:


procedure maus_an; assembler;
asm
   mov ax, 01h
   int 33h
end;

Zuerst fällt das Schlüsselwort "assembler" auf. Damit wird dem Compiler mitgeteilt, dass die folgende Prozedur komplett in Assembler geschrieben ist. Als Einleitung für den Assemblerblock steht das Schlüsselwort "asm". Der Block wird durch ein "end;" geschlossen. Dazwischen dürfen keine reinen Turbo Pascal Befehle wie "writeln( )" usw. stehen.

Nun wird der Wert 01h in das AX Register des Prozessors mit Hilfe des Befehls "mov" geschoben. Das "h" steht für hexadezimal. Auch wenn 1 in hexadezimaler und in dezimaler Schreibweise die gleiche Zahl ist, sollte man sich die hexadezimale Schreibweise angewöhnen10.

Mit dem Befehl "mov" wird eine Übertragung von der Quelle (rechter Operand) in das Ziel (linker Operand) ausgeführt. So kann man den Wert von AX an eine Variable folgendermaßen übertragen:


mov var, ax

Doch zurück zum oberen Beispiel. Nachdem im Register AX der Wert 1 gespeichert wurde, wird der Interrupt 33h11 aufgerufen. Dies ist der Mausinterrupt, der von dem Maustreiber ausgestaltet wird. Dabei haben sich gewisse Standards herausgebildet. So ist die Funktion 01h des Mausinterrupts eine Funktion zum Einschalten der Mauscursordarstellung. Die entsprechende Funktion wählt man bei fast allen Interrupts über das AX Register. In einigen Fällen wird im AH Register die Hauptfunktion und im AL Register die Unterfunktion angegeben. Interruptlisten bieten eine Übersicht über alle gängigen Interrupts samt ihren Funktionen und sollten immer zu Rate gezogen werden. Ich rate davon ab, einfach mal auf gut Glück einige Interrupts und Funktionen auszuprobieren.

Wenn alles klappt sollte jetzt auf dem Bildschirm der Mauscursor erscheinen. Diesen kann man auch bewegen. Die Steuerung und Darstellung übernimmt der Maustreiber.

Als nächstes soll eine Prozedur geschrieben werden, die die Funktion 03h des Mausinterrupts aufrufen soll. Diese liefert als Rückgabewerte im CX Register die x-Position und im DX Register die y-Position der Maus. Der Tastenstatus der Maus wird im BL Register zurückgeben. Während die Werte in CX und DX den Koordinaten entsprechen, muss man die Werte in BL noch entsprechend interpretieren. Eine 0 bedeutet in diesem Fall, dass keine Taste gedrückt ist. Die weiteren Werte sind dem Programm zu entnehmen! Die Prozedur würde folgendermaßen aussehen:


procedure maus_status; assembler;
asm
   mov ax, 03h
   int 33h
   mov status.x, cx
   mov status.y, dx
   mov status.taste, bl
end;

Die ersten 4 Zeilen sollten bekannt sein. Die restlichen 4 Zeilen stellen ebenfalls kein Problem mit der Kenntnis des "mov" Befehls dar. Die Inhalte der einzelnen Register wird in einen Record geschoben.

Alle anderen Funktionen des Programms verlaufen analog und sollten mit den oben ausgeführten Beispielen verglichen werden.

Zum Anfang dieser Seite...

Weitere Möglichkeiten des Selbststudiums

Zuerst sollte man einen Blick in seine örtliche Bibliothek wagen. Manchmal findet man dort Bücher über Assembler. Diese beginnen meist mit den Grundlagen und zeigen Möglichkeiten für den Einsatz von Interrupts.

Ich möchte an dieser Stelle mal 3 ausgewählte Bücher vorstellen. Für den Einsatz von Assembler in der Hochsprache Turbo Pascal hat sich das Buch PC Underground (ISBN 3-8158-1185-6) bewährt. Hier wird anhand von Vektorgrafiken und Soundplayern der Einsatz von Systemprogrammierung erklärt. Das Buch ist aber scheinbar nicht mehr über den normalen Buchhandel erhältlich. Man kann deshalb nur versuchen es bei Freunden oder Bekannten aufzutreiben.

Ganz anders, bezüglich der Lieferbarkeit, sieht es mit dem 2. Buch aus. Man könnte es sozusagen als eine Art Standardwerk bezeichnen.

Ich möchte an dieser Stelle darauf hinweisen, dass HPFSC eine kleine Aufwandsentschädigung von Amazon.de erhält, wenn Sie das Buch über die entsprechenden Links kaufen. Dieser Obolus ist aber nicht der Grund für die Empfehlung, sondern die folgenden Fakten:

Buchcover

Programmiersprache Assembler. Eine strukturierte Einführung.

Autor: Reiner Backer
Preis: 9,90 Euro
ISBN: 3499612240
Verlag: Rowohlt TB-V.
Seiten: 352 im Taschenbuchformat
  Bestellung bei amazon.de

Sollten Sie den Kauf in Erwägung ziehen, dann würden wir uns freuen, wenn Sie dies über diesen Link tun würden!

Als letzte Empfehlung sei das Buch "Grundlagen und Konzepte der Informatik" von Hartmut Ernst genannt.

Wie der Titel schon sagt, beschäftigt sich das Buch nicht ausschließlich mit der Assemblerprogrammierung. In einem etwa 50 Seiten starken Kapitel wird die Systemprogrammierung anhand des Microprozessors M68000 von Motorola erläutert. Dieser Chip ist von daher sehr interessant, da in vielen Embedded Systems der Mikro-Controller 68HC11 verwendet wird, der eine ähnliche Programmierung aufweist.

Der Autor erläutert die Problematik immer zuerst allgemein um sie dann auf die Programmierung des M68000 konkret anzuwenden.

Neben diesem sehr interessantem Kapitel bietet das 800 Seiten starke Buch aber noch viel mehr. Es deckt wesentliche Bestandteile des Grundstudiums der Informatik (und verwandter Disziplinen) ab. Es werden die Grundlagen der Informationstheorie, Schaltalgebra, Softwareentwicklung, Automation, Algorithmierung und Datenbanktechnik vermittelt. Weiterhin findet man ein Kapitel zur Programmierung in C und ein kleines Kapitel zur Programmiersprache Java. Beide Kapitel sind für den Einstieg ausreichend, den Rest findet man im Internet.

Der eigentliche Hammer an dem Buch ist aber das unglaubliche Preis-Leistungs-Verhältnis. Normalerweise kosten Fachbücher über eines dieser Themen schon fast 50€. Das Buch hingegen kostet gerade mal 50€!

Buchcover

Grundlagen und Konzepte der Informatik

Autor: Hartmut Ernst
Preis: 29,90 Euro
ISBN: 3528257172
Verlag: Vieweg Verlag Wiesbaden
Seiten: 888
  Bestellung bei amazon.de

Auch für dieses Buch gilt, dass wir uns freuen würden, wenn Sie einen eventuellen Kauf über diesen Link durchführen würden!

Noch ein kleiner Tipp: Wer das Studium der Informatik ins Auge gefasst hat, kann mit diesem Buch leicht feststellen, ob dies die richtige Disziplin für einen ist, denn bei der Lektüre wird schnell klar, dass ein Informatiker kein Programmierer ist!

Bei Fragen zu speziellen Problemen sollte man das nicht vergessen und mal in de.comp.lang.assembler.x86 vorbeischauen.

Bei Interesse in 3D-Engines und diverse Effekte findet man natürlich auch eine fast schon unüberschaubare Menge von Dokumenten mit einer Suchmaschine.

Eine vollständige Interruptliste kann man ebenfalls im Internet aufstöbern.

Zum Anfang dieser Seite...

Bezugsquellen von Assemblern

Neben der Einbindung von Assembler in die bekannten Hochsprachen wie Turbo Pascal und C gibt es natürlich noch völlig autonome Assembler. Diese haben alle ihre speziellen Vor- und Nachteile, wobei man sich immer selbst davon überzeugen sollte!

Das Programm wird dann mit Hilfe eines Texteditors erstellt und als Parameter an den Compiler übergeben. Die Erstellung solcher Programme ist sehr mühsam und verlangt einen hohen Zeitaufwand. Es ist daher nicht geeignet um die 508. Version eines Vokabeltrainers zu programmieren, außer dieser basiert auf Spracherkennung!!!

Name Hersteller Kosten
A86 Eric Issacson Software Shareware
MASM Microsoft In Visual C++ 6.0 enthalten und evt. im DirectX Development Kit enthalten. Einfach mal nach masm suchen.
TASM Borland Früher gab es den TASM z. B. zum C++ Builder 5.0 dazu

Zum Anfang dieser Seite...

Noch ein paar abschließende Worte

Der Einstieg in die Assembler-Programmierung ist nicht leicht. Die heutigen aktuellen Compiler sind so gut ausgereizt, dass sie in den meisten Fällen einen stark optimierten Code erstellen. Diesen kann man erst nach sehr langer Beschäftigung mit dem Thema weiter verbessern.

Andererseits bietet die Systemprogrammierung einen faszinierenden Einblick in das Zusammenspiel der einzelnen Komponenten des Computers. Dabei kann man viele interessante Dinge über den Blechkasten lernen und bei eventuellen Fehlern besser reagieren!

Man sollte sich nicht der Illusion hingeben man könne mit ein bissel Assembler die besten Grafik- und Sound-Engines programmieren. Dazu gehört immer als 2. Basis ein gutes Wissen in Mathematik (die analytische Geometrie ruft). Allerdings kann Assembler als Einstieg in das Cracken von Programmen angesehen werden, auch wenn dies ein zweifelhaftes Thema ist12!

Was auch immer Sie als geneigter Leser vorhaben, hoffe ich, dass ich mit meinem Text den Einstieg in ein sehr komplexes Gebiet erleichtern konnte. Ich warte auf Kritik und Verbesserungsvorschläge.