Adok's Way to Assembler
Folge 7
Begleit-Dateien:
Yo... welcome zur ersten Kursfolge für Fortgeschrittene. :) Heute stürzen wir
uns in das unebene und wilde Land der TSRs. Also nix wie los! Sturzhelm nicht
vergessen!
+++ Was versteht man unter TSR? +++
TSR ist die Abkürzung für Terminate and Stay Resident. Bei TSRs handelt es
sich also um speicherresidente Programme. Die Grundidee hinter einem
speicherresidenten Programm ist es, sich in einen Interrupt einzuklinken und
so im Hintergrund anderer Programme zu arbeiten. Auf diese Weise entsteht eine
Art Multitasking unter DOS. Das Einsatzgebiet der TSR-Programmierung liegt vor
allem bei Treibern - jeder Treiber unter DOS ist ein TSR. Ihr seht,
TSR-Programme sind für die Benutzung von DOS lebenswichtig. Aber auch bei
manchen Utilities und den meisten Viren handelt es sich um speicherresidente
Programme.
Wie programmiert man nun eigene TSRs? Allgemein gesagt, muß man dazu drei
Schritte tun:
1. Man schreibt die Prozedur (Interrupt-Handler), die in einen Interrupt
eingeklinkt werden soll (auf die ein Interrupt-Vektor verbogen werden
soll);
2. man verbiegt den gewünschten Interrupt-Vektor auf den Interrupt-Handler;
3. man berechnet die Anzahl der Paragraphen (ein Paragraph = 16 Bytes), die
resident im Speicher bleiben sollen, und beendet das TSR-Programm mit einer
speziellen Funktion des Interrupts 21h.
Klingt einfach, nicht wahr? :) Nun, setzen wir die Theorie in die Praxis um!
Hier ein Beispielprogramm, das ich im folgenden Zeile für Zeile erläutern
werde:
.MODEL TINY ;Speichermodell für COM
CODE SEGMENT ;Beginn Code-Seg
ASSUME CS:CODE, DS:CODE ;CS und DS auf Code-Seg
ORG 100h ;Startadresse COM
beginning: ;Startlabel
JMP install ;zur Installation springen
INTPROC PROC FAR ;neuer INT-5-Handler
STI ;Interrupts zulassen
IRET ;zurückkehren
last_byte: ;letztes Byte des Handlers
INTPROC ENDP ;Ende des Handlers
;Installation des Interrupt-Handlers
install: ;Label install
MOV DX,OFFSET INTPROC ;Offset nach DX (Segment steht in DS)
MOV AX,2505h ;Funktion 25h, Interrupt-Vektor 5
CLI ;Interrupts sperren
INT 21h ;auf die eigene Routine umleiten
STI ;Interrupts zulassen
;Programm speicherresident beenden
MOV DX,OFFSET last_byte;Anz. Bytes, die resident bleiben
SHR DX,4 ;in Paragraphen umwandeln
INC DX ;DX zur Sicherheit erhöhen
MOV AX,3100h ;Programm resident beenden
INT 21h ;
CODE ENDS ;Ende Code-Seg
END beginning ;Ende des Programms
Erzeugt aus diesem Programm eine COM-Datei, startet sie und probiert einmal,
die Taste Print Screen zu drücken. Was passiert? Richtig: nichts!
Normalerweise wird mit einem Druck auf Print Screen der Inhalt des Bildschirms
auf dem Drucker ausgegeben. Für diesen Vorgang ist der Interrupt 5 zuständig.
Obiges Beispielprogramm verbog jedoch den Interrupt-Vektor des Interrupts 5
auf einen eigenen Interrupt-Handler. Dieser enthält nur die Befehle STI und
IRET. Falls ihr es schon vergessen haben solltet: STI dient dazu, das
Interrupt-Flag zu setzen, so daß eventuell gesperrte Interrupts wieder
zugelassen werden. Neu ist IRET. Dieses Mnemoniclein wird in
Interrupt-Handlern anstelle von RET verwendet.
OK, dann gehen wir mal Zeile für Zeile das Programm durch... aber nicht von
Anfang an, denn der Anfang sollte mittlerweile ja schon mehr als
selbstverständlich sein.
Zeile 7: Mit JMP install überspringen wir den Interrupt-Handler, der ja
erst nach der Installation des Programms ausgeführt werden soll,
und begeben uns zur Installationsroutine, also zu jener Routine,
in der der Vektor von INT 5 auf unseren Handler verbogen wird.
Zeilen 9-13: Jeder Interrupt-Handler ist eine FAR-Prozedur, wie wir sie in
der vorigen Folge durchgenommen haben. Am Anfang steht der Kopf
der Prozedur. Die nächsten zwei Zeilen sind der Inhalt des
Interrupt-Handlers. Danach folgt ein Label, welches das Ende der
Prozedur darstellt. Wir werden es für die Berechnung der Anzahl
der Paragraphen, die resident bleiben sollen, benötigen. Zum
Schluß kommt der Fuß der Prozedur. Wichtig zu erwähnen ist, daß
Interrupt-Handler mit IRET anstelle von RET abgeschlossen werden
müssen.
Zeilen 17-22: Die Installationsroutine! Wir verbiegen den Vektor von INT 5 auf
unseren Handler mit Hilfe von INT 21h, Fkt. 25h. In DS muß das
Segment und in DX der Offset des Handlers stehen. Da in
COM-Dateien das DS-Register automatisch auf das Codesegment
zeigt, müssen wir uns nicht zusätzliche Tipparbeit antun.
Außerdem muß in AL die Nummer des zu verbiegenden Vektors
übergeben werden. Vor dem Aufruf von INT 21h sperren wir mit CLI
alle übrigen Interrupts, um sicherzugehen, daß INT 5 nicht
versehentlich während des Verbiegens aufgerufen werden kann, was
sonst zu einem Chaos führen würde. Danach lassen wir die
Interrupts mit STI wieder zu.
Zeilen 25-29: Nun machen wir das Proggy zu einem TSR! INT 21h, Fkt. 31h dient,
wie Fkt. 4Ch, zum Beenden des Programms. Im Unterschied zu
Fkt. 4Ch wird dabei allerdings nicht der komplette Speicher, den
das Programm eingenommen hat, freigegeben. Man muß im
DX-Register angeben, wie viele Paragraphen nach dem Beenden
resident bleiben, also nicht freigegeben werden sollen. Hier
kommt das Label last_byte ins Spiel! Wir wollen ja den
Speicherbereich, den unser Interrupt-Handler einnimmt, resident
machen. Also weisen wir DX den Offset des Ende des Handlers zu -
wir weisen DX den Offset von last_byte zu. Nun haben wir in DX
die Anzahl der Bytes, die resident bleiben müssen. Funktion 31h
erwartet ihren Parameter jedoch nicht in Bytes, sondern in
Paragraphen. Da ein Paragraph 16 Bytes entspricht, shiften wir
DX um 4 nach rechts. (Ihr erinnert euch?) Abschließend erhöhen
wir DX zur Sicherheit um 1, denn sonst könnte es sein, daß zu
wenig Speicher resident gemacht wird und das Programm abstürzt.
(Beispiel: 18 geteilt durch 16 ergibt, da es sich um ganze
Zahlen handelt, 1. Um 18 Bytes resident zu machen, müssen wir
jedoch nicht einen, sondern zwei Paragraphen an Fkt. 31h
übergeben.) Nun werden noch die Funktionsnummer ins AH-Register
geschrieben und der Interrupt gecallt - juchu! Fertig!
Dieses Programm könnt ihr als Gerüst für eigene TSRs verwenden. Umfangreiche
TSRs bestehen natürlich nicht nur aus STI und IRET, sondern aus bunten,
hüpfenden Menüpünktchen und ähnlichem Zeug. Jedenfalls, wer etwas Vernünftiges
schreiben will, will sicherlich auch wissen, wie man den ursprünglichen
Standard wiederherstellen kann, sprich: wie man einen Interrupt-Vektor auf den
Original-Handler setzen kann. Nichts leichter als das!
+++ Ermittlung des Original-Handlers +++
Zu diesem Zweck benutzt man INT 21h, Fkt. 35h. In AL muß die Nummer des
Interrupt-Vektors übergeben werden. Nach dem Aufruf von INT 21h, Fkt. 35h
findet man in ES das Segment und in BX den Offset des gesuchten Handlers vor.
Das Ermitteln des Original-Handlers ist auch dann notwendig, wenn man ein
Programm schreiben will, das per Hotkey "aufgeklappt" werden soll. Dazu
verbiegt man den Tastatur-Interrupt (INT 9) auf eine eigene Routine, welche
abfragt, ob dieser Hotkey gedrückt wurde. Falls nein, ruft die Routine den
Original-Handler auf.
+++ Verwenden von DOS-Funktionen in TSRs +++
Das Verwenden von DOS-Funktionen in Interrupt-Handlern ist leider nicht ohne
weiteres möglich. Wenn nämlich sowohl das Betriebssystem als auch ein
TSR-Programm gleichzeitig eine DOS-Funktion aufrufen, gibt's einen
Systemabsturz. Zum Glück gibt es auch für dieses Problem eine Lösung: Mit INT
21h, Fkt. 34h kann man die Adresse des INDOS-Flags ermitteln. Ihr Segment wird
in ES und ihr Offset in BX zurückgegeben. Solange das INDOS-Flag einen anderen
Wert als 0 hat, wird gerade eine DOS-Funktion ausgeführt. Was macht also der
Coder? Im nicht-residenten Teil des TSRs (dem Installations- und
Beendigungsteil) ermittelt er die Adresse des INDOS-Flags und speichert sie in
Variablen ab. Wichtig: Die Variablen müssen zwischen JMP install und dem Kopf
des Interrupt-Handlers definiert sein, um sicherzugehen, daß sie resident
bleiben! Im Interrupt-Handler selbst wird vor Aufruf einer DOS-Funktion das
INDOS-Flag abgefragt. Ist es ungleich Null, wird das TSR nicht weiter
ausgeführt.
mov ax,word ptr indos_seg
mov es,ax
mov bx,word ptr indos_ofs
cmp es:[bx],0
jne ende
Alles klar? Gut! Dann viel Spaß mit den TSRs! Euer Adok.