Assembler Tutor

Index

1. Einleitung
2. Hexadezimal, Dezimal und Binär
3. Register
4. Flags
5. Interrupts
6. Hello World (das erste Programm)
7. Rechenoperationen
8. Sprünge und Vergleiche
9. Kleine Stack Spielereien
10. Calls / Prozeduren
11. Loops (Schleifen)
12. Repeat it...
13. Etwas Bewegung..
14. Verschlüsselung und Anderes..
15. Schieben und Rollen
16. Indirekte Adressierung
17. EXE-Dateien
18. Datei-Handling
19. Assembler Anweisungen
20. Suchen nach Textstrings
21. Graphiken
22. Farbiger Text
23. Outro

1. Einleitung
Da mich ne Menge Leute nach einem deutschsprachigen Tutorial für Assembler gefragt haben und ich nie einen nennen konnte, hab ich mich entschlossen nun  selber einen zu schreiben, der die meisten Aspekte behandelt. Ausser diesem Tutorial benötigt ihr noch: Ralf Browns Interrupt List : http://www.pobox.com/~ralf
Borlands Turbo Assembler 5.0 (keine freeware,für ne 'Testversion' sucht bei http://Astalavista.box.sk nach 'TASM')
Notepad oder anderen Text-Editor Einen Taschenrechner ;) Ich werde mich in diesem Tutorial nur auf TASM beziehen und der Code wird nur bedingt unter anderen Assembler Compilern laufen.

 -- 1.1 Was ist Assembler ?

Assembler ist Maschienensprache. Assembler ist die niedrigste Programmiersprache überhaupt, da man hier direkt den Prozessor und das OS anspricht. Man kann jeden Assemblerbefehl eins zu eins in Hexcode übersetzen, was für uns der Compiler übernimmt. Ein Beispiel hierfür ist der Befehl NOP = 90h (h für Hexadezimal). Da man hier genau bestimmen kann was für ein Code in der Datei nach dem Compilieren steht, kann man gerade in Assembler die Optimierung des Codes bis ins äuserste treiben. Auch hat man unter Assembler die größte Macht über den Computer.  

*-- 1.2 Warum in Assembler programmieren ?

Es gibt mehrere Gründe dafür warum man in Assembler programmiert, bzw warum man es lernen  möchte. Ein Grund ist zum Beispiel, das man den Code extrem klein halten kann und durch gezielte optimierung einen enormen Geschwindigkeitsvorteil zu Hochsprachen herausarbeiten  kann. Des weiteren ist Assembler natürlich interessant zum Cracken und Viren schreiben.

 *-- 1.3 Anmerkungen zu TASM

Es gibt in Borlands Turbo Assembler keinen Editor, wie man ihn zum Beispiel von Qbasic oder Turbo Pascal kennt. Man schreibt seinen Code per Text-Editor in eine .asm Datei und   übergibt diese auf der DOS-Kommando Ebene dem Compiler und dem Linker. Die Parametereinstellungen kann man natürlich durch eine Batch Datei erleichtern.

---8<------ ( Compile.bat )---------

@Echo Off

if not exist %1.asm goto quit

tasm %1 /n/p/t/w/z/m4

if errorlevel 1 goto quit

tlink %1 /d/x/t

del %1.obj

:quit

---8<-------------------------------

Diese Batch Datei wird gestartet mit Compile '<Dateiname des sources ohne .asm>' Achtet darauf, das sie in C:\TASM\BIN\ zusammen mit dem Source code liegt. Und startet dann den Compiler (TASM) und den Linker (TLINK) und fertig euch eine COM Datei an. Wie man EXE Dateien mit TASM erstellt erkläre ich später, COM Dateien reichen erstmal und sind viel kleiner als EXE Dateien. In TASM wird die Groß und Kleinschreibung ignoriert, schreibt wie ihr wollt. (jedenfalls solange ihr DOS-Programme schreibt)

*-- 1.4 DOS Assembler

Im ersten Teil dieses Tutorials werde ich euch zeigen, wie man DOS-Programme mit TASM schreibt, da diese immernoch zu verwenden sind und einfach besser in die Assembler Welt einführen. Später werde ich noch auf die Besonderheiten der Windows programmierung hinweisen.

2. Hexadezimal, Dezimal und Binär

Ok das solltet ihr zwar alles schonmal in der Schule gemacht haben, aber ich weiß selber wie gut man in der Schule aufpasst, wenn man denkt das braucht man nicht ;)  Gut, es gibt verschiedenen Zahlensysteme. Das mit dem wir täglich rechnen basiert auf der Zahl 10. Es gibt 10 Grundzahlen: 0,1,2,3,4,5,6,7,8,9 .. wenn man nun in diesem  System zählt, fängt man nach der 9 eine neue Zehnerpotenz an. Dezimalzahlen werden in Assemble durch ein kleines d gekennzeichnet (31d) Das sollte soweit klar sein, schauen wir uns mal das Binärsystem an. Hier haben wir nur zwei Grundzahlen, 0 und 1. Wenn wir beim hochzählen bei 1 angelangt sind machen wir eine 0 draus und addieren vorne eine 1. Ok zählen wir binär bis 9:
0 1 10 11 100 101 110 111 1000 1001
0 1 2 3 4 5 6 7 8 9

Versucht mal mit euerem Taschenrechner weiterzuzählen ich denke den Dreh bekommt ihr schnell raus. Was machen wir nun, wenn wir eine Binärzahl ins Dezimale umrechnen wollen ? Wir zücken unseren Taschenrechner ;) oder machen es im Kopf nach folgender Methode:
Schauen wir auf die Zahl 10011. Wir fangen bei der letzten stelle an 1, also 1 * 2^0 (das ^ bedeutet hoch, also die erste Potenz von 2), dann addieren wir die 2. Stelle (1 * 2^0)+(1 * 2^1).. die nächsten beiden Stellen sind 0.. ok addieren wir sie zum  Spaß auch mal, damit ihr das Schema erkennt. (1 * 2^0)+(1 * 2^1)+(0 * 2^2)+(0 * 2^3) Also ändert sich die Zahl hier nicht. Nun noch die erste Stelle addieren und wir sind fertig
(1 * 2^0)+(1 * 2^1)+(0 * 2^2)+(0 * 2^3)+(1 * 2^4) = 1 + 2 + 0 + 0 + 16 = 19
In TASM kennzeichnet man Binärzahlen durch ein b am ende der Zahl (10011b).
Ok nun zum Hexadezimalsystem. Dieses beruht auf 16 Grundzahlen: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E. Das A entspricht der 10 im Dezimalsystem, das B der 11, das C der 12 und so weiter. Was passiert, wenn wir zu F noch eine 1 addieren wollen ?
Dann landen wir bei 10. Diese 10 entspricht dann der Dezimalen 17. Auch für Hexzahlen gibt es unter Assembler eine Besondere Kennzeichnung, das h. Allerdings erwartet TASM,   das eine Zahl mit einer Zahl anfängt, also muss man Eh als 0Eh schreiben, da TASM das E nicht als Zahl ansieht. Soviel zur Mathematik ;P

3. Register

Register sind Speicherplätze, in denen wir Daten speichern können. Sie können jeweils 16 Bit speichern. Man kann die Basisregister in 2 Teile teilen, die jeweils 8 Bit speichern. 16 Bits sind zum Beispiel 2 Buchstaben im Ascii-Format 'ab' oder eine Zahl zwischen 0 und FFFF im Hexadezimal Format (also 0 bis 65535 in Dezimal ;) ) Man benutzt die Register um mit Zahlen zu rechnen, um kurz etwas zu speichern oder um Variablen an Interrupts zu übergeben. Was Interrupts sind erklär ich später. Schauen wir uns erstmal die vier Grundregister an:
ax kann geteilt werden in : al und ah -Accumulator(zum Addieren)
bx bl und bh -Base (Basisregister zum speichern)
cx cl und ch -Count(zum Zählen)
dx dl und dh -Data(zum Daten anzeigen)

Einfach zu merken oder ? Das kleine l steht für low, das h für high. Dann gibt es noch:

