Programowanie Niskopoziomowe Wykład 11: Procedury zaawansowane Dr inż. Marek Mika Państwowa Wyższa Szkoła Zawodowa im. Jana Amosa Komeńskiego W Lesznie
Plan Wstęp Ramki stosu Rekurencja INVOKE, ADDR, PROC, PROTO Programy wielomodułowe Kod pośredni Javy
Wstęp Tematyka związana z programowaniem niskopoziomowym z punktu widzenia HLL ramki stosu zakres i czas życia zmiennych parametry stosu przekazywanie parametrów przez wartość i przez referencję tworzenie i inicjowanie zmiennych lokalnych na stosie rekurencja pisanie programów wielomodułowych modele pamięci dyrektywy INVOKE, PROC i PROTO operatory USES i ADDR
Terminologia Różnice terminologiczne podprogram funkcja (C, C++) metoda (Java) procedura (MASM, Pascal) wartości przekazywane do podprogramu argumenty wartości otrzymane przez podprogram parametry
Ramki stosu Ramka stosu (rekord aktywacyjny) jest obszarem pamięci stosu służącym do: przekazania argumentów, zapamiętania adresu powrotu z podprogramu przechowywania zmiennych lokalnych zapamiętywania zawartości rejestrów
Sekwencja tworzenia ramki stosu Odłożenie na stosie argumentów przekazywanych do podprogramu Wywołanie procedury odłożenie adresu powrotu na stosie Początek wykonywania procedury odłożenie na stosie EBP Ustawienie EBP = ESP (od tego momentu EBP pełni rolę adresu bazowego dla wszystkich parametrów podprogramu) Jeśli podprogram zawiera zmienne lokalne, zawartość ESP jest zmniejszana tak, by zarezerwować na stosie miejsce na te zmienne) Jeśli trzeba zapamiętać zawartość pewnych rejestrów, to należy je odłożyć na stos
Parametry na stosie Dwa typy argumentów: argumenty przekazywane przez wartość (zmienne i stałe) argumenty przekazywane przez referencję (adresy zmiennych)
Przekazywanie parametrów przez wartość Kopia wartości jest odkładana na stosie Przykład: w języku C++ int sum = AddTwo(val1, val2); w języku asemblerowym (uwaga odwrotna kolejność) push val2 push val1 call AddTwo stos przed wywołaniem AddTwo(5,6) val2 val1 6 5 ESP
Przekazywanie parametrów przez referencję Na stos odkładany jest adres (offset) zmiennej Przykład: w języku C++ Swap(&val1, &val2); w języku asemblerowym (uwaga odwrotna kolejność) push OFFSET val2 push OFFSET val1 call SWAP stos przed wywołaniem AddTwo(5,6) val2 val1 offset (val1) offset (val2) ESP
Przekazywanie tablicy jako argumentu procedury W językach wysokiego poziomu zawsze przez referencję Przekazywanie przez wartość nieuzasadnione: wymaga odłożenia na stos każdej komórki tablicy z osobna czasochłonne pamięciożerne Przykład (wywołanie podprogramu o nazwie ArrayFill):.data array DWORD 50 DUP(?).code push OFFSET array call ArrayFill
Dostęp do parametrów na stosie Języki wysokiego poziomu stosują różne sposoby inicjacji i dostępu do parametrów podczas wywoływania podprogramu W językach C i C++ wygląda to następująco: prolog zapisanie zawartości EBP i ustawienie nowej wartości EBP wskazującej wierzchołek stosu + ew. odłożenie niektórych rejestrów na stos epilog na zakończenie podprogramu odtworzenie ze stosu zawartości rejestru EBP + instrukcja powrotu RET
Przykład Podprogram w C int AddTwo (int x, int y) { } return x + y; Prolog podprogramu w j. asemblerowym AddTwo PROC push EBP mov EBP, ESP Zawartość stosu po wywołaniu AddTwo(5,6) 6 5 adres powrotu EBP [EBP + 12] [EBP + 8] [EBP + 4] EBP, ESP
Adresowanie tryb bazowy z przesunięciem EBP jest rejestrem bazowym Przesunięcie w postaci stałej 32 bitowe wyniki zwracane na ogół w rejestrze EAX Przykład (procedura AddTwo): AddTwo PROC push ebp mov ebp, esp ; adres bazowy ramki stosu mov eax, [ebp+12] ; drugi parametr add eax, [ebp+8] ; pierwszy parametr pop ebp ret AddTwo ENDP
Adresy symboliczne parametrów Położenie parametrów procedury względem początku ramki jest stałe Adres parametru możemy zastąpić stałą Przykład: y_param EQU [ebp + 12] x_param EQU [ebp + 8] AddTwo PROC push ebp mov ebp,esp mov eax,y_param add eax,x_param pop ebp ret AddTwo ENDP
Błędy przy przekazywaniu parametrów przez stos Wykonanie w pętli kodu: push 6 push 5 call AddTwo może spowodować przepełnienie stosu Przy wywołaniu poniższego programu powstaje błąd (niewłaściwy adres powrotu): main PROC call Example1 exit main ENDP Example1 PROC push 6 push 5 call AddTwo ret Example1 ENDP ; niewłaściwa zawartość stosu!
Czyszczenie stosu Czyszczenie stosu przywrócenie stosu do stanu sprzed wywołania procedury Dwa podejścia: konwencja wywoływania podprogramów języka C konwencja STDCALL
Konwencja wywoływania w C Przykład: Example1 PROC push 6 push 5 call AddTwo add esp,8 ; usuwa arg. ze stosu ret Example1 ENDP Jedna z najprostszych metod polegająca na dodaniu do zawartości rejestru ESP sumy rozmiarów wszystkich parametrów ESP wskazuje wtedy miejsce na stosie, w którym przechowywany jest adres powrotu z podprogramu
Konwencja STDCALL Wykonanie instrukcji RET z parametrem liczbowym równym sumie bajtów zajmowanych na stosie przez parametry procedury, wartość ta dodawana jest do rejestru EBP Przykład: AddTwo PROC push ebp mov ebp,esp ; adres bazowy ramki stosu mov eax,[ebp + 12] ; drugi parametr add eax,[ebp + 8] ; pierwszy parametr pop ebp ret 8 ; czyszczenie stosu AddTwo ENDP
Porównanie konwencji W obydwu przypadkach parametry przekazywane w odwrotnym porządku W STDCALL stos zawsze czyszczony automatycznie, w C użytkownik musi pamiętać o zdjęciu odpowiedniej liczby bajtów ze stosu W C możliwość stosowania zmiennej liczby parametrów podprogramu, w STDCALL brak takiej możliwości
Przekazywanie przez stos argumentów 8- i 16-bitowych W trybie chronionym obszar stosu podlega tym samym ograniczeniom co pozostałe obszary pamięci dostęp do podwójnych słów i zasada wyrównywania do adresów o wielokrotności 4 Zaleca się rozszerzanie wartości 8 i 16 bitowych do 32 bitów Podczas odkładania na stosie stałej instrukcją PUSH nastąpi automatyczne rozszerzenie do 32 bitów Odkładanie na stosie zmiennych o rozmiarze bajta lub słowa spowoduje błąd, wymaga więc od użytkownika rozszerzenia wartości do 32 bitów instrukcją MOVZX (lub MOVSX dla liczby ze znakiem)
Przekazywanie przez stos argumentów dłuższych niż 32 bity 12345678 00ABCDEF adres powrotu EBP Rozmiar argumentu powinien być wielokrotnością podwójnego słowa Istotny jest porządek przekazywania części tego argumentu (najpierw starsza, potem młodsza część) Przykład:.data [EBP+12] [EBP+8] [EBP+4] EBP, ESP longval DQ 1234567800ABCDEFh.code push DWORD PTR longval + 4 ; część starsza push DWORD PTR longval ; część młodsza call WriteHex64
Zapamiętywanie i odtwarzanie rejestrów Zawartość rejestrów używanych wewnątrz procedury powinna być zapamiętana tuż po zapamiętaniu zawartości ESP w EBP i jeszcze przed zarezerwowaniem miejsca dla zmiennych lokalnych Inne podejście może spowodować zmianę offsetu dla istniejących elementów stosu (adresu powrotu i argumentów podprogramu)
Wpływ operatora USES na zawartość stosu Operator USES powoduje odłożenie na stos zawartości rejestrów wymienionym po tym operatorze Asembler podczas kompilacji dodaje na początku procedury odpowiednie instrukcje PUSH, a na końcu procedury odpowiadające im instrukcje POP W takim przypadku stosowanie instrukcji z adresowaniem komórek stosu za pośrednictwem offsetu w postaci stałej spowoduje błędny dostęp do pamięci (stała nie uwzględnia instrukcji PUSH wywołanych operatorem USES) Instrukcje PUSH dodane przez USES wykonywane są przed przypisaniem EBP=ESP, co powoduje kolejny błąd przy dostępie do adresu powrotu i argumentów procedury Błędów tych można uniknąć definiując odpowiednio parametry stosu w dyrektywie PROC
DZIĘKUJĘ ZA UWAGĘ