Czytałem o opcjach generowania kodu przez GCC , ale nie mogłem zrozumieć, co robi „Generowanie kodu niezależnego od pozycji” (PIC). Podaj przykład, który wyjaśni mi, co to znaczy.
Czytałem o opcjach generowania kodu przez GCC , ale nie mogłem zrozumieć, co robi „Generowanie kodu niezależnego od pozycji” (PIC). Podaj przykład, który wyjaśni mi, co to znaczy.
Odpowiedzi:
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ę.
-fPIC
kompilację 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.
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.
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.
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 -fPIC
opcja zapewnia, że GCC wygeneruje taki kod.
-fPIC
flagi? czy nie jest to powiązane z programem? gdy program jest uruchomiony, system operacyjny przesyła go do pamięci. Czy coś brakuje?
-fPIC
uż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.
libwotnot.so
) a łączeniem się z nią ( -lwotnot
). Podczas łączenia nie musisz się martwić -fPIC
. Kiedyś -fPIC
budowano 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.
-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?
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.
Łą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
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.