SP - Stack Pointer (wird später erklärt)
BP - Base Pointer (meistens unbenutzt)
SI - Source Index (zeigt auf Quelle, wenn man Daten liest oder verschiebt)
DI - Direction Index (zeigt auf das Ziel der Daten)
IP - Instruction Pointer (zeigt auf den Befehl der gerade ausgeführt wird)

CS - Code Segment (zeigt den Anfang des Codes)
DS - Data Segment (zeigt den Anfang der Daten)
SS - Stack Segment (kommt später)
ES - Extra Segment (könnt ihr frei verwenden)
FS - siehe ES ..
GS - siehe ES ... ;)

Das war es eigentlich schon mit den Registern, wenn euch etwas spanisch vorkommt, lest einfach erstmal weiter, mit dem ersten Programm sollte es euch klar werden.

4. Flags

Wenn ein Befehl misling, gelingt oder sonstwas tut, werden Flags gesetzt. Ein paar dieser Flags zum nachlesen und vergessen (ich werde sie später nochmal erwähnen)
CF Carry Flag - Es gab einen Fehler
ZF Zero Flag - Die letzte Operation ergab 0
SF Sign Flag - Positiver Wert / Negativer Wert
TF Trap Flag - ermöglicht den Einzelschritt-Modus

Die paar reichen für dieses Tutorial aus, sie können gesetzt sein 1 oder auch nicht 0. Was man damit anfangen kann kommt später ich wollte sie mal erwähnen, das ihr wenigstens mal davon gehört habt. :)

5. Interrupts

Ok eine Sache brauchen wir noch, bevor wir das erste Programm starten können.. die Interrupts. Mit einem Interrupt unterbrechen wir unser Programm und starten ein Unterprogramm von DOS. Je nachdem welchen Interrupt wir mit welchen Funktionen aufrufen, ein anderes. Die Liste über 'fast' alle Interrupts (INT'S) ist von Ralf Brown (siehe oben) wir werden uns hier fast nur mit dem DOS-Interrupt INT 21h beschäftigen, die Funktion erklär ich dann immer. Aber zum nachlesen ist die Liste nicht schlecht. Neben den Software INT's gibt es auch Hardware INT's die dann in Kraft treten, wenn ein Gerät (Drucker) dem Programm was wichtiges mitteilen muss.

 

6. Hello World (das erste Programm)

So, endlich in diesem Abschnitt kommt das erste Programm ! Was wollen wir machen ? Ein simples Hello world auf den Bildschirm schreiben. Was brauchen wir dafür ? Einmal eine Interrupt-Funktion, die was ausdruckt und einmal eine, mit der wir das Programm beenden. Die Funktion zum ausgeben von Text ist INT 21h mit 09h in ah (dem höheren Registerteil von ax) und nem Zeiger zum Text in DX. Wie setzen wir den Wert 09h nun in ah ? Mit dem Assemblerbefehl 'mov ah,09h'. Einfach simpel oder ? Ok.. das Programm, dann mehr Erklärung:

--8<---(HelloW.asm)------
.model tiny ;nur ein kleines Programm
.code ;hier steht der code
org 100h ;wir basteln ne COM Datei
START: ;Label zum verzieren
mov ah, 09h ;09h ist die INT21h Funktion zum ausgeben von Text
mov dx, offset HelloWorld ;Wo steht der Text...
INT 21h ;schreib ihn !
ENDE: ;schönes Label oder ?
mov ah,4ch ;4Ch zum Beenden
INT 21h ;Beende !
HelloWorld db 'Hello World !!',10d,13d,'$' ;Unser String..
END START ;hier ist das ganze zuende..
----------ende-----------

Was ist das alles nun ? Die Sachen nach dem ; sind Kommentare ;) Ok.. das erste Statement '.model tiny' zeigt an, das wir nur ein kleines Programm schreiben wollen. Danach sagen wir Bescheid, das hier unser Code anfängt. Da wir eine COM Datei erstellen wollen, müssen wir das dem Compiler angeben, da COM Dateien erst bei Offset 100h anfangen, setzen wir ein org 100h vor den code. Ok, 'Start:' ist ein Label zu dem wir später mit Sprungbefehlen springen können, hier dient es nur zur Verschönerung, im Compilierten Programm taucht es eh nicht auf. Nun übergeben wir ah den ersten Parameter. (Wir hätten dies auch mit mov ax, 4c00h tun können, da sich ax aus ah und al zusammensetzt) Dann kommt in dx der Offset (die Adresse) unseres 'Hello World'. Nachdem wir den Parameter also in ah haben und die Adresse des Texts in dx können wir unseren Interupt aufrufen. Nun wieder ein Label zum verschönern ;) Danach geben wir den Parameter 4Ch in ah und beenden das Programm durch den  Interrupt aufruf. Am Ende folgt noch unser Hello World-String, den wir mit 10d,13d verzieren, damit die nächste Zeile auch in der nächsten Zeile anfängt und nicht am Hello World. Dann schließen wir den String mit einem '$' ab, um zu zeigen das er hier zuende ist. Den String haben wir mit einem db (define Byte) definiert, die Hochkommas geben an das der Inhalt ASCII Zeichen sind. Wir hätten ihn aber auch folgendermaßen definieren können :
HelloWorld db 48h, 65h, 6Ch, 6Ch, 6Fh, 20h, ... etc
Dann hätten wir die ASCII Codes für den String eingegeben. Also wie oben schon erklärt, tippt oder copy+pastet den Kram in einen Texteditor und speicher das ganze als HelloW.asm dann mit der Batchdatei von oben compilieren (Compile.bat hellow). Ihr solltet als Output von TASM folgendes bekommen:
'Turbo Link Version 7.1.30.1. Copyright (c) 1987, 1996 Borland International'
Dann seit ihr wieder auf der Commando-Ebene von DOS.. mit einem 'dir' seht ihr nun:

HELLOW COM 28 15.09.99 15:23 HELLOW.COM
HELLOW ASM 763 15.09.99 15:23 hellow.asm

Nun könnt ihr das Hallo Welt Programm auch starten... ;) denke mal das könnt ihr auch so.. Und wir werden mit einem Hello World !! begrüßt.. War ganz einfach oder ?

7. Rechenoperationen

Natürlich gibt es unter Assembler auch Kommandos für Rechenarten..  Damit ihr sie erstmal kennenlernt, werd ich sie hier auflisten, danach ein kleines Programm damit. Alle Beispiele sind mit ax, aber ihr könnt natürlich auch andere Register verwenden.

INC AX - Erhöhe AX um 1
DEC AX - Vermindere AX um 1
ADD AX, 1h - Addiere 1h zu AX
SUB AX, 1h - Subtrahiere 1h von AX
MUL BX - Multipliziere AL mit BX (hier wird immer AL verwendet !) Ergebnis steht in AX
DIV BX - Dividiere AL durch BX (auch hier wird immer AL verwendet !) Ergebnis steht in AX

 

 

Was kann man nun damit anfangen ? ;) .. Lasst uns unser Hello World etwas ändern.. ;)

--8<---(HelloW2.asm)-----
.model tiny ;nur ein kleines Programm
.code ;hier steht der code
org 100h ;wir basteln ne COM Datei
START: ;Label zum verzieren
mov AH, 08h ;09h ist die INT21h Funktion zum ausgeben von Text
inc AH ;so das wir die 8h um 1s erhöhen müssen um 9h zu bekommen ;P
mov DX, offset HelloWorld ;Wo steht der Text...
INT 21h ;schreib ihn !
WarteaufTaste: ;Ein Label...
mov AH, 00h ;Wenn wir ah = 1 setzen warten wir auf eine Tastatureingabe
add AH, 01h ;von 1 Zeichen. Das Zeichen steht nach dem INT 21h in al
INT 21h ;aber das ist für uns hier unwichtig... ;)
ENDE: ;schönes Label oder ?
mov AL, 26h ;2h * 26h = 4Ch zum Beenden
mov BX, 02h ;die 2 speichern wir in BX..
mul BX ;mit BX (2) multiplizieren..
mov AH, AL ;nun schiebe den Inhalt von AL nach AH, wo unser
;Parameter stehen muss
INT 21h ;Beende !
'HelloWorld db 'Hello World !!',10d,13d,'$ ;Unser String..
END START ;hier ist das ganze zuende..
----------ende-----------

