[PL tutorial] Asembler kurs - HTML, Asembler, Asembler


Asembler

"Jakie oprogramowanie?"

Jeśli znasz pascala to wiesz, że wstawki w assemblerze możesz umieszczać np. w podprocedurach programu.
Jeśli jednak zamierzasz pracować w samym tylko assemblerze, to nawet w sieci dostępny jest cały wahlarz kompilatorów tego języka.

Jeśli chodzi o popularność, to najpopularniejszy jest Turbo Assembler firmy Borland - zamieszczone w kursie przykłady są pisane właśnie z myślą o tym kompilatorze.
Oczywiście różnic w samym assemblerze nie ma - dotyczą nato
miast struktury programu, deklaracji poszczególnych segmentów itd.

Pakiet Tasm służy do kompilacji kodu w assemblerze na postać programu wykonywalnego (zob. "Dlaczego trzeba kompilować assembler?"), ale do stworzenia samego programu w assemblerze może posłużyć każdy edytor umożliwiający zapis pliku w postaci czystego tekstu - np. edytor z pakietu Norton Commander.

Podstawową wadą TASM jest jednak fakt, że jest to pakiet komercyjny tzn. nie jest udostępniany w sieci za darmo, pozostaje więc zakup profesjonalnego pakietu, lub używanie programów kompatybilnych.

Jeśli nie posiadasz TASM, to polecam twojej uwadze udostępniony w konwencji public domain, komp
ilator ARROWSOFT ASSEMBLER.

Jeśli chodzi o ścisłość, to do programowania w assemblerze wystarczyłby program debug.exe w pakiecie MS-DOS, ale ponieważ jest dość toporny w użyciu - nikt go praktycznie na poważnie nie stosuje.

" ARROWSOFT ASSEMBLER "

Cały kurs assemblera był dotychczas przygotowywany pod kątem Turbo Assemblera, programu, który uchodzi za najlepszy i najpopularniejszy kompilator ASM. Już w kilka dni po opublikowaniu pierwszych wersji kursu na sieci, zaczęło padać pytanie "skąd wziąć TASM?" - faktem jest bowiem, że pakiet ten jako komercyjny nie został udostępniony "do wzięcia" w internecie. Podjęte przeze mnie poszukiwania programów kompatybilnych nie przynosiły przez dłuższy czas efektów, aż do momentu odkrycia assemblera ARROWSOFT, który spełniał praktycznie wszystkie założone kryteria w zadowalającym zakresie: 1. Może być używany bezpłatnie (freeware lub public domain), 2. Jest kompatybilny z TASM przynajmniej na tyle, by umożliwić bezproblemową kompilację programów na podstawowym poziomie, 3. Jest możliwie mały tzn. nadaje się do ciągnięcia z inetu. Arrowsoft jest udostępniony w konwencji public domain (istnieje też wersja komercyjna o większych możliwościach), kompiluje programy najwyżej dla 286 (nie udostępnia instrukcji/rejestrów 386 i lepszych procesorów, lecz z tym uniedogodnieniem początkujący programiści nie powinni mieć problemów zwłaszcza jeśli zależy im by oprogramowanie chodziło na wszystkich procesorach), umożliwia kompilację programów, których kod źródłowy nie jest dłuższy niż 64kb, nie rozróżnia dyrektyw typu ".model", ".code" etc. - jest bowiem kompatybilny z MASM 3.0.

Arrowsoft assembler można pobrać np. z Poznańskiego mirr
ora Simtela, pod adresem ftp://ftp.man.poznan.pl/pub/simtelnet/msdos/asmutl/valarrow.zip, długość pliku to około 100kb. Pakiet zawiera skróconą dokumentację assemblera jak i sam assembler, linker kompatybilny z masm link, edytor+źródło tego edytora w assemblerze oraz klon programu EXE2BIN również z kodem źródłowym.

Niestety jedną z wad jest brak szybkiego opisu jak kompilować program .asm na .com, więc dla tych, którzy pobrali lub zamierzają pobrać ten pakiet przygotowałem krótką instrukcję "szybkiej kompilacji" w
pliku tekstowym vasminst.asc.

 

"Dlaczego kompilować?"

Jak się przekonasz w niedługiej przyszłości, assembler nie do końca jest językiem procesora. Oczywiście prawdą jest to, co pisałem wcześniej, ale nie jest to ścisłe.

Otóż - procesor operuje na instrukcjach zapisanych cyfrowo, podczas gdy assembler jest tekstowym odpowiednikiem tych instrukcji - np:

mov ax,ffffh - instrukcja assemblera - językiem procesora zapisana jest jako seria bajtów - B8h FFh FFh.
(Musisz przyznać, że prościej nauczyć się symboli niż dziwnego systemu numeracji...)

To właśnie proces kompilacji zamienia plik z instrukcjami assemblera na plik z serią bajtów dla procesora - takim właśnie plikiem jest program typu .com.

"Turbo Assembler"

 

Do skompilowania programu w assemblerze na postać pliku wykonywalnego (.com) potrzebne są zasadniczo dwa programy z pakietu - tasm.exe i tlink.exe.
Tasm.exe kompiluje program w assemblerze (
.asm) na postać objektu (.obj), który później musi zostać skompilowany na właściwy program .com, lub .exe przy pomocy programu tlink.exe.

Załóżmy, że mamy plik program.asm i chcemy wygenerować program .com. W tym celu należy najpierw uruchomić program tasm.exe:

TASM.EXE PROGRAM.ASM

W wyniku działania programu tasm otrzymujemy plik program..obj, który następnie nalży przepuścić przez program tlink:

TLINK.EXE PROGRAM.OBJ /T

OK, i teraz mamy już plik program .com, który możemy uruchomić.

Dlaczego .com a nie .exe?

W niniejszym kursie nauczymy się tworzyć wyłącznie programy .com - różnic jeśli chodzi o sam język nie ma, ale programy .com mają prostszą strukturę (jeśli chodzi np. o linię poleceń) a poza tym są krótsze niż exe'ki, gdyż nie zawierają kilkusetbajtowego nagłówka charakterystycznego dla plików .exe.
Parametr
/T w tlinku powoduje właśnie wygenerowanie programu typu .com.

"Systemy liczbowe"

 

Systemy dwójkowy i szesnastkowy są to najzwyklejsze w świecie systemy liczenia - tak samo jak dobrze wszystkim znany i powszechnie używany system dziesiętny. Aby najprościej zobrazować sposób działania systemów dwójkowego i szesnastkowego, powinniśmy zacząć od przyjrzenia się systemowi dziesiętnemu.

Weźmy na przykład liczbę 123 - w systemie dziesiętnym. Patrząc od prawej strony - pierwsza cyfra określa ilość jedności, następna - ilość dziesiątek i wreszcie trzecia od prawej - ilość setek. Liczba 123 jest więc równa 1*100+2*10+3*1. Matematycznie jest to 1*10^2+2*10^1+3*10^0. Podobnie sytuacja ma się z układem dwójkowym - tyle, że zamiast dziesięciu cyfr (0 .. 9) mamy tu do dyspozycji tylko dwie - 0 i 1.

Pierwsza cyfra od prawej strony określa nam więc ilość jedności, następna - ilość dwójek, następna - ilość czwórek, ósemek, szesnastek itd.

Jak więc zamienić na układ dwójkowy liczbę 67?
67 =1*64+0*32+0*16+0*8+0*4+1*2+1*1 =1000011

W zapisie matematycznym jest to:
1*2^0+1*2^1+0*2^2+0*2^3+0*2^4+0*2^5+1*2^6.

Wreszcie trzecim układem, którym się zajmiemy, jest układ szesnastkowy (hexadecymalny, stąd symbol "H" dodawany do liczb w tym układzie). Pierwszy problem polega na tym, że potrzebujemy 16 cyfr, aby móc zapisywać liczby w tym układzie - podczas, gdy istnieje tylko 10 cyfr. Aby poradzić sobie z tym problemem, za cyfry 10-16 przyjęto litery A do F, zestaw cyfr w układzie szesnastkowym ma więc postać: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. Cała reszta jest analogiczna jak w układzie dziesiętnym i dwójkowym, tj. pierwsza od prawej cyfra określa ilość jedności, następna - szesnastek, następna - dwieście pięćdziesiątek szustek itd. Liczba 123D (decymalna - dziesiętna - zapisana w układzie dziesiętnym) - ma więc w układzie szesnastkowym postać:

7*16+11*1 =7B - ściślej - 7BH - litera H na końcu informuje, że liczba jest zapisana właśnie w układzie szesnastkowym.

Ostatnia kwestia dotyczy powszechności układu szesnastkowego w programowaniu. Jak wiesz, najczęściej spotykaną jednostką informacji jest bajt (bit jest mniej praktyczny i rzadziej używany) - czyli 8 bitów. Na ośmiu bitach, czyli w jednym bajcie - można zapisać 256 liczb (0 .. 255) - 2^8. To właśnie układ szesnastkowy umożliwia zapisanie tych liczb na dwóch znakach - 16*2=256 - 0d=00H, 255D=FFH.

