Mini kurs pisania programów TSR w asemblerze.doc

(141 KB) Pobierz
"LOOP, LOOPE, LOOPZ, LOOPNE, LOOPNZ"

"LOOP, LOOPE, LOOPZ, LOOPNE, LOOPNZ"

Jak pisałem poprzednio, przy naszych obecnych umiejętnościach spokojnie można zrobić pętlę "for .. to ..." - choćby tak, jak to demonstruje ten przykład.

Oczywiście - pętla działa co można bez trudu stwierdzić - poprawnie, lecz to jest pretekstem do wprowadzenia właśnie instrukcji LOOP.

Składnia:
LOOP ETYKIETA

Tu warto od razu wyjaśnić, że loop - z angielskiego oczywiście - znaczy "pętla".
No ale co właściwie robi LOOP? Mówiąc krótko - zaledwie dwie rzeczy: Po pierwsze - zmniejsza wartość CX o jeden (DEC CX), po drugie - jeśli CX jest większe 0 powoduje bezwarunkowy przeskok do "ETYKIETA".

Jeśli chcielibyśmy powiedzieć to językiem procesora - LOOP ETYKIETA jest skrutem poniższych komend:

DEC CX
CMP CX,0
JNE ETYKIETA


Nie trzeba tu filozofa by stwierdzić, że LOOP umożliwia tylko budowę pętli typu "downto" czy - jak w basicu - "step -1" - a po ludzku pętli, w której licznik maleje a nie rośnie.
Oczywiście jest to prawda, ale prawdą jest też, że nie warto się męczyć wykonywaniem powyżej pokazanej pętli, gdy można to rozwiązać LOOP'em.
Ponieważ postraszyłem w nagłówku pół tuzinem instrukcji, najwyższy czas zakończyć ten przydługi opis LOOP - za podsumowanie musi wystarczyć przykładowy programik.

LOOPE/LOPZ, LOOPNE/LOOPNZ

Instrukcje LOOP?? mają - jak nie trudno się domyśleć - coś wspólnego z instrukcją LOOP. Tym czymś jest choćby użycie.

Składnia:
LOOPE ETYKIETA
LOOPZ ETYKIETA
LOOPNE ETYKIETA
LOOPNZ ETYKIETA

Nie trudno się też domyśleć, że LOOP?? dotyczą w jakiś sposób instrukcji skoku warunkowego...
Kończąc więc domysły wyjaśniam, że działanie instrukcji LOOP** jest następujące:

  1. Zmniejszyć CX o jeden (DEC CX - tak jak przy LOOP)
  2. Jeśli CX>0 wykonać skok warunkowy do "ETYKIETA" w zależności od typu instrukcji: LOOPE - JE, LOOPNE - JNE, LOOPZ - JZ, LOOPNZ - JNZ.
  3. Jeśli CX=0, lub nie spełniono warunku skoku warunkowego, zakończyć pętlę.

A po jakie licho jest to wszystko aż tak dokładnie zamotane? - jedną z przyczyn jest oczywiście - wyjście na przeciw programiście... teraz ma on możliwość wykonania działania w pętli, które nie tylko będzie uzależnione od wartości CX, ale nawet może on przeprowadzić porówn anie dwóch innych wartości (np. CMP AX,BX) i również na tej podstawie wykonać pętlę lub jej nie wykonać... np. procedurka upewniająca się - zadająca użytkownikowi ważne pytanie, które musi on potwierdzić 3 razy np. "czy formatować dysk" - mogłaby wyglądać tak jak to przedstawia niniejszy program.
Mam nadzieję, że po jego analizie nie będziesz mieć już żadnych wątpliwości, ale oczywiście - gdybyś jednak miał mieć - czekam na pytania.

 

II

Mini kurs pisania programów TSR w asemblerze

Przerwania w programach TSR, pamięć i zegar CMOS

W poprzednich odcinkach kursu dowiedzieliśmy się, co to jest TSR i jak się go instaluje w pamięci. Przyszedł czas na zaprzęgnięcie naszego rezydenta do bardziej konkretnych zadań, dobrym przykładem niech będzie napisanie prostego programu instalującego się w pamięci i pokazującego aktualną sekundę, taka mała wprawka przed pełnym zegarem, który każdy z was będzie mógł spokojnie sam napisać po przeczytaniu tego odcinka.

