ROZDZIAŁ SIÓDMY STANDARDOWA BIBLIOTEKA UCR.pdf

(252 KB) Pobierz
Microsoft Word - AoA VII.doc
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
WYŁĄCZNOŚĆ DO PUBLIKOWANIA TEGO TŁUMACZENIA
POSIADA RAG
HTTP://WWW.R-AG.PRV.PL
„THE ART OF ASSEMBLY LANGUAGE”
tłumaczone by KREMIK
konsultacja naukowa: NEKRO
wankenob@priv5.onet.pl
nekro@pf.pl
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
ROZDZIAŁ SIÓDMY: STANDARDOWA BIBLIOTEKA UCR
Większość języków programowania dostarcza kilku „wbudowanych” funkcji redukując wysiłek
potrzebny do napisania programu. Tradycyjnie, programiści asemblerowi nie mieli dostępu do standardowego
zbioru powszechnie używanych podprogramów dla swoich programów; w związku z tym, wydajność
programistów asemblerowych była całkiem niska ponieważ stale „wymyślali koło” w każdym programie który
napisali. Standardowa biblioteka UCR dla programistów 80x86 dostarcza takiego zbioru podprogramów
.Rozdział ten omawia mały podzbiór podprogramów dostępnych w tej bibliotece. Po przeczytaniu tego rozdziału
powinniśmy przestudiować dokumentację towarzyszącą podprogramom biblioteki standardowej.
7.0 WSTĘP
Rozdział ten dostarczy podstawowego wprowadzenia do funkcji dostępnych w Standardowej Bibliotece
UCR dla programistów asemblerowych 80x86.Ten krótki wstęp obejmuje następujące tematy:
Standardowa Biblioteka UCR dla Programistów Języka Asemblera
Podprogramy zarządzania pamięcią
Procedury wejściowe
Procedury wyjściowe
Konwersja
Predefiniowane stałe i makra
7.1 WSTĘP DO STANDARDOWEJ BIBLIOTEKI UCR
„Standardowa biblioteka UCR dla Programistów Języka Asemblera 80x86” jest zbiorem
asemblerowych podprogramów wzorowanych na standardowej bibliotece „C”. Pośród innych rzeczy, biblioteka
standardowa zawiera procedury do operowaniu na danych wejściowych, wyjściowych, konwersji, kilku
porównań i sprawdzeń, manipulowania łańcuchem, zarządzaniem pamięcią, zbiór operatorów znakowych,
operacje zmiennoprzecinkowe, manipulowania listą, portami szeregowymi I/O ,współbieżność i współprogramy
i dopasowanie do wzorca.
Ten rozdział nie będzie próbował opisać każdego podprogramu w bibliotece. Przede wszystkim
Biblioteka jest stale zmieniana, więc taki opis szybko mógłby się stać przestarzały. Po drugie, kilka z tych
podprogramów z biblioteki jest tylko dla zaawansowanych programistów, więc jest poza zasięgiem tego tekstu.
W końcu, jest sto podprogramów w tej bibliotece. Zakładając, że opiszemy je tu wszystkie, byłoby poważnym
zakłócenie dla prawdziwej pracy jaką mamy do wykonania – nauczenie się asemblera.
Dlatego też, ten tekst omawia kilka niezbędnych podprogramów, które działają przy najmniejszym
wysiłku. Zauważmy, że pełna dokumentacja biblioteki, jak również kody źródłowe i kilka przykładowych
plików znajduje się na dyskietce dołączonej do tego tekstu. Odnośny przewodnik znajduje się w dodatkach do
tego tekstu. Możemy również znaleźć ostatnią wersję Standardowej Biblioteki UCR na wielu serwisach on-line,
BBSach i wieku innych miejscach. Jest również dostępna przez anonimowe FTPy w Internecie.
Kiedy używamy Standardowej Biblioteki UCR, powinniśmy zawsze używać pliku SHELL.ASM
dostarczanego jako „szkielet” nowego programu. Plik ten zakłada konieczne segmenty, dostarcza właściwych
dyrektyw include i inicjuje dla nas konieczne podprogramy Biblioteki.
21856013.001.png
Nie powinniśmy próbować tworzyć nowego programu z podprogramów przypadkowych, chyba ,że
jesteśmy bardzo dobrze zaznajomieni z wewnętrznymi działaniami Biblioteki Standardowej.
Zauważmy, że większość podprogramów Biblioteki Standardowej używa makr zamiast instrukcji call
dla wywołania. Nie możemy, na przykład, bezpośrednio wywołać podprogramu putc. Faktycznie wywołujemy
makro putc które zawiera wywołanie do procedury sl_putc („SL” - Standard Library).
Jeśli nie wybierzemy do używania pliku SHELL.ASM ,nasz program musi zawierać kilka instrukcji do
uruchomienia biblioteki standardowej i zaspokojenia pewnych wymagań biblioteki standardowej. Dopóki
korzystamy z doświadczeń programowania w asemblerze, powinniśmy zawsze używać pliku SHELL.ASM jako
punktu startowego dla naszych programów.
7.1.1 PODPROGRAMY ZARZĄDZANIA PAMIĘCIĄ: MEMINIT,MALLOC I FREE
Biblioteka Standardowa dostarcza kilku podprogramów ,które zarządzają wolną pamięcią na stercie.
Dają one programistom asemblerowym zdolność do dynamicznego alokowania pamięci podczas wykonywania
programu i powrót tej pamięci do systemu kiedy program kiedy nie potrzebujemy dłużej programu. Poprzez
dynamiczne alokowanie i zwalnianie bloków pamięci możemy wydajniej używać pamięci na PC.
Podprogram meminit inicjuje menadżera pamięci a my musimy wywołać go przed każdym
podprogramem, który używa tego menadżera pamięci. Ponieważ wiele podprogramów Biblioteki Standardowej
używa menadżera pamięci, powinniśmy wywoływać tą procedurę wcześniej w programie .Plik SHELL.ASM
wykonuje takie wywołanie dla nas.
Podprogram malloc alokuje pamięć na stercie i zwraca wskaźnik do bloku, umieszczając go w
rejestrach es:di. Przed wywołaniem malloc, musimy załadować rozmiar bloku (w bajtach) do rejestru cx. Przy
powrocie, malloc ustawia flagę przeniesienia jeśli wystąpił błąd (niewystarczająca pamięć).Jeśli przeniesienie
jest wyzerowane ,es:di wskazuje na blok bajtów, którego rozmiar wyszczególniliśmy:
mov
cx, 1024
;przejęcie 1024 bajtów na stertę
malloc
;wywołanie MALLOC
jc
MllocError
;jeśli błąd pamięci
mov
word ptr PNTR, DI
; wskaźnik do zapisu bloku
mov word ptr PNTR+2, ES
Kiedy wywołujemy malloc ,menadżer pamięci obiecuje, że blok, który nam dał jest wolny i
wyzerowany i że nie realokuje tego bloku do momentu aż go wyraźnie nie zwolnimy. Zwracając blok pamięci z
powrotem do menadżera pamięci, możemy (być może) użyć go ponownie w przyszłości, używając podprogramu
free z Biblioteki. Free oczekuje, że podamy wskaźnik powrotny poprzez malloc:
les
di, PNTR
;pobieranie wskaźnika do zwolnienia
free
;zwolnienie bloku
jc BadFree
Jak zwykle przy większości podprogramów Biblioteki Standardowej, jeśli podprogram free ma kilka rodzajów
trudności zwróci flagę przeniesienia aby zaznaczyć, ze wystąpił błąd
7.1.2 PODRPOGRAMY STANDARDOWEGO WEJŚCIA:GETC,GETS,GETSM
Biblioteka Standardowa dostarcza kilku podprogramów wejścia, są trzy, które w szczególności
będziemy używali cały czas: getc (pobierz znak),gets (pobierz łańcuch) i getsm (pobierz łańcuch malloc).
Getc odczytuje pojedynczy znak z klawiatury i zwraca ten znak w rejestrze al. Zwraca ona stan końca
pliku (EOF) w rejestrze ah (zero oznacza, że EOF nie wystąpił, jeden znaczy, że EOF wystąpił)Nie modyfikuje
ona innych rejestrów. Jak zwykle, flaga przeniesienia zwraca stan błędu. Nie musimy przekazywać getc żadnej
wartości w rejestrze. Getc nie potwierdza znaku wejściowego do wyświetlania na ekranie .Musimy wyraźnie
wydrukować znak jeśli chcemy aby pojawił się na wyjściu monitora.
Następujący program przykładowy wykonuje nieustanną pętlę dopóki użytkownik nie naciśnie klawisza
Enter:
;Notka: „CR” jest symbolem. ,który pojawia się w pliku nagłówkowym „consts.a”. Jest to wartość 13,która jest
;kodem ASCII dla znaku powrotu karetki
Wait4Enter: getc
cmp al., cl
jne Wait4Enter
Podprogram gets odczytuje całą linię tekstu z klawiatury. Przechowuje każdy kolejny znak linii
wejściowej w tablicy bajtów której adres bazowy znajduje się w parze rejestrów es:di. Ta tablica musi mieć
miejsce na przynajmniej 128 bajtów. Podprogram gets będzie odczytywał każdy znak i umieszczał go w tablicy
z wyjątkiem dla znaku powrotu karetki. Gets kończy linię wejściową bajtem zerowym (który jest zgodny z
podprogramem obsługi łańcucha Biblioteki Standardowej).Gets potwierdza każdy znak wypisywany na
urządzeniu wyświetlającym, również operuje prostymi funkcjami edycyjnymi takimi jak backspace. Jak zwykle,
gets zwraca ustawienie przepełnienia jeśli wystąpi błąd. Następujący przykład odczytuje linię tekstu ze
21856013.002.png
standardowego urządzenia wejściowego a potem zlicza liczbę wypisywanych znaków. Kod ten jest
skomplikowany, zauważmy, że inicjuje licznik i wskaźnik do –1 uprzednio wprowadzając pętlę a potem
bezpośrednio zwiększa je o jeden .Ustawia to licznik na zero i modyfikuje wskaźnik, żeby wskazywać pierwszy
znak w łańcuchu. To uproszczenie tworzy kod mniej wydajny niż proste rozwiązanie:
Podprogram getsm również odczytuje łańcuch z klawiatury i zwraca wskaźnik do tego łańcucha w es:di. Różnica
między gets a getsm jest taka, że nie musimy podawać adresu bufora wejściowego w es:di. Getsm automatycznie
alokuje pamięć na stercie z wywołaniem malloc i zwraca wskaźnik do bufora w es:di .Nie zapomnijmy ,że
musimy wywołać meminit na początku naszego programu jeśli używamy tego podprogramu. Plik szkieletowy
SHELL.ASM wywołuje meminit za nas. Również, nie zapomnijmy wywołać free aby ściągnąć pamięć ze sterty.
Getsm
;zwraca wskaźnik w ES:DI
-
-
free
;Zwraca pamięć ze sterty
7.1.3 STANDARDOWE PODPROGRAMY WYJŚCIA:PUTC,PUTCR,PUTS,PUTH,PUTIPRINT I PRINTF
Biblioteka Standardowa dostarcza szerokiego wachlarza podprogramów wyjścia, dużo więcej niż
zobaczymy tu. Podprogramy te są reprezentatywne dla podprogramów które znajdziemy w Bibliotece.
Putc wyprowadza pojedynczy znak na urządzenie wyświetlające ,Wyprowadzony znak pojawia się w
rejestrze al. Nie wpływa na inny rejestr, chyba, że wystąpi błąd na wyjściu (flaga przeniesienia oznacza błąd
/brak błędu jak zwykle)..Zobacz dokumentację Biblioteki po więcej szczegółów.
Putcr wyprowadza „nową linię” (kombinację powrotu karetki CR /przesunięcia o jedną linię LF) na
standardowe wyjście. Jest ona odpowiednio równoważna następującemu kodowi:
mov
al., cr
;CR i LF są stałe
putc
;pojawiają się w pliku
mov
al., lf
;nagłówkowym const.a
putc
Podprogram puts (wprowadź łańcuch) drukuje łańcuch zakończony zerem który wskazuje es:di.
Zauważmy, że puts automatycznie nie wyprowadza nowej linii po wydrukowaniu łańcucha. Musimy albo
wprowadzić znak CR/LF na koniec łańcucha albo wywołać putcr po wywołaniu puts jeśli chcemy wydrukować
nową linię po łańcuchu. Puts nie wpływa na żaden rejestr (chyba że wystąpi błąd).W szczególności, nie zmienia
wartości rejestrów es:di. Następująca sekwencja kodu korzysta z tego faktu:
getsm
;odczyt łańcucha
puts
;drukuje go
putcr
;druk nowej linii
21856013.003.png
free ;zwolnienie pamięci dla łańcucha
Ponieważ powyższy podprogram zachowuje es:di (z wyjątkiem oczywiście getsm),wywołanie free
dealokuje zaalokowaną pamięć przez wywołanie getsm.
Podprogram puth drukuje wartość z rejestru al. jako dokładnie dwie heksadecymalne cyfry wliczając w
to czołowy bajt zero jeśli wartość jest z zakresu 0..Fh.Następująca pętla odczytuje sekwencję klawiszy
klawiatury i drukuje ich wartości ASCII do chwili kiedy użytkownik nie naciśnie klawisza ENTER
KeyLoop:
getc
cmp
al., cr
je
done
puth
putcr
jmp
KeyLoop
done:
Podprogram puti drukuje wartość z ax jako 16 bitową wartość całkowitą ze znakiem. Następujący kod
jest fragmentem wydruku sumy I i J :
mov
ax, I
add
ax, J
puti
putcr
Putu jest podobne do puti z wyjątkiem tego, że wyprowadza całkowitą wartość bez znaku zamiast
całkowitej ze znakiem.
Podprogramy jak puti i putu zawsze wyprowadzają liczby używając minimalnej liczby możliwych
pozycji drukowania .Na przykład ,puti używa trzech pozycji drukowania w łańcuchu drukującym wartość
123.Czasami.możemy chcieć zmusić te podprogramy wyjściowe do drukowania ich wartości używając stałej
liczby pozycji drukowania, uzupełniając każdą ekstra pozycję spacją. Podprogramy putisize i putusize
dostarczają takiej możliwości. Podprogramy te oczekują wartości liczbowych w ax i wyszczególnionej
szerokości pola w cx. Drukują one liczbę w polu szerokości przynajmniej pozycji cx .Jeśli wartość w cx jest
większa niż liczba drukowanych pozycji o wymaganych wartościach, podprogramy te wyrównują do prawej
liczbę w polu cx drukowanej pozycji. Jeśli liczba w cx jest mniejsza niż liczba drukowanych pozycji
wymaganych wartości. podprogramy te zignorują wartość w cx i użyją jednak wielu pozycji drukowania
wymaganych liczb.
Podprogram print jest jednym z wielu, często wywoływanych procedur w bibliotece. Drukuje ona
łańcuch zakończony zerem, który występuje bezpośrednio po wywołaniu print:
print
byte „drukuj ten łańcuch na wyświetlaczu”,cr,lf,0
Powyższy przykład drukuje łańcuch „drukuj ten łańcuch na wyświetlaczu” poprzedzony przez nową linie.
Zauważmy ,że print będzie drukował jakikolwiek znak bezpośrednio następujący po wywołaniu print aż do
spotkania pierwszego bajtu zerowego. W szczególności, możemy drukować sekwencje nowej linii i każdy inny
znak sterujący jak pokazano powyżej. Również zauważmy, że nie jesteśmy ograniczeni do drukowania jednej
linii tekstu z podprogramem print:
print
byte
„To przykład podprogramu PRINT”,cr.lf
byte
„drukującego kilka linii tekstu. ”,cr, lf
byte
cr, lf
byte
0
Uzyskamy cos takiego:
To przykład podprogramu PRINT
Drukującego kilka linijek tekstu.
Jest niezwykle ważne abyśmy nie zapominali o bajcie kończącym zerem. Podprogram print zaczyna
wykonywanie pierwszej maszynowej instrukcji 80x86 z bajtem zakończonym zerem. jeśli zapomnimy
wprowadzić bajt zakończony zerem po naszym łańcuchu, podprogram print chętnie pożre kolejne bajty
instrukcji naszego łańcucha (wydrukuje je),chyba że znajdzie bajt zero (bajty zerowe są powszechne w
programach asemblerowych).Będzie to przyczyną, że nasz program będzie się źle zachowywał a jest to duży
błąd początkujących programistów, kiedy stosują podprogram print. Zawsze o tym pamiętaj.
Printf, podobnie jak jego imienniczka w „C”, dostarcza zdolności do formatowania danych
wyjściowych dla pakietu Biblioteki Standardowej. Typowe wywołanie printf zawsze przybiera następującą
formę:
printf
byte
„łańcuch formatowany:,0
dword
operand 1 ,operand 2 ,......opernad n
Łańcuch formatowany jest porównywalny do dostarczanego w języku „C”. Dla większości znaków,
printf po prostu drukuje znaki w łańcuchu formatowanym aż do momentu natrafienia na bajt zakończony zerem.
Dwa wyjątki to znaki poprzedzone przez backslash (‘\”) i znak procent („%).Podobnie jak printf z C, printf
Biblioteki Standardowej używa backslasha jako znaku sterującego i znaku procenta jako obowiązującego przy
formatowaniu łańcucha.
Printf używa „\” do drukowania znaków specjalnych ,podobnie do, ale nie identycznie jak printf w C.
Printf Biblioteki Standardowej wspiera następujące znaki specjalne:
\r Drukowanie powrotu karetki ale nie przesunięcia o jedną linię
\n Drukowanie znaku nowej linii (powrót karetki /przesunięcie o jedną linie)
\b Drukowanie znaku backspace
\t Drukowanie znaku tab
\l Drukowanie znaku przesunięcia o jedną linię (ale nie powrotu karetki)
\f Drukowanie znaku przesunięcia strony
\\ Drukowanie znaku backslash
\% Drukowanie znaku procenta
\0xhh Drukowanie kodu ASCII hh, reprezentowanego przez dwie cyfry heksadecymalne
Użytkownicy C powinni zauważyć, że parę różnic pomiędzy Biblioteką Standardową a C. Po pierwsze
użycie \% drukuje znak procenta wewnątrz formatowanego łańcucha, nie „%%”.C nie pozwala używać
\% ponieważ kompilator C przetwarzając „\%” podczas kompilacji programu (pozostawi
pojedynczy„%” w kodzie obiektu) podczas gdy printf przetwarza łańcuch formatowany podczas
wykonywania. Widzi pojedynczy „%” i traktuje go jako znak doprowadzający do formatowania. Printf
Standardowej Biblioteki, z drugiej strony, przetwarza oba „\” i „%” w czasie wykonywania, dlatego też
można rozpoznać „\%”.
Łańcuchu w formie „\0xhh” muszą zawierać dokładnie dwie cyfry heksadecymalne. Bieżący
podprogram printf nie jest dość silny aby operować sekwencjami formy „=oxh” która zawiera tylko
pojedynczą cyfrę heksadecymalną. Zapamiętajmy, gdy odkryjemy, że printf będzie „odrąbywać” znaki
po napisaniu wartości
Nie ma absolutnie żadnego powodu aby używać heksadecymalnego znaku sterującego z
wyjątkiem „\0x00”.Printf przechwytuje wszystkie znaki występujące po wywołaniu printf aż do bajtu
zakończonego zerem (który jest po to abyśmy nie musieli stosować „\0x00” jeśli chcemy wydrukować
znak null, printf nie wydrukuje takiej wartości).Printf Standardowej Biblioteki nie martwi się jak te
znaki się tam znajdą W szczególności, nie jesteśmy ograniczeni do używania pojedynczego łańcucha po
wywołaniu printf. To jest zupełnie poprawne:
printf
byte „To jest łańcuch”,13,10
byte „jest w nowej lini”,13,10
byte :drukuje backspace na końcu tej linii:”
byte 8,13,10,0
Nasz kod będzie działał szybciej troszkę, jeśli unikniemy stosowania sekwencji znaków sterujących. Co
ważniejsze, sekwencja znaków sterujących zajmuje przynajmniej dwa bajty. Możemy zakodować większość z
nich jako pojedyncze bajty przez po prostu osadzenie kodu ASCII dla tego bajtu bezpośrednio do strumienia
kodu. nie zapomnijmy, nie możemy wprowadzić bajtu zero do strumienia kodu. Bajt zerowy kończy łańcuch
formatowany. zamiast tego użyjemy „\0x00”.
Sekwencje formatowe zawsze zaczynają się od „%”.Dla każdej sekwencji formatowej musimy
dostarczyć daleki wskaźnik dla powiązania danej bezpośredniej występującej w łańcuchu formatowany. Np.
printf
byte „%i, %1”, 0
dword i,j
Sekwencja formatowa przyjmuje ogólną formę „%s\cn^f” gdzie:
% jest zawsze znakiem „%”.Używamy „\%” jeśli chcemy wydrukować znak procenta
s jest albo niczym albo znakiem minus („-„)
„\c” jest również opcjonalny, może lub nie musi pojawiać się na pozycji formatowej ,”c”
przedstawia każdy znak drukowalny
„n” przedstawia łańcuch z jedną lub więcej cyfrą dziesiętną
„^” jest znakiem karetki
„f’ przedstawia jeden ze znaków formatowania: i,d,x,h,u,c,s,ld,li,lx lub lu
Zgłoś jeśli naruszono regulamin