"Procesor"

Procesor - zwany często CPU (od angielskiego "central processing unit") - jest głównym urządzeniem wchodzącym w skład systemu komputera.

To właśnie procesor wykonuje każdy program, odpowiada za komunikację z urządzeniami we/wy i transfery pamięci - słowem bez procesora komputer nie mógłby wogóle funkcjonować będąc tylko nieskoordynowanym splotem przewodów z różnych urządzeń.

Niniejszy kurs objaśni po krótce język, którym możemy rozkazywać procesorowi, terminologię, którą często spotyka się w dyskusjach i wreszcie poda kilka przykładów na spożytkowanie nabytej wiedzy.

"Porty"

Okazuje się, że tak. To właśnie i wyłącznie przez porty procesor komunikuje się ze wszystkimi urządzeniami - od drukarki, ekranu i klawiatury poprzez modem, cd-rom i kartę muzyczną - na pamięci cmos i wbudowanym zegarze czasu rzeczywistego (RTC) skończywszy.

Porty są adresowane 16-bitowo, tzn. każdy port ma swój unikalny dwubajtowy adres w systemie - np. com1 - 03F8H, lpt1 - 0378H, klawiatura - 0060H do 0064H ITD.

"Rejestry i flagi"


R
ejestry są kilkubajtową pamięcią wbudowaną do procesora, do której procesor ma najszybszy dostęp.
Większość rejestrów może być dowolnie modyfikowana przez programistę tak, że mogą one pełnić rolę podobną do zmiennych w pascalu.

 

Rejestry to:

  1. AX - accumulator register - rejestr akumulatora

  2. BX - base register - rejestr bazowy

  3. CX - counter register - rejestr licznika

  4. DX - data register - rejestr danych

 

Wszystkie powyższe rejestry są 16-bitowe - czyli dwubajtowe. Do każdego z nich jest dostęp przez podrejestry - np. dla AX istnieją rejestry AH i AL (AH=higher - wyższy bajt ax, AL=lower - niższy bajt AX).
Analogicznie - bx posiada BH i BL, CX - CH i CL, DX - DH i DL.
Oczywiście rejestry *H i *L są ośmiobitowe - jednobajtowe.

 

Procesor zawiera także rejestry segmentów.

Rejestry segmentów to:

  1. CS - Code Segment - segment kodu - określa segment pamięci, w którym znajduje się wykonywany program.

  2. DS - data segment - segment danych - określa segment pamięci, w którym znajdują się dane aktualnie wykonywanego programu.

  3. SS -stack segment - segment stosu - segment, w którym znajduje się stos.

  4. ES - extra segment - używany do operacji na łańcuchach pamięci.

W przypadku tych rejestrów nie istnieją już podrejestry z ich starszym i młodszym bajtem.
Jakkolwiek wszystkie te segmenty mogą być modyfikowane, to jednak modyfikacji wartości CS, DS i SS dokonuje się w zaawansowanym programowaniu - dla niedoświadczonych programistów takie modyfikacje mogą oznaczać tylko kłopoty.

 

Trzecią grupę rejestrów stanowią pointers - wskaźniki.

Wykaz wskaźników:

  1. IP - instruction pointer - wskaźnik wykonywanej instrukcji a ściślej - jej przesunięcia w segmencie CS

  2. SP - stack pointer - wskaźnik dna stosu - ściślej - jego przesunięcia w segmencie SS.

  3. BP - base pointer - wskaźnik bazowy.

Podobnie jak w przypadku segmentów, wartości rejestrów *P - za wyjątkiem BP - są modyfikowane wyłącznie przez procesor, lub przez program na wysokim szczeblu zaawansowania.

 

Przedostatnią grupę stanowią indeksy, do których należą:

  1. SI - source index - index źródłowy

  2. DI - destination index - index docelowy

Indeksy używane są do transferu bloków pamięci, ale mogą być bez konsekwencji używane również do innych celów.

 

 

Ostatnim rejestrem jest rejestr flag procesora. Jest to rejest 16 bitowy a każda flaga zajmuje 1 bit - może więc być aktywna, lub nieaktywna. Flagi to:

AF, CF, DF, IF, OF, PF, SF, TF, ZF.

 

Najogólniej mówiąc flagi określają aktualny stan procesora, mają też jednak istotne znaczenie przy instrukcjach skoku warunkowego, którymi zajmiemy się później.

 

"Stos"

 

Stos jest obszarem pamięci używanym do "tymczasowego" przechowywania danych istotnych dla programu.
Jak powiedzieliśmy w sekcji
"Rejestry i flagi" rejestry są obszarem kilku(nasto)bajtowym i nie nadają się raczej do przechowywania danych - raczej do ich obróbki.
Funkcję magazynu pełni właśnie stos.

 

Jak działa stos?

Wyobraź sobie kilka kartek - umownie ponumerowanych od 1 do 10 - symbolizujących dane. Jeśli na stole położysz kartkę o numerze 1, to masz już jedną kartkę "na stosie". Jeśli na kartce 1 położysz kartkę 2, to wówczas masz na stosie już dwie kartki. Dołóż jeszcze kartki 3 i 4 - 4 kartki na stosie. Zauważ jednak, że w danym momencie masz dostęp tylko do kartki 4 - tylko ją możesz w danym momencie zdjąć ze stosu. Kartki 1 2 i 3 są dla ciebie aktualnie niedostępne - aby móc zobaczyć co jest na nich napisane musisz zdjąć kartkę 4, 3 i 2. Wniosek 1: ze stosu dane mogą być pobierane w odwrotnej kolejności, niż zostały na nim umieszczone. Wniosek 2: W danym momencie masz dostęp do ostatnio umieszczonego na stosie elementu - bez możliwości natychmiastowego dostępu do elementów poprzednich.

 

Assembler - do obsługi stosu - posiada dwie analogiczne instrukcję -na_stos (push) i ze_stosu (pop), ale o tym powiemy w dalszej części tego kursu.

 

"Pamięci"

Pamięć jest chyba drugą co do ważności składową systemu komputera.
To właśnie tutaj znajduje się
stos, wykonywany program i jego dane, system operacyjny ze sterownikami - słowem wszystko, co aktualnie jest potrzebne do pracy komputera.

Istnieją 3 rodzaje pamięci: RAM, ROM i pamięć zewnętrzna.

Aby jednoznacznie określić (zaadresować) jakieś miejsce w pamięci, używa się dwóch dwubajtowych wartości - segment i przesunięcie (offset). Przesunięcie jest liczbą określającą odległość miejsca w pamięci od początku segmentu - np. czwarty bajt pamięci znajduje się w segmencie 0 na przesunięciu 4.

Jak już pisałem - wartości segment i offset są dwubajtowe, czyli maksymalna długość każdego segmentu wynosi 65536 bajtów (64KB).

Ważna uwaga

Wbrew temu, co mogłoby się wydawać, nie prawdą jest, że w pamięci można zaadresować 65536 segmentów *65536 bajtów czyli w sumie ponad 4 miliardy bajtów. Okazuje się bowiem, że początki segmentów w pamięci są przesunięte następny wzgledem obecnego o 16 bajtów - tzn. zerowy bajt segmentu x jest jednocześnie szesnastym bajtem segmentu x-1 i trzydziestym drugim segmentu x-2...

Dzięki takiej organizacji minimalizowane są dziury między programami alokowanymi w pamięci, ale sprawia to jednocześnie, że długość adresowalnego w ten sposób obszaru wynosi 16*65536 czyli 1MB.

" Przerwania procesora "

Przerwanie (ang. interrupt) - jest sygnałem dla procesora mówiącym mu, że ma czasowo przerwać aktualnie wykonywany proces i zająć się czymś innym. Bez przerwań procesor musiałby stale kontrolować wszystkie urządzenia zewnętrzne typu klawiatura, podczas gdy z przerwaniami procesor może wykonywać jakiś program i nadal ma możliwość reagować na wydarzenie zewnętrzne (np. naciśnięcie klawisza) od razu po jego wystąpieniu.

Procesor ma też instrukcję, która umożliwia wyłączenie przerwań - jeśli aktualnie wykonywany proces nie może być zakłócany. Istnieją jednak pewne specyficzne sytuacje, podczas których konieczna jest reakcja procesora - np. awaria sprzętu, pamięci czy odcięcie dopływu prądu - nie ważne jak istotny jest wykonywany aktualnie proces.
Do poinformowania procesora o tych ekstremalnych wypadkach służą tzw.
non maskable interrupts (NMI - przerwania niemaskowalne).

Procesor posiada 256 różnych przerwań - można je podzielić na dwie grupy:

  1. Hardware interrupts - przerwania sprzętowe - omówione powyżej przerwania wywoływane przez inne części sprzętu jak np. klawiatura, zegar etc.

  2. Software interrupts - przerwania programowe - przerwania, które są wywoływane przez program - np. przerwanie 21h - przerwanie dos'a - zajmiemy się tym później. Również w przypadku przerwań programowych, po napotkaniu odwołania do przerwania (instrukcja INT) - procesor przerywa wykonywanie aktualnego programu i "przeskakuje" do procedury wywołanego przerwania.