Das ganze ist wie ihr seht nur Spielerei und hat keinen Nutzen (ausser euch die Sache zu erklären) Wenn ich bei euch mal solchen Code sehen sollte werde ich euch sofort verbrennen ;P .. Ok was Passiert hier ? Wir schreiben unsere Nachricht, mit der bereits erklärten Funktion 09h des INT 21h. Die 9h erreichen wir über den Umweg, das wir zuerst   8h in AH schreiben und diese 8h dann um eins erhöhen. Danach verwenden wir die für euch neue Funktion 01h des INT 21h. Mit dieser halten wir das Programm an, bis irgendeine Taste gedrückt wird. Der ASCII Code dieser Taste steht dann in AL, aber das ist in diesem Beispiel egal. Es ging nur darum das Programm anzuhalten.Am Schluß setzten wir wieder die 4Ch zum beenden in ah, indem wir AL mit 26h füllen, diese dann mit den 2h aus BX multiplizieren. Und das Ergebnis aus AL nach AH verschieben,  wo es hingehört.

8. Sprünge und Vergleiche

Wir wollen in unserem Assembler Programm auch auf die Eingaben des Benutzers reagieren können oder ? Wir verwenden also den Befehl 'cmp' um einen Register mit einem Wert oder einem anderen Register zu vergleichen. Führen wie die Funktion 01h des INT 21h aus, haben wir als Ergebnis den ASCII Wert der Taste in al. Nun können wir einen  Vergleich ausführen: cmp al, 'J' damit vergleichen wir al mit 'J'. Wir können AL auch mit einem Register vergleichen oder einer anderen Konstante (cmp al,bl / cmp al, 14h) Nun haben wir zwar den Wert mit einem anderen verglichen, aber was dann ? Je nach Ergebnis springen wir zu einem Label ( label: ) oder laufen im Code einfach weiter. Dafür gibt es jetzt einige Sprung-Befehle (da sich einige gleichen steht in Klammern hintendran, mit welchem sie sich gleichen) :

jmp Label - Springe immer !

(die Bezeichnungen größer und kleiner ignorieren das Vorzeichen)
je Label - Springe wenn gleich (JZ)
jne Label - Springe wenn ungleich (JNZ)
ja Label - Springe wenn größer (JNBE)
jna Label - Springe wenn nicht größer (JBE)
jae Label - Springe wenn größer oder gleich (JNB)
jnae Label - Springe wenn nicht größer oder gleich (JB)
jb Label - Springe wenn kleiner (JNAE)
jnb Label - Springe wenn nicht kleiner (JAE)
jbe Label - Springe wenn kleiner oder gleich (JNA)
jnbe Label - Springe wenn nicht kleiner oder gleich (JA)
jz Label - Springe wenn 0 (JE)


(nun wird auf die Vorzeichen geachtet)
jg Label - Springe wenn größer (JNLE)
jng Label - Springe wenn nicht größer (JLE)
jge Label - Springe wenn größer oder gleich (JNL)
jnge Label - Springe wenn nicht größer oder gleich (JL)
jl Label - Springe wenn kleiner (JNGE)
jnl Label - Springe wenn nicht kleiner (JGE)
jle Label - Springe wenn kleiner oder gleich (JNG)
jnle Label - Springe wenn nicht kleiner oder gleich (jg)
jcxz Label - Springe wenn ex = 0

js Label - Springe, wenn die letzte Operation ein negatives Ergebnis hatte
jp Label - Springe wenn das Parity Flag gesetzt ist
jnp Label - Springe wenn das Parity Flag nicht gesetzt ist
jo Label - Springe wenn Overflow Flag gesetzt ist
jno Label - Springe wenn Overflow Flag nicht gesetzt ist
jc Label - Springe wenn Carriage Flag gesetzt ist
jnc Label - Springe wenn Carriage Flag Flag nicht gesetzt ist

Nein ihr müsst jetzt nicht alle Auswendig lernen.. Ihr dürft diese Tutorial auch etwas länger behalten, dann könnt ihr auch mal nachschlagen ;P Neben dem CMP Befehl gibt es noch einen weiteren Befehl, der 2 Register/Konstanten mit einander vergleicht. Dies ist der test-befehl, mit dem man allerdings nur auf Gleichheit bzw Ungleichheit überprüfen kann.  Nun wieder ein kleines, sinnloses Programm zum Verstehen:

--8<------(kekse.asm)-----------------
.model tiny
.code
org 100h
START: ;LABEL !!
mov ah, 09h ;Schreiben der Frage auf den Bildschirm
mov dx, offset frage ;hier steht die Frage
INT 21h ;schreib !
mov ah, 01h ;Einlesen einer Taste
INT 21h ;lies !
cmp al,"1" ;wurde eine 1 gedrückt ?
je eins ;wenn ja, dann springe zu Label 'eins'
cmp al,"2" ;wurde eine 2 gedrückt...
je zwei
cmp al,"3"
je drei
cmp al,"4"
je vier
jmp fehler ;wurde keine der Zahlen gedrückt springe zum Fehler
eins: ;schreibe die Nachricht für Tastendruck '1'
mov ah, 09h
mov dx, offset meins
INT 21h
jmp ende ;springe zum Ende
zwei: ;schreibe die Nachricht für Tastendruck '2'
mov ah, 09h
mov dx, offset mzwei
INT 21h
jmp ende ;springe zum Ende
drei: ;schreibe die Nachricht für Tastendruck '3'
mov ah, 09h
mov dx, offset mdrei
INT 21h
jmp ende ;springe zum Ende
vier: ;schreibe die Nachricht für Tastendruck '4'
mov ah, 09h
mov dx, offset mvier
INT 21h
jmp ende ;springe zum Ende
fehler: ;Schreibe, das der Benutzer etwas Falsches eingegeben hat..
mov ah, 09h mov dx, offset mfehler
INT 21h
ende: ;Label für Ende
mov ax,4c00h ;beende das Programm
INT 21h
;Dies sind nun wieder unsere Daten...
frage db 'Wieviele Kekse wollen sie ? [1-4]',10,13,'$'
meins db ') Was nur einen ? ',10,13,'$'
mzwei db ') Nimm 2 ;-)',10,13,'$'
mdrei db ') 3 Sind ok',10,13,'$'
mvier db ') VIELFRAS',10,13,'$'
mfehler db ') Leider Falsche Eingabe... ;( ',10,13,'$'
END START
---------------(ende)----------

 

Ok in diesem Beispiel verwenden wir nur die Interrupt Funktionen, die wir bereits kennen, 09h um etwas auf den Bildschirm auszugeben, 01h um eine Taste einzulesen und 4Ch um das Programm zu beenden. Das Programm arbeitet so, das es erst die Taste einliest (in al) und dann al nacheinander mit den verschiedenen erwünschten Tasten (1,2,3,4) vergleicht.  Wenn al mit einer der Tasten übereinstimmt, springt man zu dem jeweiligen Unterprogramm. Stimmt al mit keiner der Tasten überein, wird eine Fehlermeldung gezeigt, und das Programm beendet. So nun könnt ihr schon ein kleines Text-Adventure in Assembler schreiben ;)

9. Kleine Stack Spielereien

Der Stack ist ein Speicher, in dem ihr die Daten aus euerem Programm speichern könnt. Mit dem Stack verhält es sich wie mit einem Stapel, man kann nur oben etwas drauflegen und von oben etwas herunternehmen. Einfach was aus der Mitte klauen geht nicht (ok, ok es geht, aber nur über Umwege.. ;] ) Gut wie legen wir eine Zahl auf den Stapel ? Mit 'push' ! 'Push ax' legt die Zahl in AX auf den Stapel. Mit 'pop ax' nehmen wir die Zahl vom Stapel und legen sie wieder in ax. Wir können natürlich auch ein 'Push ax' 'pop bx' achen und so die Zahl von ax nach bx übertragen, auch wenn ein mov bx,ax einfacher gewesen wäre. Für was braucht man den Stack ? Naja, man kann in ihm auch länger Daten abspeichern, die in einem der Register verlorengehen würden. Wo der Stack anfängt zeigt euch SS das Stack Segment. Also auch hier ein simples Programm.

 

