Asembler x86/Funkcje/GNU AS: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
rozwinięcie
Linia 2:
==Definiowanie==
Funkcje w asemblerze definiujemy po prostu jako zwykłe etykiety. Gdy chcemy przeskoczyć w wykonaniu naszego programu do naszej funkcji posługujemy się nazwą etykiety jako argumentem dla odpowiedniej instrukcji modyfikującej rejestr EIP i ew. rejestr CS tak aby razem wskazywały na początek funkcji.
==Wywoływanie==
==jmp==
Instrukcja jmp modyfikuje wartość rejestru EIP, zależnie od przekazanego parametru. Istnieją 3 odmiany tej instrukcji:
* jmp short - korzystamy z niej, gdy procedura, którą chcemy wywołać jest oddalona o 128 bajtów w tył lub do przodu; najszybsza instrukcja z rodziny.
* jmp near - korzystamy z niej, gdy procedura, którą chcemy wywołać jest w tym samym segmencie co procedura wywołująca (w przypadku płaskiego modelu pamięci - w tym samym programie).
* jmp far - korzystamy z niej, gdy procedura, którą chcemy wywołać jest w innym segmencie, zmianie ulega również rejestr CS; w przypadku płaskiego modelu pamięci, korzystamy z niej gdy wywoływana funkcja znajduje się poza naszym procesem.
 
==call==
Instrukcja call działa identycznie do instrukcji jmp z tą różnicą, że przed przeskokiem układa na stosie bieżące wartości rejestrów EIP oraz ew. CS tak aby później było można wrócić do miejsca gdzie wykonany był skok przy użyciu którejś instrukcji z rodziny ret. Istnieją dwie główne odmiany instrukcji call:
 
* call near - korzystamy z niej, gdy wywoływana procedura jest w tym samym segmencie kodu (w przypadku płaskiego modelu pamięci odnosi się to do tego samego procesu)
* call far - korzystamy z niej, gdy wywoływana procedura jest poza naszym procesem.
 
===przykład===
Poniższy program wypisuje na ekranie znak znajdujący się w rejestrze AL procesora. Właściwa funkcja systemowa wyświetlająca znak na ekranie znajduje się w podprogramie printChar.
<source lang="asm">
Linia 25 ⟶ 39:
ret
</source>
== Ramki stosu ==
=== Argumenty ===
Funkcje wymagające do swojego działania określonych argumentów mogą je otrzymywać na kilka sposobów. Najbardziej intuicyjne z nich to przekazywanie przez:
 
* rejestry
== Argumenty ==
* stos
Najpopularniejszą techniką jest stos i pokażę ją na jednym przykładzie.
* określoną lokalizację w pamięci
Spośród tych trzech metod zdecydowanie najszybsze jest przekazywanie argumentów przez rejestry, jednak najpowszechniejszą praktyką przekazywania argumentów jest użycie do tego stosu. Funkcje zakodowane w niemal wszystkich językach wyższego poziomu przekazują argumenty przez stos. Swoją popularność metoda ta zawdzięcza nie swojej wydajności, lecz uniwersalności. Oto przykład obrazujący tę metodę:
<source lang="asm">
movl %edxebx, [%ebx]edx
pushl %edx
pushl %eax
call near funkcja
# ...
funkcja:
popl %ebx
popl %ecxebx
popl %ebxecx
#...
...
</source>
Zanim wywołujemy naszą funkcję odkładamy wartości dla argumentów na stos przy użyciu instrukcji push, zaś następnie zdejmujemy je z niego przy użyciu instrukcji pop.
 
===Zachowane rejestry===
Rejestr EIP zostaje wrzucony na stos wtedy, gdy skorzystamy z instrukcji call do przeskoku w miejsce naszej funkcji. W przypadku użycia jmp stos pozostaje bez zmian.
Rejestr CS zostaje utrwalony na stosie w tych samych przypadkach co EIP, lecz dodatkowo tylko gdy stosujemy skok daleki tj. call far.
Jeśli chodzi o rejestr EBP możemy go zachować sami ręcznie na stosie w celu odzyskania jego poprzedniej wartości. Rejestr EBP wskazuje na swoją zachowaną wartość i w sam w sobie w obrębie działania funkcji jest niezmienny (w przeciwieństwie do SFP - szczytu stosu, który jest ruchomy) i dzięki temu ułatwione jest manipulowanie w obrębie stosu dzięki niemu. Aby odnieść się do dowolnej wartości na stosie jedynie dodajemy/odejmujemy od rejestru EBP odpowiednie wartości.
 
===Zmienne lokalne===
Zmienne lokalne w asemblerze to po prostu dane, które dana funkcja wrzuca na stos w czasie swojego działania, odliczając wyżej wymienione elementy. Dostęp do nich osiągamy poprzez dodawanie odpowiednich wartości do wartości rejestru EBP (o ile wcześniej zaktualizowaliśmy odpowiednio jego wartość) lub odejmując wartości od rejestru ESP co jest nieco utrudnione ze względu na to, że jego wartość jest zmienna w związku z ruchomością szczytu stosu.
==Inne języki==
===Główne problemy===
Łącząc użycie asemblera z innymi językami można napotkać na różne problemy zależne od języka. Aby móc tworzyć funkcje możliwe do wywoływania przez inne języki lub móc wywoływać funkcje zakodowane w innym języku musimy dokładnie znać mechanizm wywoływania funkcji oraz stałe elementy ich działania. Poniżej znajdziesz opis struktur funkcji zakodowanych w poszczególnych językach. Nie jest to opis łączenia modułów napisanych w asemblerze z językami wyższego poziomu, gdyż ta tematyka została omówiona w rozdziale [[Asembler X86/Łączenie z językami wysokiego poziomu|Łączenie z językami wysokiego poziomu]]!
===Język C===
====Wywoływanie funkcji====
Aby wywołać funkcję zakodowaną w języku C najpierw musimy przekazać jej argumenty poprzez stos, przy czym przekazujemy je w odwrotnej kolejności (od końca) w stosunku do kolejności przedstawionej w jej prototypie. Dodatkowo po wywołaniu funkcji musimy zwolnić miejsce zajmowane przez argumenty na stosie dodając do rejestru ESP odpowiednią wartość (przesuwając tym samym szczyt stosu i uszczuplając stos). Oto przykładowe wywołanie funkcji C:
<source lang="c">
 
void funkcja(short a, char b);
...
pushl $05h
push $0389h
call far funkcja
add $3, %esp
</source>
Jako że do funkcji przekazaliśmy argumenty o łącznej wielkości 3 bajtów, po wywołaniu funkcji zwalniamy zajęte miejsce przesuwając szczyt stosu.