No właśnie: Każde z przerwań - bez względu na to, czy jest to przerwanie programowe czy sprzętowe - posiada procedurę obsługi - procedurę, która jest wykonywana w momencie wystąpienia danego przerwania - w przypadku przerwania klawiatury będzie to odczytanie znaku i jego zapis do bufora klawiatury.
Procedury obsługi danego przerwania można oczywiście zmienić tak, żeby procesor po wystąpieniu przerwania wykonywał zamiast standardowej obsługi - np. część naszego programu. Na tym właśnie polega pisanie programów rezydentnych tzw. tsr'ów.

Po wykonaniu procedury obsługi danego przerwania procesor powraca do programu, który wykonywał przed wystąpieniem sygnału przerwania i wykonuje ten program.

Ważniejsze przerwania to:

1. Sprzętowe:

08H - zegar - przerwanie wykonywane 18.2 razy na sekundę;
09H - klawiatura - przerwanie wykonywane, gdy został naciśnięty lub puszczony jakikolwiek klawisz.
70H - RTC - zegar czasu rzeczywistego.
(Są to oczywiście tylko te przerwania, które nas - początkujących assemblerowców - będą interesować jako te, które się przydają:)).

2. Przerwania programowe:

05H - bios - print screen - przerwanie uaktywniane, gdy naciśnięty zostaje klawisz
10H - bios obsługa karty graficznej
16H - bios - obsługa klawiatury
20H - dos - zakończenie programu
21H - dos - główne przerwanie funkcji/procedur

 

Już na wstępie radziłbym zaopatrzyć się w mniej lub bardziej dokładny spis przerwań z opisem ich funkcji - jest to niezbędne, gdyż nie sposób zapamiętać kilkuset różnych użytecznych funkcji.

W tym kursie można znaleźć kilka tabel przerwań, które znajdują się w dziale "Tabele, wykazy...", w rozdziale "Tabele przerwań".

Kwestię przerwań a dokładniej - ich praktycznego użycia - zgłębimy nieco później - podczas omawiania instrukcji int.

"Struktura programu"

O.K. temat ten jest rzeczą, którą najlepiej szybko przeczytać i mieć ją już z głowy:) - nigdy nie wnikałem w tajniki dyrektyw, których trzeba użyć - ten akurat punkt programu wykonuję automatycznie i bez zastanowienia.
No to tak: To, że program
.asm można pisać w każdym edytorze tekstowym pod warunkiem, że produkuje on plik w czystym ascii - to już zapewne wiecie.

Następną rzeczą są właśnie te dyrektywy - mój standardowy program wygląda zawsze tak:

.model tiny
.code
org 100h
start:
...
end start

 

Uwierzcie mi na słowo, że dotychczas takie podejście świetnie zdawało egzamin - i tak najważniejsza jest częśc między "start" i "end start" czyli to, co powyżej jest puste - to właśnie tam utkwi główny program.

Jeśli jednak chcecie mieć ogólne pojęcie o sprawie to już po krótce wyjaśniam:

.model tiny -

oznacza model pamięci, w którym zarówno kod (cs) jak i dane (ds) programu muszą się zmieścić w 64k - to jest jedna z cech programów typu .com.

.code -

oznacza code segment - segment kodu programu - właśnie tu się zaczyna.

org 100h -

program będzie się zaczynać od CS:0100H - to jest kolejna cecha programów typu .com.

Start: -

etykieta "początek" - możnaby ją z grubsza porównać do pascalowskiego begin...

Innym "szablonem", który również zda egzamin, jest szablon następujący: CSEG SEGMENT ;Ustalenie symbolu ASSUME CS:CSEG, DS:CSEG, ES:CSEG ;przyporządkowanie wartości CS, DS i ES do symbolu CSEG ORG 100H Start: ... CSEG ENDS END START Ten sposób polecałbym może nawet bardziej niż poprzedni (polecam go od niedawna), gdyż umożliwi on kompilację programu bez błędów zarówno pod TASM, ARROWSOFT i prawdopodobnie pod MASM a poza tym jest chyba nawet prostszy niż schemat poprzedni.

 

"Mov"

Mov jest jedną z częściej używanych a może nawet najczęściej używaną instrukcją, toteż poznamy ją już na samym początku.

Nazwa instrukcji (mov - move - przesuń) jest dość myląca, gdyż mov służy do kopiowania wartości między pamięcią a rejestrami, lub między rejestrami wyłącznie.
W praktyce oznacza to często, że
mov używa się do nadawania rejestrom określonych wartości np. ustawienie rejestru AX na wartość 09H (AX=09H).

Składnia:
MOV cel,zrodlo

Zarówno zrodlo jak i cel mogą być rejestrem, lub komórką pamięci, ale równocześnie tylko jeden z operatorów może być pamięcią tzn. nie możemy przesyłać danych bezpośrednio na linii pamięć-pamięć, trzeba to zrobić za pomocą dwóch transferów - pamięć-rejestr i rejestr-pamięć.
Bez trudu natomiast można kopiować wartość jednego rejestru do innego np.:

Mov AX,BX

Mam nadzieję, że jest to zrozumiałe, zanim więc zakończę - dwie anomalie mov, które trzeba sobie dobrze zapamiętać:

  1. Mov kopiuje a nie przenosi wartości, czyli po wykonaniu polecenia wartość "zrodlo" nie zostaje zmieniona ani wyzerowana.

  2. W składni mov najpierw podajemy cel, później źródło - nie jest to pomyłka, aczkolwiek do pomyłek niekiedy prowadzi, gdyż jest niezgodne z ludzkim tokiem rozumowania:).

I to w zasadzie byłoby wszystko - zachęcam do zadawania pytań i nadsyłania komentarzy, no i jeszcze przykładowy program do ściągnięcia i przemyślenia - zawiera przykłady wszystkich trzech omówionych powyżej transferów.

;Przyklady uzycia instrukcji mov

;

;Wprowadza: mov

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start:

mov ax,bx ;transfer rejestr-rejestr

mov cx,bx

mov bx,ax

mov dh,al

mov bl,ch

mov ah,09h ;transfer pamiec-rejestr

mov al,12h

mov ax,2811h

mov bx,0100h

mov ax,[bx] ;Zawartosc komorki pamieci o adresie ds:bx do ax

;W tym przypadku ds:0100h

mov [bx],ax ;transfer rejestr-pamiec

;zawartosc rejestru ax do komorki pamieci ds:bx

mov [bx],al ;Transfer jednego bajtu a nie calego slowa

;{

mov ax,4c00h

int 21h

;}

cseg ends

end start

;Uwaga:

;Linie miedzy ';{' a ';}' to procedura "wyjscie", ktora zostanie

;omowiona w dalszej czesci - tutaj jest tylko po to, ze jesli ktos

;dokona kompilacji, to bez procedury wyjscie program po uruchomieniu

;zatrzymalby sie po wykonaniu przesuniec i nie oddal kontroli do

;systemu.

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

;e-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz

"Stos - instrukcje push, pop, pusha, popa, pushf i popf"

Stos omówiliśmy już wcześniej - już wówczas sygnalizowałem istnienie dwóch instrukcji, które służą do obsługi stosu - PUSH i POP. Push - powoduje wrzucenie wartości lub zawartości rejestru na stos a POP - pobranie wartości ze stosu i wrzucenie jej do rejestru.

Składnia:
PUSH REJ16
PUSH WAR16
POP REJ16

"REJ16" i "WAR16" - oznaczają odpowiednio rejestr i wartość 16-bitową - tzn. nie można wrzucać ani pobierać ze stosu wartości 8-bitowych np. AL, AH...; inaczej: instrukcje PUSH i POP operują jedynie na słowach czyli danych 2-bajtowych.

Instrukcja PUSHA i POPA powodują wrzucenie na stos wszystkich rejestrów - nie trzeba wywoływać kilkakrotnie instrukcji PUSH i POP - wpływa to więc na skrócenie programu.
Niestety - instrukcje te nie są dostępne we wszystkich wersjach procesora, czyli ich używanie w programie wpływa na kompatybilność.
PUSHA - wrzuca wartości wszystkich rejestrów na stos,
POPA - pobiera wartości wszystkich rejestrów ze stosu.
Oczywiście, nie dotyczy to rejestrów IP i kilku innych, gdyż pobranie ze stosu wartości rejestru IP (instruction pointer - wskaźnik instrukcji - wskaźnik aktualnie wykonywanej instrukcji) spowodowałoby przeskok do miejsca w programie, już wykonanego, tj. przed instrukcję PUSHA, doprowadziłoby to do powstania "pętli nieskończonej".

Składnia:
PUSHA
POPA

Ostatnią już parą instrukcji jest PUSHF i POPF, których działanie jest identyczne do PUSHA i POPA, lecz ogranicza się wyłącznie do flag - push flags...