Co nam tym razem będzie potrzebne ? Oczywiście, przerwanie zegara, wykonywane z częstotliwością 18.2 Hz (czyli około 18 razy na sekundę), a dokładnie: 1193181/65536 Hz. Możemy "przechwycić" to przerwanie, czyli podstawić swoją własną procedurę, którą komputer będzie wywoływać ze wspomnianą częstotliwością. W naszej procedurze będziemy pobierać z komputera aktualny czas i wyświetlać liczbę sekund w lewym górnym rogu ekranu. Pojawia się tylko pytanie - po co sprawdzać czas aż 18 razy na sekundę, jeżeli mamy wyświetlać tylko sekundy, które się będą zmieniać co 18 przerwań ? Najprostszym rozwiązaniem na oszczędzenie czasu procesora jest sprawdzanie aktualnego czasu tylko co 18 wywołanie naszej procedury. Jednakże możemy postąpić jeszcze inaczej - wyświetlać sekundnik na ekranie tylko wtedy, gdy jego wskazanie jest różne od poprzedniego. To nam oszczędzi mocy procesora traconej za przez każdą sekundę na wyświetlaniu tej samej liczby 18 razy. My jednak w programie przykładowym zrezygnujemy z takiej optymalizacji, aby nie zaciemniać kodu, każdy może to sam poćwiczyć. Jeszcze jedna dygresja - po dokonaniu swoich działań nasza procedura musi zwracać sterowanie do oryginalnej (czyli pod adres, który odczytamy w czasie instalowania się naszego TSRa, dla skrócenia opisu nazywa się często ten adres "wektorem przerwania").

Teraz opis dwóch przydatnych funkcji, które nam udostępnia DOS (czyli przerwanie 21h):
 

Funkcja 25h

Nazwa:          Ustalanie adresu kodu obsługi przerwania

Wywołanie:      AH=25h

                AL - numer przerwania

                DS:DX - adres procedury obsługującej przerwanie

Powrót:         Brak

Opis:           Funkcja ustawia nową procedurę obsługi przerwania o numerze

                podanym w AL. Adres procedury obsługi przerwania powinien być

                przekazany w DS:DX.

 

Funkcja 35h

Nazwa:          Pytanie o adres kodu obsługi przerwania

Wywołanie:      AH=35h

                AL - numer przerwania

Powrót:         ES:BX - adres procedury obsługi przerwania

Opis:           Funkcja zwraca adres procedury obsługi przerwania o numerze

                podanym w AL.

Dobra, mamy już wiadomości o tym, jak przechwytywać przerwanie po zapamiętaniu adresu oryginalnej procedury obsługi. Pytanie: no to które to właściwie jest przerwanie zegarowe ? Otóż jest to przerwanie nr 8, czyli IRQ0. Należy się jednak drobne wyjaśnienie: IRQ0 oznacza, że do kontrolera przerwań (a są takie dwa układy na płycie głównej komputera) do linii nr 0 przychodzą informacje od układu zegarowego, który na tą linię wystawia sygnał żądania przerwania właśnie 18 razy na sekundę. Podobnie do IRQ0 podłączona jest klawiatura, IRQ5 często karta muzyczna i tak dalej. Numer przerwania obsługującego linię IRQx to x+8, czyli przerwanie zegarowe ma numer 8, przerwanie klawiatury - nr 9 i tak dalej. Drugim kontrolerem nie będziemy się na razie zajmować, zaznaczę tylko, że obsługuje on przerwania IRQ8 do IRQ15, a numery przerwań od drugiego kontrolera zaczynają się dla zmyłki od 40h.

Kolejna sprawa: jak odczytać aktualną sekundę ? Jest kilka sposobów, my skorzystamy z bezpośredniego dostępu do zegara CMOS umieszczonego na płycie głównej komputera. Jest on widziany w przestrzeni adresowej jako dwa kolejne porty: o numerze 70h oraz 71h, dostępne dla programisty poprzez instrukcje: out i in. Instrukcja 'out' służy do wysyłania danych do portu, instrukcja 'in' do czytania z portu. W naszym przypadku będą to instrukcje: out 70h,al oraz in al,71h. Pierwszą z nich wyślemy do zegara CMOS numer komórki, która nas interesuje (o tym dalej), a drugą odczytamy jej zawartość. Cały fragment kodu czytający aktualną sekundę będzie w związku z tym wyglądał tak:
 

xor al,al
out 70h,al
jmp $+2
in al,71h

Instrukcja jmp $+2 powoduje drobne opóźnienie wymagane do poprawnej współpracy z zegarem CMOS, natomiast xor al,al jest równoważne mov al,0 - czyli po prostu do rejestru AL wpisuje zero. Po wykonaniu wyżej podanego bloku 4 rozkazów otrzymamy aktualną sekundę w AL w kodzie BCD, który należy jeszcze przekonwertować na kody dwóch znaków liczby. Jak to jest zrobione w praktyce ujrzycie za chwilę w listingu rezydenta. Jeszcze tylko trochę więcej informacji o układzie CMOS, w którym oprócz zegara zawarta jest też pamięć przechowująca najważniejsze ustawienia naszych komputerów (czyli całą zawartość SETUPu). Oto adresy i funkcje kolejnych komórek, do których możemy się odwoływać (po opisy szczegółowe odsyłam do książek):
 

0       aktualna sekunda zegara czasu rzeczywistego (RTC) w kodzie BCD

1       sekunda ustawienia budzika w kodzie BCD

2       aktualna minuta w BCD

3       minuta ustawienia budzika w BCD

4       aktualna godzina RTC w BCD

5       godzina ustawienia budzika w BCD

6       dzień tygodnia (1=niedziela,2=poniedziałek itd.)

7       dzień miesiąca w BCD

8       miesiąc w BCD

9       rok w BCD (ostatnie dwie cyfry)

