Chodzi o rejestr BP / EBP / RBP na platformach Intel. Ten rejestr domyślnie jest segmentem stosu (nie wymaga specjalnego prefiksu, aby uzyskać dostęp do segmentu stosu).
EBP to najlepszy wybór rejestru do uzyskiwania dostępu do struktur danych, zmiennych i dynamicznie przydzielanej przestrzeni roboczej w stosie. EBP jest często używany do uzyskiwania dostępu do elementów na stosie w stosunku do stałego punktu na stosie, a nie w stosunku do bieżącego TOS. Zwykle identyfikuje adres bazowy bieżącej ramki stosu ustalony dla bieżącej procedury. Gdy EBP jest używany jako rejestr bazowy w obliczaniu przesunięcia, przesunięcie jest obliczane automatycznie w bieżącym segmencie stosu (tj. Segmencie aktualnie wybranym przez SS). Ponieważ SS nie musi być jawnie określane, kodowanie instrukcji w takich przypadkach jest bardziej wydajne. EBP może być również użyty do indeksowania segmentów adresowalnych przez inne rejestry segmentów.
(źródło - http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm )
Ponieważ na większości platform 32-bitowych segment danych i segment stosu są takie same, to powiązanie EBP / RBP ze stosem nie stanowi już problemu. Tak jest na platformach 64-bitowych: architektura x86-64, wprowadzona przez AMD w 2003 roku, w dużej mierze porzuciła obsługę segmentacji w trybie 64-bitowym: cztery rejestry segmentów: CS, SS, DS i ES są zmuszone do zerowania Te okoliczności dla 32-bitowych i 64-bitowych platform x86 zasadniczo oznaczają, że rejestr EBP / RBP może być używany bez żadnego przedrostka w instrukcjach procesora, które mają dostęp do pamięci.
Tak więc opcja kompilatora, o której pisałeś, pozwala na użycie BP / EBP / RBP do innych celów, np. Do przechowywania zmiennej lokalnej.
Przez „To pozwala uniknąć instrukcji zapisywania, ustawiania i przywracania wskaźników ramek” oznacza unikanie następującego kodu przy wprowadzaniu każdej funkcji:
push ebp
mov ebp, esp
lub enter
instrukcję, która była bardzo przydatna na procesorach Intel 80286 i 80386.
Ponadto przed powrotem funkcji używany jest następujący kod:
mov esp, ebp
pop ebp
lub leave
instrukcja.
Narzędzia do debugowania mogą skanować dane stosu i wykorzystywać te wypchane dane rejestru EBP podczas lokalizowania call sites
, tj. Wyświetlać nazwy funkcji i argumenty w kolejności, w jakiej zostały nazwane hierarchicznie.
Programiści mogą mieć pytania dotyczące ramek stosu nie w szerokim znaczeniu (że jest to pojedyncza jednostka na stosie, która obsługuje tylko jedno wywołanie funkcji i zachowuje adres zwrotny, argumenty i zmienne lokalne), ale w wąskim sensie - kiedy termin stack frames
jest wspomniany w kontekst opcji kompilatora. Z punktu widzenia kompilatora ramka stosu to po prostu kodem wejścia i wyjścia dla procedury , która wypycha kotwicę do stosu - która może być również używana do debugowania i obsługi wyjątków. Narzędzia do debugowania mogą skanować dane stosu i wykorzystywać te kotwice do śledzenia wstecznego podczas lokalizowania call sites
na stosie, tj. Do wyświetlania nazw funkcji w kolejności, w jakiej zostały nazwane hierarchicznie.
Dlatego bardzo ważne jest, aby programista zrozumiał, czym jest ramka stosu pod względem opcji kompilatora - ponieważ kompilator może kontrolować, czy wygenerować ten kod, czy nie.
W niektórych przypadkach kompilator może pominąć ramkę stosu (kod wejściowy i wyjściowy procedury), a dostęp do zmiennych będzie można uzyskać bezpośrednio za pośrednictwem wskaźnika stosu (SP / ESP / RSP) zamiast wygodnego wskaźnika podstawowego (BP / ESP / RSP). Warunki dla kompilatora, aby pomijać ramki stosu dla niektórych funkcji mogą być różne, na przykład: (1) funkcja jest funkcją-liść (tj. Jednostką końcową, która nie wywołuje innych funkcji); (2) bez wyjątków; (3) żadne procedury nie są wywoływane na stosie z parametrami wychodzącymi; (4) funkcja nie ma parametrów.
Pomijanie ramek stosu (kod wejściowy i wyjściowy procedury) może sprawić, że kod będzie mniejszy i szybszy, ale może również negatywnie wpłynąć na zdolność debugerów do śledzenia wstecznego danych w stosie i wyświetlania ich programiście. Są to opcje kompilatora, które określają, w jakich warunkach funkcja powinna spełniać, aby kompilator przyznał jej kod wejścia i wyjścia ramki stosu. Na przykład kompilator może mieć opcje dodawania takiego kodu wejścia i wyjścia do funkcji w następujących przypadkach: (a) zawsze, (b) nigdy, (c) w razie potrzeby (określając warunki).
Wracając od ogólników do szczegółów: jeśli użyjesz -fomit-frame-pointer
opcji kompilatora GCC, możesz wygrać zarówno na kodzie wejściowym, jak i końcowym procedury oraz na posiadaniu dodatkowego rejestru (chyba że jest już domyślnie włączony samodzielnie lub domyślnie przez inny opcje, w tym przypadku już korzystasz z korzyści wynikających z używania rejestru EBP / RBP i żadne dodatkowe korzyści nie zostaną uzyskane poprzez wyraźne określenie tej opcji, jeśli jest już włączona domyślnie). Należy jednak pamiętać, że w trybach 16-bitowych i 32-bitowych rejestr BP nie ma możliwości dostępu do 8-bitowych jego części, jak ma to AX (AL i AH).
Ponieważ ta opcja, oprócz umożliwienia kompilatorowi używania EBP jako rejestru ogólnego przeznaczenia w optymalizacjach, zapobiega również generowaniu kodu wyjścia i wejścia dla ramki stosu, co komplikuje debugowanie - dlatego dokumentacja GCC wyraźnie stwierdza (niezwykle podkreślając pogrubioną czcionką style), że włączenie tej opcji uniemożliwia debugowanie na niektórych komputerach
Należy również pamiętać, że inne opcje kompilatora, związane z debugowaniem lub optymalizacją, mogą niejawnie włączać -fomit-frame-pointer
lub wyłączać tę opcję.
Nie znalazłem żadnych oficjalnych informacji na gcc.gnu.org o tym, jak inne opcje wpływają -fomit-frame-pointer
na platformy x86 , https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html stwierdza tylko, co następuje:
-O włącza także -fomit-frame-wskaźnik na maszynach, na których nie koliduje to z debugowaniem.
Tak więc z dokumentacji jako takiej nie wynika jasno, czy -fomit-frame-pointer
zostanie włączony, jeśli tylko skompilujesz z jedną -O
opcją na platformie x86. Można to przetestować empirycznie, ale w tym przypadku twórcy GCC nie zobowiązują się do niezmieniania zachowania tej opcji w przyszłości bez powiadomienia.
Jednak Peter Cordes zwrócił uwagę w komentarzach, że istnieje różnica w ustawieniach domyślnych -fomit-frame-pointer
między platformami x86-16 i x86-32 / 64.
Ta opcja - -fomit-frame-pointer
- dotyczy również kompilatora Intel C ++ 15.0 , nie tylko GCC:
W przypadku kompilatora Intel ta opcja ma alias /Oy
.
Oto, co o tym napisał Intel:
Te opcje określają, czy EBP jest używany jako rejestr ogólnego przeznaczenia w optymalizacjach. Opcje -fomit-frame-pointer i / Oy pozwalają na to użycie. Opcje -fno-omit-frame-pointer i / Oy- disallow.
Niektóre debuggery oczekują, że EBP będzie używany jako wskaźnik ramki stosu i nie mogą tworzyć śledzenia stosu, chyba że tak jest. Opcje -fno-omit-frame-pointer i / Oy- kierują kompilator do generowania kodu, który utrzymuje i używa EBP jako wskaźnika ramki stosu dla wszystkich funkcji, dzięki czemu debugger może nadal tworzyć ślad po stosie bez wykonywania następujących czynności:
Dla -fno-omit-frame-pointer: wyłączanie optymalizacji za pomocą -O0 For / Oy-: wyłączanie optymalizacji / O1, / O2 lub / O3 Opcja -fno-omit-frame-pointer jest ustawiana po określeniu opcji - O0 lub opcja -g. Opcja -fomit-frame-pointer jest ustawiana po określeniu opcji -O1, -O2 lub -O3.
Opcja / Oy jest ustawiana po określeniu opcji / O1, / O2 lub / O3. Opcja / Oy- jest ustawiana po określeniu opcji / Od.
Użycie opcji -fno-omit-frame-pointer lub / Oy- zmniejsza liczbę dostępnych rejestrów ogólnego przeznaczenia o 1 i może skutkować nieco mniej wydajnym kodem.
UWAGA W przypadku systemów Linux *: obecnie występuje problem z obsługą wyjątków w GCC 3.2. Dlatego kompilator Intela ignoruje tę opcję, gdy GCC 3.2 jest zainstalowany dla C ++ i obsługa wyjątków jest włączona (ustawienie domyślne).
Należy pamiętać, że powyższy cytat dotyczy tylko kompilatora Intel C ++ 15, a nie GCC.