+ All Categories
Home > Documents > Assembler-Einführung

Assembler-Einführung

Date post: 14-Jun-2015
Category:
Upload: flashfl1307
View: 886 times
Download: 0 times
Share this document with a friend
Description:
Einführung in Assembler von Thomas Peschko (c)
78
Assembler - Einführung 1. Teil Vorstellung der wichtigsten Grundbegriffe und Grundkenntnisse sowie der wichtigsten Hardwarekomponenten
Transcript
Page 1: Assembler-Einführung

Assembler - Einführung 1. Teil

Vorstellung der wichtigsten Grundbegriffe und Grundkenntnisse sowie der

wichtigsten Hardwarekomponenten

Page 2: Assembler-Einführung

Assembler - Einführung 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

2

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 !�.

Wahr ist aber auch:

��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� ;-)

��Funktionen, welche man bei problemorientierten Hochsprachen schmerzlich vermisst sind in Assembler leicht zu realisieren.

��Assemblerprogramme lassen sich durchaus in Hochsprachenprogramme einbinden.

Page 3: Assembler-Einführung

Assembler - Einführung 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

3

2. Die Maschinensprache - Grundlagen 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.

Page 4: Assembler-Einführung

Assembler - Einführung 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

4

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. Sie haben folgenden Aufbau: Beim 8086 Prozessor galt: 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. Das zweite Register trägt den Namen Basisregister mit Kennbuchstaben B. Für den niedrigen Teil schreibt man BL, für den hohen BH und für das Register als Ganzes BX. Verwendung findet diese Register vor allem für Zugriffe auf den Arbeitsspeicher des PC�s.

Page 5: Assembler-Einführung

Assembler - Einführung 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

5

Das dritte Register trägt den Namen Countregister mit Kennbuchstaben C. Für den niedrigen Teil schreibt man CL, für den hohen Teil CH und für das Register als Ganzes CX. Verwendung findet diese Register vor allem wenn es sich ums Zählen dreht. Das ist vor allem bei Schleifen der Fall. Das vierte Register trägt den Namen Datenregister mit Kennbuchstaben D. Für den niedrigen Teil schreibt man DL, für den hohen Teil DH und für das Register als Ganzes DX. Verwendung findet diese Register vor allem mit dem Register AX zusammen, wenn Multiplikations- und Divisionsaufgaben anstehen. Bevor wir uns nun der Statusflags bemächtigen können müssen wir uns leider mit einem besonderen Zahlensystem ( Binäres Zahlensystem ) beschäftigen.

4. Das binäre Zahlensystem Eine Dezimalzahl darf nur die Ziffern 0 bis 9 beinhalten. 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. 1 * 23 + 0 * 22 + 1 * 21 + 0 * 20 = 10 Das erste Bit einer Dualzahl von rechts ist das niedrige Bit. Hier beginnt man immer mit der Bitnummer 0. Kann man die Dezimalzahl 255 noch in das Register AH schreiben ? 255 : 2 = 127 Rest 1 niedriges Bit ( 1. Ziffer von rechts ) 127 : 2 = 63 Rest 1 63 : 2 = 31 Rest 1 31 : 2 = 15 Rest 1 15 : 2 = 7 Rest 1 7 : 2 = 3 Rest 1 3 : 2 = 1 Rest 1 1 : 2 = 0 Rest 1 hochwertiges Bit Antwort: Ja, denn sie benötigt nur 8 Bits. Die Dezimalzahl 256 könnte man nicht mehr nach AH schreiben.

Page 6: Assembler-Einführung

Assembler - Einführung 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

6

4.1 Addition und Subtraktion im binären Zahlensystem

Die Addition und Subtraktion im binären Zahlensystem ist etwas eigenwillig und gewöhnungsbedürftig.

4.1.1 Addition

Erst mal das, was uns noch vertraut ist:

0 0 1 jetzt aber: 1 Die 1 ist der Übertrag ! + 0 + 1 + 0 + 1 1

0 1 1 1 0

4.1.2 Subtraktion 0 1 1 1 0 - 0 - 1 - 0 - 1 0 0 1 1

5. Das hexadezimale Zahlensystem Im hexadezimalen Zahlensystem gibt es die Ziffern 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 sowie die �Ziffern� A, B, C, D, E, F. Die hexadezimale Zahl A steht für die dezimale Zahl 10. Die hexadezimale Zahl B steht für die dezimale Zahl 11. Die hexadezimale Zahl C steht für die dezimale Zahl 12. Die hexadezimale Zahl D steht für die dezimale Zahl 13. Die hexadezimale Zahl E steht für die dezimale Zahl 14. Die hexadezimale Zahl F steht für die dezimale Zahl 15. Welche Dezimalzahl entspricht der Hexadezimalzahl AFFE ? A * 163 + F * 162 + F * 161 + E * 160 = 45054 Das erste Bit einer Hexadezimalzahl von rechts ist das niedrige Bit. Hier beginnt man immer mit der Bitnummer 0. Welche hexadezimale Zahl passt gerade noch in das Register AH ? 255 : 16 = 15 Rest F niedriges Bit ( 1. Ziffer von rechts ) 15 : 16 = 0 Rest F hochwertiges Bit Antwort: Es ist die hexadezimale Zahl FF 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.

Page 7: Assembler-Einführung

Assembler - Einführung 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

7

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 Bit Nr. 0 nennt man Carry Flag (Übertragflag) 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. Beispiel: Im Register AH steht die Dualzahl 1111 0111. Es soll nun zu dieser Zahl die Dualzahl 1000 0000 addiert werden. 1 1 1 1 0 1 1 1 + 1 0 0 0 0 0 0 0 1 1 0 1 1 1 0 1 1 1 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 ). Bit Nr. 2 Das Parity Flag 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 1 1 1 1 0 1 1 1 + 0 0 0 1 0 0 0 0 1 1 1 1 1 0 0 0 0 0 1 1 1

Page 8: Assembler-Einführung

Assembler - Einführung 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

8

Bit Nr. 4 Das Auxiliary Flag / ( Hilfsübertragflag ) 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. 1 1 0 0 1 1 1 1 + 0 0 0 0 1 0 0 0 1 1 1 0 1 0 1 1 1 Bit Nr. 6 Das Zero Flag ( Nullflag ) Wenn das Ergebnis einer Vergleichs- oder arithmetischen Operation 0 ergab, wird dieses Bit gesetzt ( ZF = 1). 1 1 1 1 0 1 1 1 - 1 1 1 1 0 1 1 1 0 0 0 0 0 0 0 0 Bit Nr. 7 Das Sign Flag ( Vorzeichenflag ) 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: 1 0 1 0 0 0 1 0 als Dezimalzahl � 94. 0 1 0 1 1 1 1 0 als Dezimalzahl + 94. ( Zweierkomplement)

Page 9: Assembler-Einführung

Assembler - Einführung 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

9

Bit Nr. 11 Das Overflow Flag ( Überlaufflag ) 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). 0 1 1 1 0 1 1 1 + 0 1 0 0 0 0 0 0 1 1 0 1 1 0 1 1 1 6.2 Kontrollflags Bit Nr. 8 Trap Flag ( Einzelschrittflag ) 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 ! Bit Nr. 9 Das Interrupt Enable Flag ( Unterbrechungsflag ) 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 . Bit Nr. 10 Das Direction Flag ( Richtungsflag ) 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. DF = 1 Stringverarbeitung nach aufsteigenden Adressen. DF = 0 Stringverarbeitung nach absteigenden Adressen.

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.

Page 10: Assembler-Einführung

Assembler - Einführung 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

