Statyczne łączenie libstdc ++: jakieś problemy?

90

Muszę wdrożyć aplikację C ++ zbudowaną na Ubuntu 12.10 z libstdc ++ GCC 4.7 na systemy z Ubuntu 10.04, które są dostarczane ze znacznie starszą wersją libstdc ++.

Obecnie kompiluję z -static-libstdc++ -static-libgcc, zgodnie z sugestią tego wpisu na blogu: Statyczne łączenie libstdc ++ . Autor ostrzega przed użyciem jakiegokolwiek dynamicznie ładowanego kodu C ++ podczas statycznej kompilacji libstdc ++, czego jeszcze nie sprawdziłem. Mimo wszystko wydaje się, że jak dotąd wszystko idzie gładko: mogę korzystać z funkcji C ++ 11 w Ubuntu 10.04, o co mi chodziło.

Zauważam, że ten artykuł pochodzi z 2005 roku i być może od tamtego czasu wiele się zmieniło. Czy jej rady są nadal aktualne? Czy są jakieś czające się problemy, o których powinienem wiedzieć?

Nick Hutchinson
źródło
Nie, linkowanie statyczne do libstdc ++ nie oznacza tego. Gdyby to sugerowało, że nie byłoby sensu tej -static-libstdc++opcji, po prostu -static
użyłbyś
@JonathanWakely -static spowoduje kernel too oldbłąd w niektórych systemach Ubuntu 1404. Plik glibc.so jest jak kernel32.dllw oknie, jest częścią interfejsu systemu operacyjnego, nie powinniśmy go osadzać w naszym pliku binarnym. Możesz użyć, objdump -T [binary path]aby zobaczyć, libstdc++.soczy jest ładowany dynamicznie, czy nie. Dla programisty golang możesz dodać #cgo linux LDFLAGS: -static-libstdc++ -static-libgccprzed importem "C"
brązowego człowieka
@bronzeman, ale mówimy o -static-libstdc++nie -statictak libc.sonie będzie statycznie powiązane.
Jonathan Wakely
1
@NickHutchinson, post z linkiem do bloga zniknął. To pytanie SO jest popularnym hitem wyszukiwania odpowiednich terminów tutaj. Czy możesz odtworzyć krytyczne informacje z tego posta na blogu w swoim pytaniu lub zaoferować nowy link, jeśli wiesz, dokąd został przeniesiony?
Brian Cain
1
@BrianCain Archiwum internetowe ma to: web.archive.org/web/20160313071116/http://www.trilithium.com/…
Rob

Odpowiedzi:

134

Ten post na blogu jest dość niedokładny.

O ile wiem, zmiany ABI w C ++ były wprowadzane z każdym głównym wydaniem GCC (tj. Z różnymi składnikami o numerze pierwszej lub drugiej wersji).

Nie prawda. Jedyne zmiany C ++ ABI wprowadzone od czasu GCC 3.4 były wstecznie kompatybilne, co oznacza, że ​​C ++ ABI był stabilny przez prawie dziewięć lat.

Co gorsza, większość głównych dystrybucji Linuksa używa migawek GCC i / lub łatek do swoich wersji GCC, przez co praktycznie niemożliwe jest dokładne ustalenie, z jakimi wersjami GCC możesz mieć do czynienia podczas dystrybucji plików binarnych.

Różnice między poprawionymi wersjami GCC dystrybucji są niewielkie i nie zmieniają ABI, np. Fedora 4.6.3 20120306 (Red Hat 4.6.3-2) jest kompatybilna z ABI z oryginalnymi wydaniami FSF 4.6.x i prawie na pewno z każdym 4.6. x z dowolnej innej dystrybucji.

W bibliotekach uruchomieniowych GNU / Linux GCC używają wersji symboli ELF, więc łatwo jest sprawdzić wersje symboli wymagane przez obiekty i biblioteki, a jeśli masz plik, libstdc++.soktóry zapewnia te symbole, zadziała, nie ma znaczenia, czy jest to nieco inna poprawiona wersja z innej wersji twojej dystrybucji.

ale żaden kod C ++ (ani żaden kod korzystający z obsługi środowiska uruchomieniowego C ++) nie może być łączony dynamicznie, jeśli ma to działać.

