ROZDZIAŁ JEDENASTY PROCEDURY I FUNKCJE, INFORMATYKA, „THE ART OF ASSEMBLY LANGUAGE” [PL]

[ Pobierz całość w formacie PDF ]
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
WYŁĄCZNOŚĆ DO PUBLIKOWANIA TEGO TŁUMACZENIA
POSIADA
RAG
„THE ART OF ASSEMBLY LANGUAGE”
tłumaczone by KREMIK
konsultacja naukowa: NEKRO
wankenob@priv5.onet.pl
nekro@pf.pl
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
ROZDZIAŁ JEDENASTY:
PROCEDURY I FUNKCJE
Budowa modularna jest jednym z kamieni węgielnych programowania strukturalnego. Program
modularny zawiera bloki kodu z pojedynczym punktem wejścia i wyjścia. Możemy użyć ponownie dobrze
napisaną sekcję kodu w innych programach lub innej części istniejącego programu. Jeśli użyjemy ponownie
istniejącą część kodu ,nie musimy tworzyć kodu, ani też debuggować tej części kodu ponieważ
(przypuszczalnie) zostało to już zrobione. Przy rosnących kosztach projektowania oprogramowania, budowa
modularna staje się bardzo ważna ponieważ oszczędza czas.
Podstawową jednostką program u modularnego jest moduł. Moduły mają różne znaczenia dla różnych
ludzi ,tutaj możemy założyć, że terminy moduł, podprogram, podprocedura, jednostka programowa ,procedura i
funkcja wszystkie są synonimami.
Procedura jest podstawowa dla stylu programowania. Języki proceduralne to Pascal,
BASIC,C++,FORTRAN,PL/I i ALGOL Przykłady języków nie proceduralnych to APL,LISP,SNOBOL4
ICON,FORTH,SETL,PROLOG i inne ,które są oparte o inne konstrukcje programistyczne takie jak
funkcjonalna abstrakcja lub dopasowanie do wzorca .Język asemblera jest zdolny pełnić obowiązki języka
proceduralnego lub nieproceduralnego .ponieważ prawdopodobnie jesteśmy dużo bardziej zapoznani z
paradygmatami programowania proceduralnego, ten tekst trzymać się będzie stymulowania konstrukcji
proceduralnych w języku asemblera 80x86
11.0 WSTĘP
Rozdział ten przedstawia wprowadzenie do procedur i funkcji w asemblerze. Omawia podstawowe
zasady, przekazywanie parametrów, wyników funkcji, zmiennych lokalnych i rekurencje Zastosujemy
większość technik, które ten rozdział omawia w typowych programach asemblerowych. Omawianie procedur i
funkcji będzie kontynuowane w następnym rozdziale; ten rozdział omawia zaawansowane techniki, które nie są
powszechnie stosowane w programach asemblerowych. poniższa część, która ma przedrostek „•” jest
niezbędna. Te części z „⊗” omawiają zaawansowane tematy które możemy zechcieć odłożyć na później.
• Procedury
⊗ Procedury bliskie i dalekie
• Funkcje
• Zachowywanie stanów maszyny
• Parametry
• Przekazywanie parametrów przez wartość
• Przekazywanie parametrów przez referencję
⊗ Przekazywanie parametrów przez wartość zwrotną
⊗ Przekazywanie parametrów przez wynik
⊗ Przekazywanie parametrów przez nazwę
• Przekazywanie parametrów w rejestrach
• Przekazywanie parametrów w zmiennych globalnych
• Przekazywanie parametrów na stos
• Przekazywanie parametrów w strumieniu kodu
 ⊗ Przekazywanie parametrów przez blok parametrów
