|
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 |
|