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