Mini kurs pisania programów TSR w asemblerze

Usuwanie rezydenta z pamięci i jakie są z tym związane problemy

W poprzednim odcinku dowiedzieliśmy się, jak napisać prosty sekundnik instalowany rezydentnie w pamięci. Cały problem w tym, że po jednorazowym zainstalowaniu takiego TSRa zabiera on nam kawałek cennej pamięci, a gdy już znudzą nam się cyferki wciąż widoczne na ekranie - pozostaje tylko reset komputera. Przyszła pora na poznanie kolejnej techniki, którą będziemy stosować, a mianowicie sposób na rozinstalowanie rezydenta, czyli powrót do stanu sprzed zainstalowania.

Na początku należy się zastanowić - co tak właściwie musimy zrobić, aby nasz komputer działał tak, jakbyśmy nigdy TSRa nie uruchamiali. Po pierwsze: należy sprawdzić, czy w ogóle nasz rezydent jest obecny w pamięci. Najprościej sprawdzić wektor przerwania, które on przechwycił podczas instalacji (czyli w przypadku sekundnika będzie to przerwanie 8), a potem upewnić się, że pod podanym adresem jest obecny nasz TSR. W tym celu możemy po prostu porównać offset (przesunięcie w segmencie) początku naszej procedury z offsetem podanym nam przez funkcję DOSu czytającą wektor przerwania (funkcja 35h przerwania 21h). Jednakże takie proste sprawdzenie może czasem nie przynieść dobrych rezultatów, gdy oprócz sekundnika w pamięci są obecne inne programy TSR o tych samych offsetach procedur podpiętych pod przerwanie zegara. Największą wiarygodność możemy uzyskać tylko przez sprawdzenie czegoś unikalnego dla naszego rezydenta. W praktyce wystarczy porównanie ciągu znaków pod znanym adresem z naszym wzorcem - kiedy się zgadzają to możemy kontunuować usuwanie TSRa z pamięci komputera.

Po stwierdzeniu obecności TSRa i sprawdzeniu przechwytywanych przez niego przerwań (w przypadku sekundnika jest to jedno przerwanie - nr 8), możemy odczytać oryginalne wektory tych przerwań (wiemy bowiem, w którym miejscu w rezydencie są one "zaszyte") i przywrócić je (funkcja 25h przerwania 21h). Pozostaje już tylko zwolnić bloki pamięci zajmowane przez sekundnik, wypisać na ekranie komunikat o pomyślnym usunięciu rezydenta i normalnie powrócić do DOSu (funkcja 4ch przerwania 21h). Oczywiście przy instalacji programu warto również sprawdzić, czy już wcześniej nie był instalowany, by uniknąć dwukrotnej instalacji. Praktyczną realizację tych kilku kroków możecie prześledzić analizując kod źródłowy podany w dalszej części.

Chwila na krótkie wyjaśnienie: DOS przydziela programom pamięć w blokach o długości będącej wielokrotnością 16 bajtów. Poza takimi blokami danych mogą wystąpić jeszcze bloki z kodem programu oraz bloki z otoczeniem (tam są przechowywane wszystkie ustawienia otoczenia programu, czyli wartości nadane przez PATH, SET, PROMPT itp. - można je wyświetlić komendą SET). Każdy program przy uruchomieniu "otrzymuje" swój blok z kopią otoczenia DOSowego, które może dowolnie modyfikować (np. zmienić ścieżkę wyszukiwania PATH) i odczytywać (chcąc pobrać parametry otoczenia). Prócz samej zawartości otoczenia na końcu bloku jest wpisywana ścieżka dostępu i nazwa pliku "właściciela", czyli programu, do którego należy dane otoczenie, np. C:\MASM\PROGS\KURS\MOJPROG1.COM. Jak czytać parametry otoczenia dowiemy się kilka odcinków dalej. Przy zakończeniu programu otoczenie jest automatycznie zwalniane - zmiany, które program w nim poczynił są tracone. Oczywiście zostawiając TSRa w pamięci fragment bloku z kodem programu zostaje (wielkość fragmentu zaznaczamy w rejestrze DX przy wywołaniu przerwania 27h), natomiast reszta jest zwalniana (czyli blok jest skracany), blok z otoczeniem również pozostaje na swoim miejscu. Dlatego często w TSRach blok otoczenia jest zwalniany już w czasie instalacji, aby zmniejszyć wielkość pamięci zajmowanej przez rezydenta. Tak też będzie w nowej wersji sekundnika. Numer segmentu otoczenia (środowiska) odczytamy ze słowa 16-bitowego umieszczonego w segmencie programu pod adresem 002ch (czyli w obszarze PSP, o tym będzie później).

A oto przydatne informacje:

Funkcja 49h
Nazwa:          Zwalnianie pamięci
Wywołanie:      AH=49h
                ES - segment, w którym znajduje się zwalniana pamięć
Powrót:         Ustawiony znacznik C : AX - kod błędu
                Nie ustawiony C : OK
Po wywołaniu tej funkcji możemy stwierdzić, czy wystąpił błąd (np. podaliśmy numer segmentu, który nie zaczyna nowego bloku pamięci) poprzez sprawdzenie znacznika C:

