Opcja GCC -fPIC

Odpowiedzi:

526

Kod niezależny od pozycji oznacza, że ​​wygenerowany kod maszynowy nie jest zależny od bycia zlokalizowanym pod określonym adresem w celu pracy.

Np. Skoki byłyby generowane raczej jako względne niż bezwzględne.

Pseudo-montaż:

PIC: Działa to niezależnie od tego, czy kod ma adres 100, czy 1000

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP

Bez PIC: Działa to tylko wtedy, gdy kod ma adres 100

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP

EDYCJA: W odpowiedzi na komentarz.

Jeśli Twój kod jest skompilowany z opcją -fPIC, nadaje się do włączenia do biblioteki - biblioteka musi być w stanie przenieść się z preferowanej lokalizacji w pamięci na inny adres, może istnieć inna już załadowana biblioteka pod adresem preferowanym przez bibliotekę.

Erik
źródło
36
Ten przykład jest jasny, ale jako użytkownik, jaka będzie różnica, jeśli utworzę udostępniony plik labrary (.so) bez opcji? Czy istnieją przypadki, w których bez opcji -fPIC moja biblioteka lib będzie nieważna?
Narek,
16
Tak, zbudowanie biblioteki współdzielonej, która nie jest PIC, może być błędem.
John Zwinck,
92
Mówiąc ściślej, biblioteka współdzielona powinna być współdzielona między procesami, ale nie zawsze może być możliwe załadowanie biblioteki pod tym samym adresem w obu. Gdyby kod nie był niezależny od pozycji, każdy proces wymagałby własnej kopii.
Simon Richter,
19
@Narek: błąd występuje, jeśli jeden proces chce załadować więcej niż jedną bibliotekę współdzieloną pod tym samym adresem wirtualnym. Ponieważ biblioteki nie są w stanie przewidzieć, jakie inne biblioteki mogą zostać załadowane, ten problem jest nieunikniony w przypadku tradycyjnej koncepcji bibliotek współdzielonych. Wirtualna przestrzeń adresowa tutaj nie pomaga.
Philipp
6
Możesz pominąć -fPICkompilację programu lub biblioteki statycznej, ponieważ w procesie będzie istniał tylko jeden program główny, więc przeniesienie środowiska wykonawczego nie będzie nigdy konieczne. W niektórych systemach programy są nadal uniezależniane od pozycji w celu zwiększenia bezpieczeństwa.
Simon Richter
61

Spróbuję wyjaśnić to, co już zostało powiedziane, w prostszy sposób.

Za każdym razem, gdy ładowana jest biblioteka współdzielona, ​​moduł ładujący (kod w systemie operacyjnym, który ładuje dowolny uruchamiany program) zmienia niektóre adresy w kodzie w zależności od miejsca załadowania obiektu.

W powyższym przykładzie „111” w kodzie innym niż PIC jest zapisywany przez moduł ładujący przy pierwszym załadowaniu.

W przypadku obiektów niewspółużytkowanych może być tak, ponieważ kompilator może dokonać pewnych optymalizacji tego kodu.

W przypadku współdzielonego obiektu, jeśli inny proces będzie chciał „połączyć” z tym kodem, musi go odczytać pod tymi samymi wirtualnymi adresami, w przeciwnym razie „111” nie będzie miało sensu. ale ta przestrzeń wirtualna może być już używana w drugim procesie.

Roee Gavirel
źródło
Whenever a shared lib is loaded, the loader changes some addresses in the code depending on where the object was loaded to.Myślę, że nie jest to poprawne, jeśli skompilowane z opcją -fpic i powodem, dla którego -fpic istnieje, tj. Ze względu na wydajność lub dlatego, że masz moduł ładujący, który nie jest w stanie się przenieść lub ponieważ potrzebujesz wielu kopii w różnych lokalizacjach lub z wielu innych powodów.
robsn
Dlaczego nie zawsze używać -fpic?
Jay
1
@Jay - ponieważ będzie wymagał jeszcze jednego obliczenia (adresu funkcji) dla każdego wywołania funkcji. Pod względem wydajności, jeśli nie jest to potrzebne, lepiej go nie używać.
Roee Gavirel
45

Kod wbudowany we współdzielone biblioteki powinien zwykle być kodem niezależnym od pozycji, aby udostępniona biblioteka mogła być łatwo ładowana pod (mniej więcej) dowolnym adresem w pamięci. Ta -fPICopcja zapewnia, że ​​GCC wygeneruje taki kod.