To też nie jest prawdą.

To powiedziawszy, statyczne linkowanie do libstdc++.ajest jedną z opcji dla Ciebie.

Powodem, dla którego może to nie działać, jeśli dynamicznie ładujesz bibliotekę (używając dlopen), jest to, że symbole libstdc ++, od których ona zależy, mogły nie być potrzebne twojej aplikacji, gdy ją (statycznie) łączysz, więc te symbole nie będą obecne w twoim pliku wykonywalnym. Można to rozwiązać, dynamicznie łącząc bibliotekę współdzieloną z libstdc++.so(co i tak jest właściwe, jeśli to od niej zależy). Interpozycja symboli ELF oznacza, że ​​symbole obecne w pliku wykonywalnym będą używane przez bibliotekę współdzieloną, ale inne nie. obecny w twoim pliku wykonywalnym zostanie znaleziony w miejscu libstdc++.so, do którego prowadzi. Jeśli Twoja aplikacja nie używa dlopen, nie musisz się tym przejmować.

Inną opcją (i tą, którą preferuję) jest wdrożenie nowszej libstdc++.sowraz z aplikacją i upewnienie się, że zostanie ona znaleziona przed domyślnym systemem libstdc++.so, co można zrobić, zmuszając dynamiczny linker do szukania we właściwym miejscu, używając $LD_LIBRARY_PATHzmiennej środowiskowej w run- time lub ustawiając RPATHplik wykonywalny w czasie łącza. Wolę używać, RPATHponieważ nie zależy to od prawidłowego ustawienia środowiska, aby aplikacja działała. Jeśli powiązanie aplikacji z '-Wl,-rpath,$ORIGIN'(zwróć uwagę na apostrofów aby powłoka próbuje rozwinąć $ORIGIN), a następnie plik wykonywalny będzie mieć RPATHz$ORIGIN których mówi linker dynamiczny szukać bibliotek dzielonych w tym samym katalogu co plik wykonywalny sama. Jeśli umieścisz nowszylibstdc++.sow tym samym katalogu, w którym znajduje się plik wykonywalny, zostanie znaleziony w czasie wykonywania, problem rozwiązany. (Inną opcją jest umieszczenie pliku wykonywalnego w /some/path/bin/i nowszej libstdc ++. Tak /some/path/lib/i połączyć się z '-Wl,-rpath,$ORIGIN/../lib'lub inną stałą lokalizacją względem pliku wykonywalnego i ustawić wartość RPATH względem $ORIGIN)

Jonathan Wakely
źródło
8
To wyjaśnienie, szczególnie dotyczące RPATH, jest wspaniałe.
nilweed
3
Wysyłanie biblioteki libstdc ++ do aplikacji w systemie Linux to zła rada. Google dla „steam libstdc ++”, aby zobaczyć cały dramat, który to przynosi. Krótko mówiąc, jeśli twój exe ładuje zewnętrzne biblioteki (takie jak opengl), które chcą ponownie dlopen libstdc ++ (na przykład sterowniki radeon), te biblioteki będą używać twojego libstdc ++, ponieważ jest już załadowane, zamiast ich własnych, co jest tym, czego potrzebują i oczekiwać. Więc wracasz do punktu wyjścia.
7
@cap, OP w szczególności pyta o wdrożenie w dystrybucji, w której system libstdc ++ jest starszy. Problem Steam polega na tym, że dołączali libstdc ++., Więc był starszy niż systemowy (prawdopodobnie był nowszy w momencie, gdy go dołączali, ale dystrybucje przeszły na jeszcze nowsze). Można to rozwiązać, ustawiając RPATH na katalogu zawierającym libstdc++.so.6dowiązanie symboliczne, które jest ustawione w czasie instalacji tak, aby wskazywało na dołączoną bibliotekę lub systemową, jeśli jest nowsza. Istnieją bardziej skomplikowane modele z połączeniami mieszanymi, takie jak używane przez Red Hat DTS, ale trudno je wykonać samodzielnie.
Jonathan Wakely
5
hej człowieku, przepraszam, jeśli nie chcę, aby mój model do wysyłania plików binarnych kompatybilnych wstecz zawierał „ufanie innym ludziom, że zachowają libstdc ++ ABI Compat” lub „warunkowe łączenie libstdc ++ w czasie wykonywania” ... jeśli to wzburzy tutaj niektóre pióra a tam, co mogę zrobić, nie mam na myśli braku szacunku. A jeśli pamiętasz dramat memcpy@GLIBC_2.14, nie możesz mnie winić za to, że mam problemy z zaufaniem :)
6
Musiałem użyć '-Wl, -rpath, $ ORIGIN' (zwróć uwagę na '-' przed rpath). Nie mogę edytować odpowiedzi, ponieważ zmiany muszą mieć co najmniej 6 znaków ....
user368507
11