; wcześniej nadajemy rejestrom wartości potrzebne do wywołania funkcji
  int  21h
  jc   Blad             ; skok gdy znacznik C jest ustawiony
; === nie ma błędu ===
Blad:
; === wystąpił błąd ===
Pytanie w jaki sposób rozpoznamy, czy użytkownik chce zainstalować program, czy go rozinstalować ? Oczywiście w tym celu musimy sprawdzić parametry podane w linii poleceń (czyli odróżnić uruchomienie: TEST.COM od: TEST.COM /u). Dla uproszczenia przyjmijmy, że jeżeli w linii poleceń znajdzie się litera 'u' to należy usunąć TSRa z pamięci.

Znaki podane w linii poleceń przy uruchamianiu programu są trzymane w bloku PSP (ang. Program Segment Prefix), który w zbiorach typu COM rezyduje na początku segmentu z programem (jak pamiętamy, program zaczyna się od adresu 100h, wcześniej jest właśnie PSP). Kolejne znaki parametrów podanych programowi są zapisywane począwszy od adresu 81h, pod adresem 80h leży bajt zawierający ilość znaków, a cały ciąg kończy się znakiem o kodzie 0dh (czyli CR). Literę 'u' znajdziemy porównując kolejne znaki aż do znaku CR albo wcześniejszego napotkania 'u'. I znowu - konkretną implementację znajdziecie w kodzie programu.

Przyszła pora na kolejne ulepszenie naszego sekundnika - będzie on zmieniał swój kolor w zależności od tego, czy klawiatura będzie w stanie CapsLock. Do tego celu przyda nam się opis zawartości komórek danych BIOSu pod adresami: 0040:0017h (czyli wygodniej jest napisać 0000:0417h - będzie to samo) i następnym (418h):

Adres 0:0417h
Numer bitu:     Znaczenie bitu zapalonego:
0               prawy Shift wciśnięty
1               lewy Shift wciśnięty
2               dowolny Ctrl wciśnięty
3               dowolny Alt wciśnięty
4               ScrollLock zapalony
5               NumLock zapalony
6               CapsLock zapalony
7               stan Insert

Adres 0:0418h
Numer bitu:     Znaczenie bitu zapalonego:
0               lewy Ctrl wciśnięty
1               lewy Alt wciśnięty
2               SysReq wciśnięty
3               stan przerwy (czyli po wciśnięciu Pause)
4               ScrollLock wciśnięty
5               NumLock wciśnięty
6               CapsLock wciśnięty
7               Insert wciśnięty
Jak widzimy, aktualny stan przełącznika CapsLock możemy odczytać sprawdzając bit nr 6 pod adresem 0:417h, gdy będzie zapalony to znaczy, że klawiatura jest w stanie CapsLock (chyba nie muszę tłumaczyć, na czym ten stan polega). Sprawdzenie jednego bitu najprościej dokonać instrukcją test, której podajemy maskę bitu (czyli jego wagę, w tym przykładzie 40h), a otrzymujemy w wyniku ustawienie lub wyzerowanie flagi ZF, czyli przepisanie do niej zawartości testowanego bitu (wyzerowanie ZF gdy bit był wyzerowany, ustawienie - gdy był ustawiony). Można też instrukcję test wykonać z parametrem nie będącym wagą jednego bitu - wtedy zostanie logicznie wymnożony (AND) bajt sprawdzany i podana wartość oraz odpowiednio ustawione flagi, podobnie jak działa instrukcja and - tylko bez zapamiętywania wyników. Dla przypomnienia podam jeszcze wagi kolejnych bitów, od 0. począwszy: 1,2,4,8,16,32,64,128, a w hex. to będzie: 1,2,4,8,10h,20h,40h,80h. Popatrzmy na fragment kodu do sprawdzenia stanu CapsLock:

  xor  ax,ax
  mov  es,ax            ; zerujemy rejestr segmentowy ES
  test byte ptr es:[417h],40h
  jz   Nie_ma_CapsLock
; CapsLock wciśnięty
Nie_ma_CapsLock:
; CapsLock nie wciśnięty
A teraz już program towarzyszący temu odcinkowi kursu pisania TSR'ów:

.model tiny
.code
.386
org 100h

Start:
  jmp  StartTutaj

; tutaj będą nasze zmienne:
staraproc dd 0
; znacznik potrzebny do sprawdzenia zainstalowania TSRa:
znacznik db 'Sekundnik, odc. 3'

NaszaProc:
  push ax
  push bx
  push di
  push es
  xor  ax,ax                 ; segment komórki ze stanem klawiatury
  mov  es,ax
  mov  bh,0ch                ; standardowy kolor jasnoczerwony do BH
  test byte ptr es:[417h],40h; sprawdzamy, czy włączony jest CapsLock
  jnz  CapsOn                ; skok gdy CapsLock wciśnięty
  mov  bh,1                  ; kolor niebieski - CapsLock wyłączony