Jonathan Leffler
źródło
Dlaczego biblioteka współdzielona nie byłaby ładowana pod dowolnym adresem w pamięci bez włączenia -fPICflagi? czy nie jest to powiązane z programem? gdy program jest uruchomiony, system operacyjny przesyła go do pamięci. Czy coś brakuje?
Tony Tannous,
1
Czy -fPICużywana jest flaga, aby zapewnić załadowanie tej biblioteki na dowolny adres wirtualny w procesie, który ją łączy? przepraszam za podwójne komentarze, które upłynęły 5 minut, nie można edytować poprzedniego.
Tony Tannous,
1
Rozróżnij między budowaniem biblioteki współdzielonej (tworzeniem libwotnot.so) a łączeniem się z nią ( -lwotnot). Podczas łączenia nie musisz się martwić -fPIC. Kiedyś -fPICbudowano bibliotekę współdzieloną, trzeba było upewnić się, że wszystkie pliki obiektowe zostaną wbudowane w bibliotekę współdzieloną. Reguły mogły ulec zmianie, ponieważ obecnie kompilatory są kompilowane z kodem PIC. Tak więc, co było krytyczne 20 lat temu i mogło być ważne 7 lat temu, w dzisiejszych czasach jest mniej ważne. Adresy poza jądrem systemu operacyjnego są „zawsze” adresami wirtualnymi.
Jonathan Leffler,
Więc wcześniej musiałeś dodać -fPIC. Bez przekazania tej flagi wygenerowany kod podczas budowania .so musi zostać załadowany na określone adresy wirtualne, które mogą być w użyciu?
Tony Tannous,
1
Tak, ponieważ jeśli nie użyłeś flagi PIC, kod nie był niezawodnie relokowalny. Rzeczy takie jak ASLR (randomizacja układu przestrzeni adresowej) nie są możliwe, jeśli kod nie jest PIC (lub przynajmniej są tak trudne do osiągnięcia, że ​​są faktycznie niemożliwe).
Jonathan Leffler,
21

Dodawanie kolejnych ...

Każdy proces ma tę samą wirtualną przestrzeń adresową (jeśli losowanie adresu wirtualnego zostanie zatrzymane za pomocą flagi w systemie Linux) (Aby uzyskać więcej informacji Wyłącz i ponownie włącz randomizację układu przestrzeni adresowej tylko dla mnie )

Jeśli więc jest to jeden plik exe bez współdzielonego linku (hipotetyczny scenariusz), to zawsze możemy nadać ten sam adres wirtualny tej samej instrukcji asm bez żadnej szkody.

Ale kiedy chcemy połączyć obiekt współdzielony z exe, nie jesteśmy pewni, jaki adres początkowy jest przypisany do obiektu współdzielonego, ponieważ będzie to zależeć od kolejności, w jakiej obiekty wspólne zostały połączone. Mówiąc to, instrukcja asm wewnątrz .so zawsze będzie miała inny adres wirtualny w zależności od procesu, z którym jest łączony

Tak więc jeden proces może nadać adres początkowy .so jako 0x45678910 we własnej przestrzeni wirtualnej, a inny proces w tym samym czasie może nadać adres początkowy 0x12131415, a jeśli nie używają adresowania względnego, .so w ogóle nie będzie działać.

Dlatego zawsze muszą używać trybu adresowania względnego, a więc i opcji fpic.

Rytuał
źródło
1
Dzięki za wyjaśnienie wirtualnego adresu.
Hot.PxL
2
Czy ktoś może wyjaśnić, że nie jest to problem z biblioteką statyczną, dlaczego nie trzeba używać -fPIC w bibliotece statycznej? Rozumiem, że łączenie odbywa się w czasie kompilacji (lub zaraz po nim), ale jeśli masz 2 biblioteki statyczne z kodem zależnym od pozycji, w jaki sposób zostaną one połączone?
Michael P,
3
Plik obiektowy @MichaelP ma tabelę etykiet zależnych od pozycji, a po połączeniu konkretnego pliku obj wszystkie etykiety są odpowiednio aktualizowane. Nie można tego zrobić w bibliotece współdzielonej.
Slava
16

Łącze do funkcji w bibliotece dynamicznej jest rozwiązywane po załadowaniu biblioteki lub w czasie wykonywania. Dlatego zarówno plik wykonywalny, jak i biblioteka dynamiczna są ładowane do pamięci podczas uruchamiania programu. Adres pamięci, pod którą ładowana jest biblioteka dynamiczna, nie może być ustalony z góry, ponieważ stały adres może kolidować z inną biblioteką dynamiczną wymagającą tego samego adresu.


