vit$oft freeware home page
;***************************************************************************;
;* Vzor residentního programu se samozaváděním do horní paměti *;
;* Public Domain by vit$oft *;
;***************************************************************************;
;
; Tento komentovaný program v jazyku Netwide Assembler (verze 0.98) je určen
; jako vzor pro začátečníky v programování residentních programů v DOSu.
; Uložte tento text pod názvem TSRUP.ASM a přeložte příkazem
;
; NASMW TSRUP.ASM -f bin -o TSRUP.COM
;
; Přeložený program lze spouštět v DOSu nebo v konsoli Windows 9x/NT.
;
; Terminate and Stay Resident (TSR) je třída 16bitových programů, které
; poskytují služby jiným současně běžícím DOSovým programům, podobně jako
; ovladače zařízení (DEVICE), které se instalují při zavádění systému. Na
; rozdíl od DEVICE, správně napsaný TSR program lze nainstalovat až v případě
; potřeby a zase odstranit z paměti, pokud již není zapotřebí.
;
; Pro pochopení, jak se TSR instalují do paměti, je třeba znát principy
; alokace paměti v DOSu. MS-DOS přiděluje operační paměť v 16 bajtových
; kvantech zvaných 'paragraf'. Každému přidělenému paměťovému bloku těsně
; předchází paragraf zvaný 'řídicí blok paměti' (Memory Control Block, MCB)
; udržující informace o jeho velikosti, procesu, kterému patří a vazbě na
; další takové bloky. Pokud program požádá o přidělení (alokaci) určitého
; množství paměti, DOS musí přerovnat řetězec paměťových bloků, vybere
; souvislou část paměti o požadované velikosti a označí ji jako obsazenou.
; Při spouštění COM programu z povelové řádky se o alokaci stará příkazový
; interpreter (obvykle COMMAND.COM), který alokuje dva paměťové bloky:
; 1. malý blok pro kopii systémových proměnných (environment)
; 2. hlavní blok pro Program Segment Prefix (PSP) a vlastní kód spouštěného
; programu. Ve prospěch spouštěného programu je alokován celý volný zbytek
; konvenční paměti.
; Po ukončení běhu programu DOS uvolní oba paměťové bloky alokované při
; startu i případné další, pokud byly přiděleny ukončovanému procesu
; v průběhu jeho činnosti. Residentní programy vyžadují, aby část jejich kódu
; zůstala v paměti i po ukončení jejich běhu. TSR proto končí voláním
; speciální funkce DOSu 31h, při kterém určí, kolik paměti má zůstat
; alokováno. O toto množství pak bude snížena velikost paměti dostupné pro
; později spouštěné programy. Velikost disponsibilní paměti lze zjistit
; příkazem MEM.
;
; Konvenční paměť v DOSu je vzácný statek, všechny ovladače a residentní
; programy by měly být optimalizovány na co nejmenší spotřebu residentní
; paměti. Tento vzorový při instalaci do paměti zároveň posouvá residentní
; kód tak, aby překrýval nepotřebnou část PSP, díky tomu zabírá po instalaci
; pouze 128 bajtů.
;
;
; Nejprve definujme několik užitečných maker.
%MACRO PROC 1 ; Makra PROC a ENDP pouze ohraničují proceduru.
%1: ; Jméno procedury bude určovat návěští.
%PUSH %1 ; Uložení kontextu prostoru jmen.
%ENDMACRO
%MACRO ENDP 1 ; Makra PROC/ENDP lze vynechat, ale zpřehledňují
%IFCTX %1 ; program a kontrolují vnořování procedur,
%POP ; podobně jako pseudoinstrukce %MACRO a %ENDMACRO.
%ELSE
%ERROR Unmatched PROC/ENDP %1
%ENDIF
%ENDMACRO
%MACRO DosFn 1-2 ; Volání služby DOSu INT 21h AH=%1 BX=%2.
%IF %1 < 256
MOV AH,%1
%ELSE
MOV AX,%1
%ENDIF
%IF %0 = 2 ; Je-li deklarován druhý parametr, vloží se do BX
MOV BX,%2
%ENDIF
INT 21h
%ENDMACRO
%MACRO Write 1+ ; Vypsání textového řetězce na standardní výstup (konsolu).
[SECTION .data]
%%String: DB %1 ; Literál je deklarován v sekci '.data' umístěné za '.text'.
%%EndString:
__SECT__
MOV DX,%%String
MOV CX,%%EndString-%%String
CALL Write#Proc
%IFNDEF Write# ; Volaná procedura Write#Proc bude vložena do kódu
%DEFINE Write# ; pouze při prvním použití makra.
JMP SHORT Write#Endp
Write#Proc: ; CX bajtů řetězce DX^
DosFn 40h,1 ; bude zapsáno do handle StdOut=1.
RET
Write#Endp:
%ENDIF
%ENDMACRO ; Write
%MACRO AbortIf 2+ ; %1=podmínka %2=chybová zpráva
J%-1 %%Continue ; Pokračuj, není-li podmínka %1 splněna,
Write %2 ; jinak vypiš chybovou zprávu %2+
DosFn 4C08h ; a havarijně ukonči program s errorlevel 8.
%%Continue:
%ENDMACRO ; AbortIf
ORG 100h ; COM program začíná na ofsetu 256, CS=DS=ES=SS ukazují na PSP.
SECTION .text
JMP SHORT Main
ShiftedResidentOrg EQU 5Ch ; Od této adresy PSP jej lze přepsat.
ResidentOrg: EQU $ ; Bude při instalaci posunut dozadu na ofset 5Ch.
Shift EQU (ResidentOrg-ShiftedResidentOrg)
PROC NewInt08 ; Nová obsluha přerušení realizuje vlastní funkci residentu.
; FAR JUMP na jejím konci řetězí tuto rutinu do případných dříve
; instalovaných obslužných rutin a nakonec na původní obsluhu přerušení
; v BIOSu, která se postará o potvrzení přerušení v řadiči 8259 a o ukončení
; instrukcí IRET.
; Přerušení Int08 vzniká hardwarově z časovače každých 55 ms. Na vstupu do
; obslužné rutiny nejsou známy hodnoty jiných registrů než CS:IP. Každý
; přístup k datům proto musí být vztažen k segmentovému registru CS, a ne
; k defaultu DS. Jelikož to prodlužuje každou instrukci přistupující k datům
; o jeden bajt prefixu, může být při čtyřech a více takových instrukcích
; výhodnější dočasně uschovat obsah DS a naplnit jej hodnotou z CS.
;
; Účelem tohoto vzorového TSR je udržovat NumLock klávesnice v zapnutém
; stavu. Správnou funkci residentu lze snadno ověřit, pokud zkusíme něco psát
; na numerické klávesnici s vypnutým NumLock. V DOSu nebo ve Windows 9x (ale
; ne v NT) bude indikátor NumLock neustále BIOSem rozsvěcován.
; Není to tedy nijak zvlášť užitečný program, ale může posloužit jako kostra
; pro složitější residentní programy.
PUSH AX
PUSH DS
SUB AX,AX
MOV DS,AX ; Na segmentu DS=0 budeme měnit stav klávesnice.
OR BYTE [417h],20h ; Nastavení stavového bitu NumLock na 'zapnuto'.
POP DS ; Před ukončením obslužné rutiny musejí být všechny
POP AX ; registry vráceny do původního stavu.
JMP 0:0 ; Immediate segment:ofset v těle této instrukce bude
OldInt08 EQU $-4 ; pojmenován OldInt08 a později přepsán aktuálním
ENDP NewInt08 ; vektorem přečteným z tabulky vektorů přerušení.
Identificator DB 'TSRUP' ; Unikátní název identifikující program.
TsrStatus DB 0 ; Tento bajt doplňuje Indentificator, aby se
%DEFINE Passive BYTE 0 ; odlišil aktivní a pasivní (nenainstalovaný)
%DEFINE Active BYTE 1 ; stav residentní instance.
SizeOfIdentificator EQU $-Identificator
SizeOfResident EQU $-Shift
ResidentParagraphs EQU (SizeOfResident+15) >> 4
ShiftedNewInt08 EQU NewInt08-Shift
ShiftedOldInt08 EQU OldInt08-Shift
ShiftedIdentificator EQU Identificator-Shift
ShiftedTsrStatus EQU TsrStatus-Shift
PROC Main ; Řídicí procedura zahajuje residentní (od)instalaci a vypisuje
; zprávy o činnosti na systémový výstup, tj.na obrazovku.
Write 'TSRUP skeleton',13,10 ; Nejprve se představí název programu.
CALL Init ; Zjišťuje zadané parametry, pak posune resident do PSP.
CMP AL,'u' ; Požadavek na odinstalaci /u ?
JNE .30
CALL InstallationCheck
AbortIf Z, 'was not installed yet.',13,10
CALL Uninstall
AbortIf C, 'cannot uninstall - another TSR was hooked later.',13,10
Write 'was uninstalled.',13,10
DosFn 4C00h ; Ukončení programu.
.30: CMP AL,'j' ; Požadavek na instalaci do konvenční paměti /j ?
JNE .70
CALL InstallationCheck
.50: AbortIf NZ, 'is installed already.',13,10
.60: Write 'was installed in conventional memory.',13,10
JMP InstallConventional
.70: CMP AL,'i' ; Požadavek na instalaci do horní paměti /i ?
AbortIf NE,'Options: /InstallToUpper /J:InstallConv /Uninstall',13,10
CALL InstallationCheck
JNZ .50 ; Skok na chybové hlášení, pokud již byl instalován.
CALL FindUMB ; Pokus o alokaci bloku horní paměti.
JC .60 ; Skok, není-li horní paměť dostupná.
Write 'was installed in upper memory.',13,10
JMP InstallUpper
ENDP Main
PROC Init ; Probere parametry příkazové řádky a pak posune resident do PSP.
MOV SI,128+1 ; Na tomto ofsetu v PSP začínají parametry.
CLD
.10: LODSB
CMP AL,'/'
JE .30
CMP AL,'-' ; Parametr může být uvozen znakem '/' nebo '-'.
JE .30
CMP AL,13 ; Toto je příznak konce parametrů.
JNE .10 ; Pokračuj ve vyhledávání uvozujícího prefixu.
JE .40 ; Jsme na konci příkazového řádku, AL=13.
.30: LODSB ; Načtení písmene za prefixem '/' nebo '-'.
OR AL,'A'^'a' ; Převod na malé písmeno
.40: PUSH AX ; a přechodné uložení.
; Příkazová řádka je zkontrolována, PSP nebude nadále zapotřebí.
; Nyní program dealokuje environment pro případ, že se bude instalovat.
MOV ES,[2Ch] ; Adresa proměnných procesu (environment) jde do ES
DosFn 49h ; a tento blok bude dealokován.
DosFn 3508h ; Přečtení vektoru (ukazatele na obsluhu) přerušení 08
MOV WORD [OldInt08+0],BX ; a jeho uložení do těla budoucího residentu.
MOV WORD [OldInt08+2],ES
PUSH DS ; Nyní provedeme posun residentní části do PSP.
POP ES ; DS i ES adresují segment PSP.
MOV SI,ResidentOrg
MOV DI,ShiftedResidentOrg
MOV CX,SizeOfResident
CLD ; Residentní část se posune na nižší adresu a překryje tak
REP MOVSB ; část PSP, která již není zapotřebí.
POP AX
RET ; Procedura vrací v AL='i','j','u' nebo 13.
ENDP Init
PROC InstallConventional ; Klasické residentní ukončení programu.
MOV [ShiftedTsrStatus],Active ; Identifikátor se označí jako aktivní.
MOV DX,ShiftedNewInt08 ; Vektor přerušení 08 se nastaví na novou
DosFn 2508h ; obslužnou rutinu.
MOV DX,ResidentParagraphs ; Množství residentní paměti.
DosFn 3100h ; Residentní ukončení programu.
ENDP InstallConventional
; Od verze 5 přišel MS-DOS s konceptem využití paměti nad konvenční hranicí
; 9000:FFFF, takzvanou horní (upper) pamětí. Její zpřístupnění vyžaduje dva
; ovladače: HIMEM a EMM386. Residentní programy mohou být nahrávány do horní
; paměti interním povelem LOADHIGH, ale je zde problém. Horní paměť bývá
; fragmentovaná a zavedení TSR vyžaduje alokaci bloku horní paměti dostatečně
; velkého pro celý program, nejen pro jeho residentní část. Proto by
; residentní programy neměly spoléhat na LOADHIGH, popř. MEMORY MAKER, ale
; místo toho se spustit konvenčním způsobem a alokovat horní paměť vlastními
; silami. Takový residentní program se pak může spokojit s nalezením pouze
; tak velkého bloku horní paměti, který stačí pro jeho poměrně malou
; residentní část.
PROC InstallUpper ; Instalace do bloku horní paměti adresovaného
SUB SI,SI ; segmentem ES, který již byl dříve alokován ve FindUMB.
SUB DI,DI
CLD
MOV CX,SizeOfResident ; Začátek PSP a přilehlý residentní
REP MOVSB ; blok bude zkopírován do horní paměti
MOV [ES:ShiftedTsrStatus],Active ; a pak označen jako aktivní.
PUSH DS
PUSH ES
POP DS ; DS je dočasně nastaven na segment horní paměti.
MOV DX,ShiftedNewInt08
DosFn 2508h ; Vektor přerušení 08 bude ukazovat do horní paměti.
POP DS
MOV AX,ES
MOV BX,AX ; Segment horní paměti se uschová do BX.
DEC AX ; O jeden paragraf níže je MCB.
MOV ES,AX ; MCB segment našeho paměťového bloku.
MOV DI,8 ; Na tomto ofsetu v MCB by mělo být jméno vlastníka,
MOV SI,Identificator ; zobrazované např. příkazem MEM /C.
%IF SizeOfIdentificator < 8
MOV CL,SizeOfIdentificator
%ELSE
MOV CL,8 ; Délka jména vlastníka bloku nesmí překročit 8.
%ENDIF
REP MOVSB ; Jméno residentního bloku je nyní nastaveno.
DosFn 50h ; Aktuální PSP se změní na segment horní paměti v BX.
PUSH DS
POP ES ; ES ukazuje opět na běžící PSP v konvenční paměti
DosFn 49h ; a celý tento program se nyní dealokuje.
; Následující instrukce již poběží v právě dealokované paměti,
; naštěstí ji DOS dosud nemohl přidělit ničemu jinému a tak vše funguje.
MOV DX,ResidentParagraphs
DosFn 3100h ; Residentní ukončení ponechá horní blok.
ENDP InstallUpper
PROC Uninstall ; Obnovuje původní vektory přerušení, dealokuje residentní
PUSH DS ; paměť adresovanou registrem ES (horní nebo konvenční).
SUB AX,AX
MOV DS,AX ; Tabulka vektorů přerušení je na segmentu 0.
MOV AX,ES ; Segment odinstalovávané instance by měl
CMP AX,[WORD (4*08h+2)] ; souhlasit s údajem v tabulce pro INT08.
POP DS
STC ; Návrat s příznakem chyby CF, pokud vektory nesouhlasí.
JNE .90 ; V takovém případě deinstalace není možná.
PUSH DS ; Jinak se přerušení vrátí k původnímu vektoru
LDS DX,[ES:ShiftedOldInt08] ; z doby před instalací.
DosFn 2508h ; Obnova vektoru přerušení.
POP DS
MOV [ES:ShiftedTsrStatus],Passive ; InstallationCheck již kopii nenajde.
DosFn 49h ; Dealokace staré residentní paměti na segmentu ES.
.90: RET
ENDP Uninstall
; Dobrý residentní program nedovolí zbytečnou vícenásobnou instalaci , což
; vyžaduje kontrolu přítomnosti TSR v paměti. Jako metodu detekce přítomnosti
; preferuji vyhledávání podle jména, což je nenáročné na spotřebu residentní
; paměti (residentně se ukládá pouze identifikační jméno). Instalovaný běžící
; resident musí být odlišen od pasivního stavu, aby nebyla kontrola
; přítomnosti zmatena případným nálezem stejného identifikátoru v nesmazaných
; paměťových blocích nebo ve vyrovnávací paměti.
PROC InstallationCheck ; Zjišťuje přítomnost dříve instalované instance TSR.
; Identifikátor této instance bude dočasně označen jako aktivní a procedura
; pak vyhledá stejný identifikátor na všech možných segmentech.
MOV [ShiftedTsrStatus],Active
MOV DX,ShiftedIdentificator ; Ofset identifikátoru v této instanci.
MOV BX,SizeOfIdentificator
CLD
SUB AX,AX ; Tento registr si pamatuje právě prohledávaný segment.
.40: DEC AX ; Budou se prověřovat všechny od FFFF až po 0000.
JZ .90 ; Skok, pokud již jsme na segmentu 0 a shoda nenalezena.
MOV ES,AX
MOV DI,DX
MOV SI,DX
MOV CX,BX ; Délka identifikátoru včetně modifikátoru TsrStatus.
REPE CMPSB
JNE .40
MOV CX,DS ; Identifikátor byl nalezen, ale
CMP CX,AX ; shoda se sebou samým se nepočítá.
JE .40 ; V tom případě prohledávání pokračuje pod akt.segmentem.
.90: MOV [ShiftedTsrStatus],Passive; Na konci se vrátí stav identifikátoru.
RET ; ZF=1 pokud ještě nebyl instalován, jinak ZF=0 a ES=segment.
ENDP InstallationCheck
; Horní paměť v MS-DOS nebude dostupná bez dvou ovladačů zavedených
; v CONFIG.SYS:
; DEVICE=HIMEM.SYS
; DEVICE=EMM386.EXE RAM a/nebo NOEMS
; Ale ani s touto konfigurací ještě nebude při vyvolání funkice DOSu 48h
; horní paměť automaticky alokována. Systém je třeba dočasně nastavit tak,
; aby začleňoval horní paměťové bloky do řetězu volných MCB (Link Status) a
; podobně je třeba pozměnit alokační strategii, která implicitně preferuje
; pouze konvenční paměť.
PROC FindUMB ; Procedura se pokusí alokovat ResidentParagraphs horní
; paměti a její segment dá do ES. CF nastaven při neúspěchu.
DosFn 5800h ; Načtení strategie přidělování paměti.
JC .90 ; Chyba vznikne v DOSu verze < 3.
MOV SI,AX ; Číselná hodnota strategie se dočasně uschová v SI.
DosFn 5802h ; Načtení stavu řetězení konvenční a horní paměti.
JC .90 ; Selže v DOSu verze < 5.
MOV DX,AX ; Stav řetězení se uloží do DL.
DosFn 5803h,1 ; Nastaví nový stav na 1=včetně horní paměti.
DosFn 5801h,41h ; Nastaví strategii na 41h=horní, nejlepší využití.
DosFn 48h,ResidentParagraphs ; Pokus o alokaci paměťového bloku.
PUSHF ; Výsledek alokace (CF=chyba) se dočasně uloží.
MOV ES,AX ; Segmentová adresa horní paměti, pokud byla přidělena.
SUB BX,BX ; Bez ohledu na výsledek je třeba obnovit původní stav.
MOV BL,DL ; Stav řetězení byl uchován v DL.
DosFn 5803h; Obnova stavu řetězení.
DosFn 5801h,SI ; Obnova alokační strategie z registru SI.
POPF ; Vrací v ES alokovaný segment, platný pokus má CF=0.
.90: RET
ENDP FindUMB
; Tento program TSRUP ve zdrojovém i přeloženém tvaru lze najít na
; stránkách vit$oft freeware.
;
; Pro kompilaci je nutný freewarový Netwide Assembler.
;
; Další odkazy k programování viz x86 Assembler WebRing,
; internetový magazín Assembly Programming Journal,
; referenční zdroj informací o přerušeních Interrupt List.
END