CapsOn:
  mov  ax,0b800h
  mov  es,ax
  xor  di,di
  xor  al,al
  out  70h,al
  jmp  $+2
  in   al,71h
  mov  bl,al
  and  bl,0fh
  add  bl,'0'
  shr  al,4
  add  al,'0'
  mov  ah,bh                 ; ładujemy do AH wcześniej ustalony kolor
  stosw                      ; i rzucamy na ekran pierwszą cyfrę
  mov  al,bl
  stosw                      ; potem drugą
  pop  es
  pop  di
  pop  bx
  pop  ax
  jmp  dword ptr cs:[staraproc]        ; skok do oryginalnej procedury

; koniec części rezydentnej

StartTutaj:
  mov  ah,9                  ; 09h: wydruk nagłówka na ekran
  mov  dx,offset Logo
  int  21h
  mov  si,81h                ; początek ciągu parametrów
  cld
Petla:
  lodsb                      ; wczytanie do AL jednego znaku z DS:SI, SI=SI+1
  cmp  al,'u'                ; może to jest 'u' ?
  je   Rozinstaluj
  cmp  al,'U'                ; a może duże 'U' ?
  je   Rozinstaluj
  cmp  al,0dh                ; może kod ENTERa (CR) ?
  je   Instaluj
  jmp  Petla                 ; skok gdy nic nie trafimy

Rozinstaluj:
  mov  ax,3508h              ; 35h: pobranie wektora przerwania
  int  21h
  cmp  bx,offset NaszaProc   ; sprawdzamy, czy się zgadzają offsety
  jne  NieMa
  mov  si,offset znacznik    ; adres lokalnego znacznika do DS:SI
  mov  di,si                 ; i znacznika sprawdzanego do ES:DI
  mov  cx,17                 ; długość znacznika w bajtach
  cld
  repe cmpsb                 ; sprawdzamy aż do różniącego się bajtu
  jnz  NieMa                 ; skok gdy się nie zgadzają znaczniki
; Teraz już nie ma przeciwwskazań do rozinstalowania TSRa
  mov  dx,word ptr es:[staraproc]      ; czytamy oryginalny wektor
  mov  ax,word ptr es:[staraproc +2]   ; z bloku TSRa
  mov  ds,ax
  mov  ax,2508h              ; 25h: ustawienie wektora przerwania
  int  21h
  mov  ah,49h                ; 49h: zwolnienie bloku pamięci z TSRem
  int  21h                   ; w ES mamy segment TSRa
  mov  ax,cs
  mov  ds,ax                 ; przywracamy do DS segment naszego programu
  mov  ah,9                  ; 09h: wydruk napisu na ekran
  mov  dx,offset Uninst
  int  21h                   ; drukujemy komunikat o pomyślym usunięciu TSRa
  mov  ax,4c02h              ; 4ch: powrót do DOSu, w AL kod błędu
  int  21h

NieMa:
  mov  ah,9                  ; 09h: wydruk napisu na ekran
  mov  dx,offset Brak
  int  21h
  mov  ax,4c04h              ; 4ch: powrót do DOSu, w AL kod błędu
  int  21h

Instaluj:
  mov  ax,word ptr ds:[2ch]  ; numer segmentu środowiska odczytamy z PSP,
  mov  es,ax                 ; wrzucimy do ES
  mov  ah,49h                ; 49h: zwolnienie bloku pamięci
  int  21h
  mov  ax,3508h              ; 35h: pobranie wektora przerwania
  int  21h                   ; wynik wpadł do ES:BX
  mov  word ptr cs:[staraproc],bx      ; trzeba jeszcze go gdzies zapamietac
  mov  word ptr cs:[staraproc +2],es
  mov  ax,2508h              ; 25h: ustawienie wektora przerwania
  mov  dx,offset NaszaProc   ; DS:DX - wektor naszej procedury
  int  21h
  mov  ah,9                  ; 09h: wydruk napisu na ekran
  mov  dx,offset Napis
  int  21h
  mov  dx,offset StartTutaj  ; do DX wpisujemy adres pierwszego bajtu,
  int  27h                   ; który ma być zwolniony, wcześniejsze
                             ; zostają w pamięci na stałe

Logo   db 'Sekundnik 1996.',13,10
       db '     parametr /u - usunięcie programu z pamięci',13,10,'$'
Napis  db 'Program zainstalowany w pamięci.',13,10,'$'
Brak   db 'Program nie był wcześniej instalowany w pamięci.',13,10,'$'
Uninst db 'Program usunięty z pamięci.',13,10,'$'

end Start
W zależności od stanu CapsLock ustawiamy odpowiednio kolor wpisywanych na ekran znaków - niech to będzie jasnoczerwony dla CapsLock włączonego oraz niebieski dla CapsLock nie aktywnego. Właściwie nie pozostaje już nic innego jak tylko poczytać listing. Co zrobić, gdy program jest w pamięci, ale został po nim zainstalowany inny rezydent oraz jak wykryć taką sytuację dowiemy się w następnym odcinku (przy okazji poznamy bardzo użyteczne przerwanie 2fh, zwane przez znawców tematu Multiplex Interrupt).


Powrót na główną stronę kursu.