Programowanie Niskopoziomowe Wykład 8: Procedury Dr inż. Marek Mika Państwowa Wyższa Szkoła Zawodowa im. Jana Amosa Komeńskiego W Lesznie
Plan Wstęp Linkowanie z bibliotekami zewnętrznymi Operacje na stosie Definiowanie i używanie procedur Projektowanie programu z procedurami
Wstęp Jak korzystać z bibliotek zewnętrznych? Jak podzielić program na podprogramy? Jak języki programowania używają stosu przy wywoływaniu podprogramów?
Łączenie z zewnętrzną biblioteką Biblioteka jest plikiem zawierającym procedury (podprogramy), które zostały zasemblowane do kodu maszynowego Biblioteka powstaje z pliku (lub kilku) zawierających kod źródłowy procedur i zasemblowanych do kodu pośredniego (object file) Pliki z kodem pośrednim są wstawiane w specjalnie sformatowanym pliku
Procedury biblioteczne Załóżmy, że program korzysta z procedury bibliotecznej WriteString Kod źródłowy programu musi zawierać dyrektywę PROTO WriteString PROTO Następnie procedura wywoływana jest instrukcją call call WriteString Podczas asemblacji asembler pozostawia puste miejsce tam gdzie miał być adres wywoływanej funkcji Linker odnajduje w pliku biblioteki procedurę WriteString i kopiuje właściwy fragment kodu maszynowego do programu wykonywalnego, wstawiając jednocześnie adres tej procedury w miejsce operandu instrukcji call W przypadku, gdy kod wywoływanej funkcji nie znajduje się w bibliotece, linker generuje błąd i nie generuje pliku wykonywalnego
Opcje linkera Linker łączy różne pliki z kodem pośrednim i pliki z bibliotekami Przykład wywołania: link plik1.obj bib1.lib bib2.lib
Linkowanie programów 32-bitowych Biblioteka kernel32.lib jest częścią platformy MS Windows Software Development Kit, zawierającą informacje o dołączaniu funkcji systemowych umieszczonych w pliku kernel32.dll Pliki *.dll to biblioteki zawierające funkcje dołączane dynamicznie (dll = dynamic link library)
Schemat linkowania programów 32-bitowych Program linkowany może być linkowany bib1.lib kernel32.lib może być linkowany wykonywany kernel32.dll
Stos Struktura danych, na której dopuszczalne są tylko dwie operacje: odłożenie elementu na wierzchołek stosu pobranie elementu z wierzchołka stosu Dwa znaczenia: abstrakcyjna struktura danych w językach wysokiego poziomu (kolejka LIFO) stos sprzętowy część pamięci operacyjnej wykorzystywana między innymi w mechanizmie wywoływania podprogramów wspierana sprzętowo przez układy procesora
Stos sprzętowy Ciągły obszar pamięci operacyjnej zarządzany bezpośrednio przez CPU, za pośrednictwem rejestru ESP (wskaźnika stosu) Zawartość rejestru bardzo rzadko jest modyfikowana bezpośrednio, zamiast tego modyfikowana jest pośrednio instrukcjami CALL, RET, PUSH i POP W programach 32-bitowych (tryb chroniony) wierzchołek stosu adresowany jest za pomocą rejestru ESP, w programach 16-bitowych (tryb rzeczywisty) za pomocą rejestru SP Stos rośnie w kierunku malejących adresów
Schemat stosu Offset 00001000 00000FFC 00000FF8 00000008 ESP = 00001000 00000FF4 00000FF0
Operacja PUSH Operacja, która dekrementuje wartość rejestru ESP o 4 (32-bitowa dana) i umieszcza daną pod adresem wskazywanym przez ESP Przed operacją Po operacji 00001000 00000008 ESP 00001000 00000008 ESP 00000FFC 00000FF8 00000FFC 00000FF8 000000E7 00000FF4 00000FF0 00000FF4 00000FF0
Operacja POP Operacja, która pobiera daną spod adresu wskazywanego przez ESP i inkrementuje wartość rejestru ESP tak by wskazywała następny element stosu Fragment stosu poniżej wierzchołka jest logicznie pusty Przed operacją Po operacji 00001000 00000008 00001000 00000008 00000FFC 000000E7 00000FFC 000000E7 00000FF8 00000001 00000FF8 00000001 ESP 00000FF4 0000002 ESP 00000FF4 00000FF0 00000FF0
Zastosowania stosu Tymczasowy obszar składowania zawartości rejestrów, które używane są w więcej niż jednym celu, po modyfikacji zawartość rejestru może być przywrócona do poprzedniej wartości Przechowywanie śladu powrotu po wywołaniu procedury instrukcją CALL Przekazanie argumentów wywołania procedury Miejsce przechowywania zmiennych lokalnych procedury
Instrukcja PUSH Działanie: dekrementacja zawartości rejestru ESP skopiowanie operandu na stos w miejscu wskazywanym przez wskaźnik stosu Typowe wywołania: push push push reg/mem16 reg/mem32 imm32
Instrukcja POP Działanie: skopiowanie elementu stosu wskazywanego przez ESP do 16- lub 32-bitowego operandu inkrementacja ESP o 2 (operand 16-bitowy) lub 4 (operand 32-bitowy) Typowe wywołania: pop pop reg/mem16 reg/mem32
Instrukcje PUSHFD i POPFD Instrukcje bezargumentowe PUSHFD odkłada na stos 32-bitową zawartość rejestru EFLAGS POPFD pobiera ze stosu 32-bitową wartość i umieszcza ją w rejestrze EFLAGS Dla 16-bitowego rejestru FLAGS stosowane są inne instrukcje PUSHF i POPF
Zapamiętywanie i odtwarzanie wartości znaczników Sekwencja instrukcji: pushfd popfd Uwaga! należy tak skonstruować program, aby instrukcja popfd nie została ominięta Sekwencja instrukcji nie posiadająca tej wady:.data znaczniki DWORD?.code pushfd pop znaczniki push znacznik popfd
Instrukcje PUSHAD i POPAD PUSHAD odłożenie na stosie zawartości wszystkich uniwersalnych rejestrów 32-bitowych w następującym porządku: EAX, ECX, EDX, EBX, ESP (wartość sprzed wykonania instrukcji pushad), EBP, ESI, EDI POPAD pobranie ze stosu zawartości wszystkich uniwersalnych rejestrów 32-bitowych w odwrotnym porządku PUSHA i POPA instrukcje przeznaczone dla 16- bitowych rejestrów uniwersalnych w porządku: AX, CX, DX, BX, SP, BP, SI i DI Głównie stosowane przed wywołaniem procedur, które używają kilka rejestrów, ale nie można stosować w przypadku, gdy wynik procedury zwracany jest w rejestrze
DEFINIOWANIE I UŻYWANIE PROCEDUR
Dyrektywa PROC Definiowanie procedury: przy użyciu dyrektyw PROC oraz ENDP procedura musi mieć unikalną nazwę, którą podaje się przed dyrektywą PROC i dyrektywą ENDP każda procedura (oprócz głównego programu) musi kończyć się instrukcją RET Etykiety: domyślny zasięg ciało procedury zasięg globalny można uzyskać przez użycie podwójnego dwukropka :: po etykiecie nie zaleca się wykonywania skoków i pętli poza wnętrze procedury (problemy ze stosem)
Procedura - przykład Procedura obliczająca obwód trójkąta argumenty (długości boków trójkąta) w rejestrach EAX, EBX i ECX wynik zwracany w EAX ObwodTroj add add ret ObwodTroj PROC eax, ebx eax, ecx ENDP
Dokumentowanie procedur Zalecenia: opis wszystkich zadań wykonywanych przez procedurę lista parametrów wejściowych i ich znaczenie oraz zbiór dopuszczalnych wartości lista parametrów wyjściowych (wartości zwracanych przez procedurę) zbiór wymagań, jakie muszą być spełnione przed wykonaniem procedury
Instrukcje CALL i RET Instrukcja CALL, której argumentem jest nazwa procedury powoduje: odłożenie na stosie adresu powrotu z procedury (zawartość licznika rozkazów EIP) załadowanie EIP adresem wywoływanej procedury Instrukcja RET: pobiera ze stosu adres powrotu ładuje go do EIP
Procedury zagnieżdżone Procedura zagnieżdżona to procedura wywoływana z wnętrza innej procedury Podobnie jak wywołanie każdej innej procedury, wywołanie procedury zagnieżdżonej powoduje odłożenie na stosie adresu powrotu, a po napotkaniu w ciele procedury instrukcji RET zdjęcie tego adresu z wierzchołka stosu
Przekazywanie argumentów Nie jest dobrym pomysłem odwoływanie się wewnątrz procedury do argumentów tej procedury w postaci zmiennych globalnych Argumenty do procedury można przekazywać za pośrednictwem rejestrów w procedurze, w której następuje wywołanie innej procedury, przed wywołaniem procedury należy wpisać wartości argumentów do odpowiednich rejestrów procedura powinna być tak napisana, aby wartości argumentów można było pobrać z właściwych rejestrów często przed wywołaniem procedury i skopiowaniem wartości argumentów do odpowiednich rejestrów, zawartość tych rejestrów należy zapamiętać na stosie, a po powrocie je odtworzyć
Operator USES W połączeniu z dyrektywą PROC służy do określenia tych rejestrów, które będą używane wewnątrz procedury. MASM wygeneruje automatycznie kod z instrukcjami push i pop Uwaga! Nie należy odkładać na stos rejestru, który ma zawierać wynik procedury
USES - przykład ArraySum PROC USES esi ecx mov eax,0 ; L1: add eax,[esi] ; add esi,type DWORD loop L1 ret ArraySum ENDP ArraySum PROC push esi push ecx mov L1: add eax,[esi] add esi,type DWORD loop L1 pop ecx pop esi ret ArraySum ENDP
DZIĘKUJĘ ZA UWAGĘ