0ah     RTC rejestr stanu A

0bh     RTC rejestr stanu B

0ch     RTC rejestr stanu C

0dh     RTC rejestr stanu D

0eh     bajt stanu ustawiany przez POST

0fh     powód wyłączenia

10h     typ stacji dysków w systemie

11h     zarezerwowane

12h     typ twardego dysku

13h     zarezerwowane

14h     bajt wyposażenia komputera

I tak dalej. Jest tych komórek 256 i kogo bardziej interesują, może zawsze zajrzeć do literatury (np. podanej już wcześniej książki: "Jak pisać wirusy"). Kolejna sprawa: jak wypisać wartość na ekranie nie używając do tego przerwania DOSu (używanie przerwań w naszej procedurze rezydentnej jest bardzo ryzykowne, o tym będzie powiedziane dokładniej w dalszych częściach kursu) ? Otóż jest sposób, należy kody znaków do wypisania "wcisnąć" bezpośrednio w obszar pamięci ekranu, na kartach VGA, CGA, EGA itp. zaczyna się ona od początku segmentu B800h, natomiast na karcie Hercules (HGC) od B000h. Pod tymi adresami mamy dostęp do kodu pierwszego znaku na ekranie (czyli tego w lewym górnym rogu), w następnym bajcie leży atrybut tego znaku, dalej kod drugiego znaku, jego atrybut itd. Kolory znaków możemy obliczyć podstawiając odpowiednie bity w bajcie atrybutów:
 

nr bitu:    7 6 5 4 3 2 1 0

znaczenie:  K R G B i r g b

K - to blink, czyli migotanie znaku (znak miga gdy bit K=1), i to intensity - jasność znaku (0=ciemniejszy, 1=jaśniejszy), RGB to kolejne składowe kolorów tła, natomiast rgb to składowe kolorów znaku. Przykład: potrzebujemy bajt atrybutu oznaczający jasnoczerwone znaki na czarnym tle, nie migające:
 

nr bitu:    7 6 5 4 3 2 1 0

znaczenie:  K R G B i r g b

wartość:    0 0 0 0 1 1 0 0

            | ^^|^^ | ^^^^^-czerwony

znak nie ---+   |   +jasny

miga      tło czarne

Czyli wychodzi na to, że poszukiwany atrybut znaku to 0ch. Można wpisać go w pamięć ekranu oddzielnie, po wpisaniu kodu znaku, jednak my te dwie rzeczy zrobimy jednocześnie - wpisując od razu całe słowo 16-bitowe rozkazem stosw, umieszczającym wartość rejestru AX pod adresem ES:DI i zwiększającym DI o 2 - tak, że wskazuje od razu na następny znak. Po uruchomieniu programu będziecie mogli się przekonać, że czas zawarty w zegarze CMOS spieszy się nieznacznie względem czasu DOSowego (np. pokazywanego przez Dos Navigatora, Nortona Commandera itp.), ponieważ przy uruchamianiu komputera DOS odczytuje zawartość CMOSa i trochę czasu mu zajmuje ustawienie swojego zegara - przez to się spóźnia. Natomiast po wyłączeniu komputera zegar CMOS chodzi sobie jakby nigdy nic - jego zasilanie jest podtrzymywane bateryjnie. Ale dość ględzenia, przyszedł czas na listing:
 

.model tiny

.code

.386

org 100h

 

Start:

  jmp  Instaluj

 

; tutaj będą nasze zmienne:

staraproc dd 0               ; dd oznacza 4 bajty (tutaj o wartości 0)

 

NaszaProc:

  push ax                    ; zapamiętujemy wartości używanych rejestrów

  push bx

  push di

  push es

  mov  ax,0b800h             ; B800h - segment pamięci ekranu karty VGA

  mov  es,ax

  xor  di,di                 ; zerujemy DI - adres w pamięci ekranu

  xor  al,al                 ; AL=0 - komórka z aktualną sekundą w BCD

  out  70h,al                ; wysyłamy do zegara CMOS

  jmp  $+2                   ; małe opóźnienie

  in   al,71h                ; odczytujemy wynik z zegara CMOS

  mov  bl,al

  and  bl,0fh                ; prawa połówka bajtu - prawa cyfra w BCD

  add  bl,'0'                ; do tego dodajemy kod zera

  shr  al,4                  ; lewa połówka bajtu - lewa cyfra w BCD

  add  al,'0'                ; do tego też dodajemy kod '0'

  mov  ah,0ch                ; atrybut napisu - jasnoczerwony na czarnym tle

  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

 

Instaluj:

  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 Instaluj    ; do DX wpisujemy adres pierwszego bajtu,

  int  27h                   ; który ma być zwolniony, wcześniejsze

                             ; zostają w pamięci na stałe

 

Napis  db 'Program zainstalowany w pamięci.',13,10,'$'

 

end Start

W następnym odcinku dowiemy się, jak naszego rezydenta wyrzucić z pamięci i do tego jeszcze kilka innych przydatnych rzeczy.
 



 

 

 

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

...

Zgłoś jeśli naruszono regulamin