_____________________________ /~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\ |~~SnakeBytes Assembler Tutor~~| |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | SnakeByte@kryptocrew.de | | www.kryptocrew.de/snakebyte/ | \_____________________________/ 1. Einleitung 1.1 Was ist Assembler ? 1.2 Warum in Assembler programmieren ? 1.3 Anmerkungen zu TASM 1.4 DOS Assembler 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 '' 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,F. 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 16. 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' machen 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 segment und endet mit 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 Anfangs- punkt 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 jmp disp ;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 !!' ;Unser String.. lenght equ $ - offset HelloWorld 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