10

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. Es entstand folgendes Konzept: Das Bild soll einen Ausschnitt des Arbeitsspeicher darstellen. Die Fächer sind die einzelnen Speicherplätze. Hier sei zum Beispiel das 12. Paragraphen- Byte

Seine 12_PB- Adresse somit: (12*16 = ) 192 Dual: 1100 0000 Hier sei zum Beispiel das 11. Paragraphen- Byte

Seine 11_PB- Adresse somit: (11*16 = ) 176 Dual: 1011 0000 Segment Unsere Byte 1 habe nun z. B. den Abstand 76 Adressen (dezimal) vom 11. Paragraphen- Byte

Abstand 1 als Offset-Adresse 1 = 0100 1100 Unsere Byte 3 hat daher den Abstand 78 Adressen (dezimal) vom 11. Paragraphen- Byte Abstand 3 als Offset-Adresse 3= 0100 1110

. .

.

.

.

.

Byte 1

Byte 2

Byte 3 . . . .

Page 11: Assembler-Einführung

Assembler - Einführung 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

11

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. Die vollständige Adresse eines Speicherplatzes: Segment- Adresse : Offset- Adresse �Byte 1� wird daher in unserem Beispiel in folgender Form angegeben: 1011 0000 : 0100 1100 Ü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ä ?). Wie macht man jetzt eine 20- Bit- Adresse daraus ? 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. Segment- Adresse mal 16 : + 0 0 0 0 0 0 0 0 1 0 1 1 0 0 0 0 Offset- Adresse: = 20- Bit- Adresse:

0 0 0 0 0 0 0 0 1 0 1 1 0 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.

0 0 0 0

1 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0

1 1 0 0

Page 12: Assembler-Einführung

Assembler - Einführung 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

12

Damit erhalten wir für die gleiche Speicherstelle �Byte 1� die 20- Bit- Adresse: 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 1 0 0

+ 0 0 0 0 0 0 0 0 1 1 0 0 0 1 0 1 1 1 0 0 Zum Abschluss der Einführung kommen nun noch 4 Segmentregister sowie 5 Index- und Zeigerregister. 7.2 Die Zeiger- und Indexregister

Codesegment- Register (CS) ���� 16 Bit : In diesem Register befindet sich eine Paragraphenbyte- Adresse mit der ein Segment beginnt in dem Befehle gespeichert werden. Nun gibt es ein weiteres Register, den Befehlszeiger (IP) ���� 16 Bit. Dieses Register beinhaltet die Offset- Adresse eines Befehls innerhalb dieses Codesegments. Ein Speicherplatz an dem im Codesegment ein Befehl sitzt bekommt also die vollständige Adresse: CS : IP Datensegment- Register (DS) ����16 Bit : In diesem Register befindet sich eine Paragraphenbyte- Adresse mit der ein Segment beginnt in dem Daten gespeichert werden. Extrasegment- Register (ES) ���� 16 Bit : 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. Nun gibt es zwei weitere Register, das Source- Index (SI) und das Destination- Index (DI) Register ���� jeweils 16 Bit. Das SI und das DI Register unterstützen unter anderem das Datensegment- und Extrasegmentregister als Offset- Register bei der Adressierung der Speicherstellen. Auch das Register BX kann mit DS verwendet werden. DS : SI DS : DI DS : BX ES : SI ES : DI ES : BX

Page 13: Assembler-Einführung

Assembler - Einführung 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

13

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

Segmentregister

Zeiger- und Indexregister

CPU

AX

BX

CX

DX

Statusregister

SS

ES

CS

DS

SP

BP

DI

SI IP

Page 14: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

1

Assembler - Einführung 2. Teil

Assembler – Tools und Umgebung

Page 15: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

2

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 Ein Segment wird im Quellcode in folgender Form dargestellt: Name segment [Ausrichtung] [Komb] [Klasse] ; Beginn des Segmentes. ; Diese ; Zeilen ; sind der Inhalt des Segmentes. [Name] ends ; Ende des Segmentes. 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 �. Es ist immer empfehlenswert Kommentare im Programmcode zu platzieren. Vor diesen Kommentaren muss jedoch ein „;“ stehen.

2. Das erste Programm Nun währen wir in der Lage ein erstes Programm zu schreiben. Es soll – das ist so Tradition – "Hallo Welt ! " ausgeben. Zu diesem Zweck öffnen wir unter Windows ein DOS- Fenster. Hierzu klickt man „START“ � „PROGRAMME“ � „MS- DOS- EINGABEAUFFORDERUNG“. Es erscheint ein DOS Fenster mit dem Inhalt: C:\Windows> _ 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> _

Page 16: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

3

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. Wir geben ein: ; ******************************************** ;*Ausgabe: „Hallo Welt!“ auf Bildschirm* ;********************************************* DATEN SEGMENT MELDUNG DB "Hallo Welt !“, "$“ DATEN ENDS STAPEL SEGMENT BYTE STACK DW 128 DUP (?) STAPEL ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL START: MOV AX, DATEN MOV DS, AX MOV DX, OFFSET MELDUNG MOV AH, 9H INT 21H MOV AH, 4CH INT 21H CODE ENDS END START

Page 17: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

4

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: TURBO ASSEMBLER VERSION ....... ASSEMBLER FILE : HALLO.ASM ERROR MESSAGES : none WARNING MESSAGES : none ….. C:\asm> _ Jetzt geben wir ein : TLINK HALLO.OBJ und drücken die RETURN – Taste. folgende Meldung erscheinen: TUROBO LINK VERSION..... C:\asm> _ Jetzt geben wir ein: HALLO.EXE und drücken die RETURN – Taste. folgende Meldung erscheinen: HALLO WELT ! C:\asm> _ 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.

Page 18: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

5

Es erscheint folgende Ausgabe: 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 Symbol Name Typ Value ??DATE Text "25/03/00" ??FILENAME Text "HALLO " ??TIME Text "21:31:19" ??VERSION Number 0101 @CPU Text 0101H @CURSEG Text CODE @FILENAME Text HALLO @WORDSIZE Text 2 MELDUNG Byte DATEN: 0000 START Near CODE: 0000 Groups & Segments Bit Size Align Combine Class CODE 16 0010 Para none DATEN 16 000D Para none STAPEL 16 0100 Byte Stack

Page 19: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

6

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. Zeilen 4,5 sind Leerzeilen. 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 23 beginnt das Segment CODE. 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.

Page 20: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

7

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 !

Page 21: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

8

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. 3.1 DEBUG als Testhilfe Ja, dann nehmen wir mal unser Programm Hallo.exe auseinander. Dazu öffnen wir wieder ein DOS- Fenster und wechseln in das Verzeichnis ASM. Wir sehen: C:\asm> _ Wir geben ein: DEBUG HALLO.EXE und drücken die RETURN – Taste. Wir sehen: C:\asm>debug hallo.exe -_ Wir geben ein: t und drücken die RETURN – Taste. 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. Wir geben ein: t und drücken die RETURN – Taste. Wir sehen: AX=1BA5 BX=0000 CX=0120 DX=0000 SP=010D BP=0000 SI=0000 DI=0000 DS=1BA5 ES=1B95 SS=1BA5 CS=1BB6 IP=0005 NV UP EI PL NZ NA PO NC 1BB6:0005 BA0000 MOV DX,0000 - 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. Wir geben ein: t und drücken die RETURN – Taste. Wir sehen: AX=1BA5 BX=0000 CX=0120 DX=0000 SP=010D BP=0000 SI=0000 DI=0000 DS=1BA5 ES=1B95 SS=1BA5 CS=1BB6 IP=0008 NV UP EI PL NZ NA PO NC 1BB6:0008 B409 MOV AH,09 -

