Jaki jest cel rejestru wskaźnika ramki EBP?

95

Jestem początkującym w języku asemblera i zauważyłem, że kod x86 emitowany przez kompilatory zwykle utrzymuje wskaźnik ramki nawet w trybie zwolnienia / optymalizacji, kiedy mógłby użyć EBPrejestru do czegoś innego.

Rozumiem, dlaczego wskaźnik ramki może ułatwić debugowanie kodu i może być niezbędny, jeśli alloca()zostanie wywołany w funkcji. Jednak x86 ma bardzo mało rejestrów, a używanie dwóch z nich do przechowywania położenia ramki stosu, gdy wystarczyłoby jedno, po prostu nie ma dla mnie sensu. Dlaczego pomijanie wskaźnika ramki jest uważane za zły pomysł, nawet w kompilacjach zoptymalizowanych / wydanych?

dsimcha
źródło
20
Jeśli myślisz, że x86 ma bardzo mało rejestrów, powinieneś sprawdzić 6502 :)
Sedat Kapanoglu
3
Powiązane: Po co używać EBP w prologu funkcji i epilogu?
legends2k
1
C99 VLA również może na tym skorzystać.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
2
stackoverflow.com/questions/1395591/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Czy wskaźnik ramki nie powoduje, że wskaźnik stosu jest zbędny? . TL; DR: 1. nietrywialne wyrównanie stosu 2. alokacja stosu ( alloca) 3. łatwość implementacji w czasie wykonywania: obsługa wyjątków, piaskownica, GC
Alexander Malakhov

Odpowiedzi:

102

Wskaźnik ramki to wskaźnik odniesienia, który pozwala debugerowi wiedzieć, gdzie znajduje się lokalna zmienna lub argument z pojedynczym stałym przesunięciem. Chociaż wartość ESP zmienia się w trakcie wykonywania, EBP pozostaje taki sam, co umożliwia osiągnięcie tej samej zmiennej przy tym samym przesunięciu (np. Pierwszy parametr zawsze będzie na poziomie EBP + 8, podczas gdy przesunięcia ESP mogą się znacznie zmienić, ponieważ będziesz naciskać) / popping rzeczy)

Dlaczego kompilatory nie odrzucają wskaźnika ramki? Ponieważ dzięki wskaźnikowi ramki debugger może dowiedzieć się, gdzie lokalne zmienne i argumenty używają tablicy symboli, ponieważ gwarantuje się, że będą one miały stałe przesunięcie względem EBP. W przeciwnym razie nie ma łatwego sposobu na określenie, gdzie w dowolnym miejscu kodu znajduje się zmienna lokalna.

Jak wspomniał Greg, pomaga to również w rozwijaniu stosu dla debuggera, ponieważ EBP zapewnia odwrotnie połączoną listę ramek stosu, pozwalając tym samym debugerowi określić rozmiar ramki stosu (zmienne lokalne + argumenty) funkcji.

Większość kompilatorów udostępnia opcję pomijania wskaźników ramek, chociaż utrudnia to debugowanie. Ta opcja nigdy nie powinna być używana globalnie, nawet w kodzie wydania. Nie wiesz, kiedy będziesz musiał debugować awarię użytkownika.

Sedat Kapanoglu
źródło
10
Kompilator prawdopodobnie wie, co robi z ESP. Pozostałe punkty są ważne, +1
erikkallen
8
Nowoczesne debugery mogą wykonywać stosy śledzenia wstecznego nawet w kodzie skompilowanym za pomocą -fomit-frame-pointer. To ustawienie jest domyślne w ostatnim gcc.
Peter Cordes,
2
@SedatKapanoglu: Sekcja danych zawiera niezbędne informacje: yosefk.com/blog/…
Peter Cordes
3
@SedatKapanoglu: .eh_frame_hdrsekcja jest również używana do obsługi wyjątków w czasie wykonywania. Znajdziesz go (z objdump -h) w większości plików binarnych w systemie Linux, to około 16k dla /bin/bash, w porównaniu z 572B dla GNU /bin/true, 108k dla ffmpeg. Istnieje opcja gcc, aby wyłączyć jego generowanie, ale jest to „normalna” sekcja danych, a nie sekcja debugowania, która jest stripusuwana domyślnie. W przeciwnym razie nie można by przeszukać funkcji bibliotecznej, która nie miała symboli debugowania. Ta sekcja może być większa niż push/mov/popinstrukcje, które zastępuje, ale ma prawie zerowy koszt wykonania (np. Pamięć podręczna uop).
Peter Cordes,
3
Odnośnie „taki jak pierwszy parametr zawsze będzie na EBP-4”: Czy nie jest to pierwszy parametr na EBP + 8 (na x86)?
Aydin K.
31

Dodam tylko moje dwa grosze do już dobrych odpowiedzi.

Łańcuch ramek stosu jest częścią dobrej architektury języka. BP wskazuje na bieżącą ramkę, w której przechowywane są zmienne lokalne podprogramu. (Lokalni mieszkańcy mają ujemne przesunięcia, a argumenty mają dodatnie przesunięcia).

Pomysł, że uniemożliwia to wykorzystanie idealnie dobrego rejestru w optymalizacji, rodzi pytanie: kiedy i gdzie optymalizacja naprawdę się opłaca?