Składnia:
PUSHF
POPF

 

Poniższy przykładzik demonstruje użycie wszystkich omówionych tu instrukcji.

;Sposob uzycia stosu

;

;Wprowadza: push, pop, pusha, popa, pushf, popf

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start:

mov ax,1234h ;wartosc przykladowa w ax

;ax=1234h

push ax ;push powoduje wrzucenie wartosci lub zawartosci rejestru na stos

mov ax,18h ;inna przykladowa wartosc - zmieniamy rejestr ax

;ax=18h

pop ax ;pop powoduje pobranie wartosci ze stosu i wrzucenie do rejestru

;czyli ax zawiera znowu 1234h

push 4321h ;Dorzuca wartosc 4321h na stos

pop bx ;pobiera wartosc 4321 ze stosu i umieszcza w bx.

;pusha ;(Komentowane, gdyz nie jest kompatybilne z Arrowsoft Assembler)

;push all - wrzuca na stos wszystkie rejestry

;tylko dla procesorow 80188 i lepszych

mov ax,1200h

mov bx,3859h

mov cx,1221h

mov dx,8888h

;popa ;(Skomentowane, gdyz nie kompiluje sie z Arrowsoft)

;pop all - pobiera ze stosu wszystkie rejestry

;tylko dla procesorow 80188 i lepszych

pushf ;wrzuca na stos rejestr flag procesora

popf ; pobiera ze stosu rejestr flag

mov ax,4c00h

int 21h

cseg ends

end start

;Pascal ani basic nie posiadaja analogicznych instrukcji.

;Uwaga:

;Program nie robi nic, co moznaby zaobserwowac, wiec jego kompilacja i

;uruchamianie zasadniczo nie maja sensu.

;

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

;e-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz


"INT - wywołanie przerwania"

Jak już o tym pisałem omawiając istotę przerwań, INT jest instrukcją uruchamiającą przerwanie programowe.

Składnia instrukcji:
INT numer_przerwania

Numer_przerwania to liczba z zakresu 0-255, musi być podana konkretna wartość; nie można używać rejestrów np.

INT AH - spowoduje komunikat o błędzie.

Mając dostęp do listy funkcji choćby nawet podstawowych przerwań - int 21H, int 16h, int 10H - otrzymujesz sporą gamę gotowych procedur, które będziesz mógł wykorzystać w swoich programach bez potrzeby ich (procedur) dołączania do kodu programu.

Polega to na tym, że odpowiednie ustawienie rejestrów (głównie ax, bx...) i wywołanie danego przerwania powoduje wykonanie określonej procedury lub funkcji - jeśli jest to funkcja, to po powrocie z danego przerwania odpowiednie rejestry będą miały odpowiednie wartości - np:

Przerwanie 21H funkcja 2CH - pobierz czas

AH = 2CH

Przerwanie zwraca:

CH =

godzina (0-23)

CL =

minuta (0-59)

DH =

sekunda (0-59)

DL =

setne sekundy (0-99)

Co oznacza ten zapis?
Znaczy to tyle, że aby otrzymać czas korzystając z usługi przerwania 21H, należy:
1. Ustawić rejestr AH na wartość 2CH (MOV AH,2CH) 2. Wywołać przerwanie 21H (INT 21H). I już - rejestry CH, CL, DH i DL zawierać będą aktualny czas.

Innym przykładem - dość często używanym - jest funkcja 4CH przerwania 21H.
Przerwanie 21H funkcja 4CH - zakończ program i zwróć kod errorlevel

AH=4CH
AL = kod wyjścia (errorlevel dla plików .bat)

Nie zwraca nic.

Tutaj postępujemy analogicznie jak w poprzednim przypadku - ustawiamy rejestry AH i AL a następnie wywołujemy przerwanie.
Prosta demonstracja funkcji int 21H/4CH

;Przerwanie 21H - funkcja 4CH: Zakoncz program i ustaw errorlevel

;

;Wprowadza: int

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start: ;Poczatek programu

mov ah,4ch ;Numer funkcji w AH

mov al,01h ;Wartosc zwracana w errorlevel po opuszczeniu programu

int 21h ;Przerwanie dos

;Jedna z czesciej uzywanych funkcji - opuszczenie programu i zwrocenie

;do systemu numeru bledu.

;

;W pascalu jej odpowiednikiem jest halt(nrbledu).

cseg ends

end start

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

;e-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz

Znając choćby te dwie funkcje - można się pokusić o napisanie prostego programu, który można będzie używać w plikach sadowych (.bat) - program, który po uruchomieniu w errorlevel zwróci aktualną godzinę.

Na pewno już sam potrafisz napisać taki program, jeśli jednak nie, to tutaj masz przygotowany ten programik (w wersji .asm i skompilowanej) oraz prosty plik wsadowy, który pozwoli przetestować działanie programu.

Ostatnią sprawą, o której warto wiedzieć jest oczywista możliwość uruchomienia dowolnego przerwania - bez parametrów.
Jak wiesz, przerwanie 05H jest przerwaniem drukowania ekranu - uruchamianym po naciśnięciu .
Co by się jednak stało, gdyby uruchomić to przerwanie przy użyciu instrukcji INT?
Sprubój - jak zwykle dostarczam
gotowca dla leniwych:).

;Print screen - drukuj ekran

;

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start:

int 05h ;uruchom przerwanie 05h - przerwanie biosu, ktore

;jest uruchamiane,

;gdy uzytkownik naciska klawisz <PRTSCR>.

mov ax,4c00H

int 21h ;zob. wyjscie.asm

cseg ends

end start

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

;e-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz

Uwaga!!!
Jeśli zamierzasz napisać jakikolwiek program sam, to pamiętaj - po pierwsze o strukturze programu, po drugie - nigdy nie zapomnij użyć funkcji wyjścia z programu.

"Deklaracja danych"

 

Sama deklaracja danych jest bardzo prosta - podobnie jak w pascalu możemy nadać zadeklarowanej danej nazwę; nie wyróżnia się tu jednak typów danych tak złożonych jak w pascalu - istnieją generalnie tylko 3: byte (bajt), word (słowo - 2 bajty) i double word (podwójne słowo - 4 bajty).

Istnieje też możliwość deklaracji łańcucha znaków - tekstu, co przyda się nam za chwilę, lub obszaru dowolnej długości wypełnionego jakąś określoną wartością - tego sposobu używa się np. do zadeklarowania "bufora", który będzie modyfikowany w trakcie działania programu.

Program pokazujący sposób deklaracji danych

;Przyklad deklaracji danych

;

;Wprowadza: db, dw, dd

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start:

mov ax,4c00h

int 21h ;zob. wyjscie.asm

dane1 db 0 ;1 bajt

dane2 dw 0 ;2 bajty - 1 slowo (word)

dane3 dd 0 ;4 bajty - double word (podwojne slowo)

;przyklady deklaracji lancuchow:

dane4 db 'napis 1 - przyklad deklaracji lancucha...'

dane5 db 124 dup('x') ;taki zapis powoduje wyprodukowanie w kodzie

;po kompilacji obszaru 124 bajtow, wypelnionych

;jakas wartoscia.

;Dotyczy takze dw i dd

cseg ends

end start

;---

Skoro już umiemy deklarować dane łańcuchowe, możemy ponownie otworzyć spis funkcji przerwania 21H i spróbować naszych sił pisząc program bazujący na funkcji 09H tego przerwania.

Przerwanie 21H, funkcja 09H - wyświetlenie napisu:
AH = 09H DS:DX = wskaźnik pierwszego znaku napisu,
Przerwanie nie zwraca nic.
Napis musi być zakończony znakiem
'$'.

Prawie wszystko jasne, jedyną zagadkę stanowi słówko "wskaźnik" w opisie tej funkcji.
Wbrew pozorom jest to proste -
DS musi być ustawione na segment pamięci, w którym znajduje się nasz napis a DX na przesunięcie tego napisu w segmencie.

Kolejną cechą programów .com, która przychodzi nam tu z pomocą jest fakt, że w tych programach CS = DS - kod programu jest zapisany w tym samym segmencie co dane programu i już przy ładowaniu pliku .com do pamięci CS jest ustawiane na tą samą wartość co DS.
Pozostaje więc problem fatalnego przesunięcia - tutaj z kolei przyda się funkcja wewnętrzna Turbo Assemblera -
OFFSET.
Funkcja ta zwraca przesunięcie danej, której nazwę podamy w nawiasach, tak, że najpierw jest obliczane przesunięcie tej danej po kompilacji i ta wartość jest podstawiana w miejscu, gdzie wywołano funkcję - np:
MOV DX, OFFSET(NAPIS) spowoduje, że podczas kompilacji zostanie obliczone przesunięcie danej napis i wyliczona wartość zostanie podstawiona za offset - przy dekompilacji lub debugowaniu programu w miejscu naszej instrukcji byłaby:
MOV DX,121H
Oczywiście przy założeniu, że akurat
121H to przesunięcie zmiennej napis.

