Artyku� ten zaadresowany jest do os鏏 chc帷ych
po陰czy� wysoki
poziom j瞛yka Pascal z efektywno軼i� Assemblera - j瞛ykiem
niskiego poziomu. Nie trudno jest zauwa篡� korzy軼i wynikaj帷e z
tego zwi您ku. Szybko嗆 w grafice i obliczeniach matematycznych,
zwi瞛這嗆 i ma豉 obj皻o嗆 kodu wynikowego, no i panowanie nad wszystkimi
elementami komputera. Za to Pascal wnosi 豉two嗆
pisania interfejs闚 dla aplikacji i udogodnienia wynikaj帷e z wysokiego
poziomu j瞛yka. Wady we wstawkach assemblerowych
widz� tylko dwie, odziedziczone po samym Assemblerze:
trudno嗆 w pisaniu w j瞛yku niskiego poziomu i nieczytelno嗆
kodu.
Wi璚 je郵i potrzebujesz du瞠j szybko軼i, precyzji dzia豉nia i
totalnej kontroli nad swoimi programami pisanym w Pascalu, to
nie widz� przeszk鏚 aby wykorzysta� wstawki Assemblera. No chyba,
瞠 nie wiesz nic o programowaniu w Assemblerze. Wtedy
prawdopodobnie nie zrozumiesz tego tekstu, to nie jest kurs dla
(t)opornych typu "Nie tylko dla or堯w" ;) Powiniene�
najpierw
przeczyta� jakie� kursy dla pocz徠kuj帷ych. Taka wiedza wystarczy
aby dobrze zrozumie� tre嗆 tego artyku逝. Co trudniejsze rzeczy
wyt逝macz�.
Je郵i jaki� fragment kodu nie chce si� skompilowa� na twoim kompilatorze,
to albo zosta� wyrwany z kontekstu, albo u篡wamy innej implementacji
j瞛yka Pascal. Moja to Borland Turbo Pascal 7 - wersja szkoleniowa
;)
1. O Assemblerze.
Przypomn� najwa積iejsze rzeczy, jakie b璠� nam potrzebne,
aby w og鏊e skorzysta� ze wstawek. Nie jest tego du穎 ale jest
to nieodzowna cz窷� wstawek Assemblerowych (no mo瞠 z wyj徠kiem
kilku zagadnie�, kt鏎e wstawi貫m jako ciekawostk� ;).
a) Skoki warunkowe
Instrukcje do procesora przekazywane s� w kolejno軼i
wyst瘼owania w pami璚i po kolei. Instrukcja, wskazywana przez
CS:IP (lub CS:EIP dla program闚 32 bitowych w trybie chronionym
procesora - trudno jednak jest znale潭 implementacj� Pascala,
kt鏎a to obs逝guje, wi璚 nie b璠� tego tu omawia�) przekazywana
jest procesorowi, nast瘼nie zwi瘯szana jest warto嗆 IP o wielko嗆
poprzedniej instrukcji, tak aby wskazywa� na nast瘼n�. S瘯 w tym,
瞠 "trudno" jest zmieni� samemu warto嗆 IP (z za這瞠nia
jest on
nie dost瘼ny dla programisty, lecz mo積a to omin望 ;). Aby manipulowa�
skokami procesora w pami璚i, mo積a wykorzysta�
skoki warunkowe lub bezwarunkowe.
Og鏊na instrukcja skoku bezwarunkowego ma posta�:
JMP @SKACZ
//to omijamy
@SKACZ:
//to robimy
Jest to jedna z cz窷ciej wykorzystywanych instrukcji. Inne skoki
(ju� warunkowe) to: JA, JB, JE (je瞠li nich nie pami皻asz, to
powt鏎z materia� z innych kurs闚 ;), wykorzystuje si� je tak samo
jak skoki bezwarunkowe:
CMP AX, BX
JA @WIEKSZE
//no i co ?
@WIEKSZE
//r鏏 co�
Jak zapewne zauwa篡貫�, samo tworzenie etykiet - jak i skoki
- wygl康aj� tak samo jak w standardowym kompilatorze
Assemblera. My郵�, 瞠 nie powinno by� z ich obs逝g� we wstawkach
problem闚.
Skok闚 warunkowych jest wi璚ej, wybra貫m moje ulubione. Warto
tak瞠 podkre郵i�, 瞠 na nowszych procesorach (Pentium) te skoki
zajmuj� tylko jeden takt procesora.
b) Stos
Sama obs逝ga stosu we wstawkach wygl康a tak samo, jak w
czystym Assemblerze. Z t� r騜nic�, 瞠 tu nie trzeba martwi� si�
deklaracj� stosu, ani przepe軟ieniem. O wszystko zatroszczy si�
kompilator - najcz窷ciej program zaka鎍za si� z komunikatem przepe軟ienia
stosu ;)
Mam nadziej�, 瞠 ka盥y pami皻a zastosowanie i obs逝g� instrukcji
PUSH reg, POP reg, PUSHF, POPF, PUSHA, POPA. Generalna zasada
to: "ile razy push`ujesz, tyle pop`ujesz" ;). Je瞠li
nie zdejmiesz push`owanej warto軼i ze stosu, to wywo豉na funkcja
nie powr鏂i
w miejsce wywo豉nia. Najgorsze jest to, 瞠 trudno jest wykry�
takie b喚dy (co sprytniejsi pisz� programy do zliczania liczby
PUSH`闚 i POP`闚 w kodzie 廝鏚這wym ;).
c) Wywo造wanie procedur
Aby we wstawce wywo豉� procedur� bez argument闚, napisan�
w Assemblerze lub w Pascalu, stosuje si� instrukcj� CALL
nazwa. Np.:
//...
MOV AL, BH
OUT 42h, AL.
//delay
CALL NOSOUND
Nie ma w tym wiele filozofii. Problemy mog� by� w wywo造waniu
funkcji (lub procedur) z argumentami (lub parametrami - bardziej
znane okre郵enie dla Pascala), a p騧niej w pobraniu wynik闚 od
nich. Ale o tym p騧niej.
Trzeba jednak pami皻a�, 瞠 ka盥e skoki procesora w inny segment
zajmuj� cenne takty, im wi璚ej takich wywo豉�, tym wolniej dzia豉
program. Tak samo nie mo積a przedobrzy� ze przerwaniami.
d) Koprocesor (FPU)
FPU (Floating Point Unit) jest bardzo przydatny gdy chcemy
zwi瘯szy� szybko嗆 wykonywania oblicze� matematycznych. Jego instrukcje
najcz窷ciej mo積a spotka� we wstawkach Assemblera.
Nie b璠� wymienia� listy komend koprocesora. Trzeba tylko
pami皻a�, 瞠 na pocz徠ku stawia si� F..., p騧niej nazwa instrukcji
(cz瘰to analogiczna do instrukcji CPU) np. FADD. Gdy chcemy u篡�
liczb ca趾owitych (co zreszt� jest nie naturalne dla koprocesora,
musi on wykona� konwersj� do liczb rzeczywistych - co zajmuje
czas) dodajemy po F.. I(i) np.: FIADD.
Przed u篡ciem FPU trzeba go najpierw zainicjowa� (zresetowa�)
(FINIT). Cz瘰to jest to jednak pomijane.
2. Co z tymi wstawkami?
Przejd幟y do sedna sprawy. S� trzy mo磧iwe sposoby pisania wstawek.
Wszystkie maj� swoje plusy i minusy ;)
a) Asm statement
Nie jest to polska nazwa, ale nigdzie nie widzia貫m innej
(wprawdzie tekstu o wstawkach Assemblera te� nigdzie nie widzia貫m
;).
Wstawka ta ma posta�:
//tekst w Pascalu
Asm
//instrukcje, nie potrzeba 鈔ednika
End;
//tekst w Pascalu
Np.:
Begin
WriteLn('Przed asm...');
Asm
Mov AX, BX
Mov CX, BX; SHL AX, CX {komentarz}
End;
WriteLn('Po end;');
End.
Ten przyk豉d praktycznie nic nie robi. Ale ilustruje spos鏏 u篡cia
wstawek ;) Zauwa�, 瞠 nie trzeba stawia� 鈔ednik闚 po
pojedynczej instrukcji (w jednej linijce). Gdy chcemy wstawi�
wiele
instrukcji w jednej linijce trzeba je odseparowa� 鈔ednikiem.
Komentarze maj� styl Pascala.
Ten typ wstawek wykorzystuje si� zw豉szcza, gdy chcemy w kodzie
Pascala, bez wi瘯szej zabawy, u篡� instrukcje Assemblera. Mo積a
je wykorzysta� wsz璠zie: w g堯wnym bloku, w funkcjach i procedurach.
Ich liczba nie jest ograniczona, mog� nast瘼owa� po sobie.
Ma ona jednak swoje minusy. Rozwa禦y przyk豉d:
Asm
Mov AX, BX
Jmp @ETYKIETA
End;
WriteLn('Tekst');
Asm
@ETYKIETA:
Mov DS, AX
End;
WriteLn('Tekst');
Wbrew pozorom ten kod nie skompiluje si�. Etykiety stawiane w
jednym bloku asm-end nie s� znane w innych blokach.
W tych wstawkach mog� by� dowolnie modyfikowane nast瘼uj帷e rejestry
CPU: AX, BX, CX, DX, SI, DI, ES i flagi. Poprzedni przyk豉d
mia� jeszcze jeden b陰d. Rejestr DS zosta� zmieniony (cho� samo
w sobie to nie jest b陰d), ale jego warto嗆 nie zosta豉 przywr鏂ona
do pierwotnego stanu. Nast瘼na instrukcja WriteLn() prawdopodobnie
wypisa豉 by bzdury - dla niej DS mia� wskazywa�
na segment w kt鏎ym jest 'Tekst'. Mo積a si� przed tym uchroni�
stosuj帷 PUSHA, np.:
Asm
PUSHA
Mov AX, 01234h
Mov DS, AX
POPA
End;
WriteLn('Tekst2');
Teraz wszystko jest ok. Rejestr DS nie jest jedynym, kt鏎ego wykorzystuje
Pascal, gdy inicjuje wstawki. Reszta to: BP, SP, SS,
no i DS. Te regu造 odnosz� si� te� do reszty wstawek.
b) Funkcja Assemblerowa
Jest to rodzaj wstawki, kt鏎y mo瞠 wyst瘼owa� tylko w funkcjach
i procedurach. Obejmuje ca陰 funkcj� (procedur�). Ma nast瘼uj帷e
w豉軼iwo軼i: nie jest generowany kod inicjacji zmiennych, je瞠li
nie wyst瘼uj� parametry, nie jest inicjowany stos, odwo豉nie si�
do @Result jest b喚dem (o @Result p騧niej).
Jako, 瞠 parametry nie s� inicjowane wszystkie s� traktowane
tak,
jak by mia造 znacznik VAR i nie mog� by� zmieniane (a przynajmniej
nie powinny).
A oto jak wygl康a Funkcja Assemblerowa:
Function Nazwa(x,y :integer); Assembler;
Asm
Mov AX, x
End;
Zaraz po 鈔edniku wyst瘼uje dyrektywa Assembler informuj帷a kompilator
o rodzaju funkcji. Zamiast Begin wyst瘼uje s這wo
kluczowe Asm. Funkcja ko鎍zy si� - jak ka盥a - End`em.
c) Inline
Instrukcja Inline jest przeznaczona do umieszczania w programie
kodu maszynowego. Pos逝giwanie si� funkcj� inline (tak naprawd�
to nie jest funkcja, tylko tak wygl康a ;) jest bardzo skomplikowane,
czasoch這nne i podatne na b喚dy. Ponadto nigdy nie znalaz貫m sensownego
zastosowania dla niej. Kod maszynowy bardziej wygodnie - jak dla
mnie - jest wstawia� w zwyk造 blok Assemblera
(o tym p騧niej).
Zdecydowa貫m si� ni� tu opisa�, poniewa� cz瘰to pojawia si� we
wstawkach Pascala. Oto przyk豉d takiej instrukcji:
Begin
inline(
$B8/$00/$4C/ {mov ax, 4c00h}
$CD/$21 {int 21h}
);
Write('Co??'); {tu nigdy nie dojdzie ;}
End.
Najpierw opisz� przyk豉d, nast瘼nie wyja郾i� konstrukcj� instrukcji
inline.
W kodzie Pascala ca豉 instrukcja inline (ka盥a z osobna oczywi軼ie)
jest traktowana jako ca這嗆. Nie wykonuje (podczas np.: debbuging`u)
pojedynczo ka盥ej fizycznej instrukcji procesora
tylko wszystkie na raz - ca造 blok inline. Tutaj napisa貫m
maszynowo instrukcj� DOS`a zamykaj帷� program. Procedura
Write() nigdy nie zostanie wykonana.
Wa積e: Kompilator ca陰 zawarto嗆 instrukcji inline "wszywa"
w kod programu NIE sprawdzaj帷 jej zawarto軼i. Niewa積e jakie
bzdury napiszemy, zawsze zostanie skompilowane. (Mo積a to oczywi軼ie
wykorzysta� do pisania najnowszych instrukcji naszego procesora
w starych kompilatorach Pascala).
Konstrukcja instrukcji inline jest bardzo prosta. Ilo嗆 danych
jest nieograniczona.
Inline(kod/kod
kod/kod);
Wci耩 nie musimy robi�, tak jak komentarzy, s� dla naszej wygody.
3. Zwracanie wyniku funkcji.
Ze wstawkami cz瘰to tak jest, 瞠 w 80% przypadk闚 s� to
funkcje. Rozwa禦y nast瘼uj帷y przyk豉d:
Function Dodaj(a, b:word):word;
Var temp:word;
Begin
Asm
Mov ax, a
Mov bx, b
Add ax, bx
Mov temp, ax
End;
Dodaj := temp;
End;
Jest to oczywi軼ie poprawny kod, ale ma swoje wady. Po pierwsze
trzeba tworzy� zmienn� tymczasow� (w tym wypadku Temp).
Mo瞠 to by� nie po膨dane, gdy operujemy na du篡ch zmiennych. Po
drugie trzeba przerywa� Asm-end; Tak瞠 nie wygl康a to najlepiej
;)
a) @Result
Podstawow� form� zwracania wyniku z funkcji, w kt鏎ej
zastosowano Asm-statement jest zmienna @Result . Jest ona
powi您ana referencj� do zmiennej trzymaj帷ej wynik funkcji.
U篡wa si� jej tak, jakby by� zwyk陰 zmienn�. Mo積a wykonywa�
na niej wszystkie operacje matematyczne. Jej typ jest ustalany
na podstawie typu funkcji. Mo積a wi璚 napisa�:
function dodaj(a,b :single):single;
begin
asm
fld a {w這瞠nie na stos zmiennej 'a'}
fld b
fadd
fst @Result
{zdj璚ie wierzcho趾a stosu do zmiennej @Result}
end;end;
Powiedzmy wprost. Konstruktorzy Pascala nie mogli wymy郵i� nic
lepszego nad @Result.
b) Funkcje Assemblerowe
Sprawa nie wygl康a ju� tak prosto i wspaniale w odniesieniu do
funkcji Assemblerowych. Tutaj konstruktorzy Pascala dali plam�
wycofuj帷 referencj� @Result. Pewnie mieli jakie� powody, ale
ja
nie znalaz貫m 瘸dnej dobrej strony tej decyzji (lub niedopatrzenia).
Z tego powodu jednak nie mo積a zrezygnowa� z funkcji Assemblerowych,
trzeba da� sobie rad� inaczej ;)
Zwracanie wyniku zale篡 od jego typu.
Typy 8-bitowe zwraca si� w rejestrze AL. Zmienne 16-bitowe
zwraca si� w rejestrze AX. Natomiast wyniki 32-bitowe zwraca
si� w parze rejestr闚 DX-AX. Tzn.: DX jest starszym s這wem
zmiennej, AX m這dszym. Gdy zwraca si� liczb� rzeczywist�,
to wynik pobierany jest ze szczytu stosu koprocesora.
Je瞠li funkcja zwraca typ String, to nale篡 zdj望 ze stosu dwie
warto軼i, najpierw segment, potem offset. Razem to tworzy adres
do Stringa. (Pami皻aj, 瞠 na pocz徠ku Stringa jest bajt okre郵aj帷y
ilo軼i� element闚).
Na pocz徠ek wydaje si� to skomplikowane, w rzeczywisto軼i tylko
zwracanie zmiennych 32-bitowych przysparza ma貫 k這poty.
Poka輳 jeszcze ma陰 funkcj� prezentuj帷� g堯wn� ide�:
function dodaj(a,b :word):word;Assembler;
asm
mov cx, a
mov bx, b
add cx, bx
mov ax, cx {Wynik w AX}
end;
4. Obs逝ga funkcji we wstawkach.
Poprzednio przy omawianiu instrukcji CALL, nie wspomnia貫m
(zreszt� specjalnie) jak wywo造wa� funkcje (lub procedury) z
parametrami. Teraz napisz� o wszystkim, co jest zwi您ane z
obs逝g� funkcji we wstawkach.
a) Wywo造wanie funkcji / procedur z parametrem.
Og鏊nie argumenty funkcj� i procedur� podaje si� poprzez
po這瞠nie ich na stos. Pierwsza po這穎na na stos warto嗆 zostaje
przypisana pierwszemu argumentowi. Czasem jednak jest
odwrotnie (tak jak w C++), w du瞠j mierze zale篡 to od
implementacji. Jednak gdy kompilator jest 100% zgodny ze
standardem, to kolejno嗆 jest taka, jak� poda貫m na pocz徠ku
(np.: Borland Turbo Pascal).
b) Odbieranie wyniku funkcji
Je瞠li jeszcze nie wiesz dobrze jak zwraca� wynik z funkcji,
to przypomnij sobie to. Odebranie wyniku funkcji polega na
odwr鏂eniu poprzedniego procesu ;)
Pisz帷 kod wywo逝j帷y funkcj� trzeba zna� typ, jaki ona zwraca
i
gdzie go zwraca (patrz 3.b ). Nie ma tu wiele do wyja郾iania -
popatrzmy wi璚 na przyk豉d.
asm
push 1000d {pierwszy argument 'a'}
push 2000d {drugi argument 'b'}
call dodaj {wywo豉nie naszej funkcji 'dodaj'}
mov c, ax {odebranie wyniku do zmiennej 'c'}
end;
writeln( c );
5. Instrukcje 32-bitowe.
Stare wersje Turbo Pascala, jak i innych kompilator闚, nie
pozwalaj� na u篡wanie najnowszych instrukcji procesor闚
(np.: MMX, itp). Wi瘯szo嗆 implementacji nie "zna" nawet
wszystkich instrukcji procesor闚 z rodziny 30486.
Najpro軼iej jest za豉twi� sobie nowy kompilator lub uaktualnienia.
Jednak nie zawsze mo積a, np. gdy chcemy zachowa� zgodno嗆 z poprzednimi
wersjami? W豉郾ie tym teraz si� zajmiemy.
Dla rozwiania w徠pliwo軼i, nawet je郵i zakodujemy pewn�
instrukcj� dost瘼n� dopiero od procesora np.: Pentium, to na wcze郾iejszych
procesorach program najprawdopodobniej si�
zawiesi lub wykona par� innych instrukcji, gdy procesor jej nie
zna.
a) Pisanie i wykorzystanie instrukcji 32-bitowych.
Przyjrzyjmy si� przyk豉dowej procedurze Pascala.
Procedure ClearSeg(Color:byte;var MSeg); Assembler;
Asm
mov AX, word ptr [MSeg+2]
mov ES, AX
mov Al, Color
mov ah, al
mov bx, ax
db 66h
shl ax, 16 {shl eax, 16}
mov ax, bx
mov DI, 0
mov CX, 16080
db 0F3h, 66h, 0ABh; {Rep Stosd}
end;
Pewnie ju� zauwa篡貫� nieznane ci (jeszcze) znaczki wtr帷one do
kodu ;) S� to reprezentacje instrukcji w kodzie maszynowym
(rozumianych przez cz這wieka). 'DB' oznacza jeden bajt. Mo積a
tak瞠 wykorzystywa� 'DW', 'DD', itd. Jednak gdy u篡wany danych
wi瘯szych od 'DB' to trzeba odwraca� warto軼i (co bajt). Sprawa
jeszcze bardziej komplikuje si� gdy chcemy u篡� danych
wi瘯szych, np.: 'DD'. Po oznaczeniu wielko軼i wpisywanych
instrukcji (najlepiej zawsze u篡waj 'DB') trzeba wypisa� kod
instrukcji maszynowych, oddzielonych przecinkami.
'Db 66h' oznacz 瞠 w tym miejscu pami璚i, mi璠zy instrukcj�
mov bx, ax a shl ax, 16, zostanie umieszczony bajt o warto軼i
66h. Ten bajt razem z instrukcj� shl ax, 16 zostanie przeczytany
przez procesor jako shl eax, 16. W tym przypadku wykorzystanie
rejestru
EAX przy酥ieszy這 program - w innym wypadku trzeba by
pisa� wi璚ej instrukcji.
Ci庵 'Db 0F3h, 66h, 0Abh' jest instrukcj� Rep Stosd, tak瞠 nie
dost瘼n� w wi瘯szo軼i implementacji, gdy� wykorzystuje rejestr
EAX.
Szczeg鏊nie wa積e jest wykorzystanie instrukcji 32-bitowych w
operacjach na koprocesorze, gdy� tam liczy si� czas.
Wcze郾iej pisa貫m o odwracaniu kodu, teraz poka輳 przyk豉d.
Db 0DAh, 0E9h {fucompp}
Dw 0E9DAh {fucompp}
Oba te zapisy przeczytane zostan� jako instrukcja koprocesora
'fucompp'.
Pisz帷 w kodzie maszynowym bardzo wa積y jest odpowiedni komentarz.
Po pewnym czasie zapis 'db 26h, 67h, 66h, 8Bh, 03h' b璠zie dla
nas nic nie znacz帷ym zapisem, kt鏎ego NA PEWNO nie b璠ziesz rozumia�!
b) Sk康 bra� kod maszynowy.
Najpierw co to jest kod maszynowy? S� to instrukcje dla
procesora w postaci kodu binarnego (lub hex'闚 jak kto woli).
Zwyk造 cz這wiek nie jest w stanie zapami皻a� takich rozkaz闚
(np.: 8CC8h), 豉twiej jest zapami皻a� mnemoniki (np.: mov ax,
cs) wymy郵one specjalnie dla nas.
Kod maszynowy mo瞠my 豉two zaczerpn望 z r騜nych debuger闚
(np.: Turbo Debuger ).
Aby to zrobi� nale篡 w陰czamy debugera i wybra� opcje
Assemble... z manu podr璚znego. Nast瘼nie wpisujemy komend�, kt鏎ej
kod chcemy spisa� (musi to by� w miar� nowy debuger). I spisujemy
zwr鏂on� komend� w postaci hex'闚, pojawi si� na lewo od
naszej instrukcji (o ile nie wyst徙i b陰d). Tak瞠 w r騜nych ksi捫kach
mo積a znale潭 kod maszynowy instrukcji.
Gdy potrzebujemy wi瘯szy kawa� kodu, to wpisywanie wszystkich
instrukcji by這 by co najmniej pracoch這nne. W takim wypadku
potrzebujemy kompilatora Assemblera (np.: Turbo Assembler i
Turbo Linkera). Kompilujemy nim tylko nasze instrukcje w
Assemblerze (mog� by� wyj皻e z kontekstu), najlepiej do postaci
czystego kodu (*.COM). Nast瘼nie nasz plik wynikowy otwieramy
programem typu Hex Edit (np.: Hex Workshop) i kopiujemy
wybrany fragment do kodu Pascala. Taki kod maszynowy najlepiej
w這篡� w klauzur� Inline(). Oczywi軼ie TRZEBA opisa� w
komentarzach kod maszynowy, aby go rozumie� po jakim� czasie!