Optymalizacja jest opłacalna tylko w ciasnych pętlach, które 1) nie wywołują funkcji, 2) w których licznik programu spędza znaczną część swojego czasu oraz 3) w kodzie, który kompilator kiedykolwiek zobaczy (tj. Funkcje niebędące bibliotekami). Zwykle jest to bardzo mała część całego kodu, szczególnie w dużych systemach.

Inny kod można przekręcić i ścisnąć, aby pozbyć się cykli, a to po prostu nie ma znaczenia, ponieważ licznika programu praktycznie nigdy nie ma.

Wiem, że o to nie pytałeś, ale z mojego doświadczenia wynika, że ​​99% problemów z wydajnością nie ma nic wspólnego z optymalizacją kompilatora. Mają wszystko wspólnego z przeprojektowaniem.

Mike Dunlavey
źródło
Dzięki @Mike, Twoja odpowiedź była dla mnie bardzo pomocna.
sixtyfootersdude
2
Usunięcie wskaźnika ramki pozwala również zaoszczędzić kilka instrukcji przy każdym wywołaniu funkcji, co samo w sobie jest małą optymalizacją. Swoją drogą, użycie wyrażenia „prosi o pytanie” jest nieprawidłowe; masz na myśli „stawia pytanie”.
augurar
@augurar: Naprawiono. Dzięki. Jestem trochę
narzekający
3
@augurar Język ewoluuje: „Zaczyna pytanie” teraz oznacza po prostu „zadaje pytanie”. Bycie normalistą w poszukiwaniu przestarzałych zastosowań niczego nie dodaje.
user3364825,
9

Z pewnością zależy to od kompilatora. Widziałem zoptymalizowany kod emitowany przez kompilatory x86, które swobodnie używają rejestru EBP jako rejestru ogólnego przeznaczenia. (Nie pamiętam jednak, z którym kompilatorem to zauważyłem).

Kompilatory mogą również zdecydować się na utrzymanie rejestru EBP, aby pomóc w rozwijaniu stosu podczas obsługi wyjątków, ale znowu zależy to od dokładnej implementacji kompilatora.

Greg Hewgill
źródło
Większość kompilatorów domyślnie -fomit-frame-pointerwłącza optymalizację. (gdy ABI na to pozwala). GCC, clang, ICC i MSVC robią to, IIRC, nawet w przypadku 32-bitowego systemu Windows. Tak, moja odpowiedź na pytanie: Dlaczego lepiej jest używać ebp niż rejestru esp do lokalizowania parametrów na stosie? pokazuje, że nawet 32-bitowy system Windows może pomijać wskaźnik ramki. 32-bitowy Linux x86 zdecydowanie potrafi i tak. I oczywiście 64-bitowe ABI pozwoliły na pomijanie wskaźnika ramki od samego początku.
Peter Cordes
4

Jednak x86 ma bardzo mało rejestrów

Jest to prawdą tylko w tym sensie, że kody operacyjne mogą adresować tylko 8 rejestrów. Sam procesor w rzeczywistości będzie miał o wiele więcej rejestrów niż to i użyje zmiany nazwy rejestrów, potokowania, wykonywania spekulatywnego i innych modnych słów procesora, aby obejść ten limit. Wikipedia ma dobry akapit wprowadzający na temat tego, co procesor x86 może zrobić, aby pokonać limit rejestrów: http://en.wikipedia.org/wiki/X86#Current_implementations .

MSN
źródło
1
Pierwotne pytanie dotyczy wygenerowanego kodu, który jest ściśle ograniczony do rejestrów, do których można odwoływać się przez kody operacyjne.
Darron
1
Tak, ale właśnie dlatego pomijanie wskaźnika ramki w zoptymalizowanych kompilacjach nie jest obecnie tak ważne.
Michael
1
Zmiana nazwy rejestrów to nie to samo, co faktyczne posiadanie większej liczby dostępnych rejestrów. Nadal istnieje wiele sytuacji, w których zmiana nazwy rejestru nie pomoże, ale bardziej „zwykłe” rejestry tak.
jalf
1

Korzystanie z ramek stosowych stało się niewiarygodnie tanie w każdym sprzęcie, nawet zdalnie nowoczesnym. Jeśli masz tanie ramki stosu, zapisanie kilku rejestrów nie jest tak ważne. Jestem pewien, że ramki szybkiego stosu w porównaniu z większą liczbą rejestrów były kompromisem inżynieryjnym, a ramki szybkiego stosu wygrały.

Ile oszczędzasz przechodząc na czysty rejestr? Czy warto?

dwc
źródło
Więcej rejestrów jest ograniczone przez kodowanie instrukcji. x86-64 wykorzystuje bity w bajcie prefiksu REX, aby rozszerzyć część instrukcji określającą rejestr z 3 do 4 bitów dla rejestrów src i dest. Gdyby było miejsce, x86-64 prawdopodobnie trafiłby do 32 rejestrów architektonicznych, chociaż zapisywanie / przywracanie tak wielu przełączników kontekstowych zaczyna się sumować. 15 to ogromny krok w porównaniu z 7, ale 31 to znacznie mniejsza poprawa w większości przypadków. (nie licząc wskaźnika stosu jako ogólnego przeznaczenia). Szybka zmiana typu push / pop jest świetna nie tylko w przypadku ramek stosu. Nie jest to jednak kompromis z liczbą reg.
Peter Cordes,