Page 22: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

9

DEBUG arbeitet im Einzelschritt Modus unser Programm ab. Man sieht deutlich, dass im Register IP schon immer die Adresse des nächsten Befehls steht. Wir geben ein: t und drücken die RETURN – Taste. Wir sehen: AX=09A5 BX=0000 CX=0120 DX=0000 SP=010D BP=0000 SI=0000 DI=0000 DS=1BA5 ES=1B95 SS=1BA5 CS=1BB6 IP=000A NV UP EI PL NZ NA PO NC 1BB6:000A CD21 INT 21 - 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. Wir geben ein: t und drücken die RETURN – Taste. Wir sehen: AX=09A5 BX=0000 CX=0120 DX=0000 SP=0107 BP=0000 SI=0000 DI=0000 DS=1BA5 ES=1B95 SS=1BA5 CS=1194 IP=0445 NV UP DI PL NZ NA PO NC 1194:0445 EAA004B407 JMP 07B4:04A0 - 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. Wir geben ein: t und drücken die RETURN – Taste. Wir sehen: AX=09A5 BX=0000 CX=0120 DX=0000 SP=0107 BP=0000 SI=0000 DI=0000 DS=1BA5 ES=1B95 SS=1BA5 CS=07B4 IP=04A0 NV UP DI PL NZ NA PO NC 07B4:04A0 80FC72 CMP AH,72 - Hier sehen wir die erste Zeile des Unterprogramms. Nach unserem INT Befehl haben wir unser Programm verlassen und haben sozusagen das Hoheitsgebiet von Microsoft betreten. Wir könnten uns auf diese Weise den Quellcode des ganzen Unterprogramms zugänglich machen und die geistigen Ergüsse der Microsoft Programmierer begutachten. In der Fachsprache nennt man das zugänglich machen des Quellcodes eines Programms Disassemblierung. Das Unterprogramm ist lang und langweilig. Bevor wir unseren Rechner abschießen geben wir diesmal ein: q und drücken die RETURN – Taste. 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 : AX=09A5 BX=0000 CX=0120 DX=0000 SP=010D BP=0000 SI=0000 DI=0000 DS=1BA5 ES=1B95 SS=1BA5 CS=1BB6 IP=000A NV UP EI PL NZ NA PO NC 1BB6:000A CD21 INT 21 - Wir geben diesmal an dieser Stelle ein p ein und drücken die RETURN – Taste.

Page 23: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

10

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. Wir geben ein: t und drücken die RETURN – Taste. Wir sehen: AX=4C24 BX=0000 CX=0120 DX=0000 SP=010D BP=0000 SI=0000 DI=0000 DS=1BA5 ES=1B95 SS=1BA5 CS=1BB6 IP=000E NV UP EI PL NZ NA PO NC 1BB6:000E CD21 INT 21 - Wir geben ein: p und drücken die RETURN – Taste. Wir sehen: Programm wurde normal beendet - Wir geben ein: q und drücken die RETURN – Taste. Wir sehen: C:\asm>_ 3.2 Editieren des Arbeitsspeichers mit DEBUG 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 geben ein: DEBUG HALLO.EXE und drücken die RETURN – Taste. Wir sehen: C:\asm>debug hallo.exe -_ Wir geben ein: t und drücken die RETURN – Taste. 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 -

Page 24: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

11

Wir geben ein: t und drücken die RETURN – Taste. Wir sehen: AX=1BA5 BX=0000 CX=0120 DX=0000 SP=010D BP=0000 SI=0000 DI=0000 DS=1BA5 ES=1B95 SS=1BA5 CS=1BB6 IP=0005 NV UP EI PL NZ NA PO NC 1BB6:0005 BA0000 MOV DX,0000 - Wir geben ein: t und drücken die RETURN – Taste. Wir sehen: AX=1BA5 BX=0000 CX=0120 DX=0000 SP=010D BP=0000 SI=0000 DI=0000 DS=1BA5 ES=1B95 SS=1BA5 CS=1BB6 IP=0008 NV UP EI PL NZ NA PO NC 1BB6:0008 B409 MOV AH,09 - 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. Da das H noch stimmt drücken wir nun die Leerzeichentaste. Wir sehen: -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. Wir geben ein: 65 und drücken die LEERZEICHEN – Taste. Wir sehen: -E 1BA5:0000 1BA5:0000 48. 61.65 6C._

Page 25: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

12

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 wir tippen ein: 6F und drücken die LEERZEICHEN – Taste. Wir sehen: 1BA5:0000 48. 61.65 6C. 6C. 6F. 20. 57. 65.6F 1BA5:0008 6C._ Hier steht nun das "l" von Welt. Wir ersetzten es durch ein "r" � Zahl: 72 wir tippen ein: 72 und drücken die LEERZEICHEN – Taste. Wir sehen: 1BA5:0000 48. 61.65 6C. 6C. 6F. 20. 57. 65.6F 1BA5:0008 6C.72 74._ Hier steht nun das "t" von Welt. Wir ersetzten es durch ein "l" � Zahl: 6C wir tippen ein: 6C und drücken die LEERZEICHEN – Taste. Wir sehen: 1BA5:0000 48. 61.65 6C. 6C. 6F. 20. 57. 65.6F 1BA5:0008 6C.72 74.6C 20._ Hier steht nun das " " hinter dem Wort Welt. Wir ersetzten es durch ein "d" � Zahl: 64 wir tippen ein: 64 und drücken die LEERZEICHEN – Taste. Wir sehen: 1BA5:0000 48. 61.65 6C. 6C. 6F. 20. 57. 65.6F 1BA5:0008 6C.72 74.6C 20.64 21._ Wir sehen nun den ASCII Code des Ausrufezeichens. Der bleibt unverändert. wir drücken die LEERZEICHEN – Taste. Wir sehen: 1BA5:0000 48. 61.65 6C. 6C. 6F. 20. 57. 65.6F 1BA5:0008 6C.72 74.6C 20.64 21. 24._ Wir sehen nun den ASCII Code des $-zeichens. Der bleibt unverändert. wir drücken die RETURN – Taste. Wir sehen: - Wir geben ein: g und drücken die RETURN – Taste. Wir sehen: 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.

Page 26: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

13

3.3 Erstellen von ausführbaren Programmen mit DEBUG 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> _ Wir geben ein: DEBUG und drücken die RETURN – Taste. Danach wird ein a eingegeben und die RETURN – Taste gedrückt. Wir sehen ( Die Segmentadresse ist wahrscheinlich eine andere): 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_ Tatsächlich landen wir am richtigen Platz!

Page 27: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

14

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 -_ Wir geben ein: n WELT.COM und drücken die RETURN – Taste. Wir geben ein: RCX und drücken die RETURN – Taste. Wir sehen: -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. Wir geben ein: 25 und drücken die RETURN – Taste.

Page 28: Assembler-Einführung

Assembler - Einführung 2. Teil (Beta 2) Copyright 2000 Thomas Peschko

15

Wir geben ein: w und drücken die RETURN – Taste. Wir sehen: CX 0000 :25 -w 00025 Bytes werden geschrieben -_ Wir geben ein: g und drücken die RETURN – Taste. Wir sehen: 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. 3.4 Disassemblierung mit DEBUG 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.

Page 29: Assembler-Einführung

Assembler - Grundlagen 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

1

Assembler I - Grundlagen 1. Teil

Assembler – Unterprogramme und Makros

Page 30: Assembler-Einführung

Assembler - Grundlagen 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

2

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. Man nennt diesen Programmierstiel dann "Spaghetti – Code".