Ktoś mógłby stwierdzić, że zamiast offset(napis) mogliśmy z góry wpisać 121H - miałby rację tzn. program prawdopodobnie by zadziałał, ale po co trudzić się skomplikowanymi wyliczeniami, skoro po pierwsze - kompilator zrobi to szybciej i dokładniej a po drugie jeśli zmienimy coś w programie, to cała matematyka na nic - wszystko trzebaby zrobić od nowa.

Dość tych teoretycznych rozważań - przystąpmy do napisania programu... Jak zwykle - dla leniwych:) - program gotowy .

;Wydruk napisu na ekran przez BIOS - przerwanie 21H funkcja 09H

;

;Wprowadza: offset, db

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start: ;Poczatek programu

mov ah,09h

mov dx,offset napis;DX=Przesuniecie napisu w segmencie cs

int 21h ;Wykonaj

mov ax,4c00h ;Wyjscie - zob. wyjscie.asm

int 21h

napis db 'Napis, ktory zostanie wypisany ',13,10,'$'

;Napis _musi_ byc zakonczony znakiem $

;13,10 to kod CR i LF - przejscie do nowej linii

cseg ends

end start

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

E-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz

Eksperymentujcie - zmieniając napis (może on być na prawdę długi), ale trzeba pamiętać, że musi kończyć się znaczkiem $.
A oto przykład
długiego napisu...

;Demo dluuuugiego napisu na ekranie

;

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start: ;Poczatek programu

mov ah,09h

mov dx,offset napis

int 21h ;zob. program napis.asm

mov ax,4c00h ;Wyjscie - zob. wyjscie.asm

int 21h

napis db 'Demo dluuugiego napisu!!!',0dh,0ah

db 'W zasadzie dlugosc napisu jest ograniczona wylacznie dlugoscia',0dh,0ah

db 'segmentu, w ktorym sie on znajduje, czyli prawie 64kb... Mozemy wiec',0dh,0ah

db 'mowic, iz napis ma praktycznie nieograniczona dlugosc.',0dh,0ah

db 'Ten programik pochodzi ze strony "Kurs podstaw Assemblera" by Grzegorz',0dh,0ah

db 'Zlotowicz, ktora to strona znajduje sie pod adresem:',0dh,0ah

db 'http://sezam.wanet.pl/kursasm/index.htm',0dh,0ah

db 'Zycze licznych sukcesow i postepow w nauce',0dh,0ah

db 'Assemblera!',0dh,0ah,'$'

;Tutaj znowu - napis zakonczony znakiem $

cseg ends

end start

" JMP - jump - skok "


I
nstrukcja jmp jest prosta - zarówno w składni jak i działaniu - powoduje bezwarunkowy przeskok do innej części programu - analogicznie jak "goto" w pascalu czy basicu.

Składnia:
JMP etykieta

Etykieta - to podobnie jak w pascalu - ciąg znaków zakończony dwukropkiem - identyfikujący miejsce w programie np.: "Start:", "Petla:" itp.

Instrukcji jmp używa się w zasadzie w połączeniu z instrukcjami skoku warunkowego, ale ma oczywiście również inne poważne zastosowania:).
Czasem można się spotkać z instrukcjami "
JMP FAR" czy "JMP SHORT" - są to odmiany jmp nie różniące się składnią - różnica polega na tym, że "FAR" (daleki) odnosi się do skoków dłuższych niż +-128 bajtów a "SHORT" (krótki) - dotyczy skoków nie większych niż +-128 bajtów.
Sygnalizuję to jednak wyłącznie dla "dopełnienia" informacji o skokach - te szczegóły dla nas - początkujących programistów - nie mają żadnego istotnego znaczenia.

 ;Demonstracja uzycia etykiet i skokow miedzy nimi

;

;Wprowadza: etykiety, jmp

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start:

jmp etyk1 ;skok do etykiety "etyk1"

mov ax,5238h

mov bx,4321h

mov cx,100h

mov ax,4c00h

int 21h ;ta czesc kodu nie zostanie wykonana

mov dx,0100h

etyk1:

jmp etyk2

mov ax,4c00h

etyk2:

jmp etyk4

mov ah,09h ;znowu "jalowy" kod

int 21h

etyk3:

mov ax,0c0c0h

jmp wystarczy

mov dx,0d0d0h

etyk4:

mov ax,4c00h

jmp etyk3 ;kto powiedzial, ze nie mozna "skakac" wstecz?

mov ah,09h

wystarczy:

mov ax,4c00h

int 21h

cseg ends

end start

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

;e-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz

;Bledne kolo czyli petla doskonala

;

;Wprowadza: jmp

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

;UWAGA!!!

;Skompilowanie i uruchomienie tego programu spowoduje

;zawieszenie sie komputera, poniewaz chodzi on "w kolko"

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start:

jmp start ;Jmp powoduje bezwarunkowy przeskok do podanej etykiety.

;Nasz program bedzie wiec wykonywal instrukcje skoku

;na swoj poczatek

;a instrukcja na poczatku jest skok na poczatek, czyli

;jest to petla nieskonczona.

;Jest to tez chyba najkrotszy (skompilowane bldnkolo.com ma

;tylko 2 bajty dlugosci) sposob na zawieszenie komputera.

;

;w pascalu i

;basicu wygladaloby to tak:

;Basic:

;10 goto 20

;20 goto 10

;

;Pascal:

;label l1;

;begin

;l1:

;goto l1;

;end.

;Roznica polega tylko na tym, ze po kompilacji przykladu

;pascalowskiego otrzymujemy ponad 1600 bajtow kodu:)).

cseg ends

end start

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

;e-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz

"Podprocedury
- instrukcje CALL i RET"

 

Jak wskazuje na to temat instrukcje CALL (wołaj) i RET (return - powrót) służą do tworzenia podprocedur:) - części programu, które będą używane częściej niż raz - tak jak procedury np. w pascalu.

Ponieważ wierzę w moc przykładów - znowu nie będę się zbyt długo rozwodził nad tematem... A więc do rzeczy:

Składnia:

CALL ETYKIETA
ret

CALL- zapamiętuje na stosie adres następnej instrukcji programu a następnie przeskakuje do etykiety "etykieta" tak, jak to robiła instrukcja JMP.

Po napotkaniu instrukcji RET procesor pobiera ze stosu adres zapamiętany tam przez CALL i przeskakuje pod ten właśnie adres czyli "wraca" do programu nadrzędnego.

Schemat użycia instrukcji CALL i RET wygląda więc następująco:

Nazwa_procedury: ; etykieta określająca nazwę procedury
;Kod podprocedury
;...
;...
;...
RET ; Powrót do programu nadrzędnego.

Gdzieś w programie natomiast - należy użyć następującego wywołania:

CALL nazwa_procedury

To tak dla wszystkich lubiących schematy i teorię.

Dla zwolenników twardej i niekiedy brutalnej praktyki - niepraktyczny (jeśli chodzi o użycie) programik demonstracyjny procedur i podprocedur.

;Sposob tworzenia i wykonywania podprocedur

;

;Wprowadza: call, ret

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start: ;Poczatek programu

jmp begin ;przeskakujemy do etykiety begin,

;tutaj zapiszemy procedury, ktore uzyjemy pozniej

napis: ;procedure napis;

;Parametry do procedury - dx=ofset napisu

mov ah,09h

int 21h ;Zob. napis.asm

ret ;Powrot do procesu nadrzednego

wyjscie: ;Procedure wyjscie;

mov ax,4c00h

int 21h

ret

;Umiescmy tu tez nasze dane:

halo db 'Czesc uzytkowniku ',13,10,'$'

nap1 db 'Napis pierwszy',13,10,'$'

nap2 db 'Napis drugi',13,10,'$'

;procedury wypisujace napisy

witaj:

mov dx,offset(halo)

call napis

ret

napis1:

mov dx,offset(nap1)

call napis

ret

napis2:

mov dx,offset(nap2)

call napis

ret

dozob db 'Do zobaczenia ',13,10,'$'

begin: ;Poczatek wlasciwy

call witaj

call napis1

call napis2

call napis1 ;znowu napis 1

;Oczywiscie procedury napis mozna uzyc w "programie glownym":

mov dx,offset(dozob)

call napis

call wyjscie

cseg ends

end start

;Jesli znasz basic to wiesz, ze tu az prosi sie analogia do

;gosub/return; w Pascalu procedury mozna deklarowac od razu i nie

;trzeba sie przejmowac takimi drobiazgami - nie mniej jednak, podobna

;funkcje do ret pelni procedura "exit", ktora powoduje przejscie na

;"wyzszy" poziom programu - nie koniecznie do programu glownego.

;

;

;

;

;

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

;e-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz

 

" Instrukcje CMP i J** "

 

Rzecz, którą za chwilę omówię jest prawie tak samo a może nawet bardziej ważna i przydatna jak omawiane już przerwania. Dotychczas ani razu nie mówiliśmy o instrukcjach warunkowych (if .. then) ani o sposobie porównywania dwóch wartości.

