Adok's Way to Assembler
Folge 7

Yo...  welcome zur ersten Kursfolge fr Fortgeschrittene. :) Heute strzen 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 Abkrzung fr 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  fr  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 gewnschten 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 fr Zeile erlutern
werde:

.MODEL TINY              ;Speichermodell fr 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                  ;zurckkehren
 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 erhhen
  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   drcken.   Was  passiert?  Richtig:  nichts!
Normalerweise wird mit einem Druck auf Print Screen der Inhalt des Bildschirms
auf  dem Drucker ausgegeben. Fr diesen Vorgang ist der Interrupt 5 zustndig.
Obiges  Beispielprogramm  verbog jedoch den  Interrupt-Vektor des Interrupts 5
auf  einen  eigenen Interrupt-Handler. Dieser enthlt  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 fr  Zeile das Programm durch... aber nicht von
Anfang   an,   denn  der  Anfang  sollte   mittlerweile   ja  schon  mehr  als
selbstverstndlich sein.

Zeile 7:      Mit JMP install  berspringen wir den Interrupt-Handler,  der ja
              erst nach der Installation des Programms ausgefhrt 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 nchsten  zwei Zeilen  sind  der  Inhalt  des
              Interrupt-Handlers. Danach folgt ein Label, welches das Ende der
              Prozedur darstellt.  Wir werden es fr die Berechnung der Anzahl
              der Paragraphen,  die resident  bleiben sollen,  bentigen.  Zum
              Schlu kommt der Fu der Prozedur.  Wichtig zu erwhnen ist, da
              Interrupt-Handler mit IRET anstelle von RET abgeschlossen werden
              mssen.
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,  mssen  wir  uns  nicht  zustzliche  Tipparbeit  antun.
              Auerdem  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 whrend des Verbiegens aufgerufen werden kann, was
              sonst  zu  einem  Chaos  fhren  wrde.  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 mssen.  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?)  Abschlieend erhhen
              wir DX zur Sicherheit um 1,  denn sonst  knnte es sein,  da zu
              wenig Speicher resident gemacht wird  und das Programm abstrzt.
              (Beispiel:  18 geteilt  durch 16  ergibt,  da es  sich  um ganze
              Zahlen handelt, 1.  Um 18 Bytes  resident zu machen,  mssen 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 knnt ihr als Gerst  fr eigene TSRs verwenden. Umfangreiche
TSRs  bestehen  natrlich  nicht  nur aus  STI  und  IRET, sondern aus bunten,
hpfenden Menpnktchen und hnlichem Zeug. Jedenfalls, wer etwas Vernnftiges
schreiben  will,  will  sicherlich  auch  wissen,  wie  man den ursprnglichen
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 gedrckt 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  mglich.  Wenn  nmlich  sowohl  das  Betriebssystem  als  auch  ein
TSR-Programm   gleichzeitig   eine   DOS-Funktion   aufrufen,   gibt's   einen
Systemabsturz.  Zum Glck gibt es auch fr dieses Problem eine Lsung: Mit INT
21h, Fkt. 34h kann man die Adresse des INDOS-Flags ermitteln. Ihr Segment wird
in ES und ihr Offset in BX zurckgegeben. Solange das INDOS-Flag einen anderen
Wert  als 0 hat, wird gerade eine  DOS-Funktion ausgefhrt. 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 mssen 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
ausgefhrt.

 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.