1.1 Aufbau und Struktur von Unterprogrammen 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 ? Die geschieht mit dem Befehl: CALL ( � rufen) 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. Der Aufruf im Hauptprogramm sieht daher so aus: CALL PROGRAMMNAME 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. Ein Unterprogramm hat folgendes Grundgerüst: PROGRAMMNAME PROC [NEAR][FAR] ← Anfang des Unterprogramms . . 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. Die PROGRAMMNAME ENDP – Anweisung markiert das Ende des Unterprogramms.

Page 31: Assembler-Einführung

Assembler - Grundlagen 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

3

1.1.1 NEAR - Unterprogramme Das Programm HALLO.EXE soll nun mittels eines Unterprogramms erstellt werden. Wir schreiben den unteren Programmcode in eine Datei UPROG1.ASM DATEN SEGMENT MELDUNG DB "HALLO WELT !","$" DATEN ENDS STAPEL SEGMENT BYTE STACK DW 128 DUP(0) STAPEL ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL START: MOV AX, DATEN MOV DS, AX CALL PRINT MOV AH, 4CH INT 21H PRINT PROC NEAR MOV AH, 09H MOV DX, OFFSET MELDUNG INT 21H 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.

Page 32: Assembler-Einführung

Assembler - Grundlagen 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

4

Wir geben am DOS – Prompt ein: C:>\ASM>DEBUG UPROG1.EXE -u …und wir sehen unser Programm im Maschinencode... 1EB8:0000 B8A71E MOV AX,1EA7 1. START 1EB8:0003 8ED8 MOV DS,AX 2. 1EB8:0005 E80400 CALL 000C 3. 1EB8:0008 B44C MOV AH,4C 8. 1EB8:000A CD21 INT 21 9. ENDE 1EB8:000C B409 MOV AH,09 4. 1EB8:000E BA0000 MOV DX,0000 5. 1EB8:0011 CD21 INT 21 6. 1EB8:0013 C3 RET 7. 1EB8:0014 83CA02 OR DX,+02 10. 1EB8:0017 3C3F CMP AL,3F 11. 1EB8:0019 7503 JNZ 001E . 1EB8:001B 83CA04 OR DX,+04 . 1EB8:001E 0AC0 OR AL,AL - 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. Durch den RET – Befehl wird wieder zurück gesprungen.

1.1.2 FAR – Unterprogramme 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 � ). 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.

Page 33: Assembler-Einführung

Assembler - Grundlagen 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

5

Ein Beispiel: Wir schreiben zunächst eine Datei DATEI_1.ASM EXTRN PRINT:FAR EXTRN MSDOS:FAR DATEN SEGMENT MELDUNG DB "Meldung aus Datei 1!","$" DATEN ENDS STAPEL SEGMENT BYTE STACK DW 128 DUP(0) STAPEL ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL START: MOV AX, DATEN MOV DS, AX MOV DX, OFFSET MELDUNG CALL PRINT CALL MSDOS CODE ENDS END START Danach wird eine Datei DATEI_2.ASM erstellt. EXTRN PRINT:FAR EXTRN MSDOS:FAR DATEN SEGMENT MELDUNG DB "Meldung aus Datei 2!","$" DATEN ENDS STAPEL SEGMENT BYTE STACK DW 128 DUP(0) STAPEL ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL START: MOV AX, DATEN MOV DS, AX MOV DX, OFFSET MELDUNG CALL PRINT CALL MSDOS CODE ENDS END START

Page 34: Assembler-Einführung

Assembler - Grundlagen 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

6

Zum Schluss eine Datei SUB_PROG.ASM PUBLIC PRINT PUBLIC MSDOS DATEN SEGMENT DATEN ENDS STAPEL SEGMENT BYTE STACK DW 128 DUP(0) STAPEL ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL START: PRINT PROC FAR MOV AH, 09H INT 21H RET PRINT ENDP MSDOS PROC FAR MOV AH, 4CH INT 21H RET MSDOS ENDP CODE ENDS END START Nun wird jede Datei einzeln mit TASM assembliert: C:\asm>TASM DATEI_1.ASM Turbo Assembler Version 1.01 Copyright (c) 1988, 1989 Borland International Assembling file: DATEI_1.ASM Error messages: None Warning messages: None Remaining memory: 411k C:\asm>TASM DATEI_2.ASM Turbo Assembler Version 1.01 Copyright (c) 1988, 1989 Borland International Assembling file: DATEI_2.ASM Error messages: None Warning messages: None Remaining memory: 411k C:\asm>TASM SUB_PROG.ASM Turbo Assembler Version 1.01 Copyright (c) 1988, 1989 Borland International Assembling file: SUB_PROG.ASM Error messages: None Warning messages: None Remaining memory: 41

Page 35: Assembler-Einführung

Assembler - Grundlagen 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

7

C:\asm>_ Dann können die einzelnen Hauptprogramme DATEI_1.OBJ und DATEI_2.OBJ jeweils mit der Unterprogrammdatei SUB_PROG.OBJ zusammengebunden werden. Dies geschieht mittels: C:\asm>TLINK DATEI_1.OBJ SUB_PROG.OBJ Turbo Link Version 2.0 Copyright (c) 1987, 1988 Borland International C:\asm>TLINK DATEI_2.OBJ SUB_PROG.OBJ Turbo Link Version 2.0 Copyright (c) 1987, 1988 Borland International C:\asm>_ Durch Eingabe von: C:\asm>Datei_1 Sehen wir: Meldung aus Datei 1! Durch Eingabe von: C:\asm>Datei_2 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.

Page 36: Assembler-Einführung

Assembler - Grundlagen 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

8

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: 1ECB:0000 B409 MOV AH,09 1ECB:0002 CD21 INT 21 1ECB:0004 CB RETF 1ECB:0005 B44C MOV AH,4C 1ECB:0007 CD21 INT 21 1ECB:0009 CB RETF 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. Ein Mako hat folgenden Aufbau: MAKRONAME MACRO VARIABLE1_DES _QUELLCODES, … . . Befehle Makrokörper . . ENDM

Page 37: Assembler-Einführung

Assembler - Grundlagen 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

9

Wie veranlasst man nun den Assembler den Makrokörper in den Quellcode einzufügen ? Dies gelingt mit dem Ausdruck: 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 : PRINT MACRO STRING MOV AH, 09H MOV DX, OFFSET STRING INT 21H ENDM MSDOS MACRO MOV AH, 4CH INT 21H 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.

Page 38: Assembler-Einführung

Assembler - Grundlagen 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

10

Jetzt wird das Hauptprogramm DATEI_3.ASM erstellt. INCLUDE MACRO.BIB DATEN SEGMENT MELDUNG DB "Meldung aus Datei 3!","$" DATEN ENDS STAPEL SEGMENT BYTE STACK DW 128 DUP(0) STAPEL ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL START: MOV AX, DATEN MOV DS, AX 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. Zum Assemblieren geben wir ein: C:\asm>TASM DATEI_3.ASM Wir sehen: Turbo Assembler Version 1.01 Copyright (c) 1988, 1989 Borland International Assembling file: DATEI_3.ASM Error messages: None Warning messages: None Remaining memory: 411k Dann wird eingegeben: C:\asm>TLINK DATEI_3.OBJ Wir sehen: Turbo Link Version 2.0 Copyright (c) 1987, 1988 Borland International C:\asm>_

Page 39: Assembler-Einführung

Assembler - Grundlagen 1. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

11