Do porównania dwóch wartości służy instrukcja CMP (compare - porównaj), której składnia jest następująca:

CMP A, B

A i B to dwie dowolne wartości, które porównujemy. Wykonanie instrukcji CMP powoduje ustawienie odpowiednich flag procesora (OF, SF, ZF), a to właśnie na tej podstawie są wykonywane instrukcje skoku, o których za chwilę - tematu które flagi są kiedy ustawiane i dlaczego tak a nie inaczej - nie chciałbym na razie zgłębiać bo może to tylko zamieszać i nie wnieść nic do sprawy...

Na etapie początkowym nauki assemblera wystarczy wiedzieć, że jeśli chcemy wykonać skok warunkowy ("If a>4 then" itp.) to najpierw wykonujemy instrukcję CMP A,4 a następnie jedną z poniższych...

Składnia instrukcji J**:
J** etykieta

"Etykieta" to oczywiście (tak jak w przypadku JMP, CALL) nazwa etykiety, do której program przeskoczy jeśli... No właśnie.

Przydatniejszych instrukcji J** jest kilka, ale są one nazwane wg pewnych reguł, których znajomość może zaoszczędzić sporo wkuwania więc w efekcie "nie taki diabeł straszny".

Pierwszą literę tych instrukcji stanowi zawsze J (jump if - skok jeśli), później może wystąpić N (not - nie) a na końcu A (above - ponad), lub E (equal - równe), lub B (below - poniżej).

Weźmy na przykład równość:
"J"+"E" czyli JE. W ten sposób możemy też zapisać nie-równość pisząc JNE... i tak dalej.

Jednej rzeczy należy się jednak nauczyć - jak już miałeś okazję zauważyć z tym assemblerem to tak nie zawsze i nie do końca "po ludzku" bywa, więc jeśli mielibyśmy instrukcje:

CMP A,B
JA et_wieksze

to ktoś mógłby zapytać, czy skok nastąpi jeśli A > B, czy jeśli B > A? Ktoś inny sprawdziłby to doświadczalnie, ale ja - z góry wyjaśniam. Tu akurat jest normalnie tzn. w powyższym przykładzie instrukcja JA wykona skok jeśli A > B - pisząc dla wyobraźni - A above B.

Myślę, że to w miarę bezboleśnie wyjaśniłem - Jak zapisać >=?
W assemblerze istnieje instrukcja
JAE (Above or Equal), której oczywiście można używać (istnieje też JBE (below or equal)) - nie są to jednak instrukcje "pierwszej potrzeby" zwłaszcza, gdy ktoś nie chce się zbyt wiele uczyć (a ktoś chce?:))), bo tak "na chłopski rozum" sprawa jest prosta:
Większe lub równe znaczy tyle samo co
"nie mniejsze" a więc - JNB... I mamy to samo przy użyciu naszego pierwotnego szablonu bez żadnych JAE....

Przeglądając zestaw instrukcji przerwania 21H można natrafić (w opisie np. otwierania pliku) na zapis, że funkcja zwraca:

CF = 0 - wykonano bez błędów,
CF = 1 - wystąpił błąd (nr błędu jest w AX).

W praktyce więc chodzi o to, żeby sprawdzić czy dana flaga jest czy nie jest aktywna... a pisałem wcześniej, że właśnie J** bazuje na flagach czym się nie należy przejmować.
Jeśli więc porównujemy dwie wartości, to istotnie nie ma to znaczenia, ale jeśli interesuje nas stan jakiejś konkretnej flagi, to istnieje kilka użytecznych instrukcji J** przeznaczonych właśnie do tego. Są to:

JC - skok, gdy CF = 1;
JO - skok, gdy OF = 1;
JP - skok, gdy PF = 1;
JS - skok, gdy SF = 1;
JZ - skok, gdy ZF = 1;

Również tutaj stosuje się nieśmiertelna zasada not, czyli można stosować zapis np. JNC, który oznacza, że CF nie jest ustawiona - CF = 0; tyczy się to oczywiście pozostałych flag także (JNO, JNP, JNS i JNZ).

Wielokrotnie pisałem powyżej, że jest wiele... że się nie ma co przejmować... i wogóle tak, że spokojnie możnaby mi zarzucić herezję i nierzetelność.

Przewidując możliwość takiej sytuacji a jednocześnie chcąc uniknąć zawiłego i pokrętnego tłumaczenia, przygotowałem (dla zapaleńców choć i "leniwym" lektura nie zaszkodzi) pełny (mam nadzieję) zestaw instrukcji J**, który jednak (zastrzegam i ostrzegam) pochodzi z książki - większości tych instrukcji wogóle nie używałem i nie zamierzam:)).

 ;Demonstracja instrukcji CMP i skoku warunkowego

;

;Wprowadza: CMP, JE, JNE, JA, JL, JNA, JNL

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start:

jmp start2

oknap db 'Warunek spelniony ',13,10,'$'

OK: ;Procedura wypisujaca napis o spelnieniu warunku

push ax

push dx ;wrzucamy na stos rejestry, ktorych uzyjemy

;w procedurze - mamy gwarancje, ze nie zakloci nam ona pracy programu glownego

mov ah,09h

mov dx,offset(oknap)

int 21h

pop dx

pop ax ;pobieramy ze stosu w odwrotnej kolejnosci

ret

nook db 'Warunek nie zostal spelniony ',13,10,'$'

nok:

push ax

push dx

mov ah,09

mov dx,offset(nook)

int 21h

pop dx

pop ax

ret

start2:

mov ax,1

mov bx,1 ;ax=bx

cmp ax,bx

je war1

call nok

jmp wy

war1:

call ok

mov ax,2 ;AX > BX

cmp ax,bx

ja war2 ;znowu warunek sie spelni

call nok ;gdyby jednak nie

jmp wy

war2:

call ok

cmp ax,bx ;porownujemy jeszcze raz, tym razem z innym warunkiem

;nadal wiemy, ze ax > bx

jna war3 ;teraz sie nie spelni i nie przeskoczy

call nok

jmp wy

war3:

call ok ;nie udalo sie...

wy:

mov ax,4c00h

int 21h

cseg ends

end start

;Jak wiec zadziala nasz program po kompilacji?

;

; Powinien dwukrotnie wypisac komunikat o

;spelnieniu warunku, za trzecim razem - o niespelnieniu.

;

;OK, mysle, ze lapiesz istote sprawy - nie chcac sie bawic zbyt wiele

;a jednoczesnie nie zostawiajac tego - kilka przykladow CMP poza

;programem:

;cmp ah,1

;cmp ax,3

;cmp ax,4444H

;cmp al,ah

;cmp ah,al

;- czyli - jak widzisz - kombinacji jest sporo (a i tak nie wszystkie

;wypisalem:)).

;

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

;e-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz

"Spis instrukcji
skoku warunkowego"

Niniejszy spis zawiera wszystkie (mam nadzieję:)) instrukcje skoku warunkowego; oczywiście wielu z nich nie używa się zbyt często, ale - jak pisałem - jest to zestawienie "dla ciekawych".

Aby móc zrozumieć co oznaczają poniższe instrukcje bez konieczności
uczenia się każdej z nich na pamięć - wystarczy przyswoić podane skróty a później na ich podstawie "rozszyfrowywać" znaczenie instrukcji skoku:

 

J -

jump if

- skok jeśli

N -

not

- nie

A -

above

- powyżej

G -

greater

- większe

E -

equal

- równe

Z -

zero flag

- ZF

B -

below

- poniżej

L -

less

- mniej

O -

Overflow flag

- OF

P -

parity flag

- PF

S -

sign flag

- SF

 

Podane zestawienia dla A i B obrazujące matematycznie kiedy warunek jest spełniony - są przygotowane przy założeniu, że wykonujemy instrukcję

CMP A, B - nie odwrotnie (dla CMP B,A wszystkie warunki będą odwrócone).

 

JA (JNBE)
A > B (CF = 0 lub ZF = 0)

JAE (JNB)
A >= B (CF = 0)

JB (JNAE)
A
JBE (JNA)
A <= B (CF="1" LUB ZF="1)
JE (JZ)
A = B (ZF = 1)

JNE (JNZ)
A <> B (ZF = 0)

JG (JNLE)
A > B (ZF = 0 lub OF = SF)

JGE (JNL)
A >= B (SF = OF)

JL (JNGE)
A OF)

JLE (JNG)
A <= B (ZF="1" lub SF <> OF)

JC
(CF = 1)

JNC
(CF = 0)

JO
(OF = 1)

JNO
(OF = 0)

JP (JPE)
(PF = 1)

JNP
(PF = 0)

JS
(SF = 1)

JNS
(SF = 0)

 

Tutaj musze zaznaczyć, że sporządzając niniejsze zestawienie opierałem się na różnych źródłach - nie mam wogóle doświadczenia z większością tych rzadziej używanych - może się więc okazać, że informacje te nie są do końca ścisłe. Przepraszam więc z góry - jeśliby tak się stało; jeśli potrafisz skorygować jakieś błędy na tej (jak i innych stronach kursu), to kliknij bez wahania link na dole strony i poinformuj mnie o nieścisłości.

 

