LEKCJA 16 - ASEMBLER TASM i BASM. ________________________________________________________________ W trakcie tej lekcji: * dowiesz si� , jak ��czy� C++ z assemblerem * poznasz wewn�trzne formaty danych ________________________________________________________________ WEWN�TRZNY FORMAT DANYCH I WSPӣPRACA Z ASSEMBLEREM. W zale�no�ci od wybranej wersji kompilatora C++ zasady wsp�pracy z asemblerem mog� si� troch� r�ni�. Generalnie, kompilatory wsp�pracuj� z tzw. asemblerami in-line (np. BASM), lub asemblerami zewn�trznymi (stand alone assembler np. MASM, TASM). Wstawki w programie napisane w assemblerze powinny zosta� poprzedzone s�owem asm (BORLAND/Turbo C++), b�d� _asm (Microsoft C++). Przy kompilacji nale�y zatem stosownie do wybranego kompilatora przestrzega� specyficznych zasad wsp�pracy. Np. dla BORLAND/Turbo C++ mo�na stosowa� do kompilacji BCC.EXE/TCC.EXE przy zachowaniu warunku, �e TASM.EXE jest dost�pny na dysku w bie��cym katalogu. Typowymi sposobami wykorzystania assemblera z poziomu C++ s�: * umieszczenie ci�gu instrukcji assemblera bezpo�rednio w �r�d�owym tek�cie programu napisanym w j�zyku C/C++, * do��czeniu do programu zewn�trznych modu��w (np. funkcji) napisanych w assemblerze. W C++ w tek�cie �r�d�owym programu blok napisany w asemblerze powinien zosta� poprzedzony s�owem kluczowym asm (lub _asm): # pragma inline void main() { asm mov dl, 81 asm mov ah, 2 asm int 33 } Program b�dzie drukowa� na ekranie liter� "Q" (ASCII 81). JAK POS�UGIWA� SI� DANYMI W ASEMBLERZE. Napiszemy w asemblerze program drukuj�cy na ekranie napis "tekst - test". Rozpczynamy od zadeklarowania �a�cucha znak�w: void main() { char *NAPIS = "tekst - test$"; /* $ - ozn. koniec */ Umie�cili�my w pami�ci �a�cuch, b�d�cy w istocie tablic� sk�adaj�c� si� z element�w typu char. Wska�nik do �a�cucha mo�e zosta� zast�piony nazw�-identyfikatorem tablicy. Zwr�� uwag�, �e po �a�cuchu znakowym dodali�my znak '$'. Dzi�ki temu mo�emy skorzysta� z DOS'owskiej funkcji nr 9 (string-printing DOS service 9). Mo�emy utworzy� kod w asemblerze: asm mov dx, NAPIS asm mov ah, 9 asm int 33 Ca�y program b�dzie wygl�da� tak: [P054.CPP] # pragma inline void main() { char *NAPIS = "\n tekst - test $"; asm { MOV DX, NAPIS MOV AH, 9 INT 33 } } Zmienna NAPIS jest pointerem i wskazuje adres w pami�ci, od kt�rego rozpoczyna si� �a�cuch znak�w. Mo�emy przes�a� zmienn� NAPIS bezpo�rednio do rejestru i przekaza� wprost przerywaniu Int 33. Program assemblerowski (tu: TASM) m�g�by wygl�da� np. tak: [P055.ASM] .MODEL SMALL ;To zwylke robi TCC .STACK 100H ;TCC dodaje standardowo 4K .DATA NAPIS DB 'tekst - test','$' .CODE START: MOV AX, @DATA MOV DS, AX ;Ustawienie segmentu danych ASM: MOV DX, OFFSET NAPIS MOV AH, 9 INT 21H ;Drukowanie KONIEC: MOV AH, 4CH INT 21H ;Zako�czenie programu END START Inne typy danych mo�emy stosowa� podobnie. Wygodn� taktyk� jest deklarowanie danych w tej cz�ci programu, kt�ra zosta�a napisana w C++, aby inne fragmenty programu mog�y si� do tych danych odwo�ywa�. Mo�emy we wstawce asemblerowskiej odwo�ywa� si� do tych danych w taki spos�b, jakgdyby zosta�y zadeklarowane przy u�yciu dyrektyw DB, b�d� DW. WEWN�TRZNE FORMATY DANYCH W C++. LICZBY CA�KOWITE typ�w char, short int i long int. Liczba ca�kowita typu short int stanowi 16-bitowe s�owo i mo�e zosta� zastosowana np. w taki spos�b: [P056.CPP] #pragma inline void main() { char *napis = "\nRazem warzyw: $"; int marchewki = 2, pietruszki = 5; asm { MOV DX, napis MOV AH, 9 INT 33 MOV DX, marchewki ADD DX, pietruszki ADD DX, '0' MOV AH, 2 INT 33 } } Zdefiniowali�my dwie liczby ca�kowite i �a�cuch znak�w - napis. Poniewa� obie zmienne (�a�cuch znk�w jest sta��) maj� d�ugo�� jednego s�owa maszynowego, to efekt jest taki sam, jakgdyby zmienne zosta�y zadeklarowane przy pomocy dyrektywy asemblera DW (define word). Mo�emy pobra� warto�� zmiennej marchewki do rejestru instrukcj� MOV DX, marchewki ;marchewki -> DX W rejestrze DX dokonujemy dodawania obu zmiennych i wyprowadzamy na ekran sum�, pos�uguj�c si� funkcj� 2 przerywania DOS 33 (21H). W wyniku dzia�ania tego programu otrzymamy na ekranie napis: Razem warzyw: 7 Jeczsze jeden szczeg� techniczny. Poniewa� stosowana funkcja DOS pracuje w trybie znakowym i wydrukuje nam znak o kodzie ASCII przechowywanym w rejestrze, potrzebna jest manipulacja: ADD DX, '0' ;Dodaj kod ASCII "zera" do rejestru Mo�esz sam sprawdzi�, �e po przekroczeniu warto�ci 9 przez sum� wszystko si� troch� skomplikuje (kod ASCII zera - 48). Z r�wnym skutkiem mo�naby zastosowa� rozkaz ADD DX, 48 Je�li prawid�owo dobierzemy format danych, fragment programu napisany w asemblerze mo�e korzysta� z danych dok�adnie tak samo, jak ka�dy inny fragment programu napisany w C/C++. Mo�emy zastosowa� dane o jednobajtowej d�ugo�ci (je�li drugi, pusty bajt nie jest nam potrzebny). Zwr�� uwag�, �e pos�ugujemy si� w tym przypadku tylko "po��wk�" rejestru DL (L - Low - m�odszy). [P057.CPP] #pragma inline void main() { const char *napis = "\nRazem warzyw: $"; char marchewki = 2, pietruszki = 5; asm { MOV DX, napis MOV AH, 9 INT 33 MOV DL, marchewki ADD DL, pietruszki ADD DL, '0' MOV AH, 2 INT 33 } } W tej wersji zadeklarowali�my zmienne marchewki i pietruszki jako zmienne typu char, co jest r�wnoznaczne zadeklarowaniu ich przy pomocy dyrektywy DB. Zajmijmy si� teraz maszynow� reprezentacj� liczb typu unsigned long int (d�ugie ca�kowite bez znaku). Ze wzgl�du na specyfik� zapisu danych do pami�ci przez mikroprocesory rodziny Intel 80x86 d�ugie liczby ca�kowite (podw�jne s�owo - double word) np. 12345678(hex) s� przechowywane w pami�ci w odwr�conym szyku. Zamieniony miejscami zostaje starszy bajt z m�odszym jak r�wnie� starsze s�owo z m�odszym s�owem. Liczba 12345678(hex) zostanie zapisana w pami�ci komputera IBM PC jako 78 56 34 12. Gdy inicjujemy w programie zmienn� long int x = 2; zostaje ona umieszczona w pami�ci tak: 02 00 00 00 (hex). M�odsze s�owo (02 00) jest umieszczone jako pierwsze. To w�a�nie s�owo zawiera interesuj�c� nas informacj�, mo�emy wczyta� to s�owo do rejestru rozkazem MOV DX, X Je�li b�dzie nam potrzebna druga po��wka zmiennej - starsze s�owo (umieszczone w pami�ci jako nast�pne), mo�emy zastosowa� pointer (czyli poda� adres nast�pnego s�owa pami�ci). [P058.CPP] # pragma inline void main() { unsigned long marchewki = 2, pietruszki = 5; const char *napis = "\nRazem warzyw: $"; asm { MOV DX, napis MOV AH, 9 INT 33 MOV DX, marchewki ADD DX, pietruszki ADD DX, '0' MOV AH, 2 INT 33 } } W przypadku liczb ca�kowitych ujemnych C++ stosuje zapis w kodzie komplementarnym. Aby m�c manipulowa� takimi danymi ka�dy szanuj�cy si� komputer powinien mie� mo�liwo�� stosowania liczb ujemnych. Najstarszy bit w s�owie, b�d� bajcie (pierwszy z lewej) mo�e spe�nia� rol� bitu znakowego. O tym, czy liczba jest ze znakiem, czy te� bez decyduje wy��cznie to, czy zwracamy uwag� na ten bit. W liczbach bez znaku, oboj�tnie, czy o d�ugo�ci s�owa, czy bajtu, ten bit r�wnie� jest (i by� tam zawsze!), ale traktowali�my go, jako najstarszy bit nie przydaj�c mu poza tym �adnego szczeg�lnego znaczenia. Aby liczba sta�a si� liczb� ze znakiem - to my musimy zacz�� j� traktowa� jako liczb� ze znakiem, czyli zacz�� zwraca� uwag� na ten pierwszy bit. Pierwszy, najstarszy bit liczby ustawiony do stanu 1 b�dzie oznacza�, �e liczba jest ujemna - je�li zechcemy j� potraktowa� jako liczb� ze znakiem. Filozofia post�powania z liczbami ujemnymi opiera si� na banalnym fakcie: (-1) + 1 = 0 Tw�j PC "rozumuje" tak: -1 to taka liczba, kt�ra po dodaniu 1 stanie si� 0. Czy mo�na jednak�e wyobrazi� sobie np. jednobajtow� liczb� dw�jkow�, kt�ra po dodaniu 1 da nam w rezultacie 0 ? Wydawa�oby si�, �e w dowolnym przypadku wynik powinien by� conajmniej r�wny 1. A jednak. Je�li ograniczymy swoje rozwa�ania do o�miu bit�w jednego bajtu, mo�e wyst�pi� taka, absurdalna tylko z pozoru sytuacja. Je�li np. dodamy 255 + 1 (dw�jkowo 255 = 11111111): 1111 1111 hex FF dec 255 + 1 + 1 + 1 ___________ _____ _____ 1 0000 0000 100 256 otrzymamy 1 0000 0000 (hex 100). Dla Twojego PC oznacza to, �e w o�miobitowym rejestrze pozostanie 0000 0000 , czyli po prostu 0. Nast�pi natomiast przeniesienie (carry) do dziewi�tego (nie zawsze istniej�cego sprz�towo bitu). Wyst�pienie przeniesienia powoduje ustawienie flagi CARRY w rejestrze FLAGS. Je�li zignorujemy flag� i b�dziemy bra� pod uwag� tylko te osiem bit�w w rejestrze, oka�e si�, �e otrzymali�my wynik 0000 0000. Kr�tko m�wi�c FF = (-1), poniewa� FF + 1 = 0. Aby odwr�ci�...
lazarusp22