Wir schauen uns das Programm mit DEBUG an. Wir geben ein: C:\asm>DEBUG DATEI_3.EXE -u Wir sehen: 1EB9:0000 B8A71E MOV AX,1EA7 1EB9:0003 8ED8 MOV DS,AX 1EB9:0005 B409 MOV AH,09 1EB9:0007 BA0000 MOV DX,0000 1EB9:000A CD21 INT 21 1EB9:000C B44C MOV AH,4C 1EB9:000E CD21 INT 21 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.

Page 40: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

1

Assembler I - Grundlagen 2. Teil

Assembler – Adressierung und Speichermodelle

Page 41: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

2

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.

1.1 Direkte Adressierung Bei diesem Speicherzugriff folgt dem Operationsteil die effektive Adresse. Operation Adresse

Speicherstelle

Zunächst ist ein generelles Problem zu beachten: 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 Danach wird folgende Operation durchgeführt: MOV AX, VAR1 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.

Page 42: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

3

1. Fall: Es soll die 0 nach AX kopiert werden: MOV AX, [VAR1] 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: MOV AX, OFFSET VAR1 Hier wurde das AX Register direkt angesprochen. Es ist auch möglich den Inhalt einer bestimmten Speicherstelle direkt auszulesen. Zum Beispiel mit MOV AX, [0040:0923] Ü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

Page 43: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

4

Beispiel: DATEN1 SEGMENT MELDUNG1 DB "HALLO WELT 1!",10,13,"$" DATEN1 ENDS DATEN2 SEGMENT MELDUNG2 DB "HALLO WELT 2!",10,13,"$" DATEN2 ENDS STAPEL SEGMENT BYTE STACK DW 128 DUP(0) STAPEL ENDS CODE2 SEGMENT ASSUME CS:CODE2 DRUCK PROC FAR MOV AH, 09H INT 21H RETF DRUCK ENDP CODE2 ENDS CODE1 SEGMENT ASSUME CS:CODE1,SS:STAPEL,ES:NOTHING START: MOV DX, OFFSET DATEN1:MELDUNG1 MOV AX, DATEN1 MOV DS, AX CALL FAR PTR CODE2:DRUCK MOV DX, OFFSET DATEN2:MELDUNG2 MOV AX, DATEN2 MOV DS, AX CALL FAR PTR CODE2:DRUCK MOV AH, 4CH INT 21H CODE1 ENDS END START Es ist nun (nach dem Assemblieren und Linken ) möglich mit 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...)

Page 44: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

5

Auf meinem Rechner ergibt sich unter anderem... 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. AX=09A5 BX=0000 CX=014E DX=0000 SP=00FC BP=0000 SI=0000 DI=0000 DS=1BA5 ES=1B95 SS=1BA7 CS=1BB7 IP=0002 NV UP EI PL NZ NA PO NC 1BB7:0002 CD21 INT 21 -p HALLO WELT 1! AX=0924 BX=0000 CX=014E DX=0000 SP=00FC BP=0000 SI=0000 DI=0000 DS=1BA5 ES=1B95 SS=1BA7 CS=1BB7 IP=0004 NV UP EI PL NZ NA PO NC 1BB7:0004 CB RETF -t AX=0924 BX=0000 CX=014E DX=0000 SP=0100 BP=0000 SI=0000 DI=0000 DS=1BA5 ES=1B95 SS=1BA7 CS=1BB8 IP=000D NV UP EI PL NZ NA PO NC 1BB8:000D BA0000 MOV DX,0000 -t 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.

Page 45: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

6

Wir haben hier als Beispiel den Prozessor ohne "Taxistand" direkt zum Unterprogramm geschickt. DATEN1:0000 DATEN2:0000 STAPEL:0000 CODE2:0000 CODE1:0000

1.2 Indirekte Adressierung 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. 1.2.1 Register – 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. Operation Name des Registers Inhalt: Adresse der Speicherstelle Register

Speicherstelle Inhalt: Info

Page 46: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

7

Beispiel: MOV [BX], DX Dies bedeutet: Schreibe den Inhalt des Registers DX an die Adresse im Arbeitsspeicher, die in BX angegeben ist. MOV AX, [BX] Dies bedeutet: Lies den Inhalt der Adresse, die in BX angegeben ist. Schiebe diesen Inhalt in das Register AX. Jetzt die Fallen:

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. Also: MOV [BX], 10 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. Wir müssen darauf hinweisen, dass wir 0000 1010 meinen: MOV [BYTE PTR BX], 10 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: Hier tappen viele Anfänger rein: MOV DL, BX Wir schreiben Daten mit einer Länge von 16 Bit an einen Ort, der nur 8 Bit groß ist. Oh, Oh.....

Page 47: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

8

1.2.2 Basis-relative – Adressierung Hier wird zu einer in einem Register gespeicherten Adresse eine Zahl addiert. Die Summe aus Adresse und Konstante ergibt die Adresse der gesuchten Speicherstelle. Operation Name des Registers Inhalt: Adresse + Konstante = Register MOV [BX + 10H], DX 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. Dieser Ausdruck kann auch auf andere Weise dargestellt werden: MOV [BX]10H, DX Diese Befehlsform ist gleichbedeutend wie die obere Schreibweise. 1.2.3 Basis-indizierte Adressierung Hier muss der Inhalt von Register1 und Register2 addiert werden um die Adresse der gesuchten Speicherstelle zu erhalten . Operation Name des Registers 1 Name des Register 2 Inhalt des Register 1 + Inhalt des Register 2 MOV AL, [BX+SI] Beim 8086 sind folgende Kombinationen erlaubt: BX+SI BX+DI BP+SI BP+DI Ab dem 80386 sind auch alle Kombinationen der 32 – Bit Register ( außer ESP ) erlaubt.

Speicherstelle Inhalt: Info

Speicherstelle Inhalt: Info

Page 48: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

9

1.2.4 Basis-indizierte Adressierung mit Displacement Die Adressbildung ist hier folgende: 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. Operation Name des Registers 1 Name des Register 2 Inhalt des Register 1 + Inhalt des Register 2 + Konstante MOV [BX+SI+80H], DX MOV LISTE[BX][DI], AL Liste ist eine Variable

1.3 Zusammenfassung zur Adressierung 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: Mit folgendem Schema kann man sich eine Eselsbrücke bauen: [Basisregister] [Indexregister] Konstante BX SI oder + oder + Konstante BP DI Somit ergeben sich folgende gültigen Kombinationen: [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]

Speicherstelle Inhalt: Info

Page 49: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

10

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 ASSUME CS:CODE, DS:DATEN,ES:NOTHING,SS:STAPEL usw. ….. die Segmentregister geladen werden. Zusätzlich war noch das Daten-Segment zu initialisieren : MOV AX, DATEN MOV DS, AX Nicht zu vergessen ist auch die Startmarke und Endmarke START: END START Wichtig war auch, dass am Ende des Programms immer das Betriebssystem die Kontrolle erhält: MOV AH, 4CH INT 21H 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.

Page 50: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

11

Im Angebot sind die Typen : 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. Ein solches "Grundgerüst" wird durch dir Direktive .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: STAPEL SEGMENT BYTE STACK DW 128 DUP(0) STAPEL ENDS Mit Zahl kann man den zu reservierenden Speicher festlegen. .DATA Damit wird das Daten - Segment angelegt. .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.

Page 51: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

12