"Operacje matematyczne"

Po tym wszystkim, co już dotychczas zrobiliśmy, to, czym zajmiemy się teraz jest proste jak ... się za chwilę przekonasz.
Nie zwlekając więc ni chwili przystąpmy do rzeczy.

INC i DEC

Są to instrukcje analogiczne jak w Pascalu, z małym wszakże wyjątkiem, ale o tym za moment - powodują zwiększenie lub zmniejszenie jakiejś wartości.

Składnia:
INC A
DEC A

No więc cała różnica polega na tym, że - jak widać - nie ma tu drugiego parametru, który określałby o ile ma być zwiększona lub zmniejszona dana wartość. Parametru tego nie ma, gdyż zawsze jest ona zmniejszana/zwiększana o 1 - słownie jeden.
Rozwiewając więc resztki wątpliwości wszystkich, którzy takie wątpliwości jeszcze zachowali powiem, że
INC zwiększa a DEC zmniejsza daną wartość...
Przykładowo, jeśli w
AX mamy wartość 03H, to wykonanie instrukcji

INC AX - zwiększy AX o 1 - AX będzie się równać 04H.

Jeśli więc w tym stanie rzeczy wykonamy instrukcję

DEC AX - to wartość AX zostanie zmniejszona o 1 - z wartości 04H na 03H.

 

Dodawanie i odejmowanie

Do tych operacji można oczywiście używać DEC i INC, ale - oczywiście - byłoby prawie nie do pomyślenia, że Assembler nie udostępnia żadnej instrukcji umożliwiającej wykonanie powyższych działań "za jednym zamachem" tzn. bez konieczności zmieniania wartości o 1. Instrukcje, które umożliwiają wykonanie dodawania lub odejmowania to ADD (dodaj) i SUB (substract - odejmij).

Składnia:
ADD A, B
SUB A, B

Wykonanie powyższych instrukcji powoduje dodanie (lub odjęcie w przypadku SUB) wartości A i B i wrzucenie wyniku do A - np.: Jeśli AX=5 i wykonamy ADD AX, 08H - to procesor najpierw doda 5+8 a następnie wrzuci wynik do aX - po wykonaniu powyższego polecenia AX będzie równe 13 (dziesiętnie) czyli 0DH. Analogicznie - wykonanie w tym stanie rzeczy instrukcji SUB AX,3 spowoduje odjęcie trójki od aktualnej wartości AX i wrzucenie wyniku do AX (13 - 3 =10) - AX=10D czyli 0AH.

 

Mnożenie

Tutaj sprawa nie jest już tak prosta jak w przypadku dodawania i odejmowania, dlatego też mnożenie i dzielenie zostaną omówione oddzielnie.
Do mnożenia służy w Assemblerze instrukcja
MUL (multiply - mnóż).

Składnia:
MUL A

Niewątpliwie wymaga to pewnych wyjaśnień - wyjaśniam więc: MUL powoduje pomnożenie wartości A razy zawartość rejestru AX, lecz sprawa wygląda nieco inaczej, gdy A jest wartością typu word (słowo - 2 bajty) lub byte (bajt).
Gdy więc A jest to 1 bajt, wówczas wykonywane jest mnożenie a*AL (przypomnę, że AL jest to młodszy bajt rejestru AX) i wynik mnożenia zostaje wrzucany do AX. Załóżmy, że AL=2 a BL=3 - wówczas wykonanie instrukcji

MUL BL - powoduje wymnożenie 2*3 i wrzucenie wyniku do AX - AX=6.

Jeśli jednak A jest wartością dwubajtową, to A jest mnożone razy AX a wynik zostaje wrzucany do rejestrów DX:AX tzn. DX zawiera 2 starsze a AX młodsze bajty - np:. Jeśli AX = 0FFFFH (65535) i BX=0FFFFH (65535 również), to wykonanie
MUL BX wymnoży BX*AX a wynik (w tym przypadku czterobajtowy 0FFFFFFFFH czyli 4294836225D) znajdzie się w DX (2 bajty) i AX (również 2 bajty).

 

Dzielenie

Instrukcją dzielącą jest w Assemblerze DIV (divide - dziel).

Składnia:
DIV A

Tutaj znowu - jak w przypadku mnożenia - ma znaczenie czy A jest bajtem (np. BH, BL), czy słowem (NP. BX).

Jeśli A jest bajtem, AX jest dzielony przez A; iloraz zostaje wrzucony do AL, reszta do AH.
Jeśli więc AL zawiera 10D (0AH), BH=3, to po
DIV BH
AL=3, AH=1 - bo oczywiście 10/3 =3 i reszty 1.

Gdy A jest słowem (2 bajty), DX:AX zostaje podzielony przez A; po dzieleniu AX zawiera iloraz a DX resztę z dzielenia - np.:
Jeśli - posługując się analogicznym przykładem co przy mnożeniu - ustawimy DX=0FFFFH, AX=0FFFFH (dzielna =0FFFFFFFFH) i BX=0FFFFH (dzielnik), to
DIV BX
ustawi AX=0FFFFH (iloraz) DX=0000H (reszta).

 

No - dość tego sporo:)) - przydałby się jakiś ilustrujący wszystko przykładzik, ale - obawiam się, że wszyscy, którzy na to liczą - zawiodą się; nie mam żadnego sensownego pomysłu a bezsensowne przykłady umieszczałem na bieżąco przy omawianiu każdego działania.
Nie ma powodów do obaw - najwyższy już czas napisać coś większego z użyciem wszystkich poznanych instrukcji - to następnym razem.

 

;Wypisanie na ekran aktualnej daty i czasu

;Wersja poprawiona

;Usunieto glupie niedopatrzenie, ktore powodowalo, ze po 2 tysiecznym

;roku program wypisywal bzdury

;

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start:

;No to cos "powazniejszego", chociaz... a zreszta.

jmp pocz ;Skok do prawdziwego poczatku

;tutaj dane i procedury

crlf db 13,10,'$' ;przejscie do nowej linii

jestdata db 'Dzisiaj jest $'

jestgodz db 'Jest godzina $'

pznak: ;procedure piszznak - znak jest dany jako parametr w AL

;przyda sie kilka razy

push ax

push bx ;zapamietac rejestry modyfikowane w procedurce

mov ah,0eh

mov bx,0

int 10h ;Int 10h/0eh - wypisanie znaku na ekran:

;Ah=0eh, Al=Ascii znaku do wypisania;

;Przerwanie nie zwraca nic.

;

;znak wypisany przywracamy AX i BX i nie ma nas!

pop bx

pop ax

ret

pisznum:

;procedura wypisujaca dwucyfrowa liczbe ktora podajemy do procedury w

;AL; algorytm jest troche lamerski, ale do naszych celow starczy

;OK - najpierw zapamietajmy rejestry, ktore bedziemy zmieniac w

;procedurce coby nie namieszac

push ax

push bx ;Ten algorytm zadziala dla AL <=99 - czyli dwoch cyfr.

;1. Dzielimy al przez 10, wypisujemy dziesiatki i jednosci

xor ah,ah ;Najprostszy sposob wyzerowania rejestru

;na wszelki wypadek

mov bh,10

div bh ;ax podzielone zostalo przez bh

;teraz al zawiera iloraz ax/10 a ah - reszte.

add al,'0' ;dodaje do wartosci al kod ascii zera

;tak, ze obecnie al zawiera kod

;ascii pierwszej cyfry w liczbie

;mozemy uzyc spoko procedury pznak

call pznak ;pierwsza cyfra wypisana

mov al,ah ;teraz al zawiera reszte z dzielenia, wypisujemy ja tez na ekran

add al,'0' ;praktycznie to samo co poprzednio

call pznak ;druga cyfra wypisana

pop bx ;wszystko zrobione, przywracamy rejestry

pop ax ;zmieniane w procedurze i...

ret ;wracamy do programu

piszdate: ;wypisuje date

mov ah,09h

mov dx,offset(jestdata)

int 21h ;No to napis poszedl...

mov ah,2ah ;Int 21h/2ah - pobranie daty systemu

int 21h ;teraz: cx=rok, dh=miesiac, dl=dzien

;piszemy dzien

mov al,dl

call pisznum ;jak to bylo powiedziane...

mov al,'-' ;piszemy myslnik

call pznak ;oplacalo sie zrobic procedurke - uzylismy ja juz kilka razy

mov al,dh ;piszemy miesiac

call pisznum;tez sie oplacalo...

mov al,'-' ;piszemy nastepny myslnik

call pznak

;no i zostaje rok, ale tu juz nie tak latwo:

;napisalismy procedurke dla dwoch cyfr a tu niespodzianka

;- trzeba

;wypisac rejestr dwubajtowy (rok jest w formacie 1980-2079), ale

;obejdziemy to bardzo prosto - odejmiemy 1900 od cx, i zostanie

