BIOS-Level Programmierung |
Geschrieben von: StarShaper | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Donnerstag, den 01. Juni 2006 um 10:30 Uhr | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
InhaltIn diesem Tutorial werden wir die unterste Ebene der Systemprogrammierung kennenlernen, die BIOS-Level Programmierung. Selbstverständlich existiert auf dieser Ebene nur eine Sprache mit der es möglich ist alles aus der Hardware rauszukitzeln was in ihr steckt - die Assemblersprache! Sie erfahren in diesem Tutorial unter Anderem wie man direkt mit Hardware Interrupts arbeitet, die Maus direkt beeinflußt und Pixel über die Grafikkarte mithilfe der BIOS Services auf den Bildschirm zeichnet. Um zu verstehen wie die Operationen vom BIOS bewerkstelligt werden, ist zunächst eine kurze Einführung in das Basic Input/Output System notwendig. Das BIOSDas BIOS ist die Basis-Software des Computers und kann im entferntesten Sinn, analog zum menschlichen Gehirn, als das Stammhirn des Rechners angesehen werden. Das BIOS regelt in der Boot-Up Phase alle niederen Funktionen des PCs. Um Zugriff auf das BIOS zu erlangen ist es erforderlich den Prozessor in den so genannten Real Mode zu schalten. Der Real Mode ist ein Operationsmodus der 80286 und x86-kompatiblen CPUs. Im Real Mode steht ein 1 MB großer segmentierter Speicher zur Verfügung. In diesem Modus findet keine Abbildung von virtuellen in physikalischen Adressraum statt. Das Programm das die Speicheradressen verwendet interpretiert diese als physikalischen Speicher und greift auf diesen über einen 20-Bit breiten Adressraum zu der in einem Bereich zwischen 0 und FFFFF liegt. Darüberhinaus ist im Real Mode kein Speicherschutz und auch keine Multitasking-Unterstützung vorhanden. Die Software hat jedoch vollständigen Zugriff auf die BIOS Routinen und peripheren Geräte. Der Real Mode wird von neuen Prozessoren im Sinne der Abwärtskompatibilität unterstützt. Das heißt jeder moderne x86-Prozessor startet im Real Mode und wird später manuell vom Betriebssystem in den Protected Mode geschalten. Unter allen modernen Windows Betriebssystemen erledigt das der NT Loader (Abkürzung: ntldr) in der Startphase. Erst danach kann auf die vollständigen 32-Bit des Speichers zugegriffen werden. Nachdem der NT Loader einige Page Tables generiert hat um einen 16 MB großen Speicher mit aktiven Paging betreiben zu können, aktiviert er das Paging und Windows kann normal geladen werden. Bei den in diesem Tutorial vorgestellten Quellcodes handelt es sich um 16-Bit Applikationen. Da nur im Real Mode ein direkter Zugriff auf die Hardware möglich ist, neue Windows Betriebssysteme der NT-Serie den Prozessor jedoch beim Start in den Protected Mode schalten und das System vollständig abschirmen, ist es ggf. erforderlich die erstellten Programme beim Start separat im DOS-Modus auszuführen. Sie können diese aber auch unter Windows NT laufen lassen. Zu diesem Zweck stellen Windows Betriebssysteme eine virtuelle DOS Maschine, auch WOW genannt, zur Verfügung. Diese läuft unter dem Namen ntvdm.exe im Taskmanager. Dabei handelt es sich um ein Win16 Subsystem das es 16-Bit Anwendungen ermöglicht so ausgeführt zu werden als ob diese direkt in einer normalen DOS Umgebung laufen würden. NTVDM nutzt dazu einen speziellen Modus der x86-Prozessoren namens Virtual-8086 Mode. Der Modus erlaubt es Anwendungen, die für den Real Mode konzipiert wurden, in einer kontrollierten Umgebung zu laufen. Ihr Programm wird also von der virtuellen Maschine emuliert während Windows NT allein darüber entscheidet wie und ob das Programm auf die Hardware zugreifen darf. Da die Programme unter einer Multitasking Umgebung laufen, können die 16-Bit Anwendungen Windows NT auch nicht zum Absturz bringen. Windows NT verhindert in jedem Fall das NTVDM Instruktionen ausführt die direkt den Speicher oder die Hardware manipulieren können! Neben der Bedingung das unsere Programme im Real Mode laufen müssen, gibt es noch einige weitere Punkte zu berücksichtigen um mit Assemblersprache auf der BIOS Ebene programmieren zu können. So benötigen wir einen entsprechenden Assemblierer und 16-Bit Linker. In diesem Tutorial greifen wir auf den Microsoft Macro Assembler 6.x zurück. Microsoft bietet den MASM 6.x nicht mehr als Retail Produkt an. Bei Bedarf lässt sich dieser jedoch separat beim Support bestellen. Sie müssen sich darum jedoch nicht kümmern. Der MASM 6.x ist samt Update Patch und 16-Bit Linker im Anhang dieses Tutorials enthalten. Windows Vista und Windows 7Bitte beachten Sie das einige der hier vorgestellten 16-Bit DOS-basierten Programme seit Windows Vista nicht mehr lauffähig sind. Das betrifft nur die Programme, die im Fullscreen Mode operieren. Alle anderen Programme werden von der virtuellen DOS-Maschine unter Windows Vista und Windows 7 ordnungsgemäß ausgeführt. Nähere Details zu dem Problem entnehmen Sie der Beschreibung unter "Some 16-bit DOS-based Programs and the Command Prompt will not run in full-screen mode in Windows Vista and in Windows 7". Falls Sie Windows Vista oder Windows 7 nutzen, müssen sie diese Programme unter DOS ausführen oder die Videotreiber von Windows XP installieren. Einführung in den Microsoft Macro AssemblerDer Microsoft Macro Assembler, auch MASM genannt, ist ein Assembler für die x86 Mikroprozessor Familie. Er wurde ursprünglich von Microsoft für die Entwicklung auf dem MS-DOS Betriebssystem entwickelt und war für einige Zeit der bekannteste Assembler für dieses Betriebssystem. Er unterstützte eine breite Anzahl an Makro Einrichtungen und strukturierten Programmier-Idiomen, inklusive High-Level Konstruktionen für Schleifen, Prozeduren Aufrufen und Alternationen. Aufgrunddessen kann der MASM auch als ein Beispiel für einen High-Level Assembler angesehen werden. Den späteren Versionen des MASM wurden kontinuierlich zusätzliche Komponenten hinzugefügt und entsprechend auf die neuen Windows Betriebssysteme angepasst. Der MASM ist eines von wenigen Microsoft Tools für die es keine getrennten 16-Bit und 32-Bit Versionen gab. Anfang der 1990er erschienen erstmals alternative Assembler. Darunter der populäre NASM und der Borland Turbo Assembler. Trotz der aufkommenden Konkurrenz war der MASM auch bis spät in die 90er der gängigste Assembler auf dem Markt. Insbesondere die Tatsache das Microsoft den kommerziellen Verkauf einstellte und begann den MASM kostenlos als Teil des DDK zu publizieren verhalf dem Assembler zu seiner anhaltenden Popularität. Aber auch der neu erschienene MASM32, mit dem es nun auch möglich war richtige Windows Applikationen zu entwickeln, sowie die weit verbreiteten Tutorials trugen dazu bei. Heute ist der MASM32 immer noch der populärste Assembler, trotz anhaltender Konkurrenz von neuen Produkten wie dem aktuellen NASM, FASM, GoAsm, oder HLA. Es existieren viele laufende Software Projekte die den MASM unterstützen, darunter IDE's (z.B. RadASM), Debugger (z.B. OllyDbg), und Disassembler (z.B. IDAPro). Das MASM32 Projekt, zu finden auf movsd.com, hat zahlreiche Dokumentationen, Tutorials, Bibliotheken und Quellcodes für die MASM32 Entwickler zusammengestellt. Der Microsoft Macro Assembler wird durch eine große Community unterstützt, darunter zahlreiche Webseiten und Support Foren wie dem MASM Forum. Trotz seines Alters bleibt der MASM nach wie vor einer der am besten unterstützten Assembler in der Geschichte der Computerprogrammierung. Obwohl der MASM kein kommerzielles Produkt mehr ist, setzt Microsoft den Support weiterhin fort. Ein Grund dafür besteht darin das im Hause Microsoft und weltweit immer noch eine große Menge an Assembler Quellcode existiert. Für die Microsoft MASM 6.x Produktlinie wurden seit der Standard Edition 6.11 weitere Updates publiziert. Microsoft hat das Angebot des MASM 6.11 eingestellt und bietet diesen nurmehr auf direkte Anfrage beim Support an. Die neuen Versionen des MASM werden seither direkt mit den Visual C++ Versionen mitgeliefert. Beispielsweise enthält die Visual C++ 2002 Distribution den MASM 7.0, Visual C++ 2003 die Version 7.1 und die aktuelle Version Visual C++ 2005 die Version 8.0 des MASM. Diese unterstützt neben der 32-Bit Entwicklung auch die 64-Bit Architektur. Die Versionsunterschiede der letzten Microsoft Macro Assembler bestehen hauptsächlich in Bugfixes, dem Hinzufügen von neuen Instruktionen für neue Prozessoren und der Verbesserung der 64-Bit Unterstützung. Neue radikale Änderungen sind für die Zukunft nicht mehr zu erwarten. Microsoft Macro Assembler installierenFalls Sie im Besitz einer neuen Visual C++ Edition sind, oder sogar eine Visual Studio Entwicklungsumgebung Ihr Eigen nennen, können Sie den mitgelieferten MASM verwenden. Sie finden diesen im Visual C++ Verzeichnis unter VC/bin und dem Namen ml.exe. Um die Quellcodes direkt mithilfe Ihrer Visual Studio IDE assemblieren zu können, müssen Sie bedenken das der ML 8.x standardmäßig mit der Option /coff assembliert und nicht mit /omf. Das bedeutet es werden Dateien im Common Object File Format (COFF) generiert. Dies ist für die Entwicklung von 32-Bit Assembler Applikationen die Standardeinstellung. Bei 16-Bit Anwendungen muss allerdings das Object Module File Format (OMF) verwendet werden! Um Dateien im alten OMF generieren zu können benötigen Sie zudem einen 16-Bit Linker. Dieser ist Bestandteil des im Anhang enthaltenen MASM 6.x. Ich möchte anmerken das ich in diesem Tutorial ausschließlich in der Kommandozeile, ohne den Balast einer IDE arbeiten werde und auf den MASM in der Version 6.14 zurückgreife. Wir arbeiten schließlich nur eine Ebene über der Hardware und sollten uns dementsprechend auch vom überflüssigen Schnickschnack die eine IDE mit sich bringt verabschieden. Nachdem Sie das Paket im Anhang heruntergeladen haben, können Sie mit der Installation des MASM 6.11 beginnen. Starten Sie dazu die Setup.exe und installieren Sie den Microsoft Macro Assembler. Installieren Sie den Assembler in dem angegebenen Pfad unter C:\MASM611\. Nachdem Sie den Assembler erfolgreich installiert haben, können Sie diesen auf die aktuelle Version updaten. Dazu befindet sich die Datei ml614.exe - ML614.exe 6.14 patch available - im Paket. Verschieben sie das Programm in das Verzeichnis des MASM 6.11 und patchen Sie den MASM auf eine aktuelle Version. Anschließend benötigen Sie noch den 16-Bit Linker. Führen Sie dazu das Programm lnk563.exe aus. Es wurden drei Dateien entpackt. Kopieren Sie diese ebenfalls in das MASM Verzeichnis. Damit haben Sie die Installation erfolgreich abgeschlossen. Arbeiten mit dem MASMWie bereits erwähnt, existiert seit geraumer Zeit auch ein 64-Bit Microsoft Macro Assembler. Der Appendix 64 (ml64.exe) weist darauf hin. Die Kommandozeilen Referenz für den ML und den ML64 ist auf den offiziellen MSDN Seiten unter Microsoft Macro Assembler Reference zu finden. Die Parameter sind wie folgt spezifiziert: ML [[options]] filename [[ [[options]] filename]] ML64 [[options]] filename [[ [[options]] filename]] ... [[/link linkoptions]] Die Assemblierung gestaltet sich denkbar einfach. Im Normalfall reicht es aus den Dateinamen mit dem enthaltenen Quellcode zu übergeben. Der Assembler sucht stets im aktuellen Verzeichnis nach verwiesenen Dateien. Statische Bibliotheken werden anhand der Erweiterung *.lib erkannt und entsprechend mit der generierten Objektdatei zu einem ausführbaren Programm gelinkt. Eine Zusammenfassung der Kommandozeilen Syntax können Sie sich auch über den Befehl /? anzeigen lassen. Das BIOSDas Basic Input Output System, kurz BIOS genannt, ist bei PCs der x86 Prozessor Architektur die Basis-Software, die der Computer direkt nach dem Einschalten lädt und ausführt. Sie wird dazu in einem nichtflüchtigen Speicher abgelegt und steht dem Steuerwerk der CPU so direkt zur Verfügung. Die primäre Funktion des BIOS besteht darin die Maschine vorzubereiten, so dass andere Softwareprogramme, wie beispielsweise die Firmware des CD/DVD-Laufwerks oder der Festplatten geladen und ausgeführt werden kann. Dieser Prozess wird als Boot-Up bezeichnet. Das BIOS steuert die grundlegenden Mechanismen beim Start jedes Rechners. Nach der Boot-Up Phase wird die Kontrolle an das Betriebssystem übergeben. Das BIOS befindet sich auf einem PROM, EPROM oder auch Flash Speicher genannt. Nach dem Start entpackt es sich in den RAM und wird anschließend vom Prozessor ausgeführt. In modernen Rechnern wird ein zusätzliches Setup Programm aus dem flüchtigen CMOS geladen. Dieses enthält benutzerspezifische Einstellungen die im BIOS Konfigurationsmenü getätigt wurden. Dazu gehört neben der Reihenfolge in der die Laufwerke geladen werden, auch die Zeit und das Datum. Als die ersten IBM-PCs erschienen, wollten viele Programmierer wissen wie man direkt mit der Computer Hardware arbeitet. Relativ schnell erschienen die ersten Publikationen die sich mit den Eigenschaften und geheimen Internas des IBM-PCs befassten. Darunter das populäre Inside the IBM-PC von Peter Norton. Wenig später entschied sich die Firma IBM den Assembler Quellcode des IBM PC/XT BIOS frei zu veröffentlichen. Spieleentwickler optimierten ihren Code mithilfe des Wissens das sie über das BIOS gewonnen hatten. Bekannte Spiele die davon profitierten waren die ersten Ego-Shooter Quake und Doom. In diesem Tutorial werden nun auch Sie die unterste Ebene der Systemprogrammierung kennenlernen. Sie werden unterhalb einer verwalteten Schicht, wie dem .NET Framework arbeiten, unterhalb des Betriebssystems und sogar noch unterhalb von MS-DOS selbst. Sie werden erfahren was passiert wenn eine Taste auf der Tatstatur gedrückt wird, wie der Tastatur Puffer ausgelesen wird, wie Farben dargestellt werden, wie Grafiken auf den Bildschirm gezeichnet werden und wie die Maus Bewegungen registriert. Bevor wir uns jedoch der praktischen Seite der Programmierung widmen werfen wir zunächst einen Blick auf den BIOS Datenbereich. Der BIOS DatenbereichAufgrund der Tatsache das das BIOS sich auf nicht flüchtigen Speicherchips befindet, ist es nicht weiter verwunderlich das Informationen in einem Datenbereich (engl. Data Area) gespeichert werden müssen. Der BIOS ROM Datenbereich beinhaltet im Gegensatz zu den fest kodierten Instruktionen, allerdings nur einen kleinen Teil der Systemdaten, die vom BIOS Code benötigt werden. Außerhalb des eigenen Datenbereichs existiert ein weiterer Speicherbereich der vom BIOS verwaltet wird. Dieser kleine Speicherbereich hält Informationen über die Konfiguration des Computers bereit. Er speichert mitunter die Einstellungen die während des BIOS Setup getätigt werden. Während Ihr Computer unter der Kontrolle des BIOS steht, finden die wichtigsten Speicheroperationen des BIOS in speziellen Bereichen statt die vom BIOS auf dem PC angelegt werden. Sobald das BIOS den Computer initialisiert sammelt es Informationen über die Ausrüstung und Eigenschaften des Computers und platziert die Ergebnisse an bestimmten Orten in Ihrem Systemspeicher. Zu den gesammelten Daten gehören Informationen über die Anzahl und Art der installierten Ports, der Typ des Display-Adapters usw. Neben den genannten Daten werden auch Flags abgespeichert, Basisadressen der I/O Adapter, Tastaturzeichen und diverse Modi. Alle diese Daten werden an ganz bestimmten Stellen im Arbeitsspeicher hinterlegt, so dass Programme gezielt darauf zugreifen können um so an Informationen über die auf dem System verfügbaren Features zu gelangen. Beispielsweise sucht das BIOS nach seriellen Ports an spezifizierten Adressen. Alle diese Daten bilden einen vom BIOS im Arbeitsspeicher angelegten Informationsblock, genannt der BIOS Datenbereich. Der Datenbereich ist fixiert und liegt über der Interrupt Vektor Tabelle. Er umfasst 256 Bytes, angefangen bei der absoluten Startadresse 00400h und endet mit der Adresse 004FFh (0040:0000 - 0040:00FF). Die nachfolgende Tabelle 1 gibt einen Überblick über die Art der gespeicherten Daten. Die Zellenbelegung kann je nach Hersteller geringfügig abweichen.
Zusätzlich ergänzt der erweiterte BIOS Datenbereich den für den Basis BIOS Datenbereich zur Verfügung stehenden Speicherbereich. Dieser zusätzliche Speicher belegt ein weiteres Kilobyte des Arbeitsspeichers. Programme lokalisieren die Adresse des Speichers mit dem Aufruf einer Basis BIOS Funktion, genannt Interrupt 15h. Obwohl die Ingenieure zahlreiche Ideen hatten, wie sie diesen zusätzlichen Speicherbereich funktionell verwenden wollten, existiert derzeit nur eine aktive Funktion in diesem erweiterten Speicher und belegt lediglich weitere 30 Byte. Das Betriebssystem Windows verwaltet seine eigenen Datentabellen und ist nicht auf den normalen oder den erweiterten BIOS Datenbereich angewiesen. Wie schon ewähnt wurde, kontrolliert das BIOS zusätzlich eine andere Art des Speichers, den CMOS in dem die Setup Einstellungen gespeichert sind. Die BIOS Setup-Routinen sind hauptsächlich mit der Verwaltung dieses flüchtigen Speichers beschäftigt. Der CMOS wird durch einen Akku mit Strom versorgt, so dass die Daten auch bei einem längerfristigen Stromausfall erhalten bleiben. Wird die Batterie entfernt findet ein BIOS Reset statt und alle benutzerspezifischen Daten gehen verloren. Tastatur Eingaben mit INT 16hEs existieren in Assembler verschiedene Methoden um mit der Tastatur I/O-Operationen durchzuführen. Die Assemblersprache bietet dem Programmierer eine enorme Flexibilität und Kontrolle im Umgang mit der Tastatur. Da wir uns in diesem Tutorial auf der BIOS Ebene bewegen, werden wir auch gleich die Funktionen nutzen die der Computerhersteller bereits ab Werk zur Verfügung stellt. Konkret gesagt, wir verwenden den BIOS Tastatur Handler Interrupt 16h. Dieser liefert uns eine Taste aus dem Tastaturpuffer. INT 16h eignet sich hervorragend dazu die erweiterten Tastaturtasten, wie die Cursortasten oder Bild ↑ und Bild ↓ Tasten zu lesen. Jede dieser erweiterten Tasten erzeugen einen 8-Bit Scan Code der auf allen IBM-kompatiblen Computern gleich ist. Tatsächlich generieren alle Tasten einen Scan Code! Jedoch ist der ASCII Code universell und daher wird den seitens der ASCII Zeichen generierten Scan Codes keine besondere Aufmerksamkeit geschenkt. Sobald aber eine erweiterte Taste gedrückt wird, ist sein ASCII Code entweder E0h oder 00h bei den Funktions Tasten (F1, F2, ...). Der Tastatureingabe folgt ein Ereignispfad, der beim Tastatur Controller Chip beginnt und damit endet dass das Zeichen in einem 30 Byte großen Array, genannt Tastatur-typeahead-Puffer, gespeichert wird. Bis zu 15 Tastatureingaben können im Puffer gleichzeitig gehalten werden. Ein Zeichen belegt jeweils 2 Byte. Wie aus Tabelle 1 hervorgeht beginnt der Puffer an der Adresse 0040:001E und endet bei der Adresse 0040:003E. 4 Byte werden für den Head- und Tail-Zeiger benötigt die jeweils angeben an welcher Stelle die nächste Taste gelesen bzw. gespeichert wird. Die aktuelle Head-Adresse wird an der Position 0040:001A gespeichert, während die Tail-Adresse an der Adresse 0040:001C zu finden ist. Der Tastaturpuffer wird auch als zirkulierender Puffer (Ringpuffer) bezeichnet, weil die Start- und Endpunkte sich konstant drehen. In der Boot-Up Phase des Rechners zeigen Head und Tail auf diesselbe Adresse. Sobald eine Taste gedrückt wird, rückt der Tail-Zeiger um 2 Byte nach vorne. Wird ein Zeichen gelesen, wird der Head-Zeiger um 2 Byte weitergerückt. Das folgende Abbild zeigt den prinzipiellen Ablauf wenn eine Taste auf der Tastatur gedrückt wird. Nachdem die Taste betätigt wurde sendet der Controller Chip einen 8-Bit großen numerischen Scan Code an den Input Port der Tastatur. Der Input Port wurde für die Behandlung von Interrupts konstruiert. Nachdem ein Interrupt ausgelöst wurde reagiert die CPU umgehend und führt die INT 9h Service Routine aus. Diese empfängt den Tastatur Scan Code vom Input Port und sucht nach dem zugehörigen ASCII Code. Anschließend werden beide Werte im typeahead-Puffer gespeichert und stehen absofort allen laufenden Programmen zur Verfügung. Unsere Eingaben werden wir mit INT 16h aus dem Tastatur-typeahead-Puffer lesen. Dabei werden der ASCII-Code als auch der Scan Code in einem Schritt gelesen. Dies ist besonders dann von Vorteil wenn erweiterte Tasten empfangen werden müssen, da diese eben keine ASCII-Codes besitzen. Auf MS-DOS Ebene und darüber können auch Funktionen aufgerufen werden die INT 21h verwenden. Im Gegensatz zu INT 16h muss hier aber im Falle einer erweiterten Taste ein zweiter Aufruf erfolgen um den Scan Code zu empfangen. Parallel zu dem bereits genannten Vorteil, besitzt INT 16h noch weitere zusätzliche Eigenschaften. So lässt sich mit INT 16h auch der Status der Tastatur Flags empfangen. Darüberhinaus ist es möglich die Typematic Rate zu setzen. Mit der Typematic Rate wird der Wert eingestellt, wann die Tastenfunktion nach dem Drücken einsetzt. Die verfügbaren Werte variieren zwischen den BIOS Herstellern geringfügig. Beim AMI BIOS sind die Werte 250, 500 (Standard) oder 1.000 Millisekunden einstellbar. Beim Award BIOS sind es 250, 500, 750, 1000 Millisekunden. Nachfolgend sehen Sie ein kleines Programm das eine einfache Zeicheneingabe mittels INT 16h realisiert und gleichzeitig die Typematic Rate der Tastatur auf 1000ms setzt: TITLE INT 16h Examples (typekey.asm) COMMENT ! Dieses Programm gibt einen zuvor mit INT 16h eingegebenen String wieder aus und setzt die Typematic Rate auf 1000ms. Last update: 25/05/06 ! .model small, stdcall ; 1 Code und 1 Data Segment .stack 200h .386 .data strsize WORD 8 enterstr BYTE "Enter some Characters: $" message BYTE 0dh, 0ah, "You entered: $" quit BYTE 0dh, 0ah, "Press ESC to quit!$" target BYTE SIZEOF strsize DUP(0) .code main PROC mov ax, @data ; Initialisiere das DS Segment Register mov ds, ax mov dx, 0 call clrScreen ; Lösche den Bildschirm mov ah, 9 ; Schreibe $-terminierten String auf Standardausgabe mov dx, OFFSET enterstr ; Adresse des Zeichen Arrays int 21h ; Rufe den Interrupt auf mov ax, 0305h ; Setze die Typematic Rate mov bh, 3 ; Verzögerung (Repeat Delay) 3 = 1000ms mov bl, 0Fh ; Repeat Rate auf 0 setzen (0Fh = schnellste) mov si, 0 ; 16-Bit Index Register mov cx, strsize ; Schleifenzähler L1: mov ah, 10h ; Tastatur Input int 16h ; Verwende den BIOS Interrupt Handler mov target[si], al ; Speicher das Zeichen im target Array inc si ; Nexter Index im String mov ah, 2 ; Einzelnes Zeichen auf Standardausgabe mov dl, al int 21h loopd L1 ; L1 Schleife (Register CX fungiert als Zähler) mov ah, 9 mov dx, OFFSET message ; Ausgabe von message int 21h mov ah, 40h ; Schreibe in File/Device mov bx, 1 ; Ausgabe Handle (Konsole = 1) mov cx, si ; Anzahl der zu schreibenden Zeichen mov dx, OFFSET target int 21h mov ah, 9 mov dx, OFFSET quit ; Ausgabe von quit int 21h L2: mov ah, 10h int 16h cmp al, 1Bh ; ESC Taste gedrückt? jne L2 ; Nein, springe an L2 mov ah, 4Ch ; Beende den Prozess (analog zu ExitProcess, 4C00h) mov al, 0 ; Gebe einen Return Code zurück int 21h ; Rufe den Interrupt auf main ENDP ;------------------------------------------------------ ; clrScreen ; Bereinigt den Bildschirm (video page 0) und lokalisiert ; den Cursor auf Reihe 0 und Spalte 0. ;------------------------------------------------------- clrScreen PROC pusha mov ax, 0600h ; Scrolle das Fenster hoch mov cx, 0 ; Obere linke Ecke (0,0) mov dx, 184Fh ; Untere rechte Ecke (24,79) mov bh, 7 ; Normale Attribute int 10h ; Rufe BIOS mov ah, 2 ; Lokalisiere Cursor auf 0,0 mov bh, 0 ; Video Page 0 mov dx, 0 ; Reihe 0, Zeile 0 int 10h popa ret clrScreen ENDP END main Das obige Beispiel nutzt die INT 16h Funktion 10h um die nächste verfügbare Taste aus dem typeahead Puffer zu entfernen. Falls sich keine Taste im Puffer befindet, wartet der Tastatur Handler solange bis eine Taste eingegeben wurde. Der Scan Code wird im AH (High) Register abgespeichert, während der ASCII Code im AL (Low) Register zu finden ist. Oft möchte man wissen wie der Inhalt der Register aussieht nachdem eine Taste betätigt wurde. Welche Scan und ASCII Codes werden von einer normalen Taste generiert? Wie sieht es bei einer erweiterten Taste aus? Was passiert mit den Status-Flags? Das folgende Programm beantwortet diese Fragen indem es nach jedem Tastendruck den Inhalt der Register ausgibt. TITLE Keyboard ShowRegisters (showkeybd.asm) COMMENT ! Dieses Programm zeigt den Inhalt der Register nach jedem Tastendruck an. Last update: 25/05/06 ! .model small, stdcall ; Code < 64k ; Data < 64k .stack 200h .386 DumpRegs PROTO ; Prototyp für die Funktion DumpRegs .code main PROC mov ax, @data mov ds, ax L1: mov ah, 10h int 16h call DumpRegs ; Funktion ist in der Win16.lib definiert (Ausgabe ; aller Registerinhalte samt CPU Flags) cmp al, 1Bh ; Escape gedrückt? jne L1 ; Nein, gehe zurück zu L1 mov ah, 4Ch ; Beende den Prozess mov al, 0 int 21h main ENDP END main Da sowohl das AH als auch das AL Register Teil des größeren 16-Bit AX Registers sind, welches widerum Teil des noch größeren 32-Bit EAX ist, können diese entsprechend mit 8-Bit adressiert werden. Sehen Sie sich dazu einmal den Inhalt des EAX Registers nach Betätigung der Taste 'R' an. Die Taste generiert den ASCII Code 52 und den Scan Code 13 in hexadezimaler Notation. Wie schon erwähnt wurde besitzen die erweiterten Tasten keinen ASCII Code, weshalb sie anhand des Scan Codes identifiziert werden müssen. Die Funktionstasten generieren deshalb allesamt den gleichen ASCII Code 00h, während Tasten wie z.B. die ↓ Bild ab Taste, den ASCII Code E0h generieren. Sie können testweise mit dem Programm experementieren und sich alle Tastencodes genauer ansehen. Tastaturpuffer und Tastatur FlagsINT 16h ist ein Tastatur Handler der eine ganze Reihe weiterer nützlicher Funktionen zur Verfügung stellt. Wir haben bereits einige dieser Funktionen kennengelernt. Dazu gehörte 03h um die Typematic Rate der Tastatur zu setzen und 10h um auf Tastenbetätigungen zu warten. INT 16h bietet aber noch weitere Funktionen die nützlich sein können. So ermöglicht es die Funktion 03h manuell Tasten in den Tastaturpuffer zu schieben. Zu diesem Zweck kann ein beliebiger Scan Code in das CL Register und ein entsprechender ASCII Code in das CH Register kopiert werden. Mit der Funktion namens 11h ist der Programmierer anschließend auch in der Lage den Tastaturpuffer zu überprüfen. Die Funktion schaut nach wartenden Tasten im Puffer. Auf diese Weise kann man eine Funktion innerhalb einer Schleife nutzen um gezielt weitere Programmaufgaben erledigen zu lassen. Falls sich eine Taste im Tastaturpuffer befindet, ist das Zero Flag (ZF) nicht gesetzt. Andernfalls ist sein Inhalt 1. Die anstehende Taste kann nach einer erfolgreichen Überprüfung aus den entsprechenden Registern (AH & AL) ausgelesen werden. Auch die Tastatur Flags offerieren Informationen die sehr nützlich sind. Beispielsweise zeigt das sechste Bit im Tastatur Flag an das die Caps Lock Taste geschaltet ist. Wenn Sie mit Word oder einer anderen Software zur Textverarbeitung arbeiten, muss die Anwendung den Flag Status kennen um darauf reagieren zu können. Dazu überprüft das Programm kontinuierlich das Status Flag auf Änderungen. Das Status Flag verrät dem Programm aber noch einige weitere Dinge. Zum Beispiel ob eine Taste momentan gedrückt wird oder nicht. Tabelle 2 zeigt die wesentlichen Status Flags der Tastatur.
Viele Programme benutzen häufig Schleifen um bestimmte Operationen immer wieder nacheinander auszuführen. Spiele wiederholen beispielsweise ständig Animationen und geben Sequenzen auf den Bildschirm aus. Oft ist es so, dass das Spiel Tasteneingaben ignoriert und nur auf bestimmte Eingaben möglichst sofort reagieren soll. Der Benutzer füllt jedoch den Tastaturpuffer möglicherweise mit unnützen Tastencodes auf. Wie kann das Programm dann auf neue Tasten umgehend reagieren? Wir können mit den bisher gewonnen Informationen dieses Problem lösen. Dazu benötigen wir ledeglich die Funktionen 11h und 10h. Das folgende Programm zeigt ununterbrochen einen laufenden Punkt auf dem Bildschirm an. Sie können versuchen den Tastaturpuffer mit beliebigen Daten aufzufüllen. Sobald Sie jedoch die Abbruchtaste betätigen reagiert das Programm umgehend darauf und beendet die Ausführung. TITLE Tastaturpuffer kontinuierlich leeren (clearkeybd.asm) COMMENT ! Dieses Programm zeigt wie man den Tastaturpuffer leeren kann, während man auf die Eingabe einer bestimmten Taste wartet. Dazu wird in einer Schleife ein Punkt ausgegeben und überprüft ob eine Taste gedrückt wurde. Falls ja, wird diese aus dem Puffer wieder entfernt. Handelte es sich um die Abbruchtaste, bricht das Programm ab. Last update: 25/05/06 ! .model small, stdcall .stack 200h .386 NewLine MACRO ; Zeilenumbruch Makro mov dl,0dh mov ah,02 int 21h mov dl,0ah mov ah,02 int 21h ENDM ClearKeyboard PROTO, scanCode:BYTE ESC_key = 1 ; Scan Code .code main PROC L1: mov ah, 2 ; Zeigt einen Punkt an mov dl, '.' int 21h mov eax, 300 ; Verzögerung auf 300ms stellen call Delay ; Prozedur Delay aufrufen INVOKE ClearKeyboard, ESC_key ; Überprüfe auf Escape Taste jnz L1 ; Wiederhole die Schleife falls ZF = 0 quit: call clrScreen mov ah, 4Ch mov al, 0 int 21h main ENDP ;--------------------------------------------------- ; ClearKeyboard ; Leert den Puffer während es auf die Eingabe einer ; bestimmten Taste wartet. ; Empfängt: Tastaur Scan Code ; Rückgabewert: Zero flag wird gesetzt falls der ; ASCII Code gefunden wurde. Ansonsten wird das ; ZF geleert. ;--------------------------------------------------- ClearKeyboard PROC, scanCode:BYTE push ax L1: mov ah,11h ; Überprüfe den Tastaturpuffer int 16h ; Wurde eine Taste gedrückt? jz noKey ; Nein, dann beende den PROC (ZF = 0) mov ah, 10h ; Ja, entferne ihn aus dem Puffer int 16h cmp ah, scanCode ; War es die Abbruchtaste? je quit ; Ja, dann beende den PROC (ZF = 1) jmp L1 ; Nein, überprüfe den Puffer nochmal noKey: ; Keine Taste gedrückt or al, 1 ; Bereinige das Zero Flag mittels x ODER 1 quit: pop ax ret ClearKeyboard ENDP ;------------------------------------------------------ ; clrScreen ; Bereinigt den Bildschirm (video page 0) und lokalisiert ; den Cursor auf Reihe 0 und Spalte 0. ;------------------------------------------------------- clrScreen PROC pusha mov ax, 0600h ; Scrolle das Fenster hoch mov cx, 0 ; Obere linke Ecke (0,0) mov dx, 184Fh ; Untere rechte Ecke (24,79) mov bh, 7 ; Normale Attribute int 10h ; Rufe BIOS mov ah, 2 ; Lokalisiere Cursor auf 0,0 mov bh, 0 ; Video Page 0 mov dx, 0 ; Reihe 0, Zeile 0 int 10h popa ret clrScreen ENDP ;----------------------------------------------------------- ; Delay ; Generiere eine n-Millisekunden Verzögerung. ; Empfängt: EAX = Millisekunden ; Rückgabewert: - ; Anmerkung: Diese Prozedur nutzt direkt die Hardware Ports! Eventuell ; funktioniert es nur unter Windows 95, 98, ME bzw. direkt im DOS-Modus. ; Unter Windows NT (2000, XP, ...) kann es sein das die NTVDM die ; Ausführung nicht gestattet bzw. das Programm nicht ordnungsgemäß abläuft. ; Quelle: "The 80x86 IBM PC & Compatible Computers" by ; Mazidi and Mazidi, page 343. Prentice-Hall, 1995. ;----------------------------------------------------------- Delay PROC MsToMicro = 1000000 ; Konvertiere in Millisekunden ClockFrequency = 15085 ; Mikrosekunden pro Takt .code pushad mov ebx, MsToMicro ; Konvertiere Millisekunden in Mikrosekunden mul ebx mov ebx, ClockFrequency ; Teile durch die Taktfrequenz von 15.085 micsek, ; um den Counter für Port 61h zu generieren. div ebx ; eax = counter mov ecx,eax L1: ; Beginne mit der Überprüfung von Port 61h. Halte ; Ausschau nach Änderungen am vierten Bit zwischen ; 1 und 0 alle 15.085 Mikrosekunden. in al, 61h ; Lese Port 61h and al, 10h ; Bereinige alle Bits außer Bit 4 cmp al, ah ; Hat es sich verändert? je L1 ; Nein, springe zurück mov ah, al ; Ja, speichere den Status dec ecx cmp ecx, 0 ; Sind wir fertig? ja L1 quit: popad ret Delay ENDP END main Video Programmierung mit INT 10hVielleicht kennen Sie noch die alten MS-DOS Spiele aus den frühen 90er Jahren? Die Grafikchips waren in ihrer Leistung und ihrem Funktionsumfang noch relativ bescheiden. Technologien heutiger High-End Grafikprozessoren die mit den neuesten Vertex- und Pixel-Shadern arbeiten, parallel mehrere Rendering-Pipelines zur Verfügung stellen und Transistoren jenseits der 130 Millionen Marke auf einem einzelnen Chip beherbergen standen noch nicht zur Verfügung. So ist es aus heutiger Sicht nicht weiter verwunderlich das die damaligen Spiele im Gegensatz zu aktuellen Grafik-Knüllern etwas antiquitiert wirken. Neue APIs wie Direct3D, DirectDraw und OpenGL stellen dem Programmierer von heute eine riesige Anzahl an Werkzeugen zur Grafikprogrammierung zur Auswahl. Hersteller moderner GPUs versuchen ständig die Spezifikationen neuer API-Versionen zu erfüllen und zu übertreffen. Doch es war ein langer und steiniger Weg bis die ersten brauchbaren APIs den Grafikprogrammieren auf dem Massenmarkt zur Verfügung standen. Als mit Windows 95 erstmals der Nachfolger von MS-DOS Ende des Jahres 1995 erschien, kam ein Problem auf das von Microsoft gelöst werden musste. Während DOS noch den direkten Zugriff auf die Videokarte, die Tastatur und Maus sowie das Soundsystem und andere Komponenten des Systems gewährte, baute Windows 95 auf dem Protected Mode auf, in dem kein direkter bzw. nur ein eingeschränkter Zugriff (signierte Treiber) auf das System gestattet wird. Microsoft benötigte eine Methode den Programmierern eine Schnittstelle zur Verfügung zu stellen ohne das Model des "Geschützten Modus" zu unterwandern. Das war die Geburtsstunde von DirectX. Im Laufe der Zeit entwickelte sich DirectX zu einer Sammlung von APIs für alle Bereiche der Spieleprogrammierung inklusive exzellenter Sound Komponenten (DirectX Sound) und vorgefertigten Installationsroutinen (DirectSetup). Microsoft verwendete aber in seinen ersten Betriebssystemen der NT-Serie die damals noch weitaus ausgreiftere OpenGL API die de facto als Industriestandard galt. Als Ende der 90er die Hardware Komponenten immer günstiger wurden und sich auch der Otto Normalverbraucher leistungsstarke Hardware kaufen konnte, wurde die Entwicklung von DirectX rasant beschleunigt und ist dafür verantwortlich das DirectX heute eine professionelle und leistungsstarke API-Sammlung zur Spieleprogrammierung ist. Mit dem Auftauchen von Windows Vista am Horizont wird diese Entwicklung fortgesetzt und die neue Windows Graphics Foundation, auch DirectX 10 genannt, bedeutet einen weiteren Quantensprung in der Programmierung. Alle neuen Schnittstellen haben eines gemeinsam: Sie stellen eine weitere Abstraktionsschicht dar und trennen den Programmierer von der Hardware und den zugrundeliegenden Technologien ab. Das ist der Preis für eine standardisierte und plattformunabhängige API. Ein Preis der von Vielen zugunsten einer höheren Produktivität gerne gezahlt wird. Wir hingegen bewegen uns knapp über der Hardwareebene und wollen in diesem Tutorial erfahren wie die Video Programmierung mithilfe der BIOS-Dienste funktioniert. Dazu sehen wir uns zunächst einmal die möglichen Level des Zugriffs an. Wenn ein Programm beispielsweise Zeichen im Textmodus auf dem Bildschirm darstellen möchte kann es effektiv zwischen drei verschiedenen Zugriffsebenen wählen.
Je nach nachdem was das Programm leisten soll, muss sich der Entwickler entscheiden welche der drei Zugriffsmethoden er auswählt. Die Methoden bauen in Schichten aufeinander auf. Während die MS-DOS Interrupts auf BIOS-Level Routinen zurückgreifen, verwenden die BIOS Routinen widerum den direkten Video Zugriff um die Ausgabe zu erzeugen. Text-ModusEs existieren zwei verschiedene Arten von Video-Modi, der Text- und der Grafik-Modus. Im Text-Modus wird der Bildschirm von oben bis unten in Reihen und Spalten aufgeteilt. Je nachdem welche Schriften/Fonts gerade ausgewählt sind wird die Zeilenhöhe bestimmt. Die Fonts befinden sich in einem Speicher und können in neuen BIOS Versionen auch geändert werden. Dadurch lassen sich individuelle Fonts für den Text-Modus gestalten. Der Video Speicher für den Text-Modus ist in multiple separate Bildschirmseiten (engl. Video Text Pages) aufgeteilt. Programme können eine Seite präsentieren und gleichzeitig Text zu anderen versteckten Seiten schreiben. Die Standard Video Seite ist die Seite 0. Jedem Zeichen wird ein Byte mit Attributwerten zugeteilt in dem die Hintergrundfarbe und die Farbe des Zeichens definiert ist. Die Zeichen sind exakt auf dem Bildschirm positioniert und können auch zum Blinken gebracht werden. Der Video Controller invertiert zu diesem Zweck die Vorder- und Hintergrundfarbe abwechselnd in einer festgelegten Taktrate. Das Blinking kann mithilfe einer BIOS Funktion an- und abgeschaltet werden. Alle Pixel auf jedem gängigen Röhren-Monitor und auch auf allen modernen LCD-Monitoren werden mithilfe eines additiven Farbmodells erzeugt. Innerhalb eines so genannten RGB-Farbraums entstehen auf diese Weise sichtbare Farben indem jeweils der Anteil einer Grundfarbe gelöscht bzw. aktiviert wird. Die Grundfarben sind Rot, Grün und Blau. Um also die Farbe eines Pixels festzulegen ist die Kontrolle der drei Grundfarben erforderlich. Dazu steht ein 4-Bit großer Werteraum zur Verfügung. Die ersten drei Bits entscheiden darüber welche Grundfarben im Pixel leuchten sollen und welche nicht. Das vierte Bit gibt die Intensität der Leuchtkraft des gesamten Pixels an. Beispielsweise wird die Farbe Schwarz intern als 0000 dargestellt, die Farbe Weiß hingegen mit 1111 und die Farbe Rot mit 0100. Ein helles strahlendes Rot erhält man indem man zusätzlich das vierte Bit auf 1 setzt. Das folgende Abbild zeigt den Aufbau eines Attribut Bytes bestehend aus 8 Bit. Das linke Nibble ist für die Hintergrundfarbe verantwortlich und das rechte Nibble für die Vordergrundfarbe. Falls das Blinking aktiviert ist wird das Intensitätsbit des Hintergrundes als Identifikationsbit verwendet. In diesem Fall ist es nicht möglich eine strahlende Farbe für den Hintergrund zu verwenden. Unter Windows ist das Blinking auf den Vollbildmodus beschränkt. Um in Assemblersprache ein entsprechendes Attribut Byte aus zwei Farben zu erzeugen ist ledeglich ein Shift sowie eine Oder Operation erforderlich. Der x86-Befehlssatz stellt hierfür den Befehl SHL (Shift left) zur Verfügung. Das nachfolgende Beispiel zeigt im Ansatz wie ein blauer Hintergrund mit gelben Zeichen erzeugt werden kann. blue = 1 yellow = 1110 mov bh, (blue SHL 4) OR yellow ; Erzeugt 00011110 Im nächsten Abschnitt werden wir uns die wesentlichen Funktionen ansehen die INT 10h dem Programmierer zur Verfügung stellt. Tabelle 3 beschreibt diese Funktionen kurz.
Die Funktion 0 von INT 10h setzt den Video-Modus. Bevor ein neuer Video-Modus gesetzt wird sollte der alte Modus in einer Variable zwischengespeichert werden. Auf diese Weise lässt sich kurz vor dem Programmende der alte Zustand wieder herstellen. Das nachfolgende Beispiel zeigt wie das prinzipiell aussehen könnte. mov ah, 0Fh ; Aufruf von 0Fh int 10h mov vmode, al ; Speichert die Nummer des Modus in der Variable vmode mov columns, ah ; Speichert die Anzahl der Spalten (Zeichen oder Pixel) mov page, bh ; Speichert die aktive Bildschirmseite (Video Page) mov ah, 0 ; Aufruf von 00h mov al, 3 ; Setzt den Video Modus 3 (farbiger Text) int 10h Die Funktionen 1-3 dienen hauptsächlich diversen Manipulationen des Cursors. Eine relativ einfache Möglichkeit den Cursor zu verstecken ist zum Beispiel einen ungültigen bzw. zu großen Wert einzugeben. Die beiden Prozeduren HideCursor und ShowCursor veranschaulichen dies. HideCursor PROC mov ah, 3 ; Empfängt die Cursor Größe int 10h or ch, 30h ; Setzt die obere Reihe auf einen ungültigen Wert mov ah, 1 ; Setzt die Cursor Größe int 10h ret HideCursor ENDP ShowCursor PROC mov ah, 1 ; Setzt die Cursor Größe mov cx, 0607h ; Standardgröße int 10h ret ShowCursor ENDP Die Funktion 6 scrollt ein rechteckiges Fenster entlang. Ein Fenster kann zuvor entsprechend definiert werden. Die Größe des Fensters auf dem Bildschirm ist gegeben durch zwei 2D-Koordinaten. Die Punkte bestimmen wo jeweils die gegenüberliegenden Eckpunkte liegen. Diese Eckpunkte spannen das Fenster auf. Die Register CH und CL definieren die Reihe und Spalte des linken oberen Eckpunktes. Der untere rechte Eckpunkt wird durch die Register DH und DL definiert. Wird entlang eines Fensters hinab oder hinauf gescrollt kann das Fenster auf diese Weise vollständig bereinigt werden. Das folgende Beispiel zeigt ein vollständiges Assembler Programm das ein Fenster mitsamt Text erzeugt. Sobald die Taste 'd' gedrückt wird, löscht die Funktion 06h den Text des erstellten Fensters. TITLE Write and delete Color Text Window (wrianddelctw.asm) COMMENT ! Dieses Programm demonstriert wie sich ein Fenster generieren lässt und sich Zeichen in diesem Fenster löschen lassen. Dazu wird der farbige Text-Modus verwendet. Last update: 25/05/06 ! .model small, stdcall .stack 200h .386 .data message BYTE "Welcome to another CodePlanet Tutorial!", 0 press BYTE "(Press 'd' to delete this message)", 0 .code main PROC mov ax, @data mov ds, ax mov ax, 0600h ; Scrollt ein Fenster entlang mov bh, 00011110b ; Setzt alle Attribute der Zellen auf blaue Hintergrundfarbe ; und gelbe Vordergrundfarbe mov cx, 050Ah ; Linker oberer Eckpunkt mov dx, 0A42h ; Rechter unterer Eckpunkt int 10h mov ah, 2 ; Positioniert den Cursor innerhalb der Fensters mov dx, 0714h ; Reihe 7, Spalte 20 mov bh, 0 ; Video Page 0 int 10h mov dx, OFFSET message ; Schreibt einen Text auf den Bildschirm call WriteString mov ah, 2 mov dx, 0814h mov bh, 0 int 10h mov dx, OFFSET press call WriteString mov ah, 10h int 16h cmp al, 64h ; Wurde die Taste 'd' gedrückt? jne Exit Del: mov ah, 6 ; Löscht die Zeichen in dem aufgespannten Fenster mov al, 0 mov ch, 5 mov cl, 0Ah mov dh, 0Ah mov dl, 42h mov bh, 00011110b int 10h mov ah, 10h ; Warte auf einen Tastendruck int 16h Exit: mov ah, 4Ch mov al, 0 int 21h main ENDP ;--------------------------------------------------------- ; Str_length ; Gibt die Länge eines einen Null-terminierten Strings zurück. ; Empfängt: pString - Zeiger auf den String ; Rückgabewert: AX = Stringlänge ;--------------------------------------------------------- Str_length PROC USES di, pString:PTR BYTE mov di, pString mov ax, 0 ; Zeichen Zähler L1: cmp byte ptr[di], 0 ; Ende des Strings? je L2 ; Ja, springe zu quit inc di ; Nein, gehe zum nächsten Zeichen inc ax ; Addiere 1 zum Zähler jmp L1 L2: ret Str_length ENDP ;-------------------------------------------------------- ; WriteString ; Schreibt einen Null-terminierten String auf die Ausgabe ; Empfängt: DS:DX zeigt auf den String ; Rückgabewert: - ;-------------------------------------------------------- WriteString PROC pusha INVOKE Str_length, dx ; AX = string length mov cx, ax ; CX = number of bytes mov ah, 40h ; write to file or device mov bx, 1 ; standard output handle int 21h ; call MS-DOS popa ret WriteString ENDP END main Vielleicht kennen Sie Programme die den Text der auf dem Bildschirm präsentiert wird, über das Soundsystem akustisch wiedergeben. Eine mögliche Variante einen Teil dieser Funktionalität zu realisieren ist die INT 10h Funktion 08h zu benutzen. Die Funktion liest das Zeichen an der aktuellen Cursor Position aus. Mithilfe der beiden nachfolgenden Funktionen kann ein Zeichen samt Attribut auch geschrieben werden. Die Funktion 09h unterscheidet sich von 0Ah nur in der Spezifikation des Attribut Bytes. Dieses ist bei 0Ah nicht anzugeben. Wird ein Zeichen geschrieben, so muss in den meisten Fällen anschließend manuell der Cursor um die Anzahl der geschriebenen Zeichen nach vorne verschoben werden. .data char BYTE 0 attribute BYTE 0 mov ah, 8 ; Aufruf von 08h. Die Funktion liest ein Zeichen ; und das zugehörige Attribut Byte aus. mov bh, 0 ; Video Page 0 int 10h mov char, al ; Speichert das Zeichen in der Variable char mov attribute, ah mov ah, 9 ; Die Funktion 08h schreibt ein Zeichen samt Attribut ; an die aktuelle Cursor Position. mov al, 'A' mov bh, 0 mov bl, 0F1h ; Attribut blau auf weiß mov cx, 1 ; Gibt an wie oft das Zeichen 'A' wiederholt wird int 10h Wie bereits erwähnt wurde ist es im Text-Modus möglich die Zeichen zum Blinken zu bringen wenn das Blinking aktiviert wurde. Mit der Unterfunktion 03h lässt sich das Blinking sowie die Intensität kontrollieren indem direkt das vierte Bit manipuliert wird. Der Quellcode textblinking.asm veranschaulicht die Anwendung der Funktion. TITLE Color Text Blinking (textblinking.asm) COMMENT ! Dieses Programm gibt einen blinkenden Text in verschiedenen Farben aus. Die Option Blinking steht unter Windows nur im Vollbildmodus zur Verfügung! Last update: 25/05/06 ! .model small, stdcall .stack 200h .386 NewLine MACRO ; Zeilenumbruch mov dl, 0dh mov ah, 02 int 21h mov dl, 0ah mov ah, 02 int 21h ENDM .data ATTRIB_HB = 10000000b string BYTE "I'm Blinking!" color BYTE 1 .code main PROC mov ax, @data mov ds, ax call clrScreen ; Leere den Bildschirm call EnableBlinking ; Aktiviere das Blinking mov cx, SIZEOF string mov si, OFFSET string L1: push cx ; Speichere den Schleifenzähler mov ah, 9 ; Schreibe das Zeichen/Attribut mov al, [si] ; Zeichen das angezeigt werden soll mov bh, 0 ; Video Page 0 mov bl, color ; Attribut or bl, ATTRIB_HB ; Setze das Blinking/Intensitäts Bit mov cx, 1 ; Zeige es einmal an int 10h mov cx, 1 ; Verschiebe den Cursor nach call AdvanceCursor ; Nächste Bildschirm Spalte inc color ; Nächste Farbe inc si ; Nächstes Zeichen pop cx ; Stelle den Schleifenzähler wieder her! loop L1 NewLine ; Makro mov ah, 4Ch mov al, 0 int 21h main ENDP ;-------------------------------------------------- ; EnableBlinking ; Aktiviert das Blinking indem das höchste Bit ; im Attribut Byte auf 1 gesetzt wird. ; Funktioniert unter Windows nur im Vollbildmodus. ;-------------------------------------------------- EnableBlinking PROC push ax push bx mov ax, 1003h mov bl, 1 ; Blink-Modus (Intensität = 0, Blinking = 1) int 10h pop bx pop ax ret EnableBlinking ENDP ;-------------------------------------------------- ; AdvanceCursor ; Verschiebt den Cursor n Spalten nach rechts. ; Empfängt: CX = Anzahl der Spalten ; Rückgabewert: - ;-------------------------------------------------- AdvanceCursor PROC pusha L1: push cx ; Schleifenzähler mit n initialisiert mov ah, 3 ; Empfange die aktuelle Cursor Position - 03h mov bh, 0 ; Video Page 0 (ins DH, DL) int 10h ; Der Aufruf verändert das CX Register! inc dl ; Inkrementiere die Spalten mov ah, 2 ; Setze die Cursor Position int 10h pop cx ; Stelle den Schleifenzähler wieder her! loop L1 ; Nächste Spalte popa ret AdvanceCursor ENDP ;------------------------------------------------------ ; clrScreen ; Bereinigt den Bildschirm (video page 0) und lokalisiert ; den Cursor auf Reihe 0 und Spalte 0. ;------------------------------------------------------- clrScreen PROC pusha mov ax, 0600h ; Scrolle das Fenster hoch mov cx, 0 ; Obere linke Ecke (0,0) mov dx, 184Fh ; Untere rechte Ecke (24,79) mov bh, 7 ; Normale Attribute int 10h ; Rufe BIOS mov ah, 2 ; Lokalisiere Cursor auf 0,0 mov bh, 0 ; Video Page 0 mov dx, 0 ; Reihe 0, Zeile 0 int 10h popa ret clrScreen ENDP END main Unter Windows NT muss das Programm im Vollbildmodus ausgeführt werden. Öffnen Sie dazu die Eigenschaften des Programms und wählen Sie die Registerkarte Bildschirm. Setzen Sie einen Haken im Kontrollkästchen Vollbild. Falls dies nicht zum Erfolg führt, können Sie die Kommandozeile separat öffnen und über Alt+Enter in den Vollbildmodus schalten. Anschließend sollten Sie das Programm ohne Probleme aufrufen können. Grafik-ModusNeben den verschiedenen Text-Modi existieren auch einige Grafik-Modi (Graphics Mode). Mithilfe der INT 10h Funktion 0Ch lassen sich Pixel und Linien sehr einfach auf den Bildschirm zeichnen. Bevor Sie jedoch Pixel auf den Bildschirm zeichnen können, müssen Sie den Video Adapter in einen standardisierten Grafik-Modus bringen. Einen Überblick über die vorhandenen Video-Modi finden Sie in der Tabelle 4. Der Modus lässt sich genau wie im Abschnitt "Text-Modus" erklärt über die Funktion INT 10h 00h setzen.
Die Funktion 0Ch schreibt nicht direkt in den Video RAM, so dass es beim Zeichnen vieler Pixel zu geringfügigen Verzögerungen kommen kann. Die Verzögerung hängt proportional mit der Anzahl der Zeichenaufrufe für einen Pixel zusammen. Neben der Möglichkeit Pixel zu zeichnen ist es auch möglich ein Pixel vom Bildschirm zu lesen. Der prinzipielle Code für das Schreiben und Lesen eines Pixels kann folgendermaßen aussehen. ;-------------------------------------------------- ; INT 10h Funktion 0Ch ; Schreibt einen Pixel auf den Bildschirm. ;-------------------------------------------------- mov ah, 0Ch mov al, pixelValue ; Der Pixelwert mov bh, videoPage ; Die Bildschirmseite mov cx, xcoord ; X-Koordinate mov dx, ycoord ; Y-Koordinate int 10h ;-------------------------------------------------- ; INT 10h Funktion 0Dh ; Liest einen Pixel vom Bildschirm. Die Funktion ; gibt das Pixel im AL-Register zurück. ;-------------------------------------------------- mov ah, 0Dh mov bh, 0 ; Die Bildschirmseite mov cx, xcoord mov dx, ycoord int 10h Bevor wir uns einer deutlich schnelleren Variante Pixel auf den Bildschirm zu zeichnen zuwenden, werden wir das bisher errungene Wissen dazu benutzen ein einfaches Schachbrett auf den Bildschirm zu zeichnen. Das folgende Programm zeichnet zunächst das einfache Gitternetz und füllt danach die weißen Felder auf. Dies dient ledeglich Demonstrationszwecken und ist für das Endresultat nur bedingt erforderlich. TITLE Chess field (chessfield.asm) COMMENT ! Dieses Programm schaltet in den Grafik-Modus 6Ah (800 x 600) und zeichnet zunächst ein Gitternetz aus Schachfeldern. Anschließend füllt das Programm abwechselnd die entsprechenden Felder weiß auf, so dass am Ende ein Schachbrett auf dem Bildschirm zu sehen ist. Last update: 25/05/06 ! .model small, stdcall .stack 200h .386 ;------------------------------------------------------ ; Video-Modi Konstanten ;------------------------------------------------------ Mode_06 = 6 ; 640x200, 2 colors Mode_0D = 0Dh ; 320 x 200, 16 colors Mode_0E = 0Eh ; 640 x 200, 16 colors Mode_0F = 0Fh ; 640 x 350, 2 colors Mode_10 = 10h ; 640 x 350, 16 colors Mode_11 = 11h ; 640 x 480, 2 colors Mode_12 = 12h ; 640 x 480, 16 colors Mode_13 = 13h ; 320 x 200, 256 colors Mode_6A = 6Ah ; 800 x 600, 16 colors white = 1111b .data saveMode BYTE ? ; Speichert den Video-Modus fRows WORD 20 ; Speichert die Reihe der horizontalen Linie fCols WORD 120 ; Speichert die Spalte der vertikalen Linie tmp WORD 0 ; Temporäre Variable innerCounter Word 0 ; Innerer Schleifenzähler outerCounter WORD 1 ; Äußerer Schleifenzähler fields BYTE 0 ; Anzahl der Felder pro Spalte fieldsCols WORD 0 ; Anzahl der Spalten .code main PROC mov ax, @data mov ds, ax mov ah, 0Fh ; Empfange den aktuell eingestellten Video-Modus int 10h mov saveMode, al ; Speichere diesen Video-Modus mov ah, 0 ; Setze den Video-Modus mov al, Mode_6A ; Nimm den Grafik-Modus mit 800 x 600 und 16 Farben int 10h Rows: ; Zeichne die horizontalen Gitterlinien mov cx, 120 ; X-Koordinate der Starlinie mov dx, fRows ; Y-Koordinate der Starlinie mov ax, 560 ; Länge der Linie mov bl, white ; Farbe der Linie call DrawHorizLine ; Zeichne die horizontale Linie add fRows, 70 ; Nächste Linie cmp fRows, 580 ; Sind wir fertig? jbe Rows ; Falls fRows <= 580 springe zurück Cols: mov cx, fCols ; Zeichne die vertikalen Gitterlinien mov dx, 20 mov ax, 560 mov bl, white call DrawVerticalLine ; Zeichne die vertikale Linie add fCols, 70 cmp fCols, 680 jbe Cols ; Falls fCols <= 680 springe zurück mov tmp, 20 ; Setze die temporäre Variable auf x = 20 add fieldsCols, 120 ; Setze die Variable auf y = 120 ; Oberer linker Eckpunkt P(120|20) L1: ; Fülle nun die weißen Felder aus indem jeweils ; 70 horizontale Linien gezeichnet werden mov cx, fieldsCols mov dx, tmp mov ax, 70 mov bl, white call DrawHorizLine inc innerCounter ; Inkrementiere den inneren Schleifenzähler add tmp, 1 cmp innerCounter, 70 jb L1 add fields, 1 ; Inkrementiere die Anzahl der weißen Felder in der Spalte cmp fields, 4 ; Haben wir 4 Felder gezeichnet? jae NextCol mov innerCounter, 0 ; Nein, inkrementiere die innere Schleife add tmp, 70 ; Addiere 70 dazu und überspringe damit das schwarze Feld jmp L1 NextCol: cmp outerCounter, 8 ; Haben wir die achte Spalte erreicht? jae Finished ; Falls outerCounter >= 8 springe inc outerCounter mov fields, 0 mov innerCounter, 0 add fieldsCols, 70 mov dx, 0 mov ax, outerCounter mov cx, 2 ; Teile durch 2. Dies überprüft ob die Spalte gerade ; oder ungerade ist. div cx ; 16-Bit unsigned Division cmp dx, 0 ; Checke ob der Rest (Remainder) in DX gleich 0 ; (Modulo Division) ist je EVENx ; Springe an EVENx, falls gleich jmp ODDx ; Andernfalls an ODDx EVENx: mov tmp, 90 jmp L1 ODDx: mov tmp, 20 jmp L1 Finished: mov ah, 10h ; Warte auf einen Tastendruck int 16h mov ah, 0 ; Stelle den anfangs gespeicherten Video-Modus wieder her mov al, saveMode int 10h mov ax, 4C00h ; Beende das Programm samt Exit Code int 21h ; Gebe die Kontrolle wieder an DOS zurück main endp ;------------------------------------------------------ ; DrawHorizLine ; Zeichnet eine horizontale Linie beginnend an der ; Position X,Y mit einem übergebenen Längenwert und Farbe. ; Empfängt: CX = X-Koordinate, DX = Y-Koordinate, ; AX = Länge, und BL = Farbe ; Rückgabewert: - ;------------------------------------------------------ DrawHorizLine PROC .data currX WORD ? .code pusha mov currX, cx ; Speichert die X-Koordinate mov cx, ax ; Schleifenzähler DHL1: push cx ; Speichert den Schleifenzähler mov al, bl ; Farbe mov ah, 0Ch ; Zeichne Pixel mov bh, 0 ; Video Page mov cx, currX ; Empfabge die X-Koordinate int 10h inc currX ; Gehe 1 Pixel nach rechts pop cx ; Stelle den Schleifenzähler wieder her Loop DHL1 popa ret DrawHorizLine ENDP ;------------------------------------------------------ ; DrawVerticalLine ; Zeichnet eine vertikale Linie beginnend an der ; Position X,Y mit einem übergebenen Längenwert und Farbe. ; Empfängt: CX = X-Koordinate, DX = Y-Koordinate, ; AX = Länge, und BL = Farbe ; Rückgabewert: - ;------------------------------------------------------ DrawVerticalLine PROC .data currY WORD ? .code pusha mov currY, dx ; Speichert die Y-Koordinate mov currX, cx mov cx, ax ; Schleifenzähler DVL1: push cx ; Speichert den Schleifenzähler mov al, bl ; Farbe mov ah, 0Ch ; Zeichne Pixel mov bh, 0 mov cx, currX ; Setze die X-Koordinate mov dx, currY ; Setze die Y-Koordinate int 10h ; Zeichne Pixel inc currY ; Gehe 1 Pixel nach unten pop cx ; Stelle den Schleifenzähler wieder her Loop DVL1 popa ret DrawVerticalLine ENDP END main Vergessen Sie nicht das Programm unter Windows im Vollbildmodus auszuführen! Memory-Mapped I/ODas Zeichnen mittels Interrupt 10h ist bei vielen Pixeln oft eine träge Angelegenheit. Der Grund liegt darin das sehr oft sehr viele Codezeilen ausgeführt werden müssen. Dies verlangsamt das Programm ganz erheblich. Eine wesentlich effizientere und oft verwendete Variante ist eine Technik namens memory-mapped graphics, die alle Daten direkt im VRAM ablegt bevor sie auf dem Bildschirm gezeichnet werden. Der Video-Modus 13h ist der bevorzugte Modus sobald man direkt mit dem Speicher arbeitet. Der Modus besitzt eine Auflösung von 320 x 200 und kann 256 Farben darstellen. Die Bildschirm-Pixel werden als zweidimensionale Arrays von Bytes dargestellt, indem jeder Pixel ein eigenes Byte belegt. Das Array beginnt mit dem Pixel in der oberen linken Ecke des Bildschirms und erstreckt sich bis zum Pixel der oberen rechten Ecke. Im Video-Modus 13h sind das exakt 320 Bytes. Das nächste Byte bildet das Pixel in der zweiten Zeile ab und so weiter. Ein Byte kann einschließlich der 0 insgesamt 256 verschiedene Ganzzahlenwerte darstellen. Damit lassen sich die 256 verschiedene Farben repräsentieren. Alle Pixel und Farbwerte können zum Video Adapter transferiert werden indem die OUT Instruktion verwendet wird. Dazu wird eine 16-Bit Port Adresse an DX zugewiesen und der Wert der an den Port gesendet wird, wird in AL abgelegt. Die Video Farbpalette befindet sich an der Port Adresse 3C8h. Die Farben werden mithilfe einer RGB Farbpalette dargestellt. Es können insgesamt 262144 verschiedene Farben ausgegeben werden. Zeitgleich können natürlich nur 256 Farben angezeigt werden, jedoch kann die Ausgabe zur Laufzeit geändert werden. Im nächsten Programm werden wir sehen wie sich Animationen programmieren lassen. Das Programm FireScreen bildet ein flackerndes Feuer auf dem Bildschirm ab. Falls Sie Probleme haben den folgenden, etwas komplexeren Quellcode nachzuvollziehen, können Sie sich parallel dazu die anderen Beispiele zum Memory-Mapping im Anhang dieses Tutorials ansehen. Diese sind teilweise einfacher strukturiert. TITLE Fire Screen (firescreen.asm) COMMENT ! Dieses Programm nutzt memory-mapped graphics um mithilfe einer RGB Farbpalette ein Feuereffekt auf dem Bildschirm darzustellen. Last update: 25/05/06 ! .model small, stdcall .stack 200h .386 .data saveMode BYTE ? ; Gespeicherter Video Modus urlmessage BYTE "http://www.codeplanet.eu/$" ; Wird am Ende angezeigt xVal = 80 ; Bildschirmweite in Pixeln yVal = 112 ; Bildschirmhöhe in Pixeln + 2 seed WORD ? ; Zufallszahlen ; RGB Farbpalette von 0-63 (maximum), die Palette repräsentiert das Feuer palette BYTE 0, 0, 0, 0, 0, 6, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 0, 8 BYTE 0, 0, 9, 0, 0, 10, 2, 0, 10, 4, 0, 9, 6, 0, 9, 8, 0, 8 BYTE 10, 0, 7, 12, 0, 7, 14, 0, 6, 16, 0, 5, 18, 0, 5, 20, 0, 4 BYTE 22, 0, 4, 24, 0, 3, 26, 0, 2, 28, 0, 2, 30, 0, 1, 32, 0, 0 BYTE 32, 0, 0, 33, 0, 0, 34, 0, 0, 35, 0, 0, 36, 0, 0, 36, 0, 0 BYTE 37, 0, 0, 38, 0, 0, 39, 0, 0, 40, 0, 0, 40, 0, 0, 41, 0, 0 BYTE 42, 0, 0, 43, 0, 0, 44, 0, 0, 45, 0, 0, 46, 1, 0, 47, 1, 0 BYTE 48, 2, 0, 49, 2, 0, 50, 3, 0, 51, 3, 0, 52, 4, 0, 53, 4, 0 BYTE 54, 5, 0, 55, 5, 0, 56, 6, 0, 57, 6, 0, 58, 7, 0, 59, 7, 0 BYTE 60, 8, 0, 61, 8, 0, 63, 9, 0, 63, 9, 0, 63, 10, 0, 63, 10, 0 BYTE 63, 11, 0, 63, 11, 0, 63, 12, 0, 63, 12, 0, 63, 13, 0, 63, 13, 0 BYTE 63, 14, 0, 63, 14, 0, 63, 15, 0, 63, 15, 0, 63, 16, 0, 63, 16, 0 BYTE 63, 17, 0, 63, 17, 0, 63, 18, 0, 63, 18, 0, 63, 19, 0, 63, 19, 0 BYTE 63, 20, 0, 63, 20, 0, 63, 21, 0, 63, 21, 0, 63, 22, 0, 63, 22, 0 BYTE 63, 23, 0, 63, 24, 0, 63, 24, 0, 63, 25, 0, 63, 25, 0, 63, 26, 0 BYTE 63, 26, 0, 63, 27, 0, 63, 27, 0, 63, 28, 0, 63, 28, 0, 63, 29, 0 BYTE 63, 29, 0, 63, 30, 0, 63, 30, 0, 63, 31, 0, 63, 31, 0, 63, 32, 0 BYTE 63, 32, 0, 63, 33, 0, 63, 33, 0, 63, 34, 0, 63, 34, 0, 63, 35, 0 BYTE 63, 35, 0, 63, 36, 0, 63, 36, 0, 63, 37, 0, 63, 38, 0, 63, 38, 0 BYTE 63, 39, 0, 63, 39, 0, 63, 40, 0, 63, 40, 0, 63, 41, 0, 63, 41, 0 BYTE 63, 42, 0, 63, 42, 0, 63, 43, 0, 63, 43, 0, 63, 44, 0, 63, 44, 0 BYTE 63, 45, 0, 63, 45, 0, 63, 46, 0, 63, 46, 0, 63, 47, 0, 63, 47, 0 BYTE 63, 48, 0, 63, 48, 0, 63, 49, 0, 63, 49, 0, 63, 50, 0, 63, 50, 0 BYTE 63, 51, 0, 63, 52, 0, 63, 52, 0, 63, 52, 0, 63, 52, 0, 63, 52, 0 BYTE 63, 53, 0, 63, 53, 0, 63, 53, 0, 63, 53, 0, 63, 54, 0, 63, 54, 0 BYTE 63, 54, 0, 63, 54, 0, 63, 54, 0, 63, 55, 0, 63, 55, 0, 63, 55, 0 BYTE 63, 55, 0, 63, 56, 0, 63, 56, 0, 63, 56, 0, 63, 56, 0, 63, 57, 0 BYTE 63, 57, 0, 63, 57, 0, 63, 57, 0, 63, 57, 0, 63, 58, 0, 63, 58, 0 BYTE 63, 58, 0, 63, 58, 0, 63, 59, 0, 63, 59, 0, 63, 59, 0, 63, 59, 0 BYTE 63, 60, 0, 63, 60, 0, 63, 60, 0, 63, 60, 0, 63, 60, 0, 63, 61, 0 BYTE 63, 61, 0, 63, 61, 0, 63, 61, 0, 63, 62, 0, 63, 62, 0, 63, 62, 0 BYTE 63, 62, 0, 63, 63, 0, 63, 63, 1, 63, 63, 2, 63, 63, 3, 63, 63, 4 BYTE 63, 63, 5, 63, 63, 6, 63, 63, 7, 63, 63, 8, 63, 63, 9, 63, 63, 10 BYTE 63, 63, 10, 63, 63, 11, 63, 63, 12, 63, 63, 13, 63, 63, 14, 63, 63, 15 BYTE 63, 63, 16, 63, 63, 17, 63, 63, 18, 63, 63, 19, 63, 63, 20, 63, 63, 21 BYTE 63, 63, 21, 63, 63, 22, 63, 63, 23, 63, 63, 24, 63, 63, 25, 63, 63, 26 BYTE 63, 63, 27, 63, 63, 28, 63, 63, 29, 63, 63, 30, 63, 63, 31, 63, 63, 31 BYTE 63, 63, 32, 63, 63, 33, 63, 63, 34, 63, 63, 35, 63, 63, 36, 63, 63, 37 BYTE 63, 63, 38, 63, 63, 39, 63, 63, 40, 63, 63, 41, 63, 63, 42, 63, 63, 42 BYTE 63, 63, 43, 63, 63, 44, 63, 63, 45, 63, 63, 46, 63, 63, 47, 63, 63, 48 BYTE 63, 63, 49, 63, 63, 50, 63, 63, 51, 63, 63, 52, 63, 63, 52, 63, 63, 53 BYTE 63, 63, 54, 63, 63, 55, 63, 63, 56, 63, 63, 57, 63, 63, 58, 63, 63, 59 BYTE 63, 63, 60, 63, 63, 61, 63, 63, 62, 63, 63, 63 screen BYTE xVal * yVal dup (?) ; Virt. Bildschirm (nach der palette!) .code main PROC mov ax, @data mov ds, ax call SetUpScreen mov seed, 7895h mov si, OFFSET screen mov cx, LENGTHOF screen xor ax, ax rep stosb ; Leere den Bildschirm. ML1: ; Das nächste Bit gibt entweder 0 oder 255 entlang der mov si, OFFSET screen ; untersten Reihe des virtuellen Bildschrims aus. add si, LENGTHOF screen sub si, xVal ; SI=OFFSET(screen)+LENGTHOF(screen)-xVal. Letzte Reihe mov cx, xVal ; Wiederhole die komplette letzte Reihe xor dx, dx L1: call Randomize ; Erzeuge Zufallswerte mov ds:[si], dl inc si dec cx jnz L1 mov cx, LENGTHOF screen ; Mildere die Werte im virtuellen Array ab, sub cx, xVal ; so dass ein sanfter Feuereffekt entsteht mov si, OFFSET screen add si, xVal L3: xor ax, ax mov al, ds:[si] add al, ds:[si+1] adc ah, 0 add al, ds:[si-1] adc ah, 0 add al, ds:[si+xVal] adc ah, 0 shr ax, 2 jz ZERO ; Am Ende? dec ax ZERO: mov ds:[si-xVal], al ; AL=((pos)+(pos+1)+(pos-1)+(pos+80))/4 - 1 inc si dec cx jnz L3 mov dx, 3DAh ; VGA-Ausgabe L4: in al, dx and al, 8h jnz L4 L5: in al, dx and al, 8h jz L5 mov cx, LENGTHOF screen shr cx, 1 mov si, OFFSET screen xor di, di rep movsw mov ah, 11h ; Überprüfe den Tastaturpuffer int 16h ; Wurde eine Taste gedrückt (ZF)? jz ML1 ; Nein, mache weiter call RestoreVideoMode ; Warte auf Tastendruck, stelle VM wieder her mov ah, 9 ; Ausgabe eines $-terminierter Strings mov dx, OFFSET urlmessage ; DS:DX = Segment/Offset des Strings int 21h mov ah, 10h ; Warte auf Tastendruck int 16h mov ax, 4C00h ; Beende das Programm samt Exit Code int 21h ; Gebe die Kontrolle wieder an DOS zurück main ENDP ;---------------------------------------------------------- ; Randomize ; Diese Prozedur erzeugt Zufallswerte für den bewegenden ; Feuereffekt. ;---------------------------------------------------------- Randomize PROC NEAR mov ax, seed ; Initialisiere mov dx, 8405h mul dx ; DX:AX = AX * DX inc ax mov seed, ax ret Randomize ENDP ;---------------------------------------------------------- ; SetUpScreen ; Diese Prozedur bereitet den Bildschrim vor. Mithilfe der Prozedur ; SetVideoMode wird der Video-Modus umgeschaltet. Die Werte ; an Port 3C8h gesendet, zeigen welche Video Palette geändert wird. ;---------------------------------------------------------- SetUpScreen PROC NEAR call SetVideoMode ; Schalte in den Mode 13h, 320 x 200, 256 Farben cli ; Leere die Interrupt Flags (Clear Interrupt Flag) cld mov dx, 3C4h mov ax, 604h out dx, ax mov ax, 0F02h out dx, ax xor ax, ax mov cx, 32767 rep stosw ; Leere den Bildschirm mov dx, 3D4h mov ax, 14h ; Deaktiviere DWORD-Modus out dx, ax mov ax, 0E317h ; Aktiviere Byte-Modus out dx, ax out dx, ax mov ax, 00409h ; Zellenhöhe out dx, ax mov si, OFFSET palette mov dx, 3C8h ; Video Paletten Port mov al, 0 ; Setze den Palettenindex out dx, al ; Beginne bei der Farbe null inc dx mov cx, 768 PLOOP: outsb ; Stringausgabe seitens ES:(E)DI auf Port zeigend dec cx jnz PLOOP ret SetUpScreen ENDP ;---------------------------------------------------------- ; SetVideoMode ; Diese Prozedur speichert den aktuellen Video-Modus, schaltet ; in einen neuen Modus und zeigt ES auf das Video Segment. ;---------------------------------------------------------- SetVideoMode PROC mov ah, 0Fh ; Empfange den aktuellen current Video-Modus int 10h mov saveMode, al ; Speichere den Modus mov ah, 0 ; Setze den Video-Modus mov al, 13h ; auf Modus 13h int 10h push 0A000h ; Video Segment Adresse pop es ; ES = A000h (Video Segment). ret SetVideoMode ENDP ;---------------------------------------------------------- ; RestoreVideoMode ; Diese Prozedur wartet auf die Betätigung einer Taste und ; stellt den Video Modus wieder her. ;---------------------------------------------------------- RestoreVideoMode PROC mov ah, 10h ; Warte auf Tastendruck int 16h mov ah, 0 ; Resete den Video-Modus mov al, saveMode ; auf den gespeicherten Video-Modus. int 10h ret RestoreVideoMode ENDP END main Programmieren mit der MausZum Ende dieses Tutorials werden wir einen abschließenden Blick auf die Maus werfen. Die Maus übermittelt ihre Bewegung über horizontale und vertikale Längenangaben an den Rechner. Eine Länge entspricht dem 1/200 Teil eines Inch (amerikanische Längeneinheit) und wird als Mickey bezeichnet. Die BIOS Dienste stellen für den Umgang mit der Maus den Interrupt 33h zur Verfügung. Darin enthalten sind verschiedene Funktionen, unter anderem zur Geschwindigkeitsmessung, Positionsangabe und Abfrage des letzten Tastendrucks. Der Mauszeiger selbst lässt sich mithilfe der INT 33h Funktionen auch verbergen und wieder anzeigen. Dazu stehen die Funktionen 01h und 02h zur Verfügung. mov ax, 1 ; Zeige den Mauszeiger int 33h mov ax, 2 ; Verberge den Mauszeiger int 33h Die Funktion 00h gibt Informationen über den Mausstatus zurück. Zudem wird beim Aufruf der Funktion eine vollständiges Reset der Maus durchgeführt. Der Mauszeiger wird auf dem Bildschirm zentriert, alle Einstellungen werden auf die Werkseinstellungen zurückgesetzt und der Zeiger wird unsichtbar. Der folgende Code demonstriert den Vorgang. mov ax, 0 ; Aufruf der Funktion 00h (Maus Reset und Statusabfrage) int 33h cmp ax, 0 ; Falls die Maus nicht verfügbar ist, ist das je MouseNotAvailable ; AX-Register 0. Andernfalls befindet sich dort ; der Wert FFFFh und im BX die Anzahl der Maustasten. Um die Position der Maus zu empfangen und zu setzen kann man die beiden Funktionen 03h und 04h verwenden. Mithilfe der Funktion 03h lässt sich auch überprüfen ob eine Taste gedrückt wird. mov ax, 3 ; Empfange die Position int 33h mov bx, dx ; Speichere die Y-Koordinate cmp cx, 222 ; Überprüfe ob die X-Koordinate des je TrueData ; Mauszeigers auf 222 liegt. mov ax, 3 ; LT (Bit 0), RT (Bit 1), MT (Bit 2) int 33h test bx, 1 ; Teste das erste Bit auf 0 mittels logischen jne Left_Button_Down ; AND (Flags werden beinflußt) test bx, 2 jne Right_Button_Down test bx, 4 ; Dezimal=4, Binär=100 (Bit 2) jne Middle_Button_Down mov ax, 4 ; Positioniere die Maus manuell mov cx, 250 ; X-Koordinate mov dx, 100 ; Y-Koordinate int 33h INT 33h stellt noch einige weitere Funktionen bereit. Eine vollständige Übersicht finden Sie auf der Seite Datasource unter DOS-Interrupt 33h (Mausfunktionen). Dem Dateianhang dieses Tutorials ist selbstverständlich ein ausführliches Beispiel zu INT 33h beigefügt. SchlussDie BIOS Ebene bietet eine große Kontrolle und Flexibilität über die I/O-Geräte des Computers. Sie können direkt mit der Hardware arbeiten und diese direkt nach Ihren eigenen Wünschen und Vorstellungen manipulieren. Darüberhinaus gewährt diese hardwarenahe Systemprogrammierung einen tiefen Einblick in die zugrundeliegende Technik die jeden Computer zu dem macht was er ist, ein multifunktionales Werkzeug. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Zuletzt aktualisiert am Samstag, den 22. Mai 2010 um 17:08 Uhr |
AUSWAHLMENÜ | ||||||||
|