Korzystając z tego przykładu pochodzącego z wikipedii, w której DrawSquare () wywołuje DrawLine (),
(Pamiętaj, że ten diagram ma wysokie adresy u dołu i niskie adresy u góry).
Czy ktoś mógłby mi wyjaśnić, co ebp
i esp
w tym kontekście?
Z tego, co widzę, powiedziałbym, że wskaźnik stosu zawsze wskazuje na górę stosu, a wskaźnik bazowy na początek bieżącej funkcji? Albo co?
edycja: Mam na myśli to w kontekście programów Windows
edit2: A jak to eip
działa?
edit3: Mam następujący kod z MSVC ++:
var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
hInstance= dword ptr 8
hPrevInstance= dword ptr 0Ch
lpCmdLine= dword ptr 10h
nShowCmd= dword ptr 14h
Wszystkie wydają się być dwordami, dlatego zabierają 4 bajty każdy. Widzę więc, że między hInstance a var_4 występuje 4-bajtowa przerwa. Czym oni są? Zakładam, że jest to adres zwrotny, jak widać na zdjęciu w Wikipedii?
(uwaga redaktora: usunąłem długi cytat z odpowiedzi Michaela, który nie należy do pytania, ale edytowano pytanie uzupełniające):
Wynika to z faktu, że przepływ wywołania funkcji jest następujący:
* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals
Moje pytanie (na koniec, mam nadzieję!) Brzmi teraz: co dokładnie dzieje się od momentu, gdy podskakuję argumenty funkcji, którą chcę wywołać do końca prologu? Chcę wiedzieć, jak ewoluują ebp, esp w tych momentach (już zrozumiałem, jak działa prolog, chcę tylko wiedzieć, co się dzieje po tym, jak wrzuciłem argumenty na stos i przed prologiem).
Odpowiedzi:
esp
jest, jak mówisz, szczytem stosu.ebp
jest zwykle ustawianyesp
na początku funkcji. Dostęp do parametrów funkcji i zmiennych lokalnych można uzyskać, odpowiednio dodając i odejmując stałe przesunięcie odebp
. Wszystkie konwencje wywoływania x86 definiują sięebp
jako zachowane między wywołaniami funkcji.ebp
sam wskazuje na podstawowy wskaźnik poprzedniej ramki, który umożliwia przechodzenie stosu w debuggerze i przeglądanie lokalnych zmiennych innych ramek do działania.Większość prologów funkcji wygląda mniej więcej tak:
Później w funkcji możesz mieć kod podobny (zakładając, że obie zmienne lokalne mają 4 bajty)
Optymalizacja FPO lub wskaźnika pominięcia ramki, którą możesz włączyć, faktycznie to wyeliminuje i użyje
ebp
jako innego rejestru i uzyska dostęp do miejscowych bezpośrednio zesp
, ale utrudnia to debugowanie, ponieważ debugger nie może już bezpośrednio uzyskiwać dostępu do ramek stosów wcześniejszych wywołań funkcji.EDYTOWAĆ:
W zaktualizowanym pytaniu brakuje dwóch wpisów w stosie:
Wynika to z faktu, że przepływ wywołania funkcji jest następujący:
hInstance
itp.)ebp
źródło
ESP jest bieżącym wskaźnikiem stosu, który zmienia się za każdym razem, gdy słowo lub adres są wypychane lub wyskakiwane na stosie. EBP jest wygodniejszym dla kompilatora sposobem śledzenia parametrów funkcji i zmiennych lokalnych niż bezpośrednie używanie ESP.
Ogólnie (i może się to różnić w zależności od kompilatora) wszystkie argumenty wywoływanej funkcji są wypychane na stos przez funkcję wywołującą (zwykle w odwrotnej kolejności, niż zadeklarowane w prototypie funkcji, ale to się zmienia) . Następnie wywoływana jest funkcja, która wypycha adres zwrotny (EIP) na stos.
Po wejściu do funkcji stara wartość EBP jest wypychana na stos, a EBP jest ustawiane na wartość ESP. Następnie ESP jest zmniejszany (ponieważ stos rośnie w pamięci), aby przydzielić miejsce dla lokalnych zmiennych i tymczasowych funkcji. Od tego momentu, podczas wykonywania funkcji, argumenty funkcji są umieszczane na stosie w dodatnich przesunięciach z EBP (ponieważ zostały wypchnięte przed wywołaniem funkcji), a zmienne lokalne znajdują się w ujemnych przesunięciach z EBP (ponieważ zostały przydzielone na stosie po wpisie funkcji). Dlatego EBP nazywany jest wskaźnikiem ramki , ponieważ wskazuje na środek ramki wywołania funkcji .
Po wyjściu z funkcji wystarczy ustawić ESP na wartość EBP (która zwalnia zmienne lokalne ze stosu i wyświetla wpis EBP na szczycie stosu), a następnie wyskakuje ze starej wartości EBP ze stosu, a następnie funkcja zwraca (wstawiając adres zwrotny do EIP).
Po powrocie do funkcji wywołującej, może następnie zwiększyć wartość ESP, aby usunąć argumenty funkcji, które wypchnął na stos tuż przed wywołaniem innej funkcji. W tym momencie stos powraca do tego samego stanu, w jakim był przed wywołaniem wywoływanej funkcji.
źródło
Masz rację. Wskaźnik stosu wskazuje na górny element stosu, a wskaźnik bazowy wskazuje na „poprzedni” szczyt stosu przed wywołaniem funkcji.
Po wywołaniu funkcji dowolna zmienna lokalna zostanie zapisana na stosie, a wskaźnik stosu zostanie zwiększony. Po powrocie z funkcji wszystkie zmienne lokalne na stosie wykraczają poza zakres. Robisz to, ustawiając wskaźnik stosu z powrotem na wskaźnik bazowy (który był „poprzednim” szczytem przed wywołaniem funkcji).
Takie przydzielanie pamięci jest bardzo , bardzo szybkie i wydajne.
źródło
EDYCJA: Aby uzyskać lepszy opis, zobacz Demontaż / Funkcje x86 i Ramki stosu w WikiBook o zestawie x86. Próbuję dodać informacje, które mogą Cię zainteresować za pomocą programu Visual Studio.
Przechowywanie EBP wywołującego jako pierwszej zmiennej lokalnej nazywa się standardową ramką stosu i może być używane do prawie wszystkich konwencji wywoływania w systemie Windows. Istnieją różnice, czy wywołujący lub odbierający zwalnia przekazane parametry i które parametry są przekazywane do rejestrów, ale są one ortogonalne w stosunku do standardowego problemu z ramką stosu.
Mówiąc o programach Windows, prawdopodobnie możesz użyć Visual Studio do skompilowania kodu C ++. Należy pamiętać, że Microsoft stosuje optymalizację o nazwie Frame Pointer Omission, która sprawia, że prawie niemożliwe jest przejście stosu bez użycia biblioteki dbghlp i pliku PDB dla pliku wykonywalnego.
To pominięcie wskaźnika ramki oznacza, że kompilator nie przechowuje starego EBP w standardowym miejscu i używa rejestru EBP do czegoś innego, dlatego masz trudności ze znalezieniem EIP dzwoniącego, nie wiedząc, ile miejsca potrzebują zmienne lokalne dla danej funkcji. Oczywiście Microsoft zapewnia interfejs API, który umożliwia wykonywanie spacerów po stosach nawet w tym przypadku, ale wyszukiwanie bazy danych tabeli symboli w plikach PDB w niektórych przypadkach jest zbyt długie.
Aby uniknąć FPO w swoich jednostkach kompilacyjnych, musisz unikać używania / O2 lub jawnie dodać / Oy- do flag kompilacji C ++ w swoich projektach. Prawdopodobnie łączysz się ze środowiskiem uruchomieniowym C lub C ++, które używa FPO w konfiguracji wydania, więc będziesz miał trudności z wykonaniem spacerów po stosie bez dbghlp.dll.
źródło
Po pierwsze, wskaźnik stosu wskazuje na spód stosu, ponieważ stosy x86 budują od wysokich wartości adresu do niższych wartości adresu. Wskaźnik stosu jest punktem, w którym następne wywołanie push (lub wywołanie) umieści następną wartość. Jego działanie jest równoważne z instrukcją C / C ++:
Wskaźnik podstawowy znajduje się u góry bieżącej ramki. ebp ogólnie wskazuje na twój adres zwrotny. ebp + 4 punkty do pierwszego parametru twojej funkcji (lub tej wartości metody klasowej). ebp-4 wskazuje na pierwszą zmienną lokalną twojej funkcji, zwykle starą wartość ebp, abyś mógł przywrócić poprzedni wskaźnik ramki.
źródło
Dawno już nie programowałem w Asemblerze, ale ten link może być przydatny ...
Procesor ma kolekcję rejestrów, które służą do przechowywania danych. Niektóre z nich są wartościami bezpośrednimi, podczas gdy inne wskazują na obszar w pamięci RAM. Rejestry są zwykle używane do określonych czynności, a każdy operand w zestawie wymaga określonej ilości danych w określonych rejestrach.
Wskaźnik stosu jest najczęściej używany podczas wywoływania innych procedur. W nowoczesnych kompilatorach wiązka danych zostanie najpierw zrzucona na stos, a następnie adres zwrotny, dzięki czemu system będzie wiedział, dokąd zwrócić, gdy otrzyma polecenie zwrotu. Wskaźnik stosu wskaże następną lokalizację, w której nowe dane mogą zostać przekazane do stosu, gdzie pozostaną, dopóki nie zostaną ponownie wyświetlone.
Rejestry podstawowe lub rejestry segmentowe wskazują tylko przestrzeń adresową dużej ilości danych. W połączeniu z drugim regresem wskaźnik Base podzieli pamięć na ogromne bloki, podczas gdy drugi rejestr wskaże element w tym bloku. Wskaźniki bazowe wskazują zatem na bazę bloków danych.
Pamiętaj, że asembler jest bardzo specyficzny dla procesora. Strona, do której linkuję, zawiera informacje o różnych typach procesorów.
źródło
Edytować Tak, w większości jest to złe. Opisuje coś zupełnie innego na wypadek, gdyby ktoś był zainteresowany :)
Tak, wskaźnik stosu wskazuje na górę stosu (czy jest to pierwsza pusta lokalizacja stosu, czy ostatnia pełna, której nie jestem pewien). Wskaźnik bazowy wskazuje lokalizację pamięci wykonywanej instrukcji. Jest to na poziomie kodów operacyjnych - najbardziej podstawowej instrukcji, jaką można uzyskać na komputerze. Każdy kod operacji i jego parametry są przechowywane w miejscu w pamięci. Jedna linia C, C ++ lub C # może zostać przetłumaczona na jeden kod operacji lub sekwencję dwóch lub więcej, w zależności od stopnia złożoności. Są one kolejno zapisywane w pamięci programu i wykonywane. W normalnych okolicznościach wskaźnik bazowy jest zwiększany o jedną instrukcję. W celu sterowania programem (GOTO, IF itp.) Można go wielokrotnie zwiększać lub po prostu zastępować kolejnym adresem pamięci.
W tym kontekście funkcje są przechowywane w pamięci programu pod określonym adresem. Kiedy funkcja jest wywoływana, pewne informacje są wypychane na stos, który pozwala programowi znaleźć ją z powrotem do miejsca, z którego funkcja została wywołana, a także parametry funkcji, następnie adres funkcji z pamięci programu jest przekazywany do wskaźnik bazowy. W następnym cyklu zegarowym komputer rozpoczyna wykonywanie instrukcji od tego adresu pamięci. W pewnym momencie POWRÓT do miejsca w pamięci PO instrukcji, która wywołała funkcję i kontynuuje od tego momentu.
źródło
esp oznacza „Extended Stack Pointer” ..... ebp dla „Something Base Pointer” .... i eip dla „Something Instruction Pointer” ...... Wskaźnik stosu wskazuje adres przesunięcia segmentu stosu . Wskaźnik bazowy wskazuje adres przesunięcia dodatkowego segmentu. Wskaźnik instrukcji wskazuje adres przesunięcia segmentu kodu. Teraz, jeśli chodzi o segmenty ... są to małe działy o wielkości 64 KB w obszarze pamięci procesorów ... Ten proces jest znany jako segmentacja pamięci. Mam nadzieję, że ten post był pomocny.
źródło