;formacie 80-179 tzn. po dwutysiecznym roku zaczalby fiksowac,

;ale nie zacznie:), gdyz odejmiemy 100 (179-100=79).

sub cx,1900

cmp cl,100

jb przed2tys;porownujemy mlodszy bajt cl z wartoscia graniczna - 100.

;Jesli jest mniejsze od 100 to znaczy, ze jest przed

;dwutysiecznym; jesli jest inaczej, to jest po dwutysiecznym

i najpierw

;wypiszemy '20' a pozniej cl-100, czyli

;np. 20 | 75 - oczywiscie bez |

;wyjdzie 2075.

mov al,20 ;no to piszemy '20'

call pisznum

sub cl,100 ;odejmujemy 100 od cl

przed2tys:

mov al,cl ;piszemy dwie ostatnie cyfry roku

call pisznum;bez wzgledu na wszystko...

mov ah,09h ;przejscie do nowej linii

mov dx,offset(crlf)

int 21h

ret ;i to juz koniec procedurki

piszczas: ;Wypisuje aktualny czas

mov ah,09h

mov dx,offset(jestgodz)

int 21h ;wypisalismy napis

mov ah,2ch ;pobieramy czas

int 21h ;teraz: ch=godz, cl=min, dh=sek

mov al,ch ;piszemy godzine

call pisznum

mov al,':'

call pznak ;tego nie trzeba wyjasniac mam nadzieje...?

mov al,cl ;minuta

call pisznum

mov al,':'

call pznak

mov al,dh ;sekunda

call pisznum

mov ah,09h ;znowu - do nowej linii

mov dx,offset(crlf)

int 21h

ret

pocz: ;Poczatek wlasciwy programu

call piszdate

call piszczas

mov ax,4c00h ;I to tyle - wychodzimy!

int 21h

cseg ends

end start

;No to napisalismy calkiem przyzwoity i nawet dlugi program. Pytanie

;pierwsze - czy dziala - w tej kwestii przezylem przed chwila dwukrotny

;szok: 1. Kompiluje sie bez bledow! 2. Dziala tzn. pisze porzadnie date

;i czas - bez pluskiew:).

;

;

;

;

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

;e-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz

"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.

;Program demonstrujacy petle przy uzyciu jmp

;

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start:

jmp pocz

;Dane i procedury

crlf db 13,10,'$'

;Skorzystamy z procedur, ktore napisalismy wczesniej

;w celu wypisania wartosci CX na ekran

;Jesli nie znasz wczesniejszego programu, obejrzyj go

;dataczas.asm

pznak:

push ax

push bx

mov ah,0eh

mov bx,0

int 10h

pop bx

pop ax

ret

piszCX:

;OK, do naszych celow przerobimy tak, aby wypisywac na ekran mlodszy

;bajt rejestru CX

push ax

push bx

mov ax,cx

mov bh,10

div bh

add al,'0'

call pznak

mov al,ah

add al,'0'

call pznak

pop bx

pop ax

ret

pocz: ;petla typu for cx=3 to 10

mov cx,3

ptl:

call piszcx ;pokaz aktualna wartosc cx na ekranie

;wykonaj inne dzialania "w petli"

;na przyklad - przejdz do nowej linii

mov ah,09h

mov dx,offset(crlf)

int 21h

cmp cx,10 ;Czy juz koniec?

je koniecptl ;Tak, zakoncz petle.

inc cx ;w przeciwnym razie - zwieksz cx

jmp ptl ;Znowu petla...

koniecptl: ;koniec petli - koniec programu?

mov ax,4c00h

int 21h

cseg ends

end start

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

;e-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz

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.

;Program demonstrujacy petle przy uzyciu loop

;

;Wprowadza: loop

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start:

jmp pocz

;Skorzystamy z procedur z poprzedniej petli - zob. ptldemo.asm

;Dane i procedury

crlf db 13,10,'$'

;Skorzystamy z procedur, ktore napisalismy wczesniej

;w celu wypisania wartosci CX na ekran

;Jesli nie znasz wczesniejszego programu, obejrzyj go - dataczas.asm

pznak:

push ax

push bx

mov ah,0eh

xor bx,bx

int 10h

pop bx

pop ax

ret

piszCX: ;OK, do naszych celow przerobimy tak, aby

;wypisywac na ekran mlodszy bajt rejestru CX

push ax

push bx

mov ax,cx

mov bh,10

div bh

add al,'0'

call pznak

mov al,ah

add al,'0'

call pznak

pop bx

pop ax

ret

pocz: ;petla typu

;for cx=10 downto 1

;lub w basicu

;for cx=10 to 1 step -1

mov cx,10

ptl:

call piszcx ;pokaz aktualna wartosc cx na ekranie

;wykonaj inne dzialania "w petli"

;na przyklad - przejdz do nowej linii

mov ah,09h

mov dx,offset(crlf)

int 21h

loop ptl ;zrobi za nas cale porownanie i zmniejszanie

;i sama zakonczy sie powtarzac, gdy CX osiagnie 0

mov ax,4c00h ;koniec programu

int 21h

cseg ends

end start

;Po uruchomieniu tego programu zobaczysz, ze komputer wyswietla cyfry -

;od 10 do.... 1 a nie do 0 - zastanow sie, dlaczego?

;

;

;

;

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

;e-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz

 

 

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.

;Wazne pytanie - demo wielokrotnej odpowiedzi

;

;Wprowadza: loopne... i pochodne

;

;Przykladowy program z pakietu

;"Kurs podstaw assemblera" by Grzegorz Zlotowicz

;

;Linie zaczynajace sie znakiem ; sa ignorowane przy kompilacji.

;

cseg segment

assume cs:cseg, ds:cseg, es:cseg

org 100h

start:

jmp pocz

;Tu - juz tradycyjnie - dane

text1 db 'Wazne pytanie, potwierdz odpowiedz "t" 3-krotnie',13,10

db 'Uwaga: Wpisuj "t" nie "T" OK? '

crlf db 13,10,'$'

pytanie db 'Czy chcesz zobaczyc co mam Ci do powiedzenia (t/n)? $'

dopowiedz db 'Po mojemu umiesz juz calkiem dobrze Assembler!!!$'

niepowiem db 'Nie to nie!!!$'

pocz:

mov ah,09h

mov dx,offset(text1)

int 21h

mov cx,3

ptlp: ;petla pytania

mov ah,09h

mov dx,offset(pytanie)

int 21h ;wypisanie lancucha

mov ah,01h ;wczytanie znaku

int 21h

;zwraca: AL=znak; automatycznie wypisuje znak na ekran

;gdy al=0 instrukcja musi byc wykonana ponownie (tak jak readkey w TP)

;by wczytac klawisz "podwojnego znaczenia".

cmp al,'t'

;tymczasem wypisujemy enter - przejscie do nowej linii

;cos jak "writeln;" w pascalu

mov ah,09h

mov dx,offset(crlf)

int 21h

loope ptlp

;skok do ptlp jesli cx>0 i jesli prawdziwy jest warunek E (equal -

;rowne) z ostatniego porownania (czyli cmp al,'t').

mov ah,09h

jne npowiem

;skok warunkowy po ostatnim CMP AL,'t'

;Wczytany znak byl rozny od t, (JNE) wiec "nie powiem"

mov ah,09h

mov dx,offset(dopowiedz)

int 21h ;teraz moge powiedziec

;skoro powiedzialem, skacze na koniec

jmp koniec

npowiem:

mov ah,09h

mov dx,offset(niepowiem)

int 21h

koniec:

mov dx,offset(crlf) ;przejscie do nowej linii

int 21h ;poprzez wypisanie entera na ekranie

;nie ustawiamy ah na 09h bo i tak juz jest ustawione

mov ax,4c00h

int 21h

cseg ends

end start

;---

;Jesli masz jakies pytania/uwagi/propozycje,

;to nie zwlekaj i pisz smialo:) na adres

;e-mail: grzezlo@wanet.pl

;Dzieki z gory!

;

;Z powazaniem - Grzegorz Zlotowicz


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.

42



Wyszukiwarka

Podobne podstrony:
kurs html rozdział II
Kurs HTML HTML BODY i META
kurs HTML
Chomikowy kurs HTML
%5bpl%5d+autocad+2000pl+ +kurs+%8credniozaawansowany JI4GVTEKN2RBNOJFIWBNNNBZGERUXYXJJARYPNY
kurs html podstawy tworzenia stron www RHQWUXUAVSLOSX6ABOMEHGX52LV2WV67LZXY6RQ
kurs html rozdział VIII
kurs html rozdział V
kurs html K3XFOFAKJ5HG5BCW7HPVBJ5WVTXMHDAJGD266GA
Kurs HTML - obrazek
Kurs HTML, Dokumenty Textowe, Komputer
kurs html rozdział I
Kurs HTML, ♥Dokumenty, Przydatne do choniczka
Kurs HTML HTML Odsylacze id 254775
Mini Kurs HTML by Tommek131
kurs html rozdział IV
kurs html rozdział IX

więcej podobnych podstron