Jeden dodatek do doskonałej odpowiedzi Jonathana Wakely'ego, dlaczego dlopen () jest problematyczny:

Ze względu na nową pulę obsługi wyjątków w GCC 5 (patrz PR 64535 i PR 65434 ), jeśli dlopen i dlclose bibliotekę, która jest statycznie połączona z libstdc ++, za każdym razem otrzymasz wyciek pamięci (obiektu puli). Więc jeśli jest jakakolwiek szansa, że ​​kiedykolwiek użyjesz dlopen, statyczne połączenie libstdc ++ wydaje się naprawdę złym pomysłem. Zauważ, że jest to prawdziwy wyciek w przeciwieństwie do łagodnego, o którym mowa w PR 65434 .

Emil Styrke
źródło
1
Funkcja __gnu_cxx::__freeres()wydaje się zapewniać przynajmniej pomoc w rozwiązaniu tego problemu, ponieważ zwalnia wewnętrzny bufor obiektu puli. Ale dla mnie jest raczej niejasne, jaką implikację ma wywołanie tej funkcji w odniesieniu do wyjątków przypadkowo rzuconych później.
phlipsy
3

Dodatek do odpowiedzi Jonathana Wakely'ego na temat RPATH:

RPATH będzie działać tylko wtedy, gdy dany RPATH jest RPATH uruchomionej aplikacji . Jeśli masz bibliotekę, która dynamicznie łączy się z dowolną biblioteką poprzez własną RPATH, RPATH biblioteki zostanie nadpisana przez RPATH aplikacji, która ją ładuje. Jest to problem, gdy nie możesz zagwarantować, że RPATH aplikacji jest taki sam jak w Twojej bibliotece, np. Jeśli spodziewasz się, że zależności znajdują się w określonym katalogu, ale ten katalog nie jest częścią RPATH aplikacji.

Na przykład, powiedzmy, że masz aplikację App.exe, która ma dynamicznie powiązaną zależność od libstdc ++. So.x dla GCC 4.9. App.exe ma tę zależność rozwiązaną przez RPATH, tj

App.exe (RPATH=.:./gcc4_9/libstdc++.so.x)

Teraz powiedzmy, że istnieje inna biblioteka Dependency.so, która ma dynamicznie połączoną zależność od libstdc ++. So.y dla GCC 5.5. Zależność tutaj jest rozwiązywana przez RPATH biblioteki, tj

Dependency.so (RPATH=.:./gcc5_5/libstdc++.so.y)

Gdy App.exe ładuje Dependency.so, nie dołącza ani nie dołącza przedrostka RPATH biblioteki . W ogóle go nie konsultuje. Jedyną braną pod uwagę RPATH będzie ta uruchomionej aplikacji lub w tym przykładzie App.exe. Oznacza to, że jeśli biblioteka opiera się na symbolach, które są w gcc5_5 / libstdc ++. So.y, ale nie w gcc4_9 / libstdc ++. So.x, wtedy biblioteka nie zostanie załadowana.

To tylko słowo ostrzeżenia, ponieważ sam napotkałem te problemy w przeszłości. RPATH jest bardzo przydatnym narzędziem, ale jego implementacja wciąż ma pewne problemy.

Jonathan McDevitt
źródło
więc RPATH dla bibliotek współdzielonych jest trochę bezcelowe! I miałem nadzieję, że poprawili nieco Linuksa pod tym względem w ciągu ostatnich dwóch dekad ...
Frank Puck
2