Damit wirklich kein Zweifel über den Programmanfang besteht, wird nach der .CODE Anweisung noch die Anweisung .STARTUP gesetzt. Diese kleine Anweisung ersetzt : ASSUME CS:CODE,DS:DATEN,ES:NOTHING, SS:STAPEL MOV DX, DATEN MOV DS, DX START: Wichtig ist natürlich auch der Programmabschluss. Mit .EXIT werden die Befehle MOV AH, 4CH INT 21H ersetzt. Zum Schluss nicht das END vergessen. Dies entspricht dem END START Damit würde unser Programm HALLO.ASM nun so aussehen: .MODEL SMALL .DATA MELDUNG DB "Hallo Welt!","$" .STACK .CODE .STARTUP MOV DX, OFFSET MELDUNG MOV AH, 09H INT 21H .EXIT END

Page 52: Assembler-Einführung

Assembler I - Grundlagen 2. Teil (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

13

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

Page 53: Assembler-Einführung

Assembler – DOS (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

1

Assembler II - DOS

ASSEMBLER – Arbeiten mit Dateien und Daten

Page 54: Assembler-Einführung

Assembler – DOS (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

2

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

MELDUNG DB "Es wird eine Datei erstellt !“, "$“DATEINAME DB "NEU_DAT.DAT",0DATEIINHALT DB "Dies wird in die Datei geschrieben.","$"

DATEN ENDS

STAPEL SEGMENT BYTE STACK

DW 128 DUP (?)

STAPEL ENDS

CODE SEGMENT

ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL

START: MOV AX, DATENMOV DS, AX

MOV DX, OFFSET MELDUNGMOV AH, 9HINT 21H

MOV AH, 3CH ; Datei erstellen und öffnen.MOV DX, OFFSET DATEINAMEMOV CX, 00H ; DateiartINT 21H

PUSH AX ; Dateinummer auf STACK auslagern.MOV AH, 40HMOV DX, OFFSET DATEIINHALTMOV CX, 23H ; Anzahl der geschriebene BytesPOP BX ; Dateinummer zurückholenINT 21H

MOV AH, 3EH ; Datei schließenINT 21H

MOV AH, 4CH ; MS-DOSINT 21H

CODE ENDS

END START

Page 55: Assembler-Einführung

Assembler – DOS (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

3

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.

Page 56: Assembler-Einführung

Assembler – DOS (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

4

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

MELDUNG DB "Dieses Programm verschlüsselt eine Datei","$"DATEINAME_QUELLE DB "QUELLE.TXT",0DATEINAME_ENCRYPT DB "ENCRYPT.TXT",0PUFFER DW 1 DUP (0)HANDLE_OF_QUELLE DW 1 DUP (0)HANDLE_OF_ENCRYPT DW 1 DUP (0)

DATEN ENDS

STAPEL SEGMENT BYTE STACK

DW 128 DUP (?)

STAPEL ENDS

CODE SEGMENT

ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL

START: MOV AX, DATENMOV DS, AX

MOV DX, OFFSET MELDUNGMOV AH, 9HINT 21H

MOV AH, 3CHMOV CX, 00HMOV DX, OFFSET DATEINAME_ENCRYPTINT 21HMOV HANDLE_OF_ENCRYPT, AX

MOV AH, 3DHMOV AL, 0HMOV DX, OFFSET DATEINAME_QUELLEINT 21HMOV HANDLE_OF_QUELLE, AX

M1: MOV AH, 3FHMOV CX, 01HMOV DX, OFFSET PUFFERMOV BX, HANDLE_OF_QUELLEINT 21H

CMP AX, 0HJE ENDE

Page 57: Assembler-Einführung

Assembler – DOS (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

5

ADD [PUFFER], 2H

MOV AH, 40HMOV CX, 2HMOV BX, HANDLE_OF_ENCRYPTMOV DX, OFFSET PUFFERINT 21H

JMP M1

ENDE: MOV AH, 3EHMOV BX, HANDLE_OF_QUELLEINT 21H

MOV AH, 3EHMOV BX, HANDLE_OF_ENCRYPTINT 21H

MOV AH, 4CHINT 21H

CODE ENDS

END START

Jetzt wird der Quellcode assembliert und gelinkt. Fertig ist das Programm ! "Und ? Wie es funktioniert?..... " ;-) 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... Was ist passiert? Das Programm sollte bis zur Marke M1: klar sein ! Danach wird es interessant. Mit MOV AH, 3FH ordnen wir nun an, dass aus der Datei dessen Identifikationsnummer in BX mittels MOV BX, HANDLE_OF_QUELLE geparkt wird genau MOV CX, 01H 1 Byte ( 01H [exadezimal] = 1 dezimal!) gelesen werden soll. Dieses Byte wird im Datensegment ab der Speicherstelle mit dem "Namen" PUFFER abgelegt. MOV DX, OFFSET PUFFER

Page 58: Assembler-Einführung

Assembler – DOS (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

6

Hmm… 1 Byte sind 8 Bit ! Wir belegen also eigentlich nur 8 Bit im Arbeitsspeicher. Warum haben wir dann mit der Anweisung PUFFER DW 1 DUP (0) genau 1 Wort = 2 Byte = 16 Bit reserviert und diese mit Nullen vorbelegt ??? Schauen wir uns einmal an wie es weiter geht. 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 ! Dies geschieht mittels CMP AX, 0H CMP = CoMPare ( vergleiche) 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. Dies geschieht mittels JE ENDE JE = Jump if Equal ( springe wenn gleich) � 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. Das ist binär... Na, wissen wir es ???? 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 Was geschieht dabei ??? (Jetzt kommt der Hard – Part)

1 1 1 1 1 1 1 1 + 1 0

1 0 0 0 0 0 0 0 1

Page 59: Assembler-Einführung

Assembler – DOS (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

7

Oh, Oh … winke, winke … Die grüne 1 ginge im Puffer verloren, wenn wir nur 1 Byte reserviert hätten ! � War also doch ganz gut mit dem PUFFER DW 1 DUP (0) Damit steht nun im Puffer: 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 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...

MOV AH, 40HMOV CX, 2HMOV BX, HANDLE_OF_ENCRYPTMOV DX, OFFSET PUFFER

INT 21H Danach springen wir auf jeden Fall wieder nach oben zur Marke M1:...... JMP M1 JMP = JuMP (springe) ...und das nächste Zeichen ist an der Reihe....bis kein Zeichen mehr da ist. Dann werden mit den Befehlzeilen...

ENDE: MOV AH, 3EHMOV BX, HANDLE_OF_QUELLEINT 21H

MOV AH, 3EHMOV BX, HANDLE_OF_ENCRYPTINT 21H

MOV AH, 4CH INT 21H ...alle Dateien geschlossen und das Programm beendet. Hinweis: Es besteht ein wichtiger Unterschied zwischen dem Ausdruck ADD [PUFFER], 2H und dem Ausdruck ADD PUFFER, 2H 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 !

Page 60: Assembler-Einführung

Assembler – DOS (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

8

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

MELDUNG DB "Dieses Programm entschlüsselt eine Datei","$"DATEINAME_ENCRYPT DB "ENCRYPT.TXT",0DATEINAME_DECRYPT DB "DECRYPT.TXT",0PUFFER DW 1 DUP (0)HANDLE_OF_ENCRYPT DW 1 DUP (0)HANDLE_OF_DECRYPT DW 1 DUP (0)

DATEN ENDS

STAPEL SEGMENT BYTE STACK

DW 128 DUP (?)

STAPEL ENDS

CODE SEGMENT

ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL

START: MOV AX, DATENMOV DS, AX

MOV DX, OFFSET MELDUNGMOV AH, 9HINT 21H

MOV AH, 3CHMOV CX, 00HMOV DX, OFFSET DATEINAME_ENCRYPTINT 21HMOV HANDLE_OF_DECRYPT, AX

MOV AH, 3DHMOV AL, 0HMOV DX, OFFSET DATEINAME_DECRYPTINT 21HMOV HANDLE_OF_ENCRYPT, AX

M1: MOV AH, 3FHMOV CX, 02HMOV DX, OFFSET PUFFERMOV BX, HANDLE_OF_ENCRYPTINT 21H

CMP AX, 0HJE ENDE

SUB [PUFFER], 2H

MOV AH, 40HMOV CX, 1HMOV BX, HANDLE_OF_DECRYPTMOV DX, OFFSET PUFFERINT 21H

JMP M1

ENDE: MOV AH, 3EH

Page 61: Assembler-Einführung

Assembler – DOS (Beta 1) Copyright 2000 Thomas Peschko

[email protected]

9

MOV BX, HANDLE_OF_DECRYPTINT 21H

MOV AH, 3EHMOV BX, HANDLE_OF_ENCRYPTINT 21H

MOV AH, 4CHINT 21H

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 MELDUNG DB "Dieses Programm hat sich selbst gelöscht","$" DATEINAME DB "SUID.EXE",0 DATEN ENDS STAPEL SEGMENT BYTE STACK DW 128 DUP (?) STAPEL ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATEN,ES:NOTHING,SS:STAPEL START: MOV AX, DATEN MOV DS, AX MOV DX, OFFSET MELDUNG MOV AH, 9H INT 21H MOV AH, 41H MOV DX, OFFSET DATEINAME INT 21H MOV AH, 4CH INT 21H 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.

Page 62: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 1 Assembler II – DOS Teil 2 ASSEMBLER – Weitere Eigenschaften von Dateien

Page 63: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 21. Operatoren, Befehle und Anweisungen 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: Es fehlt eine umfassende Referenz der ��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: DUP, FAR, NEAR, XOR…. Ein Prozessorbefehl ist ein Mnemonic. Ein Ausdruck, der den Prozessor aktiv werden lässt: MOV, JE, INT.... Eine Assembler – Anweisung gibt dem Assembler Anweisungen in welcher Weise die Assemblierung des Quellcodes zu erfolgen hat. INCLUDE, GOTO, .MODEL, .... 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 ).

Page 64: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 32. Einige Dateien wird es hart treffen ! 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. Zuerst das Programm, dann die Erklärung: .MODEL SMALL .DATA MELDUNG DB "Programm S_and_D.EXE sucht nach Textdateien",10,13,"$" NICHTS_DA DB "Keine Textdatei gefunden!",10,13,"$" NAMENSTEIL_DES_OPFERS DB "*.TXT",0 VOLLZUG DB "Eine Textdatei wurde gelöscht!",10,13,"$" .STACK .CODE .STARTUP MOV DX, OFFSET MELDUNG ;Dumm-User informieren. MOV AH, 09H INT 21H MOV AH, 4EH ;Erstes Opfer finden... MOV DX, OFFSET NAMENSTEIL_DES_OPFERS ;...mit folgendem Namensteil.. MOV CX, 07H ;...auch wenn es sich versteckt. INT 21H ;Suchen... JNC DTA_HOLEN ;Springe nach DTA_HOLEN falls ein Opfer ;gefunden wurde... MOV DX, OFFSET NICHTS_DA ;Melden, dass kein Opfer vorhanden ist... MOV AH, 09H INT 21H JMP ENDE ;..und zum Programmende springen. DTA_HOLEN: MOV AH, 2FH ;Die Startadresse der DTA des INT 21H ;ersten gefundenen Opfers besorgen...->ES:DX PUSH DS ;Datensegmentadresse auf STACK retten.. MOV AX, ES ;Segmentadresse umladen von ES nach... MOV DS, AX ;...DS MOV DX, BX ;Offsetadresse umladen von BX nach DX.. ADD DX, 1EH ;DX soll die Anfangsadresse vom Namens des Opers haben. MOV AH, 41H ;Opfer soll geloescht werden... INT 21H ;..ausfuehren.. POP DS ;Datensegmentadresse zurueck holen... MOV DX, OFFSET VOLLZUG MOV AH, 09H INT 21H ENDE: .EXIT END

Page 65: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 4Das obere Programm hat die einfache Funktion "search and destroy" realisiert. Hier die Erklärung: Die Frage ist nun: Was bewirkt "Find First" und welche Registerwerte werden erwartet ? 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: Bitfields for file attributes: Bit(s) Description (Table 0765) 7 shareable (Novell NetWare) 6 unused 5 archive 4 directory 3 volume label execute-only (Novell NetWare) 2 system 1 hidden 0 read-only 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. Damit ergibt sich:

Page 66: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 5 MOV AH, 4EH ;Erstes Opfer finden... MOV DX, OFFSET NAMENSTEIL_DES_OPFERS ;...mit folgendem Namensteil.. MOV CX, 07H ;...auch wenn es sich versteckt. INT 21H 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 ? INT 21 - DOS 2+ - GET DISK TRANSFER AREA ADDRESS AH = 2Fh Return: ES:BX -> current DTA Note: under the FlashTek X-32 DOS extender, the pointer is in ES:EBX SeeAlso: AH=1Ah 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: 00h BYTE drive letter (bits 0-6), remote if bit 7 set oder so was…das interessiert uns nicht! Was wir wollen ist aber: 1Eh 13 BYTEs ASCIZ filename+extension 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. MOV AX, ES ;Segmentadresse umladen von ES nach... MOV DS, AX ;...DS MOV DX, BX ;Offsetadresse umladen von BX nach DX.. Und dann der Korrekturzug: ADD DX, 1EH 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. 3. Das Manipulieren des Änderungsdatums einer Datei

Page 67: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 6 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 ! Hier das Programm SETDATE.ASM : -------------------------------------------------------- INCLUDE MACRO.BIB .MODEL SMALL .DATA BEGRUESSUNG DB "Programm ändert das Datum einer Datei!",10,13,"$" DATUM DB "Neues Datum eingeben: ",10,13,"$" DATEI DB "TEST.TXT",0 ZAHL DB 0 ZEICHEN DB 0 TAG DW 0 MONAT DW 0 JAHRE DW 0 REGISTER_DX DW 0 .STACK 128 .CODE .STARTUP PRINT BEGRUESSUNG PRINT DATUM INPUT_INT ZAHL MOV AL, [ZAHL] MOV BL, 0AH MUL BL PUSH AX INPUT_INT ZAHL POP BX ADD BL, [ZAHL] MOV [TAG], BX INPUT_CHAR ZEICHEN INPUT_INT ZAHL MOV AL, [ZAHL] MOV BL, 0AH

Page 68: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 7 MUL BL PUSH AX INPUT_INT ZAHL POP BX ADD BL, [ZAHL] MOV [MONAT], BX INPUT_CHAR ZEICHEN INPUT_INT ZAHL MOV AL, [ZAHL] MOV BX, 3E8H MUL BX PUSH AX INPUT_INT ZAHL MOV AL, [ZAHL] MOV BX, 64H MUL BX PUSH AX INPUT_INT ZAHL MOV AL, [ZAHL] MOV BL, 0AH MUL BL PUSH AX INPUT_INT ZAHL MOV AL, [ZAHL] POP BX ; Zehner ADD AX, BX POP BX ; Hunderter ADD AX, BX POP BX ; Tausender ADD AX, BX MOV BX, 07BCH SUB AX, BX MOV [JAHRE], AX

Page 69: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 8 SHL [MONAT], 05H SHL [JAHRE], 09H MOV DX, 0H OR DX, [TAG] OR DX, [MONAT] OR DX, [JAHRE] MOV [REGISTER_DX], DX MOV AH, 3DH MOV DX, OFFSET DATEI MOV AL, 0H INT 21H MOV BX, AX MOV AX, 5700H INT 21H MOV AX, 5701H MOV DX, [REGISTER_DX] INT 21H MOV AH, 3EH INT 21H .EXIT END Hier noch die Datei MACRO.BIB :

Page 70: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 9 -------------------------------------------------------------- PRINT MACRO STRING MOV DX, OFFSET STRING MOV AH, 09H INT 21H ENDM INPUT_INT MACRO ZAHL MOV AH, 01H INT 21H MOV AH, 0H SUB AL, 30H MOV ZAHL, AL ENDM INPUT_CHAR MACRO ZEICHEN MOV AH, 01H INT 21H MOV ZEICHEN, AL ENDM --------------------------------------------------------- Die Dateien SETDATE.ASM und MACRO.BIB müssen sich wieder im gleichen Verzeichnis befinden. Nach dem Assemblieren mit... C:\asm\Projekt\date>tasm setdate.asm Turbo Assembler Version 4.0 Copyright (c) 1988, 1993 Borland International Assembling file: setdate.asm Error messages: None Warning messages: None Passes: 1 Remaining memory: 409k 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. Wenden wir uns der Erklärung zu.

Page 71: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 10 Zum Eintragen eines Änderungsdatums einer Datei gibt es folgende DOS Funktion: Änderungsdatum und Uhrzeit einer Datei holen: Aufruf: AH = 57H AL = 00H � Datum und Zeit holen. BX = Dateinummer (� Handle) Ergebnis: CF = 0 Alles OK CX = Uhrzeit DX = Datum CF = 1 Fehler Änderungsdatum und Uhrzeit einer Datei setzen: 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 : Lesen eines an der Tastatur eingegebenen Zeichens und Anzeige: Aufruf: AH = 01H Ergebnis: AL = ASCII Wert der betätigten Taste !!!!! 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:

Page 72: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 11 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 Im grünen Bereich wird die Zahl für den jeweiligen Tag des Datum gespeichert. � 0 bis 31. Im roten Bereich wird die Zahl für den jeweiligen Monat des Datum gespeichert. � 1 bis 12 Im blauen Bereich wird die Anzahl der Jahre seit 1980 gespeichert ! 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.

Page 73: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 12 Nun befindet sich im Register AL die Zahl 20. Diese Zahl wird mit PUSH AX auf dem Stack abgelegt. Jetzt lesen wir mit erneutem Aufruf des Makros INPUT_INT ZAHL Die Zahl „1“ nach AL ein. Diese Ziffer muss nicht mehr mit einem Faktor multipliziert werden , da sie die Einerstelle der Zahl 21 darstellt. Mit POP BX Wird nun die Zahl 20 vom Stack zurück in das Register BL geholt. Anschließend wird mit ADD BL, [ZAHL] zu dem Inhalt des Registers BL (� 20) der Inhalt der Variable ZAHL (� 1) addiert. Das Ergebnis landet in BL. Nun befindet sich in BL das Bitmuster BL = 0001 0101, was der Zahl 21 entspricht. Zum Abschluss wird diese Zahl noch mit MOV [TAG], BX in der Variablen TAG abgelegt. Nach der Eingabe des Tages folgt die Eingabe eines Punktes. Dieser wird mittels des Makros INPUT_CHAR ZEICHEN zwar eingelesen aber nicht ausgewertet, da er uns nicht interessiert. Zum Einlesen des Monats wiederholen sich nun diese Arbeitsschritte. 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. 1999 � 1 * 1000 = 1000 + 9 * 100 = 900 + 9 * 10 = 90 + 9 = 9 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.

Page 74: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 13Die 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. Die geschieht mit dem Befehl SHL [MONAT], 05H Auch die Anzahl der Jahre muss verschoben werden. Hier sind 9 Stellen erforderlich. SHL [JAHRE], 09H Danach sehen die Speicherplätze folgendermaßen aus: 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. Daraus folgt für das 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 Dieser Wert wird im Speicherplatz REGISTER_DX geparkt.

Page 75: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 14 Man kann jetzt aber noch nicht die Funktion AX = 5701H aufrufen. Denn... 1. Wir brauchen noch ein Datei- Handle. 2. Wir haben keinen Wert für die Uhrzeit im CX Register. 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. Zum Abschluss muss die geöffnete Datei wieder geschlossen werden. 4. Manipulationen von Dateieigenschaften mittels DTA 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 START DB "Programm vertauscht Identität zweier Dateien !",10,13,"$" DATEI_1 DB "ERSTE*.TXT",0 DATEI_2 DB "ZWEITE*.TXT",0 MERKER_1 DB "DUMMI_1.DAT",0 MERKER_2 DB "DUMMI_2.DAT",0 FEHLER_1 DB "Erste Datei nicht gefunden !",10,13,"$" FEHLER_2 DB "Zweite Datei nicht gefunden !",10,13,"$" VOLLZUG DB "Dateinamen wurden in den Puffer geschrieben !",10,13,"$" NAME_1 DB 13 DUP (0) NAME_2 DB 13 DUP (0) .STACK 128 .CODE .STARTUP MOV AH, 09H MOV DX, OFFSET START INT 21H MOV AH, 4EH MOV CX, 07H MOV DX, OFFSET DATEI_1 INT 21H JNC WEITER_1

Page 76: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 15 MOV AH, 09H MOV DX, OFFSET FEHLER_1 INT 21H JMP ENDE WEITER_1: MOV AH, 2FH INT 21H ADD BX, 1EH PUSH DS PUSH DS MOV AX, ES MOV DS, AX MOV SI, BX POP ES MOV DI, OFFSET NAME_1 MOV CX, 0DH REP MOVSB MOV AX, DS MOV ES, AX POP DS MOV AH, 4EH MOV CX, 07H MOV DX, OFFSET DATEI_2 INT 21H JNC WEITER_2 MOV AH, 09H MOV DX, OFFSET FEHLER_2 INT 21H JMP ENDE WEITER_2: MOV AH, 2FH INT 21H ADD BX,1EH PUSH DS PUSH DS MOV AX, ES MOV DS, AX MOV SI, BX POP ES MOV DI, OFFSET NAME_2 MOV CX, 0DH REP MOVSB

Page 77: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 16 MOV AX, DS MOV ES, AX POP DS MOV AH, 09H MOV DX, OFFSET VOLLZUG INT 21H MOV AX, 4300H MOV DX, OFFSET NAME_1 INT 21H MOV BX, CX MOV AX, 4300H MOV DX, OFFSET NAME_2 INT 21H MOV AX, 4301H MOV DX, OFFSET NAME_1 INT 21H MOV CX, BX MOV AX, 4301H MOV DX, OFFSET NAME_2 INT 21H MOV DX, OFFSET NAME_1 PUSH ES MOV AX, DS MOV ES, AX MOV DI, OFFSET MERKER_1 MOV AH, 56H INT 21H POP ES MOV DX, OFFSET NAME_2 PUSH ES MOV AX, DS MOV ES, AX MOV DI, OFFSET MERKER_2 MOV AH, 56H INT 21H POP ES MOV DX, OFFSET MERKER_1 PUSH ES MOV AX, DS MOV ES, AX MOV DI, OFFSET NAME_2 MOV AH, 56H INT 21H POP ES MOV DX, OFFSET MERKER_2 PUSH ES MOV AX, DS MOV ES, AX MOV DI, OFFSET NAME_1

Page 78: Assembler-Einführung

Assembler – DOS Teil 2 (Beta 1) Copyright 2000 Thomas Peschko [email protected] 17 MOV AH, 56H INT 21H POP ES ENDE: .EXIT END


Recommended