Cwiczenie 2 - Wprowadzenie skoków i pętli
|
W ćwiczeniu tym postaram się przybliżyć wam tzw skoki oraz
pętle, które pozwalają na tworzenie bardziej skąplikowanych
programów. Przekazanie sterowania lub rozgałęzienie to sposób zmiany
kolejności wykonywania instrukcji. Wszystkie języki programistyczne
zawierają rozkazy pozwalające na wykonanie takiej czynności.
Polecenia rozgałęzienia można podzielić na dwie kategorie:
-Rozgałęzienie bezwarunkowe- program
przechodzi do nowego położenia we wszystkich wypadkach. Do wskaźnika
rozkazów zostaje załadowana nowa wartość, co powoduje kontynuację
pracy od nowego adresu. Dobrym przykładem jest polecenieJMP, które omówimy w dalszej części tego ćwiczenia.
-Rozgałęzienie warunkowe- program
przechodzi do nowego położenia, jeśli został spełniony określony
warunek. Intel zapewni szeroki zakres rozkazów rozgałęzienia
warunkowego; rozkazy te ułatwiają tworzenie złożonych struktur
logicznych. Procesor interpretuje warunek fałszu lub prawdy na
podstawie zawartości rejestrów CX i Flags
Polecenie JMP
Polecenie JMP
nakazuje procesorowi kontynuację wykonywania programu od innego
miejsca. To miejsce musi być identyfikowane przez etykiete, która
jest przekształcana przez asembler na postać adresu. Jeżeli skok
jest wykonywany do etykiety w aktualnym segmencie, to przesunięcie
etykiety zostanie załadowane do rejestru IP. W przypadku skoku do
innego segmentu także adres tego segmentu jest umieszczany w
rejestrze CS. Zakładając, iż miejsce_docelowe to etykieta lub
32-bitowy adrs typu segment-przesunięcie, to polecenie JMP ma trzy
formaty:
JMP SHORT miejsce_docelowe
JMP NEAR PTR
miejsce_locelowe
JMP FAR PTR miejsce_docelowe
Polecenie
JMP umożliwia skok do etykiety w aktualnej procedurze, skok do innej
procedury, do innego segmentu, do adresu znajdującego się poza
aktualnym programem oraz do dowolnego miejsca w pamięci RAM lub ROM.
Choć taki sposób programowania nie jest zalecany, to czasami jest to
niezbędne w czasie pracy nad oprogramowaniem systemowym.
Poniżej pokazano kilka przykładów użycia polecenia JMP:
|
jmp LI
|
; NEAR - miejsce docelowe w aktualnym segmencie
|
|
|
jmp near ptr LI
|
; NEAR - miejsce docelowe w aktualnym segmencie
|
|
|
|
jmp short nextval
|
; SHORT - skok do miejsca w zakresie od -128 do 127 bajtów
|
|
|
|
jmp far ptr error_rtn
|
; FAR - skok do innego segmentu
|
|
|
Przed operandem miejsca
docelowego można umieścić jeden z poniższych operatorów:
SHORT — skok do etykiety w zakresie od -128 do 127 bajtów od
adresu kolejnego rozkazu. Do rejestru IP jest dodawana 8-bitowa
liczba całkowita ze znakiem o nazwie przemieszczenie. NEAR PTR —
skok do etykiety w dowolnym miejscu w aktualnym segmencie kodu. W
rejestrze IP umieszczana jest 16-bitowa wartość przemieszczenia.
FAR PTR — skok do etykiety w innym segmencie. Adres segmentu
etykiety jest umieszczany w rejestrze CS, a jej przesunięcie w
rejestrze IP.
Operator SHORT jest szczególnie
przydatny w czasie kodowania skoków do przodu, ponieważ asembler nie
zna adresu docelowego aż do momentu zasemblowania tej części
programu. Proszę przyjrzeć się poniższemu kodowi:
labell:
jmp short label2 : tutaj należy użyć SHORT
labe!2: jmp labell ;
automatycznie skok SHORT
Operator NEAR PTR stanowi dla
asemblera informację, że docelowa etykieta znajduje się w tym samym
segmencie kodu — takie założenie jest zwykle przyjmowane. Jeżeli
skok jest wykonywany do etykiety poza aktualnym segmentem, to należy
użyć operatora FAR PTR. Poniżej przedstawiono przykłady użycia obu
operatorów z etykietą exi t jako miejscem docelowym skoku:jmp
near ptr exit
jmp far ptr exit
Skoki warunkowe
Dostępne są
trzy typy poleceń skuku warunkowego. Pierwszy typ wykonuje ogólne
porównania i nie ma nic wspólnego z wartościami ze znakiem lub bez
znaku. Drugi typ służy wyłącznie do porównywania operandów bez
znaku. Trzeci typ poleceń wykonuje porównanie operandów ze znakiem.
Polecenia skoku dla ogólnych operacji
porównania
|
Mnemonik
|
Opis
|
Znacznik lub rejestr
|
|
|
JZ
|
Skok, jeśli zero
|
ZF=1
|
|
|
JE
|
Skok, jeśli równe
|
ZF=1
|
|
|
JNZ
|
Skok, jeśli nie zero
|
ZF=0
|
|
|
JNE
|
Skok, jeśli nie równe
|
ZF=0
|
|
|
JC
|
Skok, jeśli przeniesienie
|
CF=1
|
|
|
JNC
|
Skok, jeśli nie ma przeniesienia
|
CF=0
|
|
|
JCXZ
|
Skok, jeśli CX = 0
|
CX=0
|
|
|
JECXZ
|
Skok, jeśli ECX = 0
|
ECX=0
|
|
|
JP
|
Skok, jeśli parzystość
|
PF=1
|
|
|
JNP
|
Skok, jeśli nieparzystość
|
PF=0
|
|
Porównanie operandów
bez znaku
|
Mnemonik
|
Opis
|
Znaczniki
|
|
|
JA
|
Skok, jeśli op1 jest większy od op2
|
CF=0 i ZF=0
|
|
|
JNBE
|
Skok, jeśli op1 nie jest mniejszy lub równy op2
|
CF=0 i ZF=0
|
|
|
JAE
|
Skok, jeśli op1 jest większy lub równy op2
|
CF=0
|
|
|
JNB
|
Skok, jeśli op1 nie jest mniejszy odop2
|
CF=0
|
|
|
JB
|
Skok, jeśli op1 jest mniejszy od op2
|
CF=1
|
|
|
JNAE
|
Skok, jeśli op1 nie jest większy lub równy op2
|
CF=1
|
|
|
JBE
|
Skok, jeśli op1 jest mniejszy lub równy op2
|
CF=1 lub ZF=1
|
|
|
JNA
|
Skok, jeśli op1 nie jest większy od op2
|
CF=1 lub ZF=1
|
|
Polecenia skoku dla
porównania liczb ze znakiem
|
Mnemonik
|
Opis
|
Znaczniki
|
|
|
JG
|
Skok, jeśli op1 jest większy od op2
|
SF=OF i ZF=0
|
|
|
JNLE
|
Skok, jeśli op1 nie jest mniejszy lub równy op2
|
SF=OF i ZF=0
|
|
|
JGE
|
Skok, jeśli op1 jest większy lub równy op2
|
SF=OF
|
|
|
JNL
|
Skok, jeśli op1 nie jest mniejszy od op2
|
SF=OF
|
|
|
JL
|
Skok, jeśli op1 jest mniejszy od op2
|
SF<>OF
|
|
|
JNGE
|
Skok, jeśli op1 nie jest większy lub równy op2
|
SF<>OF
|
|
|
JLE
|
Skok, jeśli op1 jest mniejszy lub równy op2
|
ZF=1 lub SF<>OF
|
|
|
JNG
|
Skok, jeśli op1 nie jest większy od op2
|
ZF=1 lub SF<>OF
|
|
|
JS
|
Skok, jeśli wartość ze znakiem (op1 jest ujemny)
|
SF=1
|
|
|
JNS
|
Skok, jeśli wartość bez znaku (op1 jest dodatni)
|
SF=0
|
|
|
JO
|
Skok, jeśli przepełnienie
|
OF=1
|
|
|
JNO
|
Skok, jeśli brak przepełnienia
|
OF=0
|
|
Polecenie
CMP
Kolejnym bardzo przydatnympoleceniem
przy tworzeniu struktur logicznych jest CMP. Polecenie CMP
(porównanie) wykonuje niejawne odejmowanie operandu źródłowego od
operandu docelowego, przy czym żaden operand nie jest modyfikowany.
Wynik operacji jest odzwierciedlony przez stan rejestru Flags.
Dozwolone są następujące kombinacje operandów, gdzie pierwszy
operand jest zawsze argumentem docelowym, a drugi argumentem
źródłowym:
CMP reg, reg
CMP reg, mem
CMP reg, immed
CMP mem, reg
CMP mem, immed
Nie jest możliwe użycie
rejestrów segmentów. Operacja CMD ma wpływ na znaczniki: Overflow,
Sign, Zero, Parity, Carry i Auxiliary Carry.
Wartości znaczników
Polecenie CMP dla
operandów bez znaku powoduje ustawienie znaczników Zero i Carry:
Wynik CMP
|
CF
|
ZF
|
operand docelowy < operand źródłowy
|
1
|
0
|
operand docelowy = operand źródłowy
|
0
|
1
|
operand docelowy > operand źródłowy
|
0
|
0
|
W przypadku operandów ze
znacznikiem ustawiane są znaczniki Zero, Sign i Overflow:
Wynik CMP
|
ZF
|
SF, OF
|
operand docelowy < operand źródłowy
|
?
|
SF<>OF
|
operand docelowy = operand źródłowy
|
1
|
?
|
operand docelowy >operand źródłowy
|
0
|
SF=OF
|
Skoro mamy już za sobą
wprowadzenie pod względem teoretycznym czas zobaczyć jak zastosować
powyższą teorie w praktyce. Napiszemy zatem 2 przykładowe programy
wykorzystujące skoki warunkowe ogólnych operacji porównania. Ale
zanim zobaczymy te bardziej zaawansowane pętle, zobaczmy co zrobi
program który będzie wykorzystywał jedynie instrukcję skoku
bezwarunkowego JMP.
.model small
|
; model pamięci: 1 segment programu i 1 segment danych
|
.386
|
;dostępny zbiór rozkazów procesora 80386
|
.data
|
;początek segmentu danych
|
.stack l00h
|
;segment stosu o zadeklarowanym rozmiarze
|
.code
|
;początek segmentu z kodem programu
|
.startup
|
;makroinstrukcja generująca sekwencję
; inicjującą rejestry segmentowe DS i SS
|
start: mov ah,2
|
;ustawienie etykiety a nastepnie wpisanie cyfry 2 do rejestru ah
|
mov dl,'A'
|
;wyświetlenie litery A na ekran
|
int 21h
|
;wywołanie procedury systemowej przerwania
|
jmp start
|
;skok do etykiety start
|
.exit
|
;makroinstrukcja generująca sekwencję końcową programu
|
end
|
;koniec programu źródłowego
|
Powyższy program ma na celu
wyświetlenie na ekranie litery A. Jednakże ponieważ nie podaliśmy
żadnego warunku zatrzymania, program ten będzie wkółko wyświetlał
literę A i nastąpi jego zapętlenie. Przerwanie go umożliwia nam
polecenie INT 21h oznaczające przerwanie programu w debugerze
poprzez wciśnięcie klawiszy CTRL+BREAK.
Spróbujmy zatem
napisać program, który wyświetli nam 4 razy napis hello world.
Pominiemy już początek i koniec programu gdyż on pozostanie u nas
bez zmian (patrz ćwiczenie 1), przedstawimy tylko instrukcje zawarte
pomiędzy komendami .startup i .exit.
mov cx,4
|
ustawienie wartości w rejestrze CX na 4, będzie on służył
jako licznik w naszej pętli
|
poczatek:
|
etykieta
|
mov dx, offset tekst
|
;wprowadzenie parametrów wywołania
|
mov ah, 09h
|
;procedury wyświetlenia komunikatu
|
JCXZ koniec
|
warunek przejścia do etykiety KONIEC, jeżeli rejestr CX
będzie równy 0
|
jmp poczatek
|
instrukcja skoku do etykiety poczatek, zostanie wykonana
jedynie jeśli warunek JCXZ będzie fałszywy
|
koniec:
|
etykieta
|
Teraz napiszemy
program wykonujący dokładnie tę samą czynność co powyższy tym razem
jednak z wykorzystaniem polecenia CMP i polecenia skoku JZ.
XOR al,al
|
Szybkie kasowanie rejestru poprzez odjęcie wartości
znajdujących się w tym rejestrze
|
mov cl,4
|
ustawienie rejestru cl na wartość 4
|
poczatek:
|
etykieta
|
mov dx, offset tekst
|
;wprowadzenie parametrów wywołania
|
mov ah, 09h
|
;procedury wyświetlenia komunikatu
|
inc al
|
zwiększenie rejestru al domyślnie o 1
|
CMP al, cl
|
porównanie wartości rejestru al z ustawionym przez nas
uprzednio rejestrem cl
|
JZ Koniec
|
jeżeli z porównania rejestrów otrzymamy wynik 0,
przejdziemy do etykiety koniec
|
jmp poczatek
|
instrukcja skoku do etykiety poczatek, zostanie wykonana
jedynie jeśli warunek JZ będzie fałszywy
|
koniec:
|
etykieta
|
|
|
|