Konieczne może być również upewnienie się, że nie jesteś zależny od dynamicznej biblioteki glibc. Uruchom lddwynikowy plik wykonywalny i zanotuj wszelkie zależności dynamiczne (libc / libm / libpthread są podejrzanymi o używanie).

Dodatkowym ćwiczeniem byłoby zbudowanie zestawu zaangażowanych przykładów C ++ 11 przy użyciu tej metodologii i wypróbowanie powstałych plików binarnych w prawdziwym systemie 10.04. W większości przypadków, chyba że zrobisz coś dziwnego z ładowaniem dynamicznym, od razu wiesz, czy program działa, czy ulega awarii.

Alexander L. Belikoff
źródło
1
Jaki jest problem z zależnością od dynamicznej biblioteki glibc?
Nick Hutchinson
Wydaje mi się, że przynajmniej jakiś czas temu libstdc ++ implikowało zależność od glibc. Nie jestem pewien, jak wygląda dzisiaj sytuacja.
Alexander L. Belikoff
9
libstdc ++ zależy od glibc (np. iostreamy są implementowane w kategoriach printf), ale tak długo, jak glibc na Ubuntu 10.04 zapewnia wszystkie funkcje wymagane przez nowszą libstdc ++, nie ma problemu z zależnością od dynamicznego glibc, w rzeczywistości zdecydowanie zaleca się, aby nigdy nie łączyć statycznie do glibc
Jonathan Wakely
1

Chciałbym dodać do odpowiedzi Jonathana Wakely'ego, co następuje.

Grając -static-libstdc++na Linuksie, napotkałem problem dlclose(). Załóżmy, że mamy aplikację „A” połączoną statycznie libstdc++i ładuje się ona dynamicznie połączona z libstdc++wtyczką „P” w czasie wykonywania. W porządku. Ale kiedy „A” zwalnia „P”, pojawia się błąd segmentacji. Moje założenie jest takie, że po wyładowaniu libstdc++.so„A” nie może już używać symboli związanych z libstdc++. Zauważ, że jeśli „A” i „P” są połączone statycznie libstdc++lub „A” jest połączone dynamicznie, a „P” statycznie, problem nie występuje.

Podsumowanie: jeśli Twoja aplikacja ładuje / zwalnia wtyczki, z którymi może się łączyć dynamicznie libstdc++, aplikacja również musi być z nią dynamicznie połączona. To tylko moje spostrzeżenie i chciałbym otrzymać wasze komentarze.

Fedorov7890
źródło
1
Jest to prawdopodobnie podobne do mieszania implementacji libc (powiedzmy dynamicznego łączenia z wtyczką, która z kolei dynamicznie łączy glibc, podczas gdy sama aplikacja jest statycznie połączona z musl-libc). Rich Felker, autor musl-libc, twierdzi, że problemem w takim scenariuszu jest to, że zarządzanie pamięcią glibc (używanie sbrk) przyjmuje pewne założenie i prawie oczekuje, że będzie sam w ramach jednego procesu ... nie jestem pewien, czy jest to ograniczone do chociaż konkretna wersja glibc lub cokolwiek innego.
0xC0000022L
a ludzie nadal nie dostrzegają zalet interfejsu stosu systemu Windows, który jest w stanie obsłużyć wiele niezależnych kopii libc ++ / libc w jednym procesie. Tacy ludzie nie powinni projektować oprogramowania.
Frank Puck
@FrankPuck ma przyzwoite doświadczenie zarówno w systemie Windows, jak i Linuksie. Mogę powiedzieć, że sposób, w jaki robi to „Windows”, nie pomoże ci, gdy MSVC jest stroną, która decyduje, który alokator zostanie użyty i jak. Główną zaletą, jaką widzę w przypadku stosów w systemie Windows, jest to, że możesz rozdawać fragmenty, a następnie uwolnić je za jednym zamachem. Ale w przypadku MSVC nadal napotkasz problem opisany powyżej, np. Podczas przekazywania wskaźników przydzielonych przez inne środowisko wykonawcze VC (wydanie vs debugowanie lub połączenie statyczne vs dynamiczne). Więc „Windows” nie jest odporny. Należy zachować ostrożność w przypadku obu systemów.
0xC0000022L