Istnieją dwie powszechnie stosowane metody radzenia sobie z tym problemem:

1. Relokacja Wszystkie wskaźniki i adresy w kodzie są w razie potrzeby modyfikowane, aby pasowały do ​​rzeczywistego adresu obciążenia. Relokacji dokonuje linker i moduł ładujący.

2. Kod niezależny od pozycji. Wszystkie adresy w kodzie odnoszą się do bieżącej pozycji. Obiekty współdzielone w systemach uniksowych domyślnie używają kodu niezależnego od pozycji. Jest to mniej wydajne niż przeniesienie, jeśli program działa przez długi czas, szczególnie w trybie 32-bitowym.


Nazwa „ kod niezależny od pozycji ” oznacza w rzeczywistości:

  • Sekcja kodu nie zawiera adresów bezwzględnych wymagających przeniesienia, a jedynie adresy względne. Dlatego sekcja kodu może być ładowana pod dowolnym adresem pamięci i współdzielona przez wiele procesów.

  • Sekcja danych nie jest współużytkowana przez wiele procesów, ponieważ często zawiera dane do zapisu. Dlatego sekcja danych może zawierać wskaźniki lub adresy wymagające przeniesienia.

  • Wszystkie funkcje publiczne i dane publiczne można zastąpić w systemie Linux. Jeśli funkcja w głównym pliku wykonywalnym ma taką samą nazwę jak funkcja w obiekcie współużytkowanym, wówczas wersja w main będzie miała pierwszeństwo nie tylko po wywołaniu z main, ale także po wywołaniu z obiektu współdzielonego. Podobnie, gdy zmienna globalna w main ma taką samą nazwę jak zmienna globalna w obiekcie współużytkowanym, wówczas instancja w main zostanie użyta, nawet jeśli można uzyskać do niej dostęp z obiektu współużytkowanego.


To tak zwane wstawianie symboli ma naśladować zachowanie bibliotek statycznych.

Wspólny obiekt ma tabelę wskaźników do swoich funkcji, zwaną tabelą łączenia procedur (PLT) i tabelę wskaźników do swoich zmiennych zwanych globalną tabelą przesunięć (GOT) w celu wdrożenia tej funkcji „zastępowania”. Wszystkie dostępy do funkcji i zmiennych publicznych przechodzą przez te tabele.

ps Tam, gdzie nie można uniknąć dynamicznego łączenia, istnieją różne sposoby na uniknięcie czasochłonnych funkcji kodu niezależnego od pozycji.

Możesz przeczytać więcej z tego artykułu: http://www.agner.org/optimize/optimizing_cpp.pdf

bruziuz
źródło
9

Drobny dodatek do już opublikowanych odpowiedzi: pliki obiektów, które nie zostały skompilowane w celu uzyskania niezależności od położenia, można przenieść; zawierają wpisy tabeli relokacji.

Te wpisy pozwalają modułowi ładującemu (temu bitowi kodu, który ładuje program do pamięci) przepisać adresy bezwzględne w celu dostosowania do rzeczywistego adresu ładowania w wirtualnej przestrzeni adresowej.

System operacyjny spróbuje udostępnić jedną kopię „biblioteki obiektów wspólnych” załadowanej do pamięci wszystkim programom połączonym z tą samą biblioteką obiektów wspólnych.

Ponieważ przestrzeń adresowa kodu (w przeciwieństwie do sekcji przestrzeni danych) nie musi być ciągła, a ponieważ większość programów, które łączą się z określoną biblioteką, mają dość ustalone drzewo zależności bibliotek, udaje się to przez większość czasu. W tych rzadkich przypadkach, w których występuje rozbieżność, tak, może być konieczne posiadanie w pamięci dwóch lub więcej kopii biblioteki obiektów współdzielonych.

Oczywiście każda próba losowego losowania adresu ładowania biblioteki między programami i / lub instancjami programu (w celu zmniejszenia możliwości tworzenia nadającego się do wykorzystania wzorca) sprawi, że takie przypadki będą powszechne, nierzadkie, więc tam, gdzie system włączył tę możliwość, należy podejmować każdą próbę skompilowania wszystkich bibliotek obiektów współdzielonych, aby były niezależne od pozycji.

Ponieważ wywołania tych bibliotek z treści głównego programu również zostaną przeniesione, to znacznie mniej prawdopodobne jest, że biblioteka współdzielona będzie musiała zostać skopiowana.

użytkownik1016759
źródło