• Wyniki funkcji
• Zwracanie wyniku funkcji w rejestrach
• Zwracanie wyniku funkcji na stos
• Zwracanie wyniku funkcji w komórkach pamięci
• Efekty uboczne
⊗ Przechowywanie zmiennych lokalnych
⊗ Rekurencja
11.1 PROCEDURY
W środowisku proceduralnym, podstawową jednostką kodu jest procedura. Procedura jest zbiorem
instrukcji, które obliczają jaką wartość lub wykonują jakąś czynność (taką jak drukowanie lub odczytywanie
wartości znaku). Definicja procedury jest bardzo podobna do definicji algorytmu. Procedura jest zbiorem zasad
następujących po sobie, które jeśli są zakończone tworzą jakiś wynik. Algorytm jest również taką sekwencją,
ale algorytm gwarantuje zakończenie podczas gdy procedura takiej gwarancji nie daje.
Wiele języków proceduralnych implementuje procedury stosując mechanizm call / ret. To znaczy, jakiś
kod wywołuje procedurę, procedura robi swoje a potem wraca do miejsca skąd została wywołana. Instrukcje call
i return dostarczają mechanizmy wywołania procedury 80x86.Kod wywołujący wywołuje procedurę instrukcją
call, procedura wraca do miejsca wywołania instrukcją ret .Na przykład, poniższa instrukcja 80x86 wywołuje
podprogram Biblioteki Standardowej UCR sL_putcr:
call sl_putcr
sl_putcr drukuje sekwencję carriage return /line feed na wyświetlaczu i zwraca sterowanie do instrukcji
bezpośrednio po instrukcji call sl_putrc.
Niestety, Biblioteka Standardowa UCR nie dostarcza wszystkich podprogramów jakich będziemy
potrzebowali .Większość czasu, będziemy musieli pisać swoje własne procedury. Prosta procedura może składać
się z niczego więcej niż tylko sekwencji instrukcji zakończenia z instrukcją ret. Na przykład, poniższa
„procedura’ zeruje 256 bajtów zaczynając od adresu w rejestrze bx:
ZeroBytes;
xor
ax, ax
mov
cx, 128
ZeroLoop:
mov
[bx], ax
add
bx, 2
loop
ZeroLoop
ret
Przez załadowanie rejestru bx adresem bloku 256 bajtów i wykonanie instrukcji call ZeroBytes, możemy
wyzerować określony blok.
Jako generalna zasada nie możemy zdefiniować własnej procedury w ten sposób, zamiast tego
powinniśmy użyć dyrektyw MASM’a proc i endp. Podprogram ZeroByte stosujący dyrektywy proc i endp:
ZeroBytes;
proc
xor
ax,ax
mov
cx, 128
ZeroLoop:
mov
[bx], ax
add
bx, 2
loop
ZeroLoop
ret
ZeroBytes endp
Zapamiętajmy, że proc i endp są dyrektywami asemblera. Nie generują żadnego kodu .Są one
mechanizmami pomagającymi uczynić nasz program łatwiejszym do czytania. Dla 80x86 dwa ostatnie
przykłady są identyczne; jednakże dla istoty ludzkiej, ostatni jest wyraźnie samodzielną procedurą, inna może
być po prostu przypadkowym zbiorem instrukcji wewnątrz jakiejś innej procedury. Rozważmy teraz poniższy
kod:
ZeroBytes:
xor
ax, ax
jcxz
DoFFs
ZeroLoop:
mov
[bx], ax
add
bx, 2
loop
ZeroLoop
ret
DoFFs:
mov
cx, 128
mov
ax, 0ffffh
FFLoop:
mov
[bx], ax
sub
bx, 2
loop
FFLoop
ret
Czy są to dwie procedury czy tylko jedna? Innymi słowy, możemy wywoływać program wprowadzając
ten kod przy etykiecie ZeroBytes i DoFFs lub tylko ZeroBytes? Zastosowanie dyrektyw proc i endp może
pomóc usunąć tę niejasność:
Traktujemy jako pojedynczy podprogram:
ZeroBytes:
proc
xor
ax, ax
jcxz
DoFFs
ZeroLoop:
mov
[bx], ax
add
bx, 2
loop
ZeroLoop
ret
DoFFs:
mov
cx, 128
mov
ax, 0ffffh
FFLoop:
mov
[bx], ax
sub
bx, 2
loop
FFLoop
ret
ZeroBytes:
endp
Traktujemy jako dwa oddzielne podprogramy:
ZeroBytes:
proc
xor
ax, ax
jcxz
DoFFs
ZeroLoop:
mov
[bx], ax
add
bx, 2
loop
ZeroLoop
ret
ZeroBytes:
endp
DoFFs:
proc
mov
cx, 128
mov
ax, 0ffffh
FFLoop:
mov
[bx], ax
sub
bx, 2
loop
FFLoop
ret
DoFFs
endp
Zawsze pamiętajmy, że dyrektywy proc i endp są logicznymi separatorami procedury. Mikroprocesor wraca z
procedury wykonując instrukcję ret a nie poprzez napotkanie dyrektywy endp .Poniższy kod nie jest odpowiednikiem kodu
powyższego:
ZeroBytes proc
xor ax, ax
jcxz DoFFs
ZeroLoop: mov [bx], ax
add bx, 2
loop ZeroLoop
; zagubiona instrukcja RET
ZeroBytes
endp
DoFFs
proc
mov
cx, 128
mov
ax, 0ffffh
FFLoop:
mov
[bx[, ax
sub
bx, 2
loop
FFLoop
; zaginiona instrukcja RET
DoFFs endp
Bez instrukcji ret na końcu każdej procedury,80x86 przejdzie do następnego podprogramu zamiast wrócić do miejsca
wywołania. Po wykonaniu ZeroBytes, 80x86 zacznie wykonywać podprogram DoFFs (zaczynając od instrukcji mov cx,
128).Zaraz potem, 80x86 zacznie kontynuowanie wykonywania następnych instrukcji aż do dyrektywy endp DoFFs
Procedura 80x86 przyjmuje formę:
ProcName
proc
{near | far}
;wybierz near lub far
<instrukcje procedury>
ProcName endp
Operand near lub far jest opcjonalny, następna sekcja omawia ich cele. Nazwa procedury musi być zarówno w linii proc
jaki i endp. Nazwa procedury musi być unikalna w programie.
Każda dyrektywa proc musi mieć dopasowaną dyrektywę endp. Błędne dopasowanie dyrektyw proc i endp wywoła błąd
zagnieżdżenia bloku.
11.2 PROCEDURY BLISKIE I DALEKIE
80x86 wspiera podprogramy. Wywołanie bliskie i powrót przekazują sterowanie danymi pomiędzy procedurami w tym
samym segmencie kodu. Dalekie wywołanie i powrót przekazują sterowanie między różnymi segmentami. Te dwa mechanizmy
wywołania i powrotu odkładają i ściągają adresy powrotne. Generalnie nie stosujemy bliskiej instrukcji call do wywołania
dalekiej procedury lub dalekiej instrukcji call do wywołania bliskiej procedury. Opierając się na tej zasadzie, powstaje pytanie
:jak możemy sterować emisją bliskiego lub dalekiego call lub ret?”
Większość czasu, instrukcja call stosuje następującą składnię:
call
ProcName
a instrukcja ret :
ret
lub ret przemieszczenie
Niestety, instrukcje te nie mówią MASMowi czy wywołujemy bliską czy daleką procedurę lub czy wracamy z dalekiej
czy bliskiej procedury. Dyrektywa proc zajmuje się tym. dyrektywa proc ma opcjonalny operand, który jest albo bliski albo
daleki. Bliski jest domyślny ,jeśli pole operandu jest puste .Assembler przydziela typ procedury (bliska lub daleka) do symbolu.
Kiedykolwiek MASM asembluje instrukcję call, emituje bliskie lub dalekie wywołanie w zależności od operandu .Dlatego też
deklarowanie symbolu proc lub proc near wymusza bliskie wywołanie. Podobnie zastosowanie proc far wymusza dalekie
wywołanie.
Poza sterowaniem generowania bliskiego lub dalekiego wywołania, operand proc również steruje generowaniem kodu
ret. Jeśli procedura ma bliski operand, wtedy wszystkie powroty instrukcji wewnątrz tej procedury będą bliskie.. MASM emituje
dalekie powroty wewnątrz dalekich procedur.
11.2.1 WYMUSZANIE BLISKICH LUB DALEKICH WYWOŁAŃ I POWROTÓW
Raz na jakiś czas możemy chcieć zastąpić mechanizm deklaracji near / far. MASM dostarcza mechanizmu, który pozwala
nam wymusić zastosowanie wywołań bliskie/ dalekie i powrotów.
Stosujemy operatory near ptr i far ptr do zastąpienia automatycznie przydzielonego wywołania bliskiego lub dalekiego.
Jeśli NearLbl jest bliską etykietą a FarLbl jest etykietą daleką, wtedy następująca instrukcja call wygeneruje odpowiednio bliskie
i dalekie wywołanie:
call NearLbl ;generowanie bliskiego wywołania
call FarLbl ;generowanie dalekiego wywołania
Przypuśćmy, że musimy wykonać dalekie wywołanie do NearLbl lub bliskie wywołanie do FarLbl .Możemy to wykonać
stosując poniższe instrukcje:
call far ptr NearLbl ;generowanie dalekiego wywołania
call near ptr FarLbl ;generowanie bliskiego wywołania
Wywołując bliską procedurę stosujemy daleki call lub wywołując daleką procedurę stosując bliski call, to nie jest coś co
będziemy normalnie robić. jeśli wywołamy bliską procedurę stosując daleką instrukcję call, bliski powrót pozostanie wartością
cs na stosie. Generalnie, zamiast:
call far ptr NearProc
powinniśmy zastosować jaśniejszy kod:
push
cs
call
NearProc.
Wywoływanie dalekiej procedury bliskim call jest niebezpieczną operacja. Jeśli spróbujemy takiego wywołania, bieżąca
wartość cs musi być na stosie. Pamiętajmy, że daleki ret zdejmuje segmentowy adres powrotu ze stosu .Bliska instrukcja call
odkłada tylko offset a nie segmentową część adresu powrotnego.
Poczynając od MASM v5.0, są jasne instrukcje, które możemy użyć dla wymuszenia bliskiego lub dalekiego ret. Jeśli ret
pojawia się wewnątrz procedury deklarowanej przez proc i endp, MASM automatycznie wygeneruje właściwą instrukcję
bliskiego lub dalekiego powrotu. Wykonując to użyjemy instrukcji retn i retf .Te dwie instrukcje generują odpowiednio bliski i
daleki ret.
11.2.2 PROCEDURY ZAGNIEŻDŻONE
MASM pozwala nam zagnieżdżać procedury. To znaczy, definicja jednej procedury może być całkowicie otoczoną
wewnątrz innej. Poniżej jest przykład takiej pary procedur:
OutsideProc:
proc
near
jmp
EndofOutside
InsideProc
proc
near
mov
ax, 0
ret
endp
EndofOutside:
call
InsideProc
mov
bx,0
ret
OutsideProc
endp
W odróżnieniu od języków wysokiego poziomu, zagnieżdżanie procedur w asemblerze 80x86 ni służy żadnemu
użytecznemu celowi. Jeśli zagnieżdżamy procedurę (jak powyższa InsideProc), będziemy musieli wyraźnie zakodować jmp
wokoło zagnieżdżonej procedury. Umieszczając procedury zagnieżdżonej po całym kodzie procedury zewnętrznej (ale jeszcze
pomiędzy dyrektywami zewnętrznymi proc /endp) nie osiągamy niczego. Dlatego też, to nie jest dobry powód zagnieżdżania
procedur w ten sposób.
Kiedy zagnieżdżamy jedną procedurę wewnątrz innej, musi być całkowicie zawarta wewnątrz procedury zagnieżdżającej.
To znaczy, instrukcje proc i endp dla procedury zagnieżdżanej muszą leżeć pomiędzy dyrektywami proc i endp zewnętrznymi
zagnieżdżającej procedury. Poniższy kod nie jest poprawny:
OutsideProc
proc
near
-
-
-
InsideProc
proc
near
-
-
-
OutsideProc endp
-
-
-
InsideProc endp
Procedury OutsideProc i InsideProc zachodzą na siebie, nie są zagnieżdżone. Jeśli spróbujemy stworzyć zbiór procedur
takich jak ta, MASM zaraportuje „błąd zagnieżdżanego bloku”. Rysunek 11 demonstruje to graficznie:
Rysunek 11.1 Niepoprawne zagnieżdżanie procedur
[ Pobierz całość w formacie PDF ]

  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • tejsza.htw.pl
  •