--8<---(CruelW.asm)------
.model tiny ;nur ein kleines Programm
.code ;hier steht der code
org 100h ;wir basteln ne COM Datei
START: ;Label zum verzieren
mov dx, offset HelloWorld ;Wo steht der Text...
push dx ;nun speichern wir dx im Stack
mov dx, offset ByeCruelWorld ;legen einen anderen offset in dx
pop dx ;laden unseres offsets aus dem Stack..
push 0900h ;0900h ist die INT21h Funktion zum ausgeben von Text, deshalb speichern wir den Wert im Stack..
pop ax ;und poppen ihn nach ah...
INT 21h ;schreib ihn !
ENDE: ;schönes Label oder ?
mov ah,4ch ;4Ch zum Beenden
INT 21h ;Beende !
HelloWorld db 'Hello World !!',10d,13d,'$' ;Unser String..
ByeCruelWorld db 'Commit suicide now !!',10d,13d,'$' ;Ein unerwünschter String.. ;)
END START ;hier ist das ganze zuende..
----------ende-----------

 

Das Programm gibt uns auch wieder ein Hello World !!! aus, diesmal aber haben wir um dies zu erreichen einige Umwege über den Stack gemacht, die eigentlich recht verständlich sind. Was genau passiert mit dem Stack Pointer, wenn wir etwas in den Stack legen, vom Namen her muss er doch was damit zu tun haben oder ? ;)  Also der Stack Pointer zeigt auf die Stelle im Speicher, an der die nächste Zahl in den Stack gelegt wird. Also an die Spitze des Stapels. Wenn man nun eine weitere 16Bit-Zahl pusht, werden 2 Bytes vom SP (Stack Pointer) abgezogen. Wenn man etwas poppt, werden 2 Bytes dazugezählt. Versuchen wir doch einmal folgende Spielerei mit diesem Pointer: wir Pushen eine Zahl, dann Poppen wir sie wieder. Dann vermindern wir den SP um 2, so das er wieder auf unsere Zahl zeigen müsste. Dann poppen wir auch diese Zahl und vergleichen sie mit der ersten. Wenn beide Zahlen stimmen ist unsere Experiment geglückt.

--8<---(SP_GAME.asm)------
.model tiny ;nur ein kleines Programm
.code ;hier steht der code
org 100h ;wir basteln ne COM Datei
START: ;Label zum verzieren.. ;) ..wie oft hatten wir das schon *g*
push 'SB' ;dies ist unsere Testzahl.. 53h, 42h
pop ax ;diese steht nun in AX !
sub sp, 2h ;jetzt vermindern wir den Stack Pointer um 2
pop bx ;und versuchen die gleiche Zahl in bx zu erhalten
cmp ax,bx ;ein vergleich ob beide gleich sind..
je ErfolgMsg ;sind sie gleich gehen wir zum Label ErfolgMsg
mov dx, offset Error ;ansonsten laden wir die Error Nachricht
jmp Write ;und geben sie bei Write: aus..
ErfolgMsg: ;wenn beide Zahlen gleich waren,
mov dx, offset Erfolg ;laden wir die Erfolgsnachricht
Write: ;hier wird die Nachricht, je nach dx
mov ah, 9h ;ausgegeben
int 21h
ENDE:
mov ah,4ch ;4Ch zum Beenden
INT 21h ;Beende !
Error db 'Leider stimmen die Zahlen nicht ;( ',10d,13d,'$'
Erfolg db 'Glückwunsch es hat geklappt.. ;) ',10d,13d,'$'
END START ;hier ist das ganze zuende..
----------ende-----------

Was ein Zufall, unsere Überlegungen waren richtig.. ;) es klappt, wenn man das Programm compilert und ausführt... *g*

10. Calls / Prozeduren

Prozeduren sind Unterprogramme, die dazu dienen, häufig verwendete Programmteile öfters auszuführen um Platz zu sparen. Prozeduren helfen dabei, den Code besser zu strukturieren und ihn dadurch zu optimieren. Aufgerufen wird eine Prozedur mit dem Call Befehl. Auch dieser Befehl orientiert sich, wie die Jump Befehle an einem Label zu dem er springt. Um das Unterprogramm zu beenden benutzen wir den RET Befehl, mit dem man zu dem Call zurückkehrt. Was genau passiert, wenn wir eine Prozedur callen ? Die Rückkehradresse wird in den Stack gepusht und der Instruction Pointer (IP) wird auf die neue Adresse, das Label, eingestellt. Dort wird nun der Code ausgeführt und wenn der Ret-Befehl kommt wird zu der Adresse aus dem Stack gesprungen. Ok schauen wir uns mal ein Programm an, das mit Prozeduren arbeitet...

 

--8<---(Callit.asm)------
.model tiny
.code
org 100h
START:
lea dx, MSG1 ;das gleiche wie mov dx, offset MSG1
call WriteMSG
call KEY
lea dx, MSG2 ;mov dx, offset MSG2
call WriteMSG
call KEY
ENDE: ;schönes Label oder ?
mov ah,4ch ;4Ch zum Beenden
INT 21h ;Beende !
WriteMSG:
mov ah,09h
int 21h
ret
KEY:
mov ah,1h
int 21h
ret
MSG1 db 'CALL-Programm',10d,13d,'$' ;Ein String..
MSG2 db ' by SnakeByte',10d,13d,'$'
END START ;hier ist das ganze zuende..
----------ende-----------

Schätze mal das ist soweit verständlich ;) Nun wissen wir wie ein Call funktioniert, doch uns fällt auf, das man die ganze Aktion auch anders hätte starten können (nur zur Information es bringt nicht viel *g* ) Das ist nur um nochmal zu verdeutlichen, was genau bei einem CALL passiert.. ;)

--8<---(FakeCall.asm)------
.model tiny
.code
org 100h
START:
  lea dx, MSG1 ;das gleiche wie mov dx, offset MSG1
  push offset RETURN
  jmp WriteMSG
RETURN:
  call KEY
  lea dx, MSG2 ;mov dx, offset MSG2
  push offset RETURN2
  jmp WriteMSG
RETURN2:
  call KEY
ENDE: ;schönes Label oder ?
  mov ah,4ch ;4Ch zum Beenden
  INT 21h ;Beende !
  WriteMSG:
  mov ah,09h
  int 21h
  pop AX
  jmp AX
KEY:
  mov ah,1h
  int 21h
  ret
  MSG1 db 'CALL-Programm',10d,13d,'$' ;Ein String..
  MSG2 db ' by SnakeByte',10d,13d,'$'
END START ;hier ist das ganze zuende..
----------ende-----------

 

11. Loops (Schleifen)

Was sind Schleifen ? Wenn ihr schonmal in einer höheren Sprache programmiert habt, kennt ihr sicherlich 'do while' oder 'loop until' schleifen. Wenn nicht, dann erklär ich euch was eine Schleife macht: Eine Schleife führt einen bestimmten Codeabschnitt solange aus, bis eine vorher festgelegte Bedingung erfüllt ist. Bei uns verwenden wir den Register CX als Zähler für die Schleife. Wir setzen also in CX den Wert, der die Anzahl der Durchläufe enthält. CX wird mit jedem Durchlauf automatisch um eins vermindert.
--8<---(LoopIT.asm)------
.model tiny
.code
org 100h
START:
  mov cx, 3h ;wir setzen den Zähler auf 3
  Schleife:
    lea dx, MSG1 ;das gleiche wie mov dx, offset MSG1
    mov ah, 9h
    int 21h
  loop schleife ;zurück zum Label Schleife
ENDE: ;schönes Label oder ?
  mov ah,4ch ;4Ch zum Beenden
  INT 21h ;Beende !
  MSG1 db 'Schleife ..1,2,3.. ',10d,13d,'$' ;Ein String..
END START ;hier ist das ganze zuende..
---------ende-----------

Hiermit zeigen wir den Text 3 mal.. Man kann Schleifen auch ineinander Verschachteln, man muss dann allerdings darauf achten, das man CX speichert (push/pop)

12. Repeat it...

