Beruflich Dokumente
Kultur Dokumente
Teil
Vorstellung der wichtigsten Grundbegriffe und Grundkenntnisse sowie der
wichtigsten Hardwarekomponenten
Assembler - Einführung 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
1. Einleitung
Wenn man durchs Internet surft kann man den Eindruck gewinnen, dass es keine deutschsprachige
Internetseite gibt, die es fertig bringt eine Einführung in Assembler ins Netz zu stellen.
Aus diesem Grund hab’ ich mich mit den letzten drei Büchern, die in der Buchhandlung noch zu haben
waren bewaffnet und hoffe mit dieser kleinen Einführung und meinen bescheidenen Kenntnissen
diesem Misstand ein Ende zu bereiten ;-)
Naja, viel Bestärkung habe ich allerdings bei meinen Recherchen nicht erhalten. Mal hörte ich „Was
willste denn mit dem alten Quatsch. Mach lieber Java, das läuft immer und auf allem“ oder
„Äh Assembler ?. Das ist doch nur Tipperei !“.
ü Man hat endlich mal Gelegenheit zu verstehen wie ein Computer eigentlich funktioniert.
ü Assembler gibt einem die volle Kontrolle über den Computer (einschließlich der Kontrolle über
das Ticken der Systemuhr und vieles mehr... ).
à Daher ist der Assembler bei Systemprogrammieren immer noch geachtet.
ü Assembler ermöglicht das Schreiben von sehr kompakten und extrem schnellen Programmen
(..schneller als Programme, welche mit Hochsprachen erstellt wurden ) .
à Hacker lieben Assembler für diese Eigenschaften. Denn wer lässt sich
schon einen Virus andrehen, der die Größe von Office 2000 hat und so
lange geladen werden muss wie Windows 98 ??? .. ja ok, leicht
übertrieben..;-)...und außerdem „ Allein der Gedanke ist verwerflich“ ;-)
peschko@aol.com 2
Assembler - Einführung 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Ja, welche Sprache versteht denn der Prozessor ( CPU ) des Computers?
Nun, es ist eigentlich keine Sprache sondern Zahlen. Wollen wir direkt der CPU des Computers einen
Befehl erteilen, müssen wir dies mittels einer Zahl tun. Die Gesamtheit der Befehle die ein
Prozessortyp kennt nennt man seinen Befehlssatz.
Die Dezimalzahl 10 bedeutet: addiere zwei Zahlen. Die Zahl 5 kann die CPU zum Beispiel anweisen
den Inhalt eines Speicherplatzes an eine andere Speicherstelle zu verschieben usw.. Der 8086 , der
Urahne unseres Pentium III, kennt 90 Grundbefehle. Jeder Befehl hat zu seiner Identifikation eine
eigene Zahl. Diese Befehle versteht unser Pentium III auch, da er ja ein Nachkomme des 8086 ist.
Der Pentium III hat jedoch noch zusätzliche Befehle bekommen, die jedoch dem 8086 überhaupt
nichts sagen. Wir können also alle Befehle des 8086 durchaus auch bei nachfolgenden
Prozessorgenerationen einsetzen.
Es ist völlig egal, welche Programmiersprache wir einsetzen. Am Ende übersetzt der
jeweilige Sprachencompiler die Additionsanweisung des Quellcodes in die Zahl 10. Diese kann dann
an die CPU geschickt werden. Nun, aber warum gibt es dann mehr Programmiersprachen als Sand
am Meer ? Ganz einfach: Der Arbeitsschritt eines einzelnen Grundbefehls ist sehr klein. Es ist daher
sehr verführerisch mehrere Grundbefehle zu einer Anweisung zusammenzufassen.
Jede Hochsprache ( C, C++, Cobol, Pascal..) macht dies mit einem anderen Hintergedanken.
Cobol zum Beispiel fasst Grundbefehle so zusammen, dass Anweisungen entstehen, welche
besonders gut kaufmännische Probleme lösen helfen. Will man aber wissen wie viel Speicherplatz auf
der Festplatte noch vorhanden ist tut man sich mit Cobol etwas schwer.
Das menschliche Gehirn hat aber mit Zahlen in aller Regel Probleme. Vor allem, wenn es gleich 90
sind und es sich auch noch zu jeder Zahl den dazugehörigen Grundbefehl merken muss. Deshalb hat
man zu einem Trick gegriffen. Und der heißt Assembler. Statt der Dezimalzahl 10 merkt man sich
einfach den Mnemonic ADD ( was natürlich für ADDition steht). Entsprechend wählt man für die Zahl 5
den Mnemonic MOV ( was, wer hätt’s gedacht, für MOVe steht ). Nein falsch, Mnemonic ist kein
Tippfehler, sondern ein Kunstwort aus memory und name (ob Keanu Reeves das weiß ? Ich mein
„...und wie es funktioniert ?“). Wir schreiben in unserem Quellcode einfach ADD und MOV. Der
Assembler macht daraus die Zahlen 10 sowie 5 und die CPU addiert und bewegt. Was mit wem
addiert und von wo nach wohin bewegt wird kommt später.
Aber auch die Befehlssätze der einzelnen Prozessortypen unterscheiden sich voneinander.
Ein Prozessor für eine Spielekonsole hat mehr Befehle, welche die Erzeugung von Bildern, Tönen und
Bewegungen unterstützen. Für einen Prozessor in einer Steuerung für Produktionsabläufe wären sie
vermutlich überflüssig.
Man sieht also recht deutlich, dass nicht jeder Maschinencode auf jedem Prozessor
läuft.
peschko@aol.com 3
Assembler - Einführung 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
3. Die Central Processing Unit ( CPU ) oder einfach: Prozessor des PC’s
Nähern wir uns nun vorsichtig dem eigentlichen Objekt der Begierde.
Eine CPU besitzt erwartungsgemäß ein Rechenwerk, denn sie soll ja addieren können. Das
Rechenwerk kann aber auch Subtrahieren, Multiplizieren und Dividieren. Das Rechenwerk kann sogar
zusätzlich noch logische Bedingungen erstellen und prüfen.
Was aber dekodiert die Grundbefehle die wir nacheinander in die CPU schieben und entscheidet was
zu tun ist? Hierfür ist das Steuerwerk in der CPU zuständig.
Nun muss es aber auch noch Speicherplätze in der CPU geben, wo wir unsere Grundbefehle und
Daten parken können bis die CPU Zeit hat sie von dort einzulesen. Zusätzlich sind auch
Speicherplätze auf der CPU notwendig an denen die CPU ihre Arbeitsergebnisse für uns zur
Abholung bereithält. Diese Speicherplätze nennt man Register.
Als besonderen Service der CPU gibt es noch Speicherplätze auf der CPU, deren Inhalt uns über den
Erfolg oder Misserfolg eines Arbeitsschrittes informieren. Außerdem können an diesen
Speicherplätzen noch generelle Anweisungen gegeben werden die dann für die gesamte Zeit der
Ausführung der Grundbefehle gelten.
All diese Speicherplätze werden unter dem Begriff Statusflags zusammengefasst.
Schauen wir uns mal die Register zu Beginn etwas genauer an. Mit ihnen haben wir am meisten zu
tun. Im Mittelpunkt stehen die 4 Allzweckregister.
Ein Register ist eine Speicherort bestehend aus 16 Bits. Ein Bit ist ein nicht mehr weiter zu
unterteilender Speicherplatz. Der Inhalt eines Bits kann sozusagen nur eine 1 oder eine 0 sein.
Ein „leeres“ Bit kann es nicht geben, denn entweder ist das Bit gesetzt oder es ist nicht gesetzt.
Ein Register:
Bit 15 Bit 14 Bit 13 Bit 12 Bit 11 Bit 10 Bit 9 Bit 8 Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
0 0 0 0 0 1 0 1 0 0 0 0 1 0 1 0
Low – Teil
High – Teil
Ein Register lässt sich in zwei 8- Bit- Register unterteilen. Der hohe und der tiefe Teil lassen sich
getrennt ansprechen. Man kann aber auch alle 16 Bits auf einmal auslesen oder beschreiben.
Jedes der 4 Allzweckregister hat einen eigenen Namen und einen eigenen Kennbuchstaben, sowie
einen hauptsächlichen Verwendungszweck.
Das erste Register trägt den Namen Akkumulator und trägt den Kennbuchstaben A.
Will man zum Ausdruck bringen, dass man alle 16 Bit des Registers meint, so hängt man noch ein X
an. Die Kennung lautet dann AX. Meint man nur Bit 0 bis einschließlich Bit 7, so schreibt man AL
(Akkumulator- Low). Für Zugriffe auf Bit 8 bis Bit 15 schreibt man AH (Akkumulator – High). Das
Register AX wird hauptsächlich im Zusammenhang mit arithmetischen Operationen und logischen
Vergleichen verwendet.
peschko@aol.com 4
Assembler - Einführung 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Bevor wir uns nun der Statusflags bemächtigen können müssen wir uns leider mit einem besonderen
Zahlensystem ( Binäres Zahlensystem ) beschäftigen.
Zahlen die nur aus den Ziffern 1 und 0 bestehen haben einen besonderen Namen.
Es handelt sich dabei um „Dualzahlen“. Jedes Zahlensystem hat eine sogenannte Basis. Diese Basis
entspricht der Anzahl der im Zahlensystem zur Verfügung stehenden Ziffern. Im binären
Zahlensystem ist die Basis = 2.
Es stellt sich nun die Frage : „Wie rechnet man eine Dualzahl gebildet aus auf-
einander folgenden Bitnummern um in
eine Dezimalzahl ?“
Ganz einfach:
Betrachten wir doch einmal die Dualzahl in AL im Beispiel weiter oben.
3 2 1 0
1 * 2 + 0 * 2 + 1 * 2 + 0 * 2 = 10
Antwort: Ja, denn sie benötigt nur 8 Bits. Die Dezimalzahl 256 könnte man
nicht mehr nach AH schreiben.
peschko@aol.com 5
Assembler - Einführung 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Die Addition und Subtraktion im binären Zahlensystem ist etwas eigenwillig und gewöhnungsbedürftig.
4.1.1 Addition
0 1 1 10
4.1.2 Subtraktion
0 1 1 1 0
- 0 - 1 - 0 - 1
0 0 1 1
Wie man Hexadezimalzahlen addiert und subtrahiert soll hier nicht gezeigt werden, da es für die
Assemblerprogrammierung keine große Bedeutung hat.
So, nun sind wir gerüstet um uns mit den Statusflags auseinandersetzen zu können.
peschko@aol.com 6
Assembler - Einführung 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
6. Die Statusflags
Ja, auch das Statusregister ist beim 8086er ein 16 Bit- Register. Von diesen 16 Bit
werden jedoch nur 9 Bits benötigt. 6 Bits sind sogenannte Statusflags. Sie werden von der CPU
gesetzt oder rückgesetzt. Die restlichen 3 Bits sind Kontrollbits. Sie werden vom Programmierer
gesetzt oder gelöscht.
Bit 15 Bit 14 Bit 13 Bit 12 Bit 11 Bit 10 Bit 9 Bit 8 Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
0 0 0 0 1 1 1 1 1 1 0 1 0 1 0 1
OF DF IF TF SF ZF AF PF CF
6.1 Statusflags
Es zeigt an, wenn nach einer Addition oder Subtraktion der Wertebereich in einem Register
überschritten worden ist, wenn die Zahl des Ergebnisses zu viele Stellen hat. In diesem Fall steht in
Bit 0 eine 1.
Mit anderen Worten: Das Carry Flag zeigt einen Übertrag aus dem höchstwertigen Bit an.
1111 0111
+ 1000 0000
1
1 0111 0111
Um das Ergebnis speichern zu können wären 9 Bits notwendig. Wir haben jedoch nur 8 Bits ( 1 Byte).
Das Ergebnis kann nicht mehr in AH gespeichert werden.
Daraus folgt CF = 1. Dieses Flag kann jedoch durch den Befehl CLC gelöscht
(CF = 0 ) und durch STC gesetzt werden ( CF = 1 ).
dient der Fehlerprüfung bei Datenübertragungen über sie serielle Schnittstelle. Dieses Flag wird auf 1
gesetzt, wenn das Ergebnis einer Operation in den niederwertigen 8 Bits ( Low – Byte ) eine gerade
Anzahl an 1 en aufweist. Ansonsten ist PF = 0.
In unserem Beispiel: à PF = 0
1111 0111
+ 0001 0000
1 1 1 1
1 0000 0111
peschko@aol.com 7
Assembler - Einführung 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
wird gesetzt (AF = 1 ), wenn ein Übertrag von Bit 3 nach Bit 4 erfolgt. Es wird hauptsächlich im
Zusammenhang mit dem BCD Code verwendet.
1100 1111
+ 0000 1000
1
1101 0111
Wenn das Ergebnis einer Vergleichs- oder arithmetischen Operation 0 ergab, wird dieses Bit gesetzt
( ZF = 1).
1111 0111
- 1111 0111
0000 0000
Auch Dualzahlen können Vorzeichen haben. Zur Darstellung von vorzeichenbehafteten Dualzahlen
wird aber kein + oder – verwendet, sondern das höchstwertige Bit. Hierbei entspricht
der Ziffer 1 das – und der Ziffer 0 das +.
Wenn wir die Dualzahl 0101 1110 als vorzeichenbehaftet betrachten erhalten wir:
1010 0010
als Dezimalzahl – 94.
0101 1110
als Dezimalzahl + 94.
( Zweierkomplement)
peschko@aol.com 8
Assembler - Einführung 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Dieses Bit wird gesetzt ( OF = 1 ), wenn ein Übertrag ins höchstwertige Bit erfolgt.
( Nicht zu verwechseln mit CF à Übertrag aus dem höchstwertigen Bit).
0111 0111
+ 0100 0000
1
1011 0111
6.2 Kontrollflags
Durch setzten dieses Flags ( TF = 1 ) schaltet der Prozessor in den Einzelschrittmodus. Dadurch kann
man sich den Inhalt der Register nach jedem Grundbefehl anschauen und analysieren. Sehr
vorteilhaft bei der Fehlersuche im Programmcode !
Gesetzt wird dieses Bit durch den Befehl STI. Rückgesetzt kann das IF mit dem Befehl CLI werden.
Es kommt vor, dass ein Programm seinen vorgesehen Ablauf nicht einhalten kann. Durch ( IF = 0 )
kann zum Beispiel verhindert werden, dass ein Programm durch das Drücken von STRG + C
abgebrochen wird .
Wird eine Zeichenkette (String) im Speicher abgelegt, so muss jeder Speicherplatz eine eigene
Adresse haben, damit man weiß an welchem Ort ein Zeichen abgelegt wurde. Soll eine solche
Zeichenkette verarbeitet werden, müssen die einzelnen Adressen der Reihe nach aufgerufen werden.
7. Arbeitsspeicher
Unter dem Arbeitsspeicher kann man sich so etwas wie einen Notizblock des Prozessors vorstellen.
Er kann in den Arbeitsspeicher schnell Daten auslagern, die in seinen Registern im Moment keinen
Platz mehr haben. Andererseits kann er sie im Bedarfsfall auch schnell wieder zurückholen.
Natürlich muss man wissen an welchen Ort man die Daten geschrieben hat. Und da kommen die
Adressen ins Spiel.
peschko@aol.com 9
Assembler - Einführung 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
7.1 Adressbildung
Der Prozessor kann die Adressen von Speicherplätzen nur an Registern ausgeben. Die Register sind
beim 8086er aber auf eine 16 Bit Architektur ausgerichtet. Mit 16 Bit können wir die Dezimalzahlen 0
bis 65 535 darstellen. Wir könnten also mit einem Register 65 535 Byte durchnummerieren. Jedes
Byte hätte damit eine eigene Zahl und somit eine eigene Adresse.
Jetzt waren 65 535 Byte aber auch für damalige Verhältnisse keine berauschende Speicherkapazität.
Schon für einen 1MB großen Arbeitsspeicher hätte man ein 20 Bit Register benötigt, denn man hätte
1 048 576 Byte adressieren können. Das war aber beim 8086er so eine Sache. Also musste man sich
anders helfen.
Bei Intel hat sich deshalb ein schlauer Kopf damals etwas einfallen lassen.
Obwohl der 8086er nur 16 Bit Register hatte, war er an einen 20 Bit Adressbus angeschlossen.
Das bedeutet der 8086er hätte eine 20 Bit Adresse abschicken können, was aber nicht ging, da er
eben eigentlich nur mit 16 Bit breiten Adressen umgehen konnte.
Besagter schlauer Kopf hat sich nun folgendes überlegt: Wir nehmen die 1 048 576 Bytes und teilen
diese Zahl durch 16. Was das bringt ? Na ja, das ergibt 65 535. Das bedeutet mit einem 16 Bit
Register könnten wir jedem sechzehnten (16.) Byte eine Adresse geben. Diese Byte heißen
Paragraphen. Die Paragraphen müssen also durch 16 teilbar sein.
Das Bild soll einen Ausschnitt des Arbeitsspeicher darstellen. Die Fächer sind die einzelnen
Speicherplätze.
.
.
Byte 1
Byte 2
Unsere Byte 1 habe nun z. B. den Abstand 76 Adressen (dezimal) vom 11. Paragraphen- Byte
Abstand 1 als Offset-Adresse 1 = 0100 1100 Byte 3
.
.
Unsere Byte 3 hat daher den Abstand 78 Adressen (dezimal) vom 11. Paragraphen- Byte .
Abstand 3 als Offset-Adresse 3= 0100 1110 .
peschko@aol.com 10
Assembler - Einführung 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Der blaue Pfeil bezeichnet einen Bereich im Arbeitsspeicher. So ein Bereich hat einen besonderen
Namen: Segment. Das Segment beginnt mit einem Paragraphenbyte und umfasst maximal 64 KByte.
Mit der Adresse eines Paragraphenbyte kann man den Anfang eines Segmentes definieren. Diese
Anfangsadresse nennt man daher Segment- Adresse.
Die Offset- Adresse eines Speicherplatzes ist der Abstand (gerechnet in Speicherplätzen ) von
diesem Paragraphenbyte im Arbeitsspeicher.
Übrigens:
Wenn in Windows 95 eine Anwendung an die Wand gefahren wird, kommt die allseits bekannte
Meldung „Allgemeine Schutzverletzung“. Nach anklicken von „Details“ kommen dann einige Adressen
in obiger Form ( allerdings hexadezimal ! ) mit ihrem jeweiligen Inhalt ( Hä ?).
Es wird die Segment- Adresse mit 16 multipliziert . Im binären Zahlensystem bedeutet dies einfach
das Anhängen von 4 Nullen. Dann addiert man einfach die so erhaltene Segment- Adresse und die
Offset- Adresse und erhält eine 20- Bit- Adresse.
0 0 0 0 0 0 0 0 1 0 1 1 0 0 0 0 0 0 0 0
Offset- Adresse:
0 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0
=
0 0 0 0 0 0 0 0 1 0 1 1 0 1 0 0 1 1 0 0
Die so erhaltene 20- Bit- Adresse hat aber einen Schönheitsfehler. Und zwar kann es jetzt sein,
dass ein und die selbe Speicherplatzstelle mehrere Adressen hat.
Sagen wir zum Beispiel : Das Segment beginnt am Paragraphenbyte 12. Unser „Byte1“ hat zu
dieser Segmentadresse einen Abstand von 92 Adressen.
peschko@aol.com 11
Assembler - Einführung 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Damit erhalten wir für die gleiche Speicherstelle „Byte 1“ die 20- Bit- Adresse:
0000000011000000 0000
0000000000000101 1100
+
0000000011000101 1100
Zum Abschluss der Einführung kommen nun noch 4 Segmentregister sowie 5 Index- und
Zeigerregister.
In diesem Register befindet sich eine Paragraphenbyte- Adresse mit der ein Segment beginnt in dem
Befehle gespeichert werden.
Ein Speicherplatz an dem im Codesegment ein Befehl sitzt bekommt also die vollständige Adresse:
CS : IP
In diesem Register befindet sich eine Paragraphenbyte- Adresse mit der ein Segment beginnt in dem
Daten gespeichert werden.
In diesem Register befindet sich eine Paragraphenbyte- Adresse mit der ein Segment beginnt in dem
ebenfalls Daten gespeichert werden.
Reicht der Platz im Datensegment nicht aus, so kann das ES als zusätzlicher Datenspeicher für
Variablen verwendet werden. Sehr wichtig ist dieses Register für das Kopieren, Verschieben und
Vergleichen von Zeichenketten.
Das SI und das DI Register unterstützen unter anderem das Datensegment- und
Extrasegmentregister als Offset- Register bei der Adressierung der Speicherstellen.
DS : SI
DS : DI
DS : BX
ES : SI
ES : DI
ES : BX
peschko@aol.com 12
Assembler - Einführung 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
8. Der Stack
Der Stack ist ein Bereich des Arbeitsspeichers in dem Daten abgelegt werden, wie Schriftstücke auf
einem Aktenstapel. Das neueste Schriftstück wird oben auf den Stapel gelegt. Das erste Schriftstück
liegt somit ganz unten im Stapel. Im Stacksegment- Register (SS) steht wieder die Adresse eines
Paragraphenbyte als Startadresse des Segments. Auch der Stack „wächst“ von der höheren Adresse
zur niedrigeren Speicheradresse!!! . Das Stackpointer- Register (SP) beinhaltet immer den Offset
der niederwertigsten Speicherplatzstelle im Stacksegment, in die gerade Daten eingelesen wurden.
Möchte man auf eine beliebige Speicherplatzstelle dazwischen zugreifen, muss zur Adressierung des
Offsets das Basepoint (BP)- Register eingesetzt werden.
SS : SB
SS : BP
Im Stack sollen nur Daten nach dem LIFO ( Last In , First Out ) Prinzip abgelegt werden. Zuletzt
eingelesene Daten sollen auch zuerst wieder ausgelesen werden.
SS
BP
SP
9. Zusammenfassung
Allzweckregister
AX
BX
Segmentregister
CX
DX
CS
DS
ES
Statusregister CPU
SS
1
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
1. Programmaufbau
Wie im ersten Teil schon erwähnt wird ein Assemblerprogramm in verschiedene Segmente aufgeteilt.
Ein Assemblerprogramm enthält in der Regel mindestens drei Segmente :
• STACK
• Datensegment
• Codesegment
Wir haben nun bei der direkten Segmentierung die Möglichkeit diese Segmente persönlich anzulegen
und nötigenfalls die Segment- Startadressen in die entsprechenden Zeiger- und Indexregister
(DS, CS) zu schreiben.
Als Alternative steht bei neueren Assemblern noch die indirekte Segmentierung über Speichermodelle
zur Verfügung. Dies ist interessant, wenn die 64- KB- Barriere der Segmente überwunden werden
muss. Die einzelnen Speichermodelle können hierzu ein Programm teilweise in beliebig viele
Segmente aufteilen. Es stehen insgesamt 6 Speichermodelle zur Verfügung.
1. Direkte Segmentierung
Bei den rot geschriebenen Wörtern handelt es sich um Assemblerbefehle. Diese müssen in genau
dieser Form eingesetzt werden. Bei den grün geschriebenen Wörtern handelt es sich um einen
notwendigen Zusatz, der das Segment von anderen Segmenten im Programm unterscheidbar macht.
Diese sind aber wahlfrei. Man kann sich etwas „schönes“ ausdenken J. Es ist immer empfehlenswert
Kommentare im Programmcode zu platzieren. Vor diesen Kommentaren muss jedoch ein „;“ stehen.
Zu diesem Zweck öffnen wir unter Windows ein DOS- Fenster. Hierzu klickt man „START“ à
„PROGRAMME“ à „MS- DOS- EINGABEAUFFORDERUNG“.
wir geben ein : CD.. und drücken die RETURN – Taste. Es erscheint folgende Ausgabe:
C:\> _
wir geben ein : MD ASM und drücken die RETURN – Taste. Es erscheint folgende Ausgabe:
C:\> _
Jetzt haben wir ein Verzeichnis mit dem Namen ASM angelegt. In dieses Verzeichnis wollen wir nun
wechseln. Dazu geben wir ein: CD ASM und drücken die RETURN – Taste.
Es erscheint folgende Ausgabe:
C:\asm> _
2
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
Jetzt geben wir ein : EDIT HALLO.ASM und drücken die RETURN – Taste.
Es öffnet sich als blaues Fenster der DOS- Editor. Hier schreiben wir nun unseren Programmcode in
die Datei „HALLO.ASM“. Die Dateiendung „ASM“ ist wichtig !
Andere Schreibprogramme sind problematisch, da sie in der Regel nicht nur den Text an sich in der
Datei abspeichern, sondern noch zusätzlich Steuerungszeichen für die Darstellungsweise des Textes.
Diese Steuerungszeichen drehen dann beim Assemblieren des Textes den Assembler durch den
Wind. Die so verursachten Fehlermeldungen sind geeignet einen an den Rand des
Nervenzusammenbruchs zu bringen, da man im Quellcode einfach keinen Fehler finden kann.
; ********************************************
;*Ausgabe: „Hallo Welt!“ auf Bildschirm*
;*********************************************
DATEN SEGMENT
DATEN ENDS
STAPEL ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL
MOV AH, 9H
INT 21H
CODE ENDS
END START
3
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
Nun muss unser Quellcode noch gespeichert werden. Hierzu fahren wir mit der Maus im DOS Fenster
auf "DATEI" à "SPEICHERN" à "BEENDEN".
Wir sehen wieder auf dem Bildschirm im DOS Fenster den Eingabe Prompt:
C:\asm> _
Jetzt müssen zwei Programme TASM.EXE ( der Assembler) UND TLINK.EXE ( der Linker) in das
Verzeichnis ASM kopiert werden. Das sind die Programmiertools von BORLAND. Von Microsoft gibt
es diese Programme auch. Dort haben sie die Bezeichnungen MASM.EXE und LINK.EXE. Bei der
Erstellung von anspruchsvolleren Programmen sollte man immer darauf achten, dass man die
aktuellste Version dieser Programme hat. Auch bei der Syntax der jeweiligen Assembler gibt es kleine
Unterschiede zwischen BORLAND und Microsoft. Die hier vorgestellten Programme wurden mit
TASM und TLINK erstellt.
Jetzt geben wir ein : TASM HALLO.ASM und drücken die RETURN – Taste.
Wenn Du keine Tippfehler in Deinem Quellcode hast und auch keine ";" vergessen hast, müsste nun
folgende Meldung erscheinen:
Jetzt geben wir ein : TLINK HALLO.OBJ und drücken die RETURN – Taste.
Jetzt geben wir ein: HALLO.EXE und drücken die RETURN – Taste.
Nun haben wir endlich das erste Programm zum laufen gebracht ! Mit der Hochsprache C++ hätte
man einfach COUT << "Hallo Welt !" eingegeben.
Nun wollen wir uns einmal anschauen wie unser Programm vom Assembler übersetzt wird. Dazu
geben wir nun ein: TASM / ZI HALLO,, und drücken die RETURN – Taste.
Es wurde dadurch eine Datei HALLO.LST erstellt. Diese Datei wird nun im DOS Editor durch die
Eingabe von : EDIT HALLO.LST und drücken der RETURN – Taste geöffnet.
4
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
1 ;**********************************************
2 ;*Ausgabe : "Hallo Welt !" auf Bildschirm*
3 ;**********************************************
4
5
6 0000 DATEN SEGMENT
7
8
9 0000 48 61 6C 6C 6F 20 57 + MELDUNG DB "Hallo Welt !","$"
10 65 6C 74 20 21 24
11
12 000D DATEN ENDS
13
14
15 0000 STAPEL SEGMENT BYTE STACK
16
17 0000 80*(????) DW 128 DUP (?)
18
19 0100 STAPEL ENDS
20
21
22
23 0000 CODE SEGMENT
24
25 ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAP
L
26
27 0000 B8 0000s START: MOV AX, DATEN
28 0003 8E D8 MOV DS, AX
29 0005 BA 0000r MOV DX, OFFSET MELDUNG
30
31 0008 B4 09 MOV AH, 9H
32 000A CD 21 INT 21H
33
34 000C B4 4C MOV AH, 4CH
35 000E CD 21 INT 21H
36
37 0010 CODE ENDS
38
39 END START
5
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
Im oberen Ausdruck können wir schön erkennen, wie sich der Assembler die Anordnung unseres
Programms im Arbeitsspeicher vorgestellt hat.
In der ersten Spalte werden Zeilennummern angegeben. Von diesen macht der Assembler Gebrauch,
wenn er uns Fehlermeldungen um die Ohren haut. Hinter einer Fehlermeldung befindet sich immer die
Zeilennummer in der sich der Fehler befindet. Diese Zeilennummern sind nicht Bestandteil unseres
Programms im Arbeitsspeicher.
Zeilen 1,2,3 sind Kommentarzeilen. Der Text hinter dem Semikolon befindet sich nur zur Information
eines nachfolgenden Programmierers im Quellcode. Auch ein nachfolgender Programmierer soll
wissen worum es in diesem Programm geht. Dieser Text ist nicht Bestandteil unseres Programms im
Arbeitsspeicher.
In Zeile 6 teilen wir dem Assembler mit, dass wir nun ein Segment (Bereich im Arbeitsspeicher)
beginnen wollen. Der Assembler antwortet uns mit dem Hinweis, dass er ab dem nächsten
verfügbaren Paragraphenbyte dieses Segment mit Namen DATEN beginnt und die erste
Speicherstelle in dem Segment DATEN mit der Offset- Adresse 0000 adressiert.
In Zeile 9 teilen wir dem Assembler mit, dass eine Zeichenkette Hallo Welt ! im Segment DATEN des
Arbeitsspeichers abgelegt werden soll. Der Rechner kann aber keine Buchstaben sondern nur Zahlen
im Arbeitsspeicher ablegen. Also wandelt er gemäß dem ASCII Code die Buchstabe in Zahlen um.
Man kann zum Beispiel gut erkennen, dass die hexadezimale Zahl 20 für ein Leerzeichen steht.
Da 13 Zeichen inklusive Leer- und $-zeichen abgelegt werden sollen und jedes Zeichen 1 Byte
(1 Speicherplatz ) belegt, befinden wir uns am Ende der Zeichenkette an
Speicherplatz 000D (= 0013 dezimal ). Außerdem teilen wir mit MELDUNG dem Assembler mit, dass
wir der Speicherstelle 0000 den Namen MELDUNG zuordnen, und dass wir an anderer Stelle des
Programms durch Verwendung des Namens MELDUNG auf diese Speicherstelle zugreifen wollen. Mit
DB (DEFINE Byte) geben wir zu verstehen, dass Speicherplätze nur byteweise belegt werden sollen
In Zeile 12 Teilen wir dem Assembler mit dem ENDS Befehl mit, dass hier dieses Segment endet.
Der Assembler bestätigt dies mit dem Hinweis, dass für ihn damit an der Offset- Adresse 000D dieses
Segment beendet ist.
Für Zeile 15 gilt entsprechend das Gleiche wie für Zeile 6. Der Zusatz BYTE bedeutet, dass das
Segment an der nächsten Adresse beginnen soll. Der Zusatz STACK zeigt an, dass wir nur mit
SS:SP auf diesen Bereich zugreifen wollen. Ehrlich gesagt brauchen wir den STACK in diesem
Programm gar nicht. Aber 1. kann man daran erkennen wie man einen STACK anlegt und 2. ist es
sehr einfach das Reservieren von Speicherplatz zu demonstrieren.
In Zeile 17 hätten wir nun der Startadresse wieder einen Namen, etwa MAKE_PROG_LOOK_BIG,
zuordnen können. Da wir aber eh nicht auf diesen Speicherplatz zugreifen wollen, wäre dies
überflüssig. Der Ausdruck DW steht für DEFINE Word. Mit dieser Direktive geben wir an, dass wir
Speicherplatz immer nur als Vielfaches von 2 Byte (=1 Word) belegen wollen. Die Zahl 128 bewirkt,
dass 128 solcher Einheiten ( eine Einheit besteht aus je 2 Byte ) im Arbeitsspeicher reserviert werden.
Insgesamt werden also 256 Byte im Arbeitsspeicher reserviert. Damit wird klar warum das Segment
STAPEL an Adresse 0100 (hexadezimal) in Zeile 19 endet. Die hexadezimale Zahl 0100 entspricht
der Dezimalzahl 256. Das "?" mit der DUP ( ) Direktive bewirkt, dass die einzelnen Speicherplätze mit
keinem Standartwert vorbelegt werden. Ihr Inhalt bleibt so, wie wir ihn vorgefunden haben. Die
hexadezimale Zahl 80 entspricht der Dezimalzahl 128.
In Zeile 25 müssen wir nun dem Prozessor mitteilen, welche Segmentadressen nun den einzelnen
Allzweckregistern zugeordnet werden. Im CS- Register landet die Segmentadresse vom
Codesegment, weil dort die Befehle abgelegt sind usw.
In Zeile 27 kopieren (bewegen) wir die Segmentadresse des Datensegmentes in das CPU
Register AX.
6
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
In Zeile 27 kopieren (bewegen) wir die Segmentadresse des Datensegmentes nun von AX in das
Datensegmentregister DS.
Eine Vereinfachung zu MOV DS, DATEN ist nicht möglich, da der MOV Befehl dies nicht zulässt.
In Zeile 29 bewirkt der Ausdruck OFFSET MELDUNG, dass die Speicherstelle mit dem Namen
Meldung in das Register DX kopiert wird. Zusätzlich kommt zum Ausdruck, dass diese Speicherstelle
nur den Beginn eines Speicherbereiches darstellt. Damit kann der Prozessor nun unsere
Zeichenkette HALLO WELT ! finden. Im DX Register steht die Adresse an der, der String beginnt. Da
es aber viele Speicherstellen mit der Adresse 0000 im Programm gibt, teilen wir ihm zusätzlich im DS
Register mit in welchem Segment der Prozessor die Adresse suchen soll.
Die Zeilen 27 und 28 sind immer notwendig, wenn wir auf Adressen im Datensegment zugreifen
wollen!!!
Warum verschieben wir die Startadresse unseres Strings gerade in das DX Register ?
Die Antwort auf diese Frage steht in Zeile 32.
Dort wird mit dem Befehl INT unser Programm unterbrochen um ein Unterprogramm des
Betriebssystems mit der "Nummer" 21 zu starten. Dieses Unterprogramm ist Bestandteil des
Betriebssystems und wurde mit ihm geliefert.
Dieses Unterprogramm schaut immer zuerst in das Register AH und sieht nun dort die hexadezimale
Zahl 9H ( Das H steht für hexadezimal). Das Unterprogramm weiß dadurch, dass es eine
Zeichenkette auf dem Bildschirm ausgeben soll. Das Unterprogramm verlangt, dass die Adresse der
ersten Speicherstelle dieser Zeichenkette im Register DX steht. Das Unterprogramm nimmt allerdings
automatisch an, dass diese Speicherstelle sich in dem Segment befindet, dessen Adresse in DS
angegeben ist. Jetzt gibt das Unterprogramm nacheinander den Inhalt der Speicherstellen auf dem
Bildschirm aus. Wenn das Unterprogramm jedoch auf das $-zeichen trifft, signalisiert dies dem
Unterprogramm, dass die Zeichenkette zu Ende ist und es seine Arbeit beenden kann. Nun gibt das
Unterprogramm die Kontrolle an unser Programm zurück.
In Zeile 35 rufen wir schon wieder dieses Unterprogramm auf. Mit der Zahl 4CH im Register AH teilen
wir diesmal dem Unterprogramm mit, dass unser Programm zu Ende ist. Das Unterprogramm gibt jetzt
die Kontrolle nicht mehr an unser Programm sondern ans Betriebssystem zurück. Wir sehen den
DOS- Prompt auf dem Bildschirm.
In Zeile 39 Befindet sich der Ausdruck END START. Das Label hinter END signalisiert dem Assembler
an welcher Stelle der erste Befehl in unserem Programm zu finden ist (bei uns in Zeile 27 ). Jetzt weiß
der Prozessor welche Startadresse er in sein Register IP laden muss (nämlich die Offset- Adresse
von START= 0000).
In Zeile 27 steht in der 3. Spalte 0000s. Das "s" bedeutet, dass dies eine Segmentadresse sein soll. In
Zeile 29 steht in der 3. Spalte 0000r. Das "r" steht hier für Relativadresse ( Offset- Adresse). Wie
kommt der Assembler auf die Idee dem Datensegment die Adresse 0000 zugeben ? Was ist, wenn zu
Beginn des Arbeitsspeicher (am ersten Paragraphenbyte) schon ein anderes Programm seine Daten
geparkt hat ? Die Antwort: Der Assembler hat gar keinen Einfluss darauf, wo unser Programm im
Arbeitsspeicher abgespeichert wird. Die "Parkplatzeinweisung" macht der Lader des Betriebssystems.
Er kennt die Adresse des letzten noch freien Speicherplatzes. Sollte zum Beispiel die letzte freie
Speicherstelle die Adresse 0400H haben, so addiert der Lader einfach zu jeder Segmentadresse in
unsrem Programm 0400H dazu und legt das Programm an dieser Stelle im Arbeitsspeicher ab.
Übrigens: Dies ist auch der Grund warum man Adressen nicht addieren darf !
7
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
3. DEBUG
Eines der wichtigsten Hackertools ist mit Sicherheit das Programm DEBUG. Es ermöglicht einen Blick
hinter die Kulissen. Außerdem ist es das einzige Programm mit dem man an jedem Rechner mit MS-
DOS und in allen Lebenslagen ein Programm schreiben kann
(Vorausgesetzt man versteht etwas von Assembler) . Dies ist möglich, weil DEBUG mit MS- DOS
geliefert wird. Die Programme haben jedoch die Einschränkung, dass sie nur aus einem Segment
bestehen können. Da wir aber mit DEBUG an fremden Rechnern keine Programme schreiben wollen,
für die Mann- Jahre notwendig sind, ist dies meistens auch kein Problem.
Dazu öffnen wir wieder ein DOS- Fenster und wechseln in das Verzeichnis ASM.
Wir sehen:
C:\asm> _
Wir sehen:
C:\asm>debug hallo.exe
-_
Wir sehen:
C:\asm>debug hallo.exe
-t
AX=1BA5 BX=0000 CX=0120 DX=0000 SP=010D BP=0000 SI=0000 DI=0000
DS=1B95 ES=1B95 SS=1BA5 CS=1BB6 IP=0003 NV UP EI PL NZ NA PO NC
1BB6:0003 8ED8 MOV DS,AX
-
DEBUG zeigt uns in den ersten beiden Zeilen die Inhalte der Register unserer CPU nach Ausführung
der Zeile 27 in obigem Ausdruck. In der dritten Zeile sehen wir die nächste zur Ausführung
vorgesehene Programmzeile. Außerdem sehen wir nun in welchen Bereich des Arbeitsspeichers der
Lader das Programm geladen hat. Die Adressen der jeweiligen Segmente sind hier blau unterlegt.
Es ist klar, dass bei jedem Rechner andere Segmentadressen als in diesem Beispiel auftreten
werden. Der Arbeitsspeicher ist je nach Rechner unterschiedlich groß und es werden vorher
unterschiedliche Programme in den Speicher geladen.
8
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
DEBUG arbeitet im Einzelschritt Modus unser Programm ab. Man sieht deutlich, dass im Register IP
schon immer die Adresse des nächsten Befehls steht.
Wie man sieht steht nun tatsächlich im hohen Teil von AX eine 09, die wir ein Rechenschritt zuvor
dorthin "gemoved" haben.
Wir sind nun wieder an die Stelle gekommen, an der das Unterprogramm "Nr.21H"des
Betriebssystems aufgerufen wird. Schauen wir uns doch mal an wohin die Reise im Arbeitsspeicher
geht.
Hä? Was ist das ? Wir sind an der Speicherstelle 1194:0445 gelandet ! Dies ist aber noch nicht unser
Unterprogramm. Wir müssen einen Sprung (JMP) zur Adresse 07B4:04A0 ausführen, um zum
Unterprogramm zu gelangen. Wir sind in einer Tabelle gelandet. Jede Zahl bei einem INT Befehl führt
zu einer Speicherstelle an der lediglich die Adresse des eigentlichen Unterprogramms steht.
Dadurch wurde DEBUG verlassen und wir sehen den DOS Prompt. Wir fangen wieder von
vorne ( 3.1 DEBUG ) an bis wir wieder an die Stelle kommen :
9
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
Nun wird das gesamte Unterprogramm am Stück durchgeführt. Daher sehen wir nun:
Hallo Welt !
AX=0924 BX=0000 CX=0120 DX=0000 SP=010D BP=0000 SI=0000 DI=0000
DS=1BA5 ES=1B95 SS=1BA5 CS=1BB6 IP=000C NV UP EI PL NZ NA PO NC
1BB6:000C B44C MOV AH,4C
-
und landen wieder in unserem Programm.
C:\asm>_
Jetzt haben wir bis auf unseren String HALLO WELT ! alles von unserem Programm im
Arbeitsspeicher gesehen. Und den String werden wir uns nun auch noch anschauen.
Zu diesem Zweck müssen wir erst einmal herausfinden wo sich der String im Arbeitsspeicher befindet.
1. Schritt:
Wir öffnen wieder ein DOS- Fenster und wechseln in das Verzeichnis ASM.
Wir sehen:
C:\asm> _
Wir sehen:
C:\asm>debug hallo.exe
-_
C:\asm>debug hallo.exe
-t
10
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
Nun haben wir den String HALLO WELT ! in den Arbeitspeicher geladen. Ohne diesen Schritt wäre es
Zufall, was wir an den entsprechenden Speicherstellen antreffen.
2. Schritt:
Der String HALLO WELT ! steht bei mir an der Adresse 1BA5: 0000 Bei Dir ergibt sich hier mit
Sicherheit für den vorderen Teil eine andere Adresse. Der String HALLO WELT ! benötigt
mit Leerzeichen und $-zeichen 13 Speicherstellen. Das Ende des String befindet sich dann an der
Adresse1BA5 : 000D.
.
Wir geben ein: E 1BA5:0000 und drücken die RETURN – Taste.
Wir sehen:
-E 1BA5:0000
1BA5:0000 48._
Mit der hexadezimalen Zahl 48 sehen wir hier das "H" von HALLO. Wir haben nun 3 Möglichkeiten:
1) Durch drücken der Leerzeichen- Taste ohne Veränderung des Wertes zum nächsten Zeichen
vorzurücken.
2) Einen neuen Wert einzugeben und dann mit Drücken der Leerzeichen- Taste zum nächsten
Wert vorzurücken.
3) Durch drücken der RETURN Taste den Editiermodus zu verlassen ohne etwas zu verändern.
Ich habe mich gerade dafür entschieden aus unserm Programm die englischsprachige Version
HELLO WORLD! zumachen.
-E 1BA5:0000
1BA5:0000 48. 61._
Das "e" ist im ASCII Code ist einfach 4 Buchstabe weiter. Das bedeutet wir ersetzten die Zahl 61
durch die Zahl 65.
-E 1BA5:0000
1BA5:0000 48. 61.65 6C._
11
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
Wir drücken so oft die LEERZEICHEN- Taste bis wir an diesen Punkt kommen:
-E 1BA5:0000
1BA5:0000 48. 61.65 6C. 6C. 6F. 20. 57. 65._
Hier steht nun das "e" von Welt. Wir ersetzten es durch ein "o" à Zahl: 6F
Hier steht nun das "l" von Welt. Wir ersetzten es durch ein "r" à Zahl: 72
Hier steht nun das "t" von Welt. Wir ersetzten es durch ein "l" à Zahl: 6C
Hier steht nun das " " hinter dem Wort Welt. Wir ersetzten es durch ein "d" à Zahl: 64
Wir sehen nun den ASCII Code des Ausrufezeichens. Der bleibt unverändert.
Wir sehen nun den ASCII Code des $-zeichens. Der bleibt unverändert.
Hello World!
Programm wurde normal beendet
-
Wir geben ein: q und drücken die RETURN – Taste.
Wir sehen:
C:\asm>_
Wenn wir nun hallo eingeben und RETURN drücken kommt wieder die Meldung Hallo WELT !.
Dies liegt daran, dass das Programm mit den alten Werten von der Festplatte neu geladen wird.
12
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
Wir schreiben nun direkt ein Programm in den Arbeitsspeicher und lassen es anschließend mit
DEBUG assemblieren und ausführen. Diese ausführbaren Dateien können ( wegen DEBUG) nur aus
einen Segment bestehen. Dies bedeutet, dass wir die Daten und den Programmcode in das gleiche
Segment schreiben müssen. Daher müssen wir dafür Sorge tragen, dass der Prozessor nicht auf
unsren String "Hallo Welt !" trifft und denkt das seien Befehle die er jetzt ausführen muss.
DEBUG setzt nämlich alle Segmentregister auf den gleichen Wert. Dieses Problem lässt sich dadurch
entschärfen, indem man die Daten im richtigen Moment einfach überspringt.
Dazu öffnen wir wieder ein DOS- Fenster und wechseln in das Verzeichnis ASM.
Wir sehen:
C:\asm> _
1B72:0100 _
Da wir als erstes den String "Hallo Welt !" abspeichern wollen müssen wir zuerst einen Sprung
eingeben:
Jetzt brauchen wir ein wenig Kopfrechnen. Der Sprungbefehl mit Adresse benötigt 2Byte im Speicher.
Überspringen wollen wir den String "Hallo Welt !","$". Dafür brauchen wir noch einmal 13 Byte. Der
Befehl MOV AH, 09H könnte erst im 14 Byte abgespeichert werden. Da müssen wir mit dem JMP
Befehl hinspringen.
Das bedeutet: Speicherplatz 0100 für JMP
0101 für Sprungadresse
0102 H
.
.
010E $
010F MOV AH, 09H
.
.
Wir springen also zu 010F
Wir geben ein: JMP 010F und drücken die RETURN – Taste.
Wir sehen:
-a
1B72:0100 JMP 010F
1B72:0102_
Wir geben ein: DB "Hallo Welt !","$" und drücken die RETURN – Taste.
Wir sehen:
-a
1B72:0100 JMP 010F
1B72:0102 DB "Hallo Welt !","$"
1B72:010F_
13
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
Wir geben ein: MOV AH, 9 und drücken die RETURN – Taste.
Wir sehen:
-a
1B72:0100 JMP 010F
1B72:0102 DB "Hallo Welt !","$"
1B72:010F MOV AH,9
1B72:0111_
Das Unterprogramm möchte bekanntlich im DX Register die Offset- Adresse des Strings haben.
Der String beginnt in Speicherstelle 0102. Diese Speicherstelle laden wir nun nach DX.
Wir geben ein: MOV DX, 102 und drücken die RETURN – Taste.
Wir sehen:
C:\asm>debug
-a
1B72:0100 JMP 010F
1B72:0102 DB "Hallo Welt !","$"
1B72:010F MOV AH,9
1B72:0111 MOV DX, 102
1B72:0114_
Wir geben nun den restlichen Text ein, bis wir diese Stelle erreichen:
-a
1B72:0100 JMP 010F
1B72:0102 DB "Hallo Welt !","$"
1B72:010F MOV AH,9
1B72:0111 MOV DX, 102
1B72:0114 INT 21
1B72:0116 MOV AH, 4C
1B72:0118 INT 21
1B72:011A_
Nun wird nichts mehr eingegeben sondern einfach nur die RETURN – Taste gedrückt.
Wir sehen:
-a
1B72:0100 JMP 010F
1B72:0102 DB "Hallo Welt !","$"
1B72:010F MOV AH,9
1B72:0111 MOV DX, 102
1B72:0114 INT 21
1B72:0116 MOV AH, 4C
1B72:0118 INT 21
1B72:011A
-_
-n WELT.COM
-RCX
CX 0000
:_
DEBUG möchte jetzt, dass wir die Anzahl der Bytes eingeben sie unser Programm belegt.
Bei uns sind dies 25 Bytes.
14
Assembler - Einführung 2. Teil (Beta 2)
Copyright 2000 Thomas Peschko
Wir sehen:
CX 0000
:25
-w
00025 Bytes werden geschrieben
-_
Hallo Welt !
C:\asm>_
Wir haben auf diese Weise eine COM Datei geschrieben. Der Unterschied zur EXE Datei besteht
darin, dass die COM Datei nur 64 KB groß sein kann und aus einem Segment besteht.
Mit dem Parameter u kann man sich das Programm Hallo.exe im Arbeitsspeicher anzeigen lassen.
DEBUG erkennt aber nicht wo unser Programm zu Ende ist.
Microsoft(R) Windows xx
(C)Copyright Microsoft Corp 19xx-19xx.
C:\WINDOWS>cd..
C:\>cd asm
C:\asm>debug hallo.exe
-u
1BB6:0000 B8A51B MOV AX,1BA5
1BB6:0003 8ED8 MOV DS,AX
1BB6:0005 BA0000 MOV DX,0000
1BB6:0008 B409 MOV AH,09
1BB6:000A CD21 INT 21
1BB6:000C B44C MOV AH,4C
1BB6:000E CD21 INT 21
1BB6:0010 3C2A CMP AL,2A
1BB6:0012 7503 JNZ 0017
1BB6:0014 83CA02 OR DX,+02
1BB6:0017 3C3F CMP AL,3F
1BB6:0019 7503 JNZ 001E
1BB6:001B 83CA04 OR DX,+04
1BB6:001E 0AC0 OR AL,AL
-
Es gibt natürlich noch andere Programme zum Disassemblieren ( CODEVIEW) die wesentlich
komfortabler sind als DEBUG. Diese Einführung erhebt keinen Anspruch auf vollständige Darstellung
aller Möglichkeiten und Sachverhalte. Sie soll lediglich einen Eindruck vom grundsätzlichen Vorgehen
vermitteln.
15
Assembler - Grundlagen 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
peschko@aol.com 1
Assembler - Grundlagen 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
1. Unterprogramme
In den bisher gezeigten Programmen wurde schon deutlich, dass manche Programmteile sich öfters
wiederholen oder mittels Schleifen mehrmals durchlaufen werden.
Bei größeren Programmen bedeutet dies zusätzliche Schreibarbeit. Außerdem werden solche
Programme schnell unübersichtlich, wenn nicht mehr klar ist in welcher Reihenfolge und in welchen
Fällen welche Sprünge ausgeführt werden. Selbst der ursprüngliche Programmierer tut sich
irgendwann schwer da noch durchzublicken. Es kommt der Zeitpunkt an dem der Programmier im
Programmcode nur noch planlos umherhüpft und sich in den endlosen Weiten des Programmcodes
verliert.
Aus diesem Grund ist man auf die Idee gekommen, dass man die Programmteile welche öfters
durchlaufen werden einfach in ein eigenes Programm packt.
Dieses Unterprogramm wird dann einfach im Hauptprogramm an der benötigten Stelle aufgerufen.
Das Hauptprogramm wartet dann solange, bis das Unterprogramm seine Aufgabe durchgeführt hat
und fährt danach mit der Ausführung des restlichen Programmcodes fort.
Wie startet man nun ein solches Unterprogramm aus dem Hauptprogramm heraus ?
Nun muss man natürlich auch noch angeben welches Unterprogramm (Namen) man aufruft. Den
Namen kann man sich selbst auswählen. Der Befehl CALL darf nicht verändert werden.
Dieses Unterprogramm befinden sich meist am Ende des Hauptprogramms. Der Assembler muss nun
aber auch erkennen können an welcher Stelle ein bestimmtes Unterprogramm beginnt und an welcher
Stelle es endet.
.
.
Anweisungen
.
.
RET
PROGRAMMNAME ENDP ← Ende des Unterprogramms
Die Ausdrücke NEAR und FAR sind grün, weil wir uns für einen Ausdruck von beiden entscheiden
müssen. (Die eckigen Klammern werden nicht geschrieben !)
Es wird der Ausdruck NEAR gewählt, wenn sich das Unterprogramm mit dem Namen
PROGRAMMNAME im gleichen Segment wie sein Aufruf (CALL PROGRAMMNAME ) befindet.
Die RET – Anweisung zeigt dem Assembler an, an welcher Stelle wieder ins Hauptprogramm zurück
gesprungen wird.
peschko@aol.com 2
Assembler - Grundlagen 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Das Programm HALLO.EXE soll nun mittels eines Unterprogramms erstellt werden.
Wir schreiben den unteren Programmcode in eine Datei UPROG1.ASM
DATEN SEGMENT
DATEN ENDS
DW 128 DUP(0)
STAPEL ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL
CALL PRINT
RET
PRINT ENDP
CODE ENDS
END START
Ja, ok…. Dieses Programm würde niemand mittels eines Unterprogramms schreiben. Es eignet sich
aber besonders um die Verwendung eines Unterprogramms zu zeigen.
Nachdem dieses Programm assembliert und gelinkt ist werden wir uns den Maschinencode mittels
DEBUG anschauen.
Damit ist es möglich uns ein Bild von der Wirkung der einzelnen Befehle zu machen.
peschko@aol.com 3
Assembler - Grundlagen 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
C:>\ASM>DEBUG UPROG1.EXE
-u
Auch hier erkennt DEBUG nicht an welcher Stelle unser Programm endet. Der rote Teil ist nicht mehr
Bestandteil unseres Programms. Es ist zu erkennen, dass das Unterprogramm im selben Segment
steht wie das restliche Programm. Es ist also wirklich ein NEAR Sprung.
Am rechten Rand steht die Reihenfolge in der die einzelnen Programmzeilen ausgeführt werden. Es
ist zu erkennen, dass der CALL Befehl eigentlich ein Sprungbefehl ist. Die OFFSET – Adresse hinter
dem Sprungbefehl gibt das Sprungziel an. Die OFFSET – Adresse 000C hat bei uns den "Namen"
PRINT.
Es kommt relativ oft vor, dass Routinen mehrmals in einem Programm verwendet werden. So zum
Beispiel Routinen für das Ausgeben von Text, das Öffnen von Dateien, das Beenden von
Programmen usw.....
Das Hauptprogramm und die Standartroutinen werden in getrennte Dateien geschrieben. Auf diese
Weise kann man die einmal geschriebenen Standartroutinen immer wieder in jedes neue Programm
einbinden ( spart viel Tipperei J ).
Hinweis: Die Dateien DATEI_2.ASM und SUB_PROG.ASM müssen nicht vollständig neu abgetippt
werden. Wenn man DATEI_1.ASM hat kann man durch kleine Abänderungen und durch
das Abspeichern unter einem anderen Namen die oben genannten Dateien leicht erstellen.
peschko@aol.com 4
Assembler - Grundlagen 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Ein Beispiel:
EXTRN PRINT:FAR
EXTRN MSDOS:FAR
DATEN SEGMENT
DATEN ENDS
DW 128 DUP(0)
STAPEL ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL
CALL PRINT
CALL MSDOS
CODE ENDS
END START
EXTRN PRINT:FAR
EXTRN MSDOS:FAR
DATEN SEGMENT
DATEN ENDS
DW 128 DUP(0)
STAPEL ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL
CALL PRINT
CALL MSDOS
CODE ENDS
END START
peschko@aol.com 5
Assembler - Grundlagen 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
PUBLIC PRINT
PUBLIC MSDOS
DATEN SEGMENT
DATEN ENDS
DW 128 DUP(0)
STAPEL ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL
RET
PRINT ENDP
RET
MSDOS ENDP
CODE ENDS
END START
C:\asm>TASM DATEI_1.ASM
Turbo Assembler Version 1.01 Copyright (c) 1988, 1989 Borland International
C:\asm>TASM DATEI_2.ASM
Turbo Assembler Version 1.01 Copyright (c) 1988, 1989 Borland International
C:\asm>TASM SUB_PROG.ASM
Turbo Assembler Version 1.01 Copyright (c) 1988, 1989 Borland International
C:\asm>_
Dann können die einzelnen Hauptprogramme DATEI_1.OBJ und DATEI_2.OBJ jeweils mit der
Unterprogrammdatei SUB_PROG.OBJ zusammengebunden werden.
C:\asm>_
Sehen wir:
Meldung aus Datei 1!
Sehen wir:
Meldung aus Datei 2!
Es tauchen nun in den Dateien DATEI_1.ASM und DATEI_2.ASM die Ausdrücke EXTRN und PUBLIC
auf.
In der Datei DATEI_1.ASM werden die Unterprogramme PRINT und MSDOS aufgerufen. Der
Assembler kann aber diesmal diese Unterprogramme in der Datei DATEI_1.ASM nicht finden ! Wenn
nun nicht die Hinweise EXTRN PRINT:FAR und EXTRN MSDOS:FAR in der Datei wären, würde der
Assembler eine Fehlermeldung ausgeben. Mit diesen Hinweisen wird dem Assembler mitgeteilt, dass diese
Unterprogramme mit den Namen PRINT und MSDOS eben außerhalb (extern) und im wahrsten Sinne weit weg
(far) von unserer Datei DATEI_1.ASM (nämlich in der Datei SUB_PROG.ASM ) existieren.
Der Assembler lässt also das Programm DATEI_1.OBJ lediglich zwei "Ösen" auswerfen, an denen dann der
Linker (TLINK) die Unterprogramme einklinken kann. Dazu muss der Assembler aber die Datei SUB_PROG.ASM
zwei "Haken" auslegen lassen. Damit der Assembler dies veranlasst braucht es die Ausdrücke PUBLIC PRINT
und PUBLIC MSDOS.
Das mit den Haken und Ösen wollen wir nun etwas genauer anschauen. Wir geben ein:
C:\asm>DEBUG DATEI_1.EXE
-u
1EC9:0000 B8A71E MOV AX,1EA7
1EC9:0003 8ED8 MOV DS,AX
1EC9:0005 BA0000 MOV DX,0000
1EC9:0008 9A0000CB1E CALL 1ECB:0000
1EC9:000D 9A0500CB1E CALL 1ECB:0005
1EC9:0012 0000 ADD [BX+SI],AL
1EC9:0014 0000 ADD [BX+SI],AL
1EC9:0016 0000 ADD [BX+SI],AL
1EC9:0018 0000 ADD [BX+SI],AL
1EC9:001A 0000 ADD [BX+SI],AL
1EC9:001C 0000 ADD [BX+SI],AL
1EC9:001E 0000 ADD [BX+SI],AL
Zunächst erkennen wir hier ( bei anderen Rechnern werden sich andere Segmentadressen ergeben ),
dass in den ersten zwei Zeilen die Adresse des Datensegments ( 1EA7 ) ins DS Register geschoben
wird. Für DX ist in der dritten Zeile die Offset – Adresse 0000 zu entnehmen. Dies bedeutet, dass
unsere Zeichenkette an Adresse 1EA7 : 0000 beginnt.
peschko@aol.com 7
Assembler - Grundlagen 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Wir suchen nun den String "Meldung aus Datei 1!" im Arbeitsspeicher auf. Dazu geben wir ein:
-E 1EA7:0000
und sehen zunächst das M von Meldung. Durch wiederholtes Drücken der Leerzeichentaste schauen
wir uns die ganze Zeichenkette an.
-E 1EA7:0000
1EA7:0000 4D. 65. 6C. 64. 75. 6E. 67. 20.
1EA7:0008 61. 75. 73. 20. 44. 61. 74. 65.
1EA7:0010 69. 20. 31. 21. 24.
Die 24 ist wieder das $ - Zeichen, welches das Ende des Strings kennzeichnet. Als nächstes wollen
wir uns das Unterprogramm anschauen, welches den String ausgibt. Dazu geben wir ein:
-u 1ECB:0000
Wir sehen:
Wir sehen nun auch an Adresse 1ECB:0005 den Beginn des Unterprogramms, welches nach Ablauf
unseres Gesamtprogramms die Kontrolle an das Betriebssystem zurück gibt. Ebenfalls sehen wir nun
auch, dass wir ein Unterprogramm vom Typ FAR benötigen. Der CALL sitzt im Segment 1EC9. Das
aufgerufene Unterprogramm sitzt im Segment 1ECB. Beide Segmente sind unterschiedlich !
2. Makros
Makros haben einen ähnlichen Aufbau wie Unterprogramme. Die Wirkung im Quellcode ist jedoch
eine völlig andere. Ein Unterprogramm wird nur einmal in den Arbeitsspeicher geschrieben. Dann wird
von verschiedenen Stellen des Hauptprogramms zu diesem Unterprogramm gesprungen.
Der Assembler ersetzt beim Assemblieren den Aufruf des Makros einfach durch den Inhalt des
Makro ! Dies bedeutet, dass durch einen Makroaufruf kein Sprung entsteht. Wenn wir zum Beispiel ein
Makro dreimal hintereinander aufrufen wird der Inhalt dieses Makro im Quellcode lediglich dreimal
hintereinander gehängt.
.
.
Befehle Makrokörper
.
.
ENDM
peschko@aol.com 8
Assembler - Grundlagen 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Wie veranlasst man nun den Assembler den Makrokörper in den Quellcode einzufügen ?
MAKRONAME VARIABLE1_DES_MAKRO, …
Neu sind die Zusätze "VARIABLE1_DES_MAKRO, …" und " VARIABLE1_DES _QUELLCODES,..." .
Mit diesem Zusatz erfährt der Assembler, dass die beiden unterschiedlichen Ausdrücke das gleiche
meinen und austauschbar sind.
Wie der Name Variable schon vermuten lässt, werden über diese Ausdrücke Zahlenwerte
ausgetauscht. Es können mehrere Variablen hintereinander geschrieben werden.
Ein Beispiel:
Wir erstellen zuerst eine Makro – Bibliothek. Zu diesem Zweck erstellen wir mit dem DOS- Editor die
Datei MACRO.BIB und schreiben folgende Makros :
ENDM
MSDOS MACRO
ENDM
Der Assembler muss nun diese Datei nach den Makros durchsuchen, die im Hauptprogramm
verwendet werden. Damit er das tut, muss im Hauptprogramm auf diese Datei hingewiesen werden.
Dies geschieht mit dem Ausdruck INCLUDE.
peschko@aol.com 9
Assembler - Grundlagen 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
INCLUDE MACRO.BIB
DATEN SEGMENT
DATEN ENDS
DW 128 DUP(0)
STAPEL ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL
PRINT MELDUNG
MSDOS
CODE ENDS
END START
Der Assembler setzt hier die beiden Ausdrücke MELDUNG und STRING gleich. Es wird die
Offset – Adresse der Zeichenkette MELDUNG übergeben.
C:\asm>TASM DATEI_3.ASM
Wir sehen:
Turbo Assembler Version 1.01 Copyright (c) 1988, 1989 Borland International
C:\asm>TLINK DATEI_3.OBJ
Wir sehen:
Turbo Link Version 2.0 Copyright (c) 1987, 1988 Borland International
C:\asm>_
peschko@aol.com 10
Assembler - Grundlagen 1. Teil (Beta 1)
Copyright 2000 Thomas Peschko
C:\asm>DEBUG DATEI_3.EXE
-u
Wir sehen:
Man kann hier nicht erkennen, dass dieses Programm mittels Makros erstellt wurde.
Fazit: Programme werden mittels Unterprogrammen geschrieben, wenn sie möglichst klein sein
sollen.
Für den Fall, dass jedoch die Geschwindigkeit der Programme im Vordergrund steht, bedient
man sich lieber Makros.
peschko@aol.com 11
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
peschko@aol.com 1
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
1. Adressierung
Im letzten Teil dieses Kapitels werden wir uns mit der direkten Adressierung und der indirekten
Adressierung beschäftigen.
Ich denke ein anschauliches Beispiel verdeutlicht auch hier den Unterschied. Jeder hat diese Situation
schon erlebt. Man hat es eilig und jemand quatscht einen auf der Strasse an: " Äh, wissen Sie wo das
Kino ist ?".
Jetzt gibt es die Möglichkeit, dass man es weiß und dem Ratsuchenden sagt: " Ja, da gehen Sie jetzt
da hoch und dann oben rechts. Dann stehen Sie genau davor." Das wäre die direkte Adressierung.
Wenn man es nicht weiß könnte man sagen: "Hm, ich weis nicht wo das Kino ist. Aber drei Straßen
weiter auf der linken Seite ist ein Taxistand. Der kann Ihnen sagen wo Sie das Kino finden." Das wäre
die indirekte Adressierung. Man hat einen Ort angegeben an dem man die eigentliche Adresse findet.
Operation Adresse
Speicherstelle
Die Konventionen zur Adressierung werden zum Teil lasch gehandhabt. Das hat zur Folge, dass es in
manchen Fällen zu mehrdeutigen oder missverständlichen Ausdrücken kommt.
Beispiel:
Es wird eine Variable VAR1 definiert und mit dem Wert 0 belegt.
VAR1 DW 0
Diese Anweisungen führen bei manchen Assemblern zu einer Warnmeldung. Es ist nicht ganz klar
was gemeint ist. Man kann der Ansicht sein, dass die Zahl 0 in das Register AX zu kopieren ist. Jetzt
wissen wir aber auch, dass der Ausdruck VAR1 genauso als Bezeichner für die Speicherstelle
verwendet werden kann. Nehmen wir einmal an, dass besagte Speicherstelle im Segment die Offset –
Adresse 50H hat. Dann könnte man auch der Ansicht sein, dass VAR1 ein Bezeichner ist der für die
Zahl 50H steht.
Im ersten Fall würde in AX eine 0 stehen. Im zweiten Fall würden wir in AX die Zahl 50H vorfinden.
Wie schon gesagt wollen uns manche Assembler diese Entscheidung nicht abnehmen und verlangen
Eindeutigkeit.
peschko@aol.com 2
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Hier steht VAR1 für die Adresse der Speicherstelle . Die eckigen Klammern geben nun an, dass der
INHALT von dieser Speicherstelle nach AX zu kopieren ist.
2. Fall: Es soll die Offset – Adresse der Speicherstelle nach AX kopiert werden:
Hier wurde das AX Register direkt angesprochen. Es ist auch möglich den Inhalt einer bestimmten
Speicherstelle direkt auszulesen. Zum Beispiel mit
Übrigens:
Man kann auch einen CALL direkt adressieren:
Einen NEAR CALL mit: CALL NEAR PTR SEGMENT:OFFSET
Einen FAR CALL mit: CALL FAR PTR SEGMENT:OFFSET
peschko@aol.com 3
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Beispiel:
DATEN1 SEGMENT
DATEN1 ENDS
DATEN2 SEGMENT
DATEN2 ENDS
DW 128 DUP(0)
STAPEL ENDS
CODE2 SEGMENT
ASSUME CS:CODE2
RETF
DRUCK ENDP
CODE2 ENDS
CODE1 SEGMENT
ASSUME CS:CODE1,SS:STAPEL,ES:NOTHING
CODE1 ENDS
END START
C:\asm>DEBUG DIREKT.EXE
und anschließender Eingabe des Parameters t durch das Programm klicken.. äh ENTERn ;-)
( Wie schon gesagt, bei einem INT 21H - Befehl sollte mit einem p weiter geschaltet werden...)
peschko@aol.com 4
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
C:\asm>debug direkt.exe
-t
AX=0000 BX=0000 CX=014E DX=0000 SP=0100 BP=0000 SI=0000 DI=0000
DS=1B95 ES=1B95 SS=1BA7 CS=1BB8 IP=0003 NV UP EI PL NZ NA PO NC
1BB8:0003 B8A51B MOV AX,1BA5
-t
AX=1BA5 BX=0000 CX=014E DX=0000 SP=0100 BP=0000 SI=0000 DI=0000
DS=1B95 ES=1B95 SS=1BA7 CS=1BB8 IP=0006 NV UP EI PL NZ NA PO NC
1BB8:0006 8ED8 MOV DS,AX
-t
AX=1BA5 BX=0000 CX=014E DX=0000 SP=0100 BP=0000 SI=0000 DI=0000
DS=1BA5 ES=1B95 SS=1BA7 CS=1BB8 IP=0008 NV UP EI PL NZ NA PO NC
1BB8:0008 9A0000B71B CALL 1BB7:0000
-t
AX=1BA5 BX=0000 CX=014E DX=0000 SP=00FC BP=0000 SI=0000 DI=0000
DS=1BA5 ES=1B95 SS=1BA7 CS=1BB7 IP=0000 NV UP EI PL NZ NA PO NC
1BB7:0000 B409 MOV AH,09
-t
Es ist zu erkennen, dass bei mir ( bei Dir mit Sicherheit nicht ) der Bezeichner CODE1 für die
Segment – Adresse 1BB8 steht. Im Instruction Pointer steht immer schon die Offset – Adresse des
nächsten Befehls.
Zunächst das Übliche: Die Adresse des Strings "HALLO WELT 1!" wird geladen. Der CALL – Befehl
kommt uns auch nicht mehr ganz neu vor. Wir sehen, dass die Sprungadresse DIREKT angegeben
ist. Was aber dem ungeübten Auge nicht auffällt ist, dass der Stack Pointer ( Pfeile ) um 4 Bytes
( Speicherplätze ) gewachsen ist ( ja, gewachsen. Wie gesagt: Der Stack wächst von oben nach
unten. ).
Nun, da wir einen FAR – CALL haben springen wir zu einem Speicherplatz in ein anderes Segment
mit einem anderen Offset. Dies bedeutet: Wir müssen zwei Adressen speichern. Denn der Prozessor
will ja beim RETF – Befehl wissen wohin er zurückspringen soll !
Jede Adresse benötigt 2 Byte.
Mit der Segmentadresse 1BB7 befinden wir uns im Segment CODE2 . Hier ist das Unterprogramm,
welches mit der Anweisung MOV AH, 09H beginnt.
Mit dem RETF endet das Unterprogramm. Es ist zu sehen, dass in das Segment CODE1 gesprungen
wird. Hierzu wird die Rücksprungadresse benötigt. Die wird vom Stack genommen. Dadurch
schrumpft der Stack wieder um 4 Bytes.
peschko@aol.com 5
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Wir haben hier als Beispiel den Prozessor ohne "Taxistand" direkt zum Unterprogramm geschickt.
DATEN1:0000
DATEN2:0000
STAPEL:0000
CODE2:0000
CODE1:0000
Bei dieser Art der Adressierung kann man im wahrsten Sinne des Wortes alle Register ziehen. Auch
hier gibt es zunächst ein paar kleine Gemeinheiten.
Es gibt viele "Orte" an denen man im PC Speicheradressen verstauen kann. Beim 8086 gab es bereits
17 Möglichkeiten der Adressierung.
Hier wird mittels eines Registers adressiert. Dies bedeutet: Wir schauen in ein Register um dort eine
Adressangabe zu finden. Danach suchen wir den Speicherplatz, der die angegebenen Adresse hat
auf. In diesem Speicherplatz ist die eigentliche Information die wir suchen. Wir können hier aber auch
Daten ablegen.
Speicherstelle
Inhalt: Adresse der
Inhalt: Info
Speicherstelle
Register
peschko@aol.com 6
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Beispiel:
MOV [BX], DX
Dies bedeutet: Schreibe den Inhalt des Registers DX an die Adresse im Arbeitsspeicher, die in BX
angegeben ist.
Dies bedeutet: Lies den Inhalt der Adresse, die in BX angegeben ist. Schiebe diesen Inhalt in das
Register AX.
1. Falle:
Wir wollen die Zahl 10 an eine Speicherstelle schreiben deren Offset - Adresse ( Segmentadresse
liegt, sofern nicht durch ASSUME geändert, in DS vor ) in BX angegeben ist.
Der Assembler macht jetzt "Hä ??" und gibt eine Fehlermeldung aus. Warum ? Die Zahl 10
ist binär 1010. Uns ist klar: 1010 braucht 4 Bit. Da eine Speicherstelle 8 Bit hat, passt unsere Zahl 10
wunderbar in eine Speicherstelle rein. Manchen Assemblern ist das aber nicht klar ! Denn es könnte
sich ja auch um den Ausdruck 0000 0000 0000 1010 ( Word ) handeln.
Manche werden sich nun fragen: Warum funktioniert dann MOV [BX], DX . Ja, hier haben wir es nur
mit Registern zu tun. Und dass diese beiden Register jeweils eine Breite von 16 Bit haben rafft der
Assembler dann doch.
2. Falle:
MOV DL, BX
Wir schreiben Daten mit einer Länge von 16 Bit an einen Ort, der nur 8 Bit groß ist. Oh, Oh.....
peschko@aol.com 7
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Hier wird zu einer in einem Register gespeicherten Adresse eine Zahl addiert.
Die Summe aus Adresse und Konstante ergibt die Adresse der gesuchten Speicherstelle.
Register
Die Adresse der Speicherstelle an der der Inhalt des Registers DX gespeichert wird ist wie folgt
bestimmt: Man nimmt den Inhalt des Registers BX als Adresse und addiert die hexadezimale
Zahl 10H zu dieser Adresse hinzu. Damit erhält man die Adresse der gesuchten Speicherstelle.
MOV [BX]10H, DX
Hier muss der Inhalt von Register1 und Register2 addiert werden um die Adresse der gesuchten
Speicherstelle zu erhalten .
BX+SI
BX+DI
BP+SI
BP+DI
Ab dem 80386 sind auch alle Kombinationen der 32 – Bit Register ( außer ESP ) erlaubt.
peschko@aol.com 8
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Es wird der Inhalt des ersten Registers zum Inhalt des zweiten Registers und dazu noch eine
Konstante addiert. Das Ergebnis ergibt die Adresse der gewünschten Speicherstelle.
MOV [BX+SI+80H], DX
Man erkennt, dass alle bisher behandelten Adressierungsarten Spezialfälle der basis-indizierten
Adressierung sind.
Da unsere Programme auch auf jeder alten Krücke laufen sollen, werde ich hier nur die 8086
Konventionen darstellen:
BX SI
BP DI
[BX] [BP]
[SI] [DI]
[BX+SI] [BP+SI]
[BX+DI] [BP+DI]
[BX+Konstante] [BP+Konstante]
[BX+SI+Konstante] [BP+SI+Konstante]
[BX+DI+Konstante] [BP+DI+Konstante]
[SI+Konstante] [DI+Konstante]
[Konstante]
peschko@aol.com 9
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
2. Speichermodelle
Alle Programme bisher wurden mit Hilfe der direkten Segmentierung erstellt. Jedes Segment wurde
mit der Anweisung:
NAME SEMGMENT
NAME ENDS
erzeugt.
Darüber hinaus mussten von uns auch immer mittels der Zeile
START:
END START
Wichtig war auch, dass am Ende des Programms immer das Betriebssystem die Kontrolle erhält:
Diese Einzelschritte können "automatisiert" werden, da sie bei jedem Programm auftreten.
Zuerst wenden wir uns der Segmentierung durch den Assembler zu:
Mit der direkten Segmentierung konnten wir die Abfolge der Segmente, die Anzahl der Segmente
sowie ihre Größe beeinflussen.
Dies kann auch der Assembler für uns machen. Die Auswahl ist dabei aber auf einige Typen
beschränkt. Diese Typen nennt man Modelle.
peschko@aol.com 10
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
TINY Programm-Code und Daten müssen in ein 64 KByte – Segment passen ! Code und
Daten sind NEAR.
SMALL Programm-Code und Daten müssen in je ein 64 KByte – Segment passen ! Code und
Daten sind NEAR.
MEDIUM Programm-Code kann größer als 64 KByte sein; Daten müssen in ein 64 KByte-
Segment passen. Code ist FAR, Daten sind NEAR.
COMPACT Programm-Code muss in ein 64 KByte-Segment passen; Daten können größer als
64 KByte sein, aber kein Datenbereich darf für sich allein größer als 64 KByte sein.
Daten und Code sind FAR.
LARGE Programm-Code und Daten können beide größer als 64 KByte sein, aber kein
Datenbereich darf für sich allein größer als 64 KByte sein. Daten und Code sind FAR.
HUGE Programm-Code und Daten können beide größer als 64 KByte sein, auch einzelne
Datenbereiche dürfen größer als 64 KByte sein. Daten und Code sind FAR.
.MODEL Typ
ausgewählt. Bei soviel Auswahl hat man die Qual der Wahl. Auch hier gibt es eine einfache
Faustregel:
Verwende ein möglichst einfaches ( weit oben stehendes ) Modell.
Der entstehende Maschinencode ist somit schneller und auch einfacher zu analysieren.
Trotzdem müssen wir nicht jegliche Möglichkeit zur Einflussnahme aus der Hand geben. Wir können
dem Assembler auch bei Modellen sagen, welcher Programmteil in welchen Segmenten abgelegt
werden soll. Es gibt hier die Direktiven:
.STACK Zahl
Mit dieser Direktive wird der STACK angelegt. Diese Direktive ersetzt sozusagen den Ausdruck:
DW 128 DUP(0)
STAPEL ENDS
.DATA
.CODE [Name]
Damit wird das Code – Segment angelegt. Bei den Modellen MEDIUM, LARGE und HUGE können in
einem Assembler – Programm mehrere Code – Segmente vorkommen. Um diese unterscheiden zu
können, müssen sie mit einem Namen versehen werden.
peschko@aol.com 11
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Damit wirklich kein Zweifel über den Programmanfang besteht, wird nach der .CODE Anweisung noch
die Anweisung
.STARTUP
gesetzt.
START:
Mit
.EXIT
END
END START
.MODEL SMALL
.DATA
.STACK
.CODE
.STARTUP
.EXIT
END
peschko@aol.com 12
Assembler I - Grundlagen 2. Teil (Beta 1)
Copyright 2000 Thomas Peschko
Achtung: Die Segmentierung über Modelle ist immer eine Quelle von endlosen Problemen und viel
Ärger ! Die Programmierung über Modelle wird nicht von allen Versionsnummern
von TASM unterstützt. Auch sind die Konventionen bei MASM (von Microsoft ) anders.
Das Beispielprogramm oben wurde mit
Turbo Assembler Version 4.0 Copyright (c) 1988, 1993 Borland International
assembliert.
Dies hier soll lediglich eine Einführung sein, die erste Gehversuche ermöglichen soll.
Noch ein Tipp: Es gibt den Borland Turbo – Assembler 4.0 auch auf CD. Inbegriffen ist da ein
Online – Handbuch und Anschauungsprogramme. Zu beziehen über den Buchhandel.
Verlag: Franzis
peschko@aol.com 13
Assembler – DOS (Beta 1)
Copyright 2000 Thomas Peschko
Assembler II - DOS
ASSEMBLER – Arbeiten mit Dateien und Daten
peschko@aol.com 1
Assembler – DOS (Beta 1)
Copyright 2000 Thomas Peschko
Wer nun den Eindruck hat, dass unsere Programme hauptsächlich nur Unterprogramme vor ihren
Karren spannen und sich darauf beschränken den "Laden" zu managen, liegt damit nicht ganz falsch.
Die Schwierigkeit beim Programmieren besteht hauptsächlich darin zu wissen, welche INTERRUPT-
Routine was macht und in welchen Registern sie Eingabewerte erwartet und in welchen Registern sie
Werte zurück gibt (auch hier gilt: "Wissen ist Macht !") .
1. Dateien
Da wir uns nicht unnötig Schreibarbeit aufhalsen wollen, öffnen wir einfach wieder unser altes
HALLO.ASM. Zunächst ändern wir es wie folgt ab. Danach speichern wir es einfach unter dem neuen
Namen DATEI.ASM.
; ********************************************
;* Programm 2 *
;*********************************************
DATEN SEGMENT
DATEN ENDS
STAPEL ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL
CODE ENDS
END START
peschko@aol.com 2
Assembler – DOS (Beta 1)
Copyright 2000 Thomas Peschko
Es kommt wieder das INTERRUPT Unterprogramm "Nr. 21" zum Einsatz. Wieder wird zunächst ein
String ausgegeben. Im nächsten Block wird die Funktionsnummer 3CH in das Register AH geladen.
Dadurch wird das Unterprogramm veranlasst eine Datei zu erstellen und diese zu öffnen.
Zur Erstellung der Datei benötigt das Unterprogramm den Namen der Datei als Zeichenkette.
Das Unterprogramm des Betriebssystems erwartet nun im DX Register die Adresse des ersten
Speicherplatzes der Zeichenkette als Offset- Adresse. Im Register CX muss eine Zahl angegeben
werden, an der das Unterprogramm die Art der gewünschten Datei erkennt. Nach dem alle Register
mit den vom Unterprogramm verlangten Informationen geladen sind wird das Unterprogramm mit INT
21H aufgerufen.
Da mehrere Dateien gleichzeitig geöffnet sein können, lädt das Unterprogramm nach dem Erstellen
und Öffnen der Datei eine Identifikationsnummer in das Register AH. Damit kann die Datei auch von
anderen Programmen angesprochen werden. Der alte Wert 3CH im Register AH wird dadurch
überschrieben.
Als nächster Schritt muss unbedingt diese Identifikationsnummer gerettet werden. Denn der Befehl
MOV AH, 40H würde nun wieder diese Identifikationsnummer durch Überschreiben vernichten. Jetzt
kommt zum Erstenmal der STACK ins Spiel. Mit dem Befehl PUSH AX wird nun der Inhalt des
Registers AX in dem reservierten Speicherbereich STACK abgelegt. Es wird immer die letzte freie
Speicherstelle im STACK von einem PUSH Befehl hierfür eingesetzt.
Nun kann in das Register AH der Wert 40H geladen werden. Damit erfährt das Unterprogramm, dass
in eine Datei geschrieben werden soll. Zum Schreiben in eine Datei benötigt das Unterprogramm den
Text als Zeichenkette. Das Unterprogramm des Betriebssystems erwartet nun im DX Register die
Adresse des ersten Speicherplatzes der Zeichenkette als Offset- Adresse.
Im Register CX muss eine Zahl angegeben werden, an der das Unterprogramm die Anzahl der zu
schreibenden Bytes erkennt. Hierbei ist zu beachten, dass ein Zeichen (Buchstabe) 1 Byte benötigt.
Im BX Register verlangt das Unterprogramm nun die Identifikationsnummer der Datei, in die
geschrieben werden soll. Zum Glück haben wir diese ja auf dem STACK gespeichert. Mit dem Befehl
POP holen wir den Inhalt des letzten belegten Speicherplatzes auf dem STACK in das Register BX
zurück. Da zwischenzeitlich keine weiteren Werte auf diesen STACK geladen wurden, lädt der Befehl
POP die Identifikationsnummer der Datei in das Register BX. Jetzt kann wieder das Unterprogramm
"Nr. 21H" aufgerufen werden, das die Zeichenkette von DATEIINHALT in diese Datei schreibt.
Im vorletzten Block wird nun die Datei wieder geschlossen. Dies ist wichtig, da sonst die Datei nicht
von anderen Programmen bearbeitet werden kann. Hierzu wird in das Register AH der Wert 3EH
geladen. Im Register BX wartet das Unterprogramm wieder die Identifikationsnummer der zu
schließenden Datei. Diese Nummer befindet sich bereits im Register BX, da wir sie schon dorthin
geladen und zwischenzeitlich nicht überschrieben haben. Nach dem alle Register mit den vom
Unterprogramm verlangten Informationen geladen sind wird das Unterprogramm mit INT 21H
aufgerufen.
Im letzten Block wird nun wieder die Kontrolle an das Betriebssystem zurückgegeben.
Nun müssen wir den Quellcode assemblieren und linken. Nach einem Doppelklick auf das Programm
DATEI.EXE sollte nun die Meldung „Es wird eine Datei erstellt !“ auf dem Bildschirm erscheinen.
Zusätzlich sollte auch eine Datei mit dem Namen NEU_DAT.DAT erstellt worden sein. Wenn man
diese Datei mit dem EDIT Befehl öffnet, sollte man den Text „ Dies wird in die Datei geschrieben“
lesen können.
Dieses Programm hat primär dazu gedient die elementaren Funktionen der Dateihandhabung zu
demonstrieren.
peschko@aol.com 3
Assembler – DOS (Beta 1)
Copyright 2000 Thomas Peschko
Als nächstes soll nun ein Programm 3 erstellt werden, das den Inhalt einer Textdatei mit dem Namen
QUELLE.TXT verschlüsselt und in einer zweiten Datei mit dem Namen ENCRYPT.TXT verschlüsselt
abspeichert. Zu diesem Zweck wird zu jedem ASCII Code eines Buchstabens bzw. eines Zeichens die
Zahl 2 addiert. Dies bedeutet zum Beispiel, dass für ein a im Text ein c dargestellt wird. Da ein a den
ASCII Code 61 hat wird daraus ein c (ASCII Code 63), wenn wir zum ASCII Code des Buchstaben a
die Zahl 2 addieren.
Diese Art der Verschlüsselung ist natürlich primitiv, aber man kann den Algorithmus auch beliebig
kompliziert gestalten. Auf jeden Fall kann man auf diese Weise das Programmieren üben. Da wir uns
abermals nicht unnötig Schreibarbeit aufhalsen wollen, öffnen wir nochmals unser altes HALLO.ASM.
Zunächst ändern wir es wie folgt ab. Danach speichern wir es einfach unter dem neuen
Namen VERSCHL.ASM ab.
;****************************************
;*Programm 3 *
;****************************************
DATEN SEGMENT
DATEN ENDS
STAPEL ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL
CMP AX, 0H
JE ENDE
peschko@aol.com 4
Assembler – DOS (Beta 1)
Copyright 2000 Thomas Peschko
ADD [PUFFER], 2H
JMP M1
CODE ENDS
END START
Jetzt wird der Quellcode assembliert und gelinkt. Fertig ist das Programm !
Ganz einfach ! Wir erstellen zuerst eine Datei QUELLE.TXT in dem selben Ordner in dem sich unser
Programm VERSCHL.EXE befindet.
In diese Datei schreiben wir mittels des DOS – Editor nun etwas beliebiges rein.
Zum Beispiel: Das ist streng geheim ! ;-)
Dann speichern wir den Text ab und schließen die Datei QUELLE.TXT
Als nächstes doppelklicken wir auf unser Programm VERSCHL.EXE , welches wir ja schon
geschrieben und assembliert haben.
Jetzt befindet sich eine neue Datei mit dem Namen ENCRYPT.TXT im Ordner.
Wenn wir nun diese Datei mit dem DOS – Editor öffnen sehen wir Smilies, Herzchen, Karos usw....
Tja, das ist der Text, aber verschlüsselt...
ordnen wir nun an, dass aus der Datei dessen Identifikationsnummer in BX mittels
peschko@aol.com 5
Assembler – DOS (Beta 1)
Copyright 2000 Thomas Peschko
Hmm… 1 Byte sind 8 Bit ! Wir belegen also eigentlich nur 8 Bit im Arbeitsspeicher.
genau 1 Wort = 2 Byte = 16 Bit reserviert und diese mit Nullen vorbelegt ???
Im Register AX wird die Anzahl der gelesen Byte zurückgegeben. Wenn wir nun die Zahl 0 mit der
Zahl in AX vergleichen und es ergibt sich Gleichheit, dann wissen wir, dass in AX die Zahl 0 steht !
CMP AX, 0H
Der folgende Befehl bezieht sich nun hierauf. Wenn in AX eine 0 steht, dann wird alles bis zur Marke
ENDE übersprungen und die Programmausführung hinter der Marke ENDE : fortgesetzt.
JE ENDE
à Das bedeutet es wird erst nach ENDE : gesprungen, wenn es nichts mehr zu lesen gab !
Zu schwierig ?....
Wenn es aber etwas zu lesen gab, dann steht das Zeichen ( besser gesagt sein ASCII Code ) jetzt in
der Speicherstelle mit dem "Namen" PUFFER.
Nehmen wir einmal an, dass wir das Zeichen mit dem höchsten ASCII Code eingelesen hätten.
Dieses Zeichen hat die Codezahl 255.
genau ! à 1 1 1 1 1 1 1 1 Diese binäre Zahl braucht ganz genau 8 Bit also 1 Byte !
Jetzt addieren wir aber zu dem Inhalt des Puffers einfach die Zahl 2 mittels der Anweisung
ADD [PUFFER], 2H
1111 1111
+ 10
10000 0001
peschko@aol.com 6
Assembler – DOS (Beta 1)
Copyright 2000 Thomas Peschko
Die grüne 1 ginge im Puffer verloren, wenn wir nur 1 Byte reserviert hätten ! J
War also doch ganz gut mit dem
Wenn wir dadurch nun den ASCII Code des Zeichens im Puffer völlig entstellt... äh, ich meine
verschlüsselt haben, können wir nun den neuen Inhalt des Puffers in unsere Datei ENCRYP.TXT
übertragen.
Dies geschieht durch die Befehlsfolgen...
Danach springen wir auf jeden Fall wieder nach oben zur Marke M1:......
JMP M1
...und das nächste Zeichen ist an der Reihe....bis kein Zeichen mehr da ist.
Im ersten Fall wird zum INHALT der Speicherstelle PUFFER die Zahl 2 addiert. Im zweiten
Fall wird zur OFFSET – ADRESSE der Speicherstelle mit dem "Namen" PUFFER die
Zahl 2 addiert !
peschko@aol.com 7
Assembler – DOS (Beta 1)
Copyright 2000 Thomas Peschko
Das nachfolgende Programm 4 liest den verschlüsselten Text aus der Datei ENCRYPT.TXT wieder
ein und wandelt es um in unseren ursprünglichen Text um ihn dann in die Datei DECRYPT.TXT zu
schreiben.
;****************************************
;*Programm 4 *
;****************************************
DATEN SEGMENT
DATEN ENDS
STAPEL ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL
CMP AX, 0H
JE ENDE
SUB [PUFFER], 2H
JMP M1
peschko@aol.com 8
Assembler – DOS (Beta 1)
Copyright 2000 Thomas Peschko
CODE ENDS
END START
Zum Abschluss dieses Kapitels soll noch gezeigt werden wie ein Programm "Selbstmord" begeht. Ein
Programm kann sich auch selbst löschen. Diese Methode eignet sich jedoch nur begrenzt zur
Vertuschung.
;****************************************
;*Datei löscht sich selbst *
;****************************************
DATEN SEGMENT
DATEN ENDS
STAPEL ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL
CODE ENDS
END START
Dieser Quellcode muss, wie man am Programm sieht, als SUID.ASM abgespeichert werden.
Die Löschung wird durch die Funktion 41H (Delete Directory Entry) bewirkt.
peschko@aol.com 9
Assembler – DOS Teil 2 (Beta 1)
Copyright 2000 Thomas Peschko
peschko@aol.com 1
Assembler – DOS Teil 2 (Beta 1)
Copyright 2000 Thomas Peschko
Die Grundlagen sind nun weitestgehend erledigt. Wir könnten nun alle Tore und Türen zur ultimativen
Programmierung aufstoßen. Ja, ja... wären da nicht noch folgende Probleme:
ü Prozessorbefehle
ü Operatoren
ü Assembler – Anweisungen
ü DOS – Interrupte
Ein Operator wirkt immer auf einen oder mehrere Operanden. Beim Ausdruck
3–2
ist das Minuszeichen der Operator. Der Ausdruck besitzt den Wert 1. Weitere Operatoren in
Assembler sind:
Ein Prozessorbefehl ist ein Mnemonic. Ein Ausdruck, der den Prozessor aktiv werden lässt:
Eine Assembler – Anweisung gibt dem Assembler Anweisungen in welcher Weise die
Assemblierung des Quellcodes zu erfolgen hat.
Da ich überhaupt keine Lust habe hier einen einige MB großen Upload mit Operatoren,
Prozessorbefehle und Anweisungen zu machen, werde ich hier einen Buchtipp geben:
Die meisten Autoren geben leider nicht genau an für welchen Assembler ihr Buch geschrieben ist.
Es gibt verschiedene Hersteller von Assemblern sowie unterschiedliche Versionsnummern. In dieser
Hinsicht herrscht ein babylonisches Sprachengewirr unter den Assemblerkonventionen.
Eine angenehme Ausnahme macht da die:
Assembler Referenz
von
Oliver Müller
Verlag: Franzis
Dieses Buch hat zwar auch fast 600 Seiten, ist aber trotzdem keine "Schwarte". Es enthält eigentlich
alle Informationen die man braucht ( incl. Beispielen ).
peschko@aol.com 2
Assembler – DOS Teil 2 (Beta 1)
Copyright 2000 Thomas Peschko
In diesem Tutorial möchte ich die allseits beliebte ;-) Funktion "Find First" von MS DOS vorstellen.
Manchmal hat man das Problem, dass man etwas sucht aber einfach nicht weiß wie es heißt und ob
es so etwas überhaupt gibt. Wenn sich dieses Problem auf Dateien bezieht, kann die Funktion "Find
First" eine wertvolle Hilfestellung geben.
Ich denke ein Beispiel veranschaulicht die Aufgabenstellung:
Es soll ein Programm erstellt werden, das Textdateien mit beliebigem Namen findet. Dieses
Programm soll die erste gefundene Textdatei löschen. Diese Textdatei soll auch gefunden und
gelöscht werden, wenn sie die Dateiattribute "versteckt" und "schreibgeschützt" besitzt.
.MODEL SMALL
.DATA
.STACK
.CODE
.STARTUP
DTA_HOLEN:
ENDE:
.EXIT
END
peschko@aol.com 3
Assembler – DOS Teil 2 (Beta 1)
Copyright 2000 Thomas Peschko
Das obere Programm hat die einfache Funktion "search and destroy" realisiert.
Hier hat sich die Interruptliste von Ralf Brown als sehr nützlich erwiesen. Hier werden unter anderem die
Interrupts von MS DOS aufgelistet. Da diese Liste sehr umfangreich ist, bietet er auf seiner Homepage auch ein
Programm als Hilfsmittel zur Betrachtung der Liste an.
Suchabfrage
Die Funktion FIND FIRST MATCHING FILE wird über den Wert 4EH im Register AH mittels des Interrupts 21H
aufgerufen. Die Anfangsadresse der Zeichenkette, welche den Namen bzw. Namensteil der zu suchenden Datei
im Arbeitsspeicher darstellt, muss mittels DS:DX adressiert werden. Der Ausdruck "*.TXT",0 bedeutet, dass die
Endung der gesuchten Datei auf jeden Fall TXT sein muss. Der vordere Teil kann beliebig sein. Dies bewirkt,
dass diese Funktion nur Textdateien sucht.
Als Rückgabewert wird das Carry – Flag (CF) zurückgesetzt, wenn eine entsprechende Datei gefunden wurde
( clear if successful ). Sollte keine entsprechende Datei gefunden worden sein, so wird das Carry – Flag gesetzt.
Im Register CX werden die Attribute der Datei als Maske erwartet. Es wird auf die Funktion 4301H zur Erklärung
der Maske verwiesen. Dort finden wir den Aufbau der Maske:
Hier finden wir den Grund warum wir in das Register CX die Zahl 7 kopieren. Die Zahl 7 ist als binäre
Zahl 00000111. Das bedeutet, dass die gesuchte Datei eine Systemdatei oder versteckt sowie auch
schreibgeschützt sein kann.
Sollte diese Suche von Erfolg sein, wird das Carry – Flag NICHT gesetzt (à CF =0). Hierauf reagiert der Befehl
JNC DTA_HOLEN . Jump if Not Carry. Es wird im Fall von CF = 0 zur Marke DTA_HOLEN weiter unten
gesprungen.
Nun interessiert uns natürlich der Name der gefundenen Datei. Den vollständigen Namen benötigen wir, damit wir
die Datei umnieten können. Jede Datei hat einen "Personlanausweis" . In diesem Vorspann steht zum Beispiel
das Datum sowie die Uhrzeit der Erstellung der Datei.. uvm. Ganz am Ende dieses Vorspanns steht auch der
Name der Datei. Dieser Vorspann heißt DISK TRANSFER AREA (DTA). Sein Aufbau ist bei der Funktion
FINDFIRST aufgeführt.
.
. ---all versions, documented fields---
15h BYTE attribute of file found
16h WORD file time (see #1005 at AX=5700h)
18h WORD file date (see #1006 at AX=5700h)
1Ah DWORD file size
1Eh 13 BYTEs ASCIZ filename+extension
Die Frage ist nur: Wie kommen wir an diese DTA ran ?
Zur Umsetzung dieses niederträchtigen Ansinnens bedienen wir uns der Funktion GET DISK TRANSFER AREA
ADDRESS . Und ? Was sehen wir da ?
Nach Ausführung dieser Funktion befindet sich die Startadresse der DTA in ES:BX. Wenn wir nun anfangen von
der Adresse ES:BX einzulesen bekommen wir:
Daraus folgt: Wir müssen zu BX noch 1EH Speicherstellen addieren, damit in BX die Adresse des
Speicherplatzes steht, der den Anfang des Dateinamens enthält.
Jetzt erwartet aber die Funktion Delete Directory Entry die Startadresse des Speicherplatzes mit dem
Namen der zu löschenden Datei in DS:DX. Bevor wir den oben erwähnten Korrekturzug ausführen
verschieben wir ES:BX nach DS:DX.
Da wir zum Abschluss den User über seinen Verlust in Kenntnis setzen wollen, müssen wir die Segmentadresse
des Datensegments vor dem Überschreiben durch MOV DS, AX retten. Dies geschieht mittels PUSH DS. Vor
der Ausgabe der Kondolenzmeldung muss dieser Wert mit POP DS wieder nach DS kopiert werden.
peschko@aol.com 5
Assembler – DOS Teil 2 (Beta 1)
Copyright 2000 Thomas Peschko
Wird der Inhalt einer Datei geändert und anschließend die Datei wieder abgespeichert, so wird das
Änderungsdatum der Datei aktualisiert. Diese Aktualisierung nimmt das Betriebssystem vor. Wir können das
aber auch selbst in die Hand nehmen !
Das folgende Programm hat eigentlich keinen großen praktischen Nähwert. Es ist vermutlich leichter die
Systemzeit am Computer zurückzustellen, als dieses Programm zu schreiben. Es ermöglicht jedoch einen
wertvollen Einblick in „Zeit- und Datumsmechanik“ einer Datei.
Von Virenprogrammieren wird immer dieses Datum und die Uhrzeit der letzten Änderung gesichert. Nach der
Infektion wird dieses Datum inklusive Uhrzeit wieder hergestellt. Die Datei soll ja nicht als letztes
Änderungsdatum das Datum und die Uhrzeit der Infektion tragen !
--------------------------------------------------------
INCLUDE MACRO.BIB
.MODEL SMALL
.DATA
.STACK 128
.CODE
.STARTUP
PRINT BEGRUESSUNG
PRINT DATUM
INPUT_INT ZAHL
MUL BL
PUSH AX
INPUT_INT ZAHL
POP BX
MOV [TAG], BX
INPUT_CHAR ZEICHEN
INPUT_INT ZAHL
MUL BL
PUSH AX
INPUT_INT ZAHL
POP BX
MOV [MONAT], BX
INPUT_CHAR ZEICHEN
INPUT_INT ZAHL
MUL BX
PUSH AX
INPUT_INT ZAHL
MUL BX
PUSH AX
INPUT_INT ZAHL
MUL BL
PUSH AX
INPUT_INT ZAHL
POP BX ; Zehner
ADD AX, BX
POP BX ; Hunderter
ADD AX, BX
POP BX ; Tausender
ADD AX, BX
SUB AX, BX
MOV [JAHRE], AX
peschko@aol.com 7
Assembler – DOS Teil 2 (Beta 1)
Copyright 2000 Thomas Peschko
MOV DX, 0H
OR DX, [TAG]
OR DX, [MONAT]
OR DX, [JAHRE]
MOV [REGISTER_DX], DX
MOV AL, 0H
INT 21H
MOV BX, AX
INT 21H
INT 21H
INT 21H
.EXIT
END
--------------------------------------------------------------
ENDM
ENDM
---------------------------------------------------------
Die Dateien SETDATE.ASM und MACRO.BIB müssen sich wieder im gleichen Verzeichnis befinden.
C:\asm\Projekt\date>tasm setdate.asm
Turbo Assembler Version 4.0 Copyright (c) 1988, 1993 Borland International
C:\asm\Projekt\date>tlink setdate.obj
Turbo Link Version 6.00 Copyright (c) 1992, 1993 Borland International
.... kann das Programm SETDATE.EXE gestartet werden. Das Datum muss in der Form TT.MM.JJJJ
eingegeben werden. Zum Beispiel 07.08.1999. Ein Datum vor dem 01.01.1980 ist nicht möglich.
Geändert wird das Änderungsdatum einer Datei mit dem Namen TEST.TXT, welche zuvor im gleichen
Verzeichnis mit dem Editor erstellt werden muss.
Zum Eintragen eines Änderungsdatums einer Datei gibt es folgende DOS Funktion:
Aufruf: AH = 57H
AL = 00H à Datum und Zeit holen.
BX = Dateinummer (à Handle)
Ergebnis: CF = 0 Alles OK
CX = Uhrzeit
DX = Datum
CF = 1 Fehler
Aufruf: AH = 57H
AL = 01H à Datum und Zeit setzen.
BX = Dateinummer (à Handle)
Ergebnis: CF = 0 Alles OK
CX = neue Uhrzeit
DX = neues Datum
CF = 1 Fehler
Wenn wir nun ein neues Datum eingeben wollen müssen wir dies über die Tastatur tun. Hierfür gibt es
die DOS Funktion :
Aufruf: AH = 01H
1. Problem:
Nun, wenn wir die Taste „1“ auf der Tastatur drücken ist AL nicht etwa 1 ( AL = 0000 0001 ), sondern
es befindet sich in AL der ASCII Code der Ziffer 1. Da dies die hexadezimale Zahl 31 ist, befindet sich
in AL nun folgendes Bitmuster à AL = 0001 1111. Damit in AL wirklich eine 1 steht muss jetzt vom
Inhalt des Registers AL noch hexdezimal 30 abgezogen werden.
Genauer gesagt: Es kann mit dieser Funktion z. B. gar nicht die Zahl 12 eingelesen werden. Man kann
lediglich beim ersten Aufruf der Funktion die Ziffer 1 und beim zweiten Aufruf der Funktion die Ziffer 2
mittels ihres jeweiligen ASCII Codes einlesen. Danach müssen die beiden Ziffern wieder zur Zahl 12
zusammengebaut werden.
2. Problem:
peschko@aol.com 10
Assembler – DOS Teil 2 (Beta 1)
Copyright 2000 Thomas Peschko
Die Funktion 57H erwartet das neue Datum im Register DX. Dabei gilt folgende Aufteilung:
Register DX:
Bit 15 Bit 14 Bit 13 Bit 12 Bit 11 Bit 10 Bit 9 Bit 8 Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
0 0 1 0 0 1 1 1 1 0 0 1 0 1 0 1
Dies bedeutet : Wenn wir das Datum 21.12.1999 setzen wollen, muss dazu obiges Bitmuster im
Register DX stehen.
Die Frage ist nur: Wie bekommen wir dieses Bitmuster mittels der Tastatur da rein ???
INPUT_INT ZAHL
MOV AL, [ZAHL]
Zunächst wird das Makro INPUT_INT aufgerufen. Innerhalb des Makros geschieht nun folgendes:
Mit
MOV AH, 01H
wird ein Zeichen von der Tastatur eingelesen. Der ASCII Code des Zeichens landet in AL. Danach wird mit
SUB AL, 30H
aus dem ASCII Code der Wert der Ziffer extrahiert. Da AL nur der niedrige Teil von AX ist, wird noch mit
MOV AH, 0H
nach dem Ausführen der Funktion 01H das Register AH auf Null gesetzt.
Wenn die Taste „2“ gedrückt wurde gilt: AX = 00 02
Dieses Ergebnis wird mittels
MOV ZAHL, AL
von AL in die Variable ZAHL kopiert. Der Befehl MUL verlangt aber einen Operanden in AL. Deshalb wird mit
MOV AL, [ZAHL]
Die Zahl (unnötigerweise) wieder nach AL zurückkopiert.
Die Ziffer “2” stellt aber die Zehnerstelle der Zahl 21 dar. Deshalb wird nun nach BL die Zahl 10 geschrieben.
MOV BL, 0AH
Anschließend wird der Inhalt von AL (à 2) mittel des Befehls
MUL BL
mit 10 multipliziert.
peschko@aol.com 11
Assembler – DOS Teil 2 (Beta 1)
Copyright 2000 Thomas Peschko
Nun befindet sich im Register AL die Zahl 20. Diese Zahl wird mit
PUSH AX
auf dem Stack abgelegt.
In ähnlicher Weise wird in einem dritten Arbeitsschritt zunächst die Tausenderstelle der Jahreszahl
eingelesen und mit der Zahl 1000 multipliziert bevor sie auf dem Stack zwischengelagert wird.
Die gleichen Arbeitschritte fallen für die Hunderter-, Zehner- und Einerstelle der Jahreszahl an.
Anschließend befindet sich im Register AX die binäre Jahreszahl. Für Computer hat die Geburt Jesu
keinerlei Bedeutung. Für Computer ist die Erfindung von MS DOS bedeutungsvoll. Für Computer
leben wir also sozusagen im Jahre 20 nach Gates.
Aus diesem Grund muss nun von der Jahrezahl immer 1980 abgezogen werden. Dies passiert mittels
MOV BX, 07BCH
SUB AX, BX
Nun verfügen wir über die nötigen Daten, um das Register DX füttern zu können. Das Problem ist,
dass die einzelnen Daten in einer bestimmten Abfolge im Register DX stehen müssen.
peschko@aol.com 12
Assembler – DOS Teil 2 (Beta 1)
Copyright 2000 Thomas Peschko
Die Daten stehen jetzt noch in den Variablenfelder TAG, MONAT, JAHRE:
0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0
0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 1
Man kann erkennen, dass die Zahl im Feld MONAT um 5 Stellen nach links verschoben werden muss,
damit sie ihre richtige Position erreicht.
Auch die Anzahl der Jahre muss verschoben werden. Hier sind 9 Stellen erforderlich.
SHL [JAHRE], 09H
Bit 15 Bit 14 Bit 13 Bit 12 Bit 11 Bit 10 Bit 9 Bit 8 Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1
Bit 15 Bit 14 Bit 13 Bit 12 Bit 11 Bit 10 Bit 9 Bit 8 Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0
Bit 15 Bit 14 Bit 13 Bit 12 Bit 11 Bit 10 Bit 9 Bit 8 Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
0 0 1 0 0 1 1 0 0 0 0 0 0 0 0 0
Danach wird jeder Speicherplatz mit dem DX Register ODER verknüpft. Dies geschieht durch die
Befehle
OR DX, [TAG]
OR DX, [MONAT]
OR DX, [JAHRE]
Dies bewirkt, dass ein Bit an einer bestimmten Stelle im DX Register dann gesetzt wird, wenn
mindestens ein Bit an der gleichen Stelle in einem der drei Variablenfelder gesetzt wurde.
Bit 15 Bit 14 Bit 13 Bit 12 Bit 11 Bit 10 Bit 9 Bit 8 Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
0 0 1 0 0 1 1 1 1 0 0 1 0 1 0 1
peschko@aol.com 13
Assembler – DOS Teil 2 (Beta 1)
Copyright 2000 Thomas Peschko
Man kann jetzt aber noch nicht die Funktion AX = 5701H aufrufen. Denn...
Das Datei – Handle besorgen wir uns durch das Öffnen der Datei. Was die Uhrzeit betrifft
übernehmen wir einfach die alte Uhrzeit der Datei durch Aufruf der Funktion AX = 5700H.
Jetzt kopieren wir das gewünschte Datum in das Register DX und rufen die Funktion AX = 5701H auf.
Im letzten Programm dieses Kapitels sollen die Namen sowie die Dateiattribute zweier Dateien
ausgetauscht werden.
Zu diesem Zweck assemblieren wir das folgende Listing XCHANGE.ASM und legen im gleichen
Verzeichnis mittels des Windows Explorers zwei Textdateien mit den Namen ERSTE_DATEI.TXT und
ZWEITE_DATEI.TXT an. In die erste Datei schreiben wir „Dies ist Datei 1“und in die zweite Datei den
Text “Dies ist Datei 2“.
Bei der Datei ERSTE_DATEI.TXT klicken wir alle Dateiattribute ( versteckt, schreibgeschützt, Archiv )
an. Bei der Datei ZWEITE_DATEI.TXT deaktivieren wir alle Eigenschaften.
Nach Ausführen des Programms wurde nicht der Inhalt der Dateien ausgetauscht ! Es wurden
lediglich die „Personalausweise“ der Dateien ausgetauscht. Der Text „Dies ist Datei 1“ steht immer
noch in der ersten Datei. Die erste Datei hat jetzt jedoch den Namen ZWEITE_DATEI.TXT und besitzt
auch deren Attribute.
.MODEL SMALL
.DATA
.STACK 128
.CODE
.STARTUP
JNC WEITER_1
peschko@aol.com 14
Assembler – DOS Teil 2 (Beta 1)
Copyright 2000 Thomas Peschko
JMP ENDE
WEITER_1:
PUSH DS
PUSH DS
MOV AX, ES
MOV DS, AX
MOV SI, BX
POP ES
MOV DI, OFFSET NAME_1
MOV AX, DS
MOV ES, AX
POP DS
JNC WEITER_2
JMP ENDE
WEITER_2:
ADD BX,1EH
PUSH DS
PUSH DS
MOV AX, ES
MOV DS, AX
MOV SI, BX
POP ES
MOV DI, OFFSET NAME_2
MOV AX, DS
MOV ES, AX
POP DS
MOV BX, CX
MOV CX, BX
ENDE:
.EXIT
END
peschko@aol.com 17