Was aber, wenn man nur ein Commando wiederholen will ?  Lohnt sich der Aufwand mit loop ? Nein, dafür haben wir nämlich das REP-Kommando. Auch hier setzen wir wieder die Anzahl in CX und schreiben dann hinter das REP unseren Befehl, den wir mehrfach ausführen wollen... ;)
--8<---(REP.asm)------
.model tiny
.code
org 100h
START:
  mov cx, 3h ;wir setzen den Zähler auf 3
  rep call write ;wiederhole den call zu write 3 mal..
ENDE: ;schönes Label oder ?
  mov ah,4ch ;4Ch zum Beenden
  INT 21h ;Beende !
Write:
  lea dx, MSG1 ;das gleiche wie mov dx, offset MSG1
  mov ah, 9h
  int 21h
ret
MSG1 db 'Schleife ..1,2,3.. ',10d,13d,'$' ;Ein String..
END START ;hier ist das ganze zuende..
----------ende-----------

 

13. Etwas Bewegung...

Nun geht es darum, eine größere Menge an Bytes von einem Ort zum anderen zu bewegen. Oder sie zu laden, zu ändern und dann wieder zu speichern. Dafür gibt es die drei Befehle movsb, lodsb und stosb. Diese drei Befehle arbeiten mit den Registern DI (Direction Index) und SI (Source Index). In diesen beiden Registern wird immer die Adresse (offset) der zu bearbeitenden Daten gespeichert. DI gibt den Platz an, an den Daten geschrieben werden, SI den Offset von dem sie kommen. MOVSB schreibt ein Byte von SI nach DI, wenn man mehrere Bytes copieren will, muss man vor das ganze ein REP setzen (siehe vorheriges Kapitel). Mit LODSB lädt man ein Byte von SI nach AL. Dabei wird SI um eins erhöht, so das SI nun auf das nächste Byte zeigt. STOSB dagegeb speichert das Byte aus AL an dem Offset auf den DI zeigt. Hier wird DI dabei um eins vergrößert. Schauen wir uns ein kleines Beispielprogramm an, das eine Zeichenkette entschlüsselt und dann ausgibt:
--8<---(CRYPT.asm)------
.model tiny
.code
org 100h
START:
  mov cx, 9d ;wir setzen den Zähler auf 9
  mov si, offset msg1 ;laden des Offsets in SI
  mov di, si ;da Ziel = Quelle ist
;muss si = di sein..
Crypt: ;unsere Schleife
  lodsb ;wir laden das erste Byte in al
  dec al ;vermindern es um 1
  stosb ;speichern es wieder
loop Crypt ;und machen das ganze 9 mal
Write:
  lea dx, MSG1 ;das gleiche wie mov dx, offset MSG1
  mov ah, 9h ;nun zeigen wir die entschlüselte
  int 21h ;Nachricht auf dem Bildschirm..
ENDE: ;schönes Label oder ?
  mov ah,4ch ;4Ch zum Beenden
  INT 21h ;Beende !
  MSG1 db 'UPQTFDSFU'
StringEnde db 10d,13d,'$' ;Ende des Strings..
END START ;hier ist das ganze zuende..
----------ende-----------

Eine der Sachen, die mir an Assembler am Besten gefallen, ist, das man nicht nur die Daten verändern kann, sondern auch das Programm an sich, während es läuft. Und dies nicht nur auf der Festplatte, sondern auch im Speicher, wodurch sich zum Beispiel das gesamte Programm verschlüsseln lässt, oder man auf 'etwas' andere Art und Weise den Programmablauf ändern kann. Im folgenden Beispiel ändern wir einen JE in einen JNE, so das sich das Programm anders verhält.

 

--8<---(JUMP.asm)------
.model tiny
.code
org 100h
START:
  mov di, offset JUMP ;wir laden in DI die Adresse, die wir ändern
  mov al, 75h ;75h ist der HEX-Code für JNE
  stosb ;den schreiben wir über den JE
  mov ax, 0h ;Wir setzen AX und BX = 0
  mov bx, 0h
  cmp ax,bx ;Hier vergleichen wir 2 gleiche Zahlen
JUMP:
  JE WriteMSG2 ;sind sie gleich, dann springe.. (dies ist
;der Befehl, den wir ändern.. )
  lea dx, MSG1 ;ansonsten gib MSG1 aus..
  jmp WriteMSG
WriteMSG2: ;hierher springt der JE
  Lea dx, MSG2 ;schreibe MSG2
WriteMSG: ;schreibe die Nachricht..
  mov ah, 9h
  int 21h
ENDE: ;schönes Label oder ?
mov ah,4ch ;4Ch zum Beenden
INT 21h ;Beende !
MSG1 db 'Dies erscheint nur beim geänderten JNE !!! *g*',10d,13d,'$' ;String
MSG2 db 'Dies erscheint wenn der Original JE ausgeführt wird..',10d,13d,'$' ;String
END START ;hier ist das ganze zuende..
----------ende-----------

Dies ist nur eine der vielen Möglichkeiten, wie man den Code während der Ausführung ändert.. Ich werde später bei der Indirekten Adressierung nochmal genauer darauf eingehen.

14. Verschlüsselung und anderes..

Es gibt noch eine Menge anderer Funktionen in Assembler, die sich zum Verschlüsseln und Codieren des Codes oder anderer Daten eignen. Das Problem beim verschlüsseln von Programmen ist nicht die komplexität des Verschlüsselungsalgorithmusses, sondern der Schlüssel, der ja auch im Programm vorhanden sein muss. Da auch ein Cracker diesen Schlüssel im Programm findet, kann er auch das Programm entschlüsseln. Ok, ich erklär erstmal einige der Funktionen...

XCHG AX,BX : Mit diesem Befehl tauscht man die Daten zweier Register
NEG AX : Hier wird AX negiert aus 00000020h wird FFFFFFE0h
NOT AX : Addiert 1 und multipliziert mit -1 ( 5 = -6)
XOR AX,BX : Bitweise Verknüpfung mit einem exclusiven oder:

16h XOR 20h = 36h
            10110b
XOR 100000b
=        110110b

Im Klartext taucht im Ergebnis immer dort eine 1 auf, wo entweder im 1. oder 2. Operant eine 1 war. Dort wo an beiden Operanten eine 0 oder eine 1 war steht im Ergebnis eine 0.

OR AX, BX : Bitweise Oder-Verknüpfung
16h OR 20h = 36h
        10110b
OR 100000b
=    110110b

Hier enthält das Ergebnis dort eine 1, wo in einem der beiden Operanten eine 1 stand. Aber es kommt nur dort eine 0 vor, wo in beiden Operanten eine 0 stand.
NOR AX,BX : Verknüpfung über NOT und OR
Diese Verknüpfungen kann man entweder dazu verwenden, irgendwelche Mathematischen Rechnungen auszuführen ( igitt ) oder um Texte oder ähnliches zu verschlüsseln damit nicht jeder Lamer seinen Namen in euer Programm mit nem Hexeditor schreiben kann... 
Dazu eignen sich besonders gut NEG, NOT und XOR. Wenn mal eine Zahl nämlich zweimal negiert oder zweimal ein NOT mit ihr durchführt erhält man die Ursprungszahl. Genauso ist es mit XOR, wodurch sich bei einer Byte-weisen Verschlüsselung FFh Möglichkeiten bieten, bei einer Word Verschlüsselung sogar FFFFh Möglichkeiten. Das Beispiel ist das gleiche wie vorher, nur statt dem dec benutzen wir diesmal XOR, NEG und NOR

--8<---(CRYPT2.asm)------
.model tiny
.code
org 100h
START:
  mov cx, 7d ;wir setzen den Zähler auf 7
  mov si, offset msg1 ;laden des Offsets in SI
  mov di, si ;da Ziel = Quelle ist
;muss si = di sein..
Crypt: ;unsere Schleife
  lodsb ;wir laden das erste Byte in al
  neg al
  xor al, 34h
  not al
  stosb ;speichern es wieder
loop Crypt ;und machen das noch ein paar mal
Write:
  lea dx, MSG1 ;das gleiche wie mov dx, offset MSG1
  mov ah, 9h ;nun zeigen wir die entschlüselte
  int 21h ;Nachricht auf dem Bildschirm..
ENDE: ;schönes Label oder ?
mov ah,4ch ;4Ch zum Beenden
INT 21h ;Beende !
MSG1 db 7Dh, 56h, 59h, 59h, 5Ch, 1bh, 1bh
StringEnde db 10d,13d,'$' ;Ende des Strings..
END START ;hier ist das ganze zuende..
---------ende-----------

 

15. Schieben und Rollen

Jetzt geht es um 4 Befehle, die für Ordnung in den Registern sorgen:  ROR, ROL, SHR, SHL
Jeder Register besteht aus 16 Bit. Da diese 16 Bit nicht immer so angeordnet sind, wie wir das brauchen muss man sie verschieben. Folgendes Beispiel an einem 8-Bit Register: (bin zu faul 16 stück zu malen *g*)
[1|0|0|1|0|1|0|1] SHR AH,1 = [0|1|0|0|1|0|1|0]

In ah befand sich der Wert 149, der durch das verschieben zu 10 geändert wird. Mit SHR verschieben wir die Bits von links nach rechts (sh_R_ wie rechts), dabei Fallen am rechten Ende die Bits heraus und von Links rücken immer nur Nullen nach. Genauso verhält es sich mit SHL, nur das hier von Rechts die Nullen nachrücken und die Bits am Linken Ende herausfallen.
[1|0|0|1|0|1|0|1] SHL AH,1 = [0|0|1|0|1|0|1|0]

Aus 149 wird in diesem Fall also 42. Was ist nun ROR und ROL ? Mit den beiden Befehlen rotieren die Register ;) Sie werden verwendet wie SHR und SHL aber bei ihnen fallen die Bits nicht am Ende weg, sondern werden am anderen Ende wieder drangefügt.
[1|0|0|1|0|1|0|1] ROR AH,1 = [1|0|0|0|1|0|1|0]
149 --> 138

[1|0|0|1|0|1|0|1] ROL AH,1 = [0|0|1|0|1|0|1|1]
149 --> 43

So hier ein kleines Programm, das eine Dezimalzahl in Binärcode ausgibt. Der Befehl mov byte ptr cs:[si] einfach nicht beachten *g* er wird im nächsten Kapitel erklärt.. ;) Wenn ihr eine andere Zahl testen wollt (0-255) einfach unter dezimalzahl eintragen..

--8<---(DEC2BIN.asm)------
.model tiny
.code
org 100h
START:
  mov cx, 8h
loopit:
  dec cx
  mov ah, dezimalzahl ;lade die dezimalzahl in ah
  shr ah, cl ;verschiebe ah nach rechts, solange bis nur das cx'te
;Byte übrig ist
  and ah, 00000001h ;setze alle bytes ausser dem letzten = 0
  add ah, 48d ;als ascii zahl darstellen
  mov si, offset bin + 8h ;wo speichern wir die zahl ?
  sub si, cx
  mov byte ptr cs:[si],ah ;speicher sie (mehr zu byte ptr später)
  inc cx
loop loopit ;wiederhole das ganze
lea dx, MSG1 ;das gleiche wie mov dx, offset MSG1
mov ah,09h
int 21h
ENDE: ;schönes Label oder ?
mov ah,4ch ;4Ch zum Beenden
INT 21h ;Beende !
MSG1 db 'Die Binaerzahl lautet :'
bin db ' ',10d,13d,'$'
dezimalzahl db 23d
END START ;hier ist das ganze zuende..
----------ende-----------

 

16. Indirekte Adressierung

Wie ihr weiter oben schon gelesen habt, gibt es innerhalb des Codes verschiedene Segmente, die ihr nach euerem belieben frei definieren könnt. Bisher haben wir nur mit CS, dem Code Segment und SS dem Stack Segment gearbeitet. Bisher standen auch die Daten im Code Segment, da dieses einfacher zu Handhaben ist. Nun wollen wir aber ein eigenes Segment für die Daten einrichten. Dies geschieht mit dem Befehl ASSUME.
Ein Beispiel:

--8<---(~~~~~~~~)------
code segment
assume cs:code,ds:data       ;Definiert die einzelnen Segmente
;euer code hierher..
code ends
data segment
;hier kommen die daten rein..
data ends
end
----------ende-----------

Genauso könnt ihr auch das Stack Segment, oder ES, FS und GS definieren.  Ein Segment beginnt immer mit <Name> segment und endet mit <Name> ends. Wenn wir vom Code Segment auf ein anderes zugreifen wollen müssen wir dies dem Programm mitteilen. Dies geschieht durch Angabe des Segmentes

mov ah, byte ptr ds:[offset value]
mov ax, word ptr ds:[offset value2]

Im ersten Beispiel wird der Wert des Bytes an Stelle value in ah geschoben in   Beispiel 2 der Wert des Words (2 bytes) an Offset value2 in ax.  Hier ein wirklich simples Beispiel, das mit 2 Segmenten arbeitet. Dies ist allerdings eine EXE-Datei (in COM Dateien ist alles ein Segment) und wird deshalb anders kompiliert. (Siehe dafür nächstes Kapitel)

--8<---(Segments.asm)------
code segment ;dies ist CS
assume cs:code,ds:data,ss:stackSeg ;Definiert die einzelnen Segmente
start:
  push data ;wir initialisieren die einzelnen
  pop ds ;Segmente...
  push stackSeg
  pop ss
  mov ah,9h ;wir laden den String aus DS
  mov dx, ds:[offset string]
  int 21h
  mov ah, 4ch ;ENDE
  int 21h
  code ends ;hier endet unser Code
  data segment ;hier starten die Daten
  string db 'Unser String.. ;) ',10d,13d, '$'
  data ends ;hier enden sie
  stackSeg segment STACK ;das ist unser Stack, wenn viel mit
  db 50h dup (?) ;dem Stack gearbeitet wird mehr als 50h
  stackSeg ends ;Bytes definieren !
end start
end
----------ende-----------

Die Segmente kann man nur auf 2 Arten ändern, entweder über ax, indem man einen Wert in AX legt und dann ein mov DS, ax macht oder über push und pop.
push cs
pop ds

setzt zum Beispiel DS = CS. Man kann auch später im Programm noch die Segmente, wenn man   diese Arten benutzt verändern. Es ist egal, ob ihr euer Daten Segment vor oder nach das  Code Segment setzt, da beim Laden von EXE-Dateien zuerst im Header der EXE der Anfangspunkt des Codes gesucht wird und nicht direkt von vorne gestartet wird, wie in COM Dateien. Vielleicht sollte ich auch noch die Anweisung db 50h dup (?) erklären. Hier werden  50h, also 125 Bytes definiert, die noch nichts (0) enthalten. Ein dw 100h dup ('BS') würde zum Beispiel 256 Words mit den Buchstaben SB (für SnakeByte *g) definieren. SB deshalb, da ein Word immer umgedreht geschrieben wird. In der Datei steht dann SBSBSBS... ;P

17. EXE-Dateien

Wo ist der Unterschied zwischen einer EXE-Datei und einer COM-Datei ? Für uns liegt er erstmal darin, das wir das org 100h weglassen müssen und nun unsere Segmente frei definieren können ! Hier können wir nun auch die Daten vor den Code oder zwischen 2 Code Abschnitte (dann aber CS neu setzen *g*) legen können. Auch müssen wir EXE-Dateien anders compilieren. Hier wieder eine Batch Datei:

---8<------ ( CompEXE.bat )---------
@Echo Off
if not exist %1.asm goto quit
tasm %1 /p/w/z/m4
if errorlevel 1 goto quit
tlink %1/d/l
del %1.map
del %1.obj
:quit
---8<-------------------------------

Durch diese Anweisungen weiß unser Compiler und Linker, das er eine EXE-Datei zu erzeugen hat. Diese ist normalerweise um 256 Bytes größer, da im Header noch wichtige Informationen  über die Ausführung und die Segmente der EXE enthalten sind. Eine EXE Datei beginnt immer mit MZ bzw ZM und wird selbst wenn sie in *.COM umbenannt wird laufen. (einige COM Dateien sind versteckte EXE Dateien -> command.com auf vielen Systemen) Auch kann eine EXE Datei nun größer sein als die 64kb die uns in COM Dateien eine Grenze setzen.

 

18. Datei-Handling

Dateien sind immer brauchbar, wenn man etwas abspeichern oder lesen will. Deshalb werde ich euch hier zeigen, wie man über den DOS-Interrupt 21h Dateien erstellt und ändert. Was ihr dazu braucht sind folgende Funktionen (immer den Wert in ah legen.. ;) ) ich liste sie nur kurz auf, am Besten also nochmal in PPC oder Ralf Browns Interrupt List nachlesen, da im folgenden Sourcecode auch nicht alle dieser Interruptfunktionen angesprochen werden.
4eh Find First File Suche nach einer Datei
4fh Find Next File Sucht die nächste Datei, wenn Wildcards (*.txt) verwendet werden
3ch Create File Erstelle neue Datei
3dh Open File Öffnet eine Datei (al ist Modus -> 02h read/write)
* 3eh Close File Schließt die Datei wieder
41h Delete File Lösche eine Datei
* 42h Set File Pointer Bewegt einen Zeiger in der Datei
* 40h Write File Schreibt etwas in die Datei an die Stelle auf die der Zeiger zeigt

Die Funktionen 3dh und 3ch geben in ax den sogenannten File Handle aus, der die Datei  identifiziert, wenn man zum Beispiel mehrere Dateien geöffnet hat. Dieser wird für  einige dieser Int-Funktionen (mit * gekennzeichnet) in BX benötigt. Also am besten gleich nach dem Öffnen oder Erstellen irgendwo speichern.

---8<------ ( Files.asm )---------
.model tiny
.code
org 100h
Start:
  mov ah, 3Ch ;erstelle eine neue Datei (immer)
  lea dx, filename ;Zeiger zum Dateinamen
  xor cx, cx ;keine attribute
  int 21h
  xchg ax,bx ;schiebe das Handle in bx
  mov ah, 40h ;schreibe etwas in die Datei
  lea dx, Text ;was soll hineingeschrieben werden ?
  mov cx, (offset endText-offset Text) ;wieviel soll geschrieben werden ?
  int 21h mov ah, 3eh ;nun schließen wir die Datei wieder.. ;)
  int 21h
  mov ah, 3dh ;und nun wird es wieder geöffnet.. ;)
  xor al, al ;nur zum lesen..
  lea dx, filename int 21h
  jc ENDE ;ist carriage flag gesetzt beende das Ganze
  xchg ax, bx ;speichern des Handles
  mov ax, 4202h ;gehe zum Ende der Datei..
  xor cx, cx ;cx = dx = 0
  xor dx, dx
  int 21h
  push ax ;ax ist die Länge des Dateiinhaltes, wenn
;sie kleiner als 64kb ist.. ;)
  mov ax, 4200h ;gehe zum anfang der Datei
  xor cx,cx
  xor dx,dx
  int 21h
  mov ah, 3fh ;lesen der gesamten Datei
  pop cx ;in den Buffer
  push cx lea dx, buffer
  int 21h
  mov ah, 3eh ;die Datei wird geschlossen
  int 21h
  mov si, offset buffer ;hänge an die Daten der Datei
  pop cx ;ein '$', damit wir sie auf dem
  add si, cx ;Bildschirm ausgeben können
  mov byte ptr [si],'$'
  mov ah, 9h ;Zeige die gelesenen Daten auf dem
  lea dx, buffer ;Bildschirm
  int 21h
ENDE:
  mov ah, 4ch ;beende das Programm
  int 21h
  filename db 'testit.txt',0h
  Text db 'Doller Text oder ?? *g*',10d,13d
  endText:
  buffer:
end start
---8<-------------------------------

 

19. Assembler Anweisungen

Es gibt auch Anweisungen, die man dem Compiler / Linker im Programmtext mitgeben kann, die einem die Arbeit erleichtern. diese werden einfach an den Anfang des Programmes geschrieben. Hier liste ich einfach mal einige auf:
.model tiny Hiermit legt ihr die Größe eueres Programms fest. Mögliche Einstellungen sind: tiny -> insgesamt max 64kb small -> code & data jeweils < 64kb medium -> code > 64kb, data < 64kb compact -> code < 64kb, data > 64kb large / huge -> code & data > 64 kb Normal reicht 'small' oder tiny aber für die meisten Programme dicke.. ;)
.code Art der Sektion ( code / data / stack )
org 100h Wo soll das Programm starten ? 100h für COM 0h für EXE, 0h für SYS..
jumps Alle Sprünge über 256 Bytes, die normal als FAR JMP declariert werden müssten, werden automatisch berechnet (Sehr hilfreich !)
radix 10 Diese Anweisung gibt an, in welchem Zahlenformat Zahlen ohne Angabe sind (h für hex, d für dezimal b für binär). Wenn man hier 10 angibt wird das Dezimalsystem verwendet, bei 16 das hexadezimale System. (schützt vor vergessenen h's, nach denen man sich totsuchen kann *g* )
dreizehn equ 13d Hiermit kann man Konstanten deklarieren, die man öfters ändert. Im Programmtext steht dann zum Beispiel ein ' mov ax, dreizehn' der Kompiler setzt dann automatisch die Zahl ein, die man mit equ definiert hat.
.286 Hiermit kann man den Prozessortyp angeben, der   benötigt wird um das Programm zu starten. Je höher der Prozessortyp ist, umso mehr zusätzliche Befehle können verwendet werden, da mit jeder neuen Prozessorklasse weitere Befehle eingeführt werden. (mögliche Werte: .286, .386, .486, .586, .286P, 386P ..)
include xyz.inc Mit diesem Befehl könnt ihr Include Dateien in euer Programm einbinden diese können auch andere Dateinamenerweiterungen haben (.asm, .pal...) und enthalten auch Assemblerquelltext. Darin stehen dann zum Beispiel andere Prozeduren, Daten etc. Diese könnt ihr dann auch aus euerem Programm aufrufen. Dies hilft größere Projekte übersichtlich zu halten.

 

20. Suchen nach Text-Strings

Was ist wenn wir Texte bearbeiten wollen ? Dann müssen wir die Möglichkeit haben in diesen Texten nach Strings zu suchen. Dies geschieht mit dem Befehlt scasb. Er vergleicht das Byte in al mit dem Inhalt des Offsets auf den di zeigt. Nach dem Vergleichen wird di um 1 erhöht. Um nun mehrere Vergleiche durchzuführen, wiederholt man diese Anweisung solange bis, entweder die Länge des Textes erreicht ist, oder man Erfolg hat. Danach wird mit cmp der Rest des zu suchenden Strings verglichen. Aber genug der Worte,  lasst uns das ganze wieder an einem Beispiel betrachten. Wir durchsuchen die Datei C:\Autoexec.bat nach dem String 'rem' , welcher ein Kommentar einleitet...
---8<------ ( scasb.asm )---------
.model tiny ;nur ein kleines Programm
.code ;hier steht der code
org 100h ;wir basteln ne COM Datei
START: ;Label zum verzieren
  mov ah, 3dh ;öffnen der autoexec.bat
  xor al, al ;nur zum lesen..
  lea dx, filename ;dx zeigt auf den Dateinamen
  int 21h
  jc Ende ;wenn es irgendwelche Fehler gibt beenden wir..
  mov bx, ax ;speichern des Handels
  mov ax, 4202h ;gehe zum Ende der Datei..
  xor cx, cx ;cx = dx = 0
  xor dx, dx
  int 21h
  push ax ;ax ist die Länge des Dateiinhaltes, wenn
;sie kleiner als 64kb ist.. ;)
;der Rest steht in DX
  mov ax, 4200h ;gehe zum anfang der Datei
  xor cx,cx ;CX=DX=0
  xor dx,dx
  int 21h
  mov ah, 3fh ;lesen der gesamten Datei
  pop cx ;in den Buffer
  push cx ;speicher die Dateilänge wieder im Stack
  lea dx, buffer
  int 21h
  mov ah, 3eh ;die Datei wird geschlossen
  int 21h
  mov dx, 0h ;Setze dx als Zähler auf 0
  lea di, buffer ;hier fangen wir an zu suchen
SearchOn:
  pop cx ;Dateilänge in cx
  lea si, string ;was wollen wir finden ?
  lodsb ;lade das erste Byte in ax
  repnz scasb ;Suche starten
  cmp cx,0 ;wenn cx=0 dann wurde alles durchsucht..
  jz disp ;Ergebnisse anzeigen
  push cx
  mov cx, laenge ;den Rest des Strings vergleichen
  repz cmpsb
  cmp cx,0 ;kompletter String gleich ?
  jnz SearchOn ;wenn nicht weitersuchen
  inc dx ;Zähler erhöhen
  pop cx ;Restliche Länge überprüfen
  push cx
  cmp cx, 0
  jne SearchOn ;weitersuchen
  mp dispj ;anzeigen
disp:
  lea di, showstring ;in di ist der String den wir anzeigen wollen
  mov ax, dx ;wenn dx < 10 dann wird er umgerechnet in Dezimal
  cmp ax, 10d
  jnae showit
  cmp ax, 100d
  jae toogreat
  xor dx, dx
  mov cl, 10d
  div cl
  add dl, 48d
  mov byte ptr cs:[di], dl
  add al, 48d
  mov byte ptr cs:[di], al
  inc di
 
showit:
add dl, 48d
mov byte ptr cs:[di], dl
lea dx, showstring ;Anzeigen
mov ah, 9h
int 21h
Ende:
mov ah, 4ch ;Beenden..
int 21h
toogreat: ;Zahl ist größer als 9
lea dx, toogre
mov ah, 9h
int 21h
jmp Ende
filename db 'C:\Autoexec.bat',0h
string db 'rem'
laenge equ $-offset string
toogre db 'String öfters als 9 mal gefunden',10d,13d,'$'
showstring db ' ',10d,13d,'$'
buffer:
END START ;hier ist das ganze zuende..
---8<-------------------------------

 

21. Graphiken

Ok, das letzte Thema mit dem ich mich hier beschäftigen will, sind Graphiken. Hier werde ich euch zeigen, wie man einen Pixel auf den Bildschirm schreibt, und eine Linie zieht. Wir verwenden hier einen neuen Interrupt. Der Interrupt 10h ist für all das zuständig, was die Ausgabe auf den Bildschirm betrifft. (man kann mit ihm auch Text ausgeben...). Was uns interessiert, sind die Funktionen 0h (Video-Modus Wechsel) und 0ch (Put-Pixel). Da wir  normalerweise im Text-Modus arbeiten, müssen wir zuerst einmal den Bildschirmmodus wechsel, so das wir vernünftige Bilder zeigen können (*g*) Wir wechseln also in den MCGA Modus. Dieser lässt uns 255 Farben darstellen, auf einem Bildschirm von 320*200 Pixeln Größe. Die Interruptfunktion 0h wird dazu benutzt um in diesen Modus zu gelangen und auch um wieder den Text-Modus einzustellen. Mit 0Ch werden wir dann ein Pixel auf den Bildschirm ausgeben. Solange, bis wir ein Rechteck erhalten.
---8<------ ( grafix.asm )---------
.model tiny ;nur ein kleines Programm
.code ;hier steht der code
org 100h ;wir basteln ne COM Datei
START: ;Label zum verzieren
  mov ax, 0013h ;setzen des Video Modus (MCGA)
  int 10h ;320*200*256
;wir zeichnen eine Linie
  mov word ptr [startlinex], 20h
  mov word ptr [endlinex], 30h
  mov word ptr [startliney], 50h
  mov word ptr [endliney], 50h
  call drawline
;und noch eine..
  mov word ptr [startlinex], 30h
  mov word ptr [endlinex], 30h
  mov word ptr [startliney], 40h
  mov word ptr [endliney], 50h
  call drawline
  mov word ptr [startlinex], 20h
  mov word ptr [endlinex], 30h
  mov word ptr [startliney], 40h
  mov word ptr [endliney], 40h
  call drawline
;bis wir ein Quadrat haben.. ;P
  mov word ptr [startlinex], 20h
  mov word ptr [endlinex], 20h
  mov word ptr [startliney], 40h
  mov word ptr [endliney], 50h
  call drawline
  mov ah,1h ;auf Tastendruck warten..
  int 21h
  mov ax, 0003h ;zurück zum Textmodus
  int 10h
Ende:
  mov ah, 4ch ;Beenden..
  int 21h
 
drawline:
  call putpixel
  mov ax, word ptr [startlinex]
  sub ax, word ptr [endlinex]
  mov bx, word ptr [startliney]
  sub bx, word ptr [endliney]
  cmp ax, bx
  ja putx ;x - Pixel malen
  jb puty ;y - Pixel malen
  cmp ax, 0h ;wenn ax = bx = 0, dann haben wir genug
  je return ;gezeichnet
  inc word ptr [startlinex] ;sowohl x, als auch y, um 1 verschieben
  inc word ptr [startliney] ;(für Schrägen)
  call putpixel jmp drawline
putx: ;Pixel auf X-Koordinate zeichnen
  inc word ptr [startlinex]
  call putpixel
  jmp drawline
puty: ;Pixel auf Y-Koordinate zeichnen
  inc word ptr [startliney]
  call putpixel
  jmp drawline
  return:
  ret
putpixel: ;cx,dx sind die Koordinaten
  mov ah, 0Ch ;Pixel zeichen
  mov cx, word ptr [startlinex]
  mov dx, word ptr [startliney]
  mov al, 1 ;Farbe
  mov bx, 1h
  int 10h
ret
startlinex dw 0h ;unsere Koordinaten
endlinex dw 0h ;sind hier gespeichert
startliney dw 0h
endliney dw 0h
buffer:
END START ;hier ist das ganze zuende..
---8<-------------------------------

 

22. Farbiger Text

Ok, wenn wir schon bei bunten Pixeln sind, hier noch eine kleine Anleitung, wie man farbigen Text auf dem Bildschirm ausgibt. Auch hier verwenden wir wieder den Interrupt 10h, der für die Bildschirmausgabe zuständig ist. Diesmal verwenden wir den Bildschirmmodus 12h. Mit der Funktion 2h des Int 10h legen wir die Position des Cursors auf dem Bildschrim fest, an der der Text erscheinen soll. Dann wird mit der Funktion 9h der Buchstabe ausgegeben. Dann wird der Cursor wieder verschoben und der nächste Buchstabe wird angezeigt.

 

---8<------ ( Farbe.asm )---------
.model tiny ;nur ein kleines Programm
.code ;hier steht der code
org 100h ;wir basteln ne COM Datei
START: ;Label zum verzieren
  mov ah, 0h ;wir ändern den Bildschirmmodus
  mov al, 12h
  int 10h
  lea si, HelloWorld ;si zeigt auf unseren String
  mov cx, lenght ;cx = Stringlänge
LoopIt:
push cx ;cx speichern
lodsb ;Buchstaben des Strings in al lesen
mov ah, 2h ;Cursor setzen
mov bh, 0h ;auf Page 0
mov dh, 2h ;Zeile
mov dl, byte ptr XXX ;XXX ist die Spalte
int 10h
inc byte ptr XXX ;Farbe erhöhen
mov ah, 09h ;Farbiger Charakter ausgeben
mov bh, 0h ;Page 0
mov bl, byte ptr XXX ;Farbe
mov cx, 1h ;schreibe ihn einmal..
int 10h
pop cx ;cx laden
loop LoopIt ;wiederholen, bis der komplette
;String geschrieben ist
mov ah, 2h ;Cursor setzen
mov bh, 0h ;auf Page 0
mov dh, 3h ;Zeile
mov dl, 3h ;Spalte
int 10h
mov ah,1h ;auf Tastatureingabe warten
int 21h
mov ah, 4Ch
INT 21h ;Beende !
HelloWorld db 'Hello World !!'
lenght equ $ - offset HelloWorld ;Unser String..
xxx db 0h
END START ;hier ist das ganze zuende..
---8<-------------------------------

 

23. Outro...

Ok, das wars für dieses Mal. Ich hoffe ich habe es geschafft euch einen Einstieg in die Assembler Programmierung zu verschaffen. Wenn ihr Fragen habt oder Vorschläge, was man noch in dieses Tutorial packen sollte schreibt mir einfach eine Mail (SnakeByte@kryptocrew.de). Neue Versionen dieses Tutorials und anderen interessanten Kram findet ihr auf meiner  Website: http://www.kryptocrew.de/snakebyte/

Das wars dann auch..

cu soon SnakeByte