Rozważ następujący scenariusz:
- Biblioteka współdzielona libA.so, bez zależności.
- Biblioteka współdzielona libB.so, z libA.so jako zależnością.
Chcę skompilować plik binarny, który łączy się z biblioteką libB. Czy powinienem połączyć plik binarny tylko z libB, czy też z libA?
Czy istnieje sposób na połączenie tylko z bezpośrednimi zależnościami, pozwalając na rozwiązanie nierozwiązanych symboli z zależności w czasie wykonywania?
Martwi mnie fakt, że implementacja biblioteki libB może się w przyszłości zmienić, wprowadzając inne zależności (na przykład libC, libD, libE). Czy będę miał z tym problemy?
Innymi słowy:
- Pliki libA: a.cpp ah
- Pliki libB: b.cpp bh
- główne pliki programu: main.cpp
Oczywiście b.cpp zawiera ah, a main.cpp zawiera bh
Polecenia kompilacji:
g++ -fPIC a.cpp -c
g++ -shared -o libA.so a.o
g++ -fPIC b.cpp -c -I.
g++ -shared -o libB.so b.o -L. -lA
Których z poniższych opcji powinienem użyć?
g++ main.cpp -o main -I. -L. -lB
lub
g++ main.cpp -o main -I. -L. -lB -lA
Nie mogłem skorzystać z pierwszej opcji. Linker skarży się na nierozwiązane symbole z biblioteki libA. Ale to brzmi dla mnie trochę dziwnie.
Dziękuję bardzo.
- Zaktualizowane komentarze:
Kiedy łączę plik binarny, konsolidator spróbuje rozwiązać wszystkie symbole z main i libB. Jednak libB ma niezdefiniowane symbole z libA. Dlatego linker narzeka na to.
Dlatego też muszę połączyć się z libA. Jednak znalazłem sposób na ignorowanie nierozwiązanych symboli z bibliotek współdzielonych. Wygląda na to, że powinienem użyć następującego wiersza poleceń, aby to zrobić:
g++ main.cpp -o main -I. -L. -lB -Wl,-unresolved-symbols=ignore-in-shared-libs
Wygląda na to, że nadal można skorzystać z tej -rpath
opcji. Jednak muszę to trochę lepiej zrozumieć.
Czy ktoś zna możliwe pułapki podczas korzystania z tej -Wl,-unresolved-symbols=ignore-in-shared-libs
opcji?
- Zaktualizowano komentarze 2:
-rpath
nie powinny być używane do tego celu. Przydatne jest wymuszenie znalezienia biblioteki w podanym katalogu. -unresolved-symbol
Podejście wygląda o wiele lepiej.
Dzięki jeszcze raz.
źródło
Odpowiedzi:
Wygląda na to, że już tam jesteś przez większość drogi. Dobra robota z twoim dochodzeniem. Zobaczmy, czy mogę pomóc wyjaśnić „dlaczego” za tym stoi.
Oto, co robi konsolidator. Kiedy łączysz swój plik wykonywalny („main” powyżej), zawiera on pewne symbole (funkcje i inne rzeczy), które są nierozwiązane. Spojrzy w dół listy bibliotek, które następują, próbując rozwiązać nierozwiązane symbole. Po drodze stwierdza, że niektóre symbole są dostarczane przez bibliotekę libB.so, więc zauważa, że są teraz rozwiązywane przez tę bibliotekę.
Jednak odkrywa również, że niektóre z tych symboli używają innych symboli, które nie zostały jeszcze rozwiązane w twoim pliku wykonywalnym, więc teraz musi rozwiązać również te symbole. Bez linków do libA.so, Twoja aplikacja byłaby niekompletna. Gdy łączy się z libA.so, wszystkie symbole są rozwiązane i łączenie jest zakończone.
Jak widzieliście, użycie
-unresolved-symbols-in-shared-libs
, nie rozwiązuje problemu. Po prostu odracza to, aby te symbole były rozwiązywane w czasie wykonywania. Do tego-rpath
służy: określenie bibliotek, które mają być przeszukiwane w czasie wykonywania. Jeśli tych symboli nie można rozwiązać, aplikacja nie uruchomi się.Nie jest łatwo ustalić zależności bibliotek, ponieważ symbol może być dostarczony przez więcej niż jedną bibliotekę i być usatysfakcjonowany, łącząc się z dowolną z nich.
Tutaj jest inny opis tego procesu: Dlaczego kolejność łączenia bibliotek czasami powoduje błędy w GCC?
źródło
Do dynamicznego linkowania tylko z bezpośrednimi zależnościami możesz użyć
-Wl,--as-needed
dodając biblioteki po-Wl,--as-needed
:Do sprawdzenia bezpośrednich zależności należy użyć readelf zamiast ldd, ponieważ ldd pokazuje również zależności pośrednie.
$ readelf -d main | grep library 0x0000000000000001 (NEEDED) Shared library: [libB.so] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
ldd pokazuje również zależności pośrednie:
$ LD_LIBRARY_PATH=. ldd ./main linux-vdso.so.1 (0x00007fff13717000) libB.so => ./libB.so (0x00007fb6738ed000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb6734ea000) libA.so => ./libA.so (0x00007fb6732e8000) /lib64/ld-linux-x86-64.so.2 (0x00007fb673af0000)
Jeśli używasz cmake , możesz dodać następujące wiersze, aby uwzględnić tylko bezpośrednie zależności:
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed ${CMAKE_EXE_LINKER_FLAGS}") set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--as-needed ${CMAKE_SHARED_LINKER_FLAGS}")
źródło
Inną opcją jest użycie
libtool
Jeśli zmienisz
g++
wywołanie na,libtool --mode=compile g++
aby skompilować kod źródłowy, a następnielibtool --mode=link g++
utworzysz aplikację zlibB
, tolibA
zostanie ona automatycznie połączona.źródło
To ciekawy post - też waliłem się tym w głowę, ale myślę, że przegapiłeś tutaj punkt ..
Pomysł jest następujący, prawda?
Rozważmy dalej, że ...
Teraz libB.so i libA.so zostały skompilowane tak, jak opisano powyżej. Po tym Twoja pierwsza opcja powinna działać, tj .:
Myślę, że twój problem wynika z tego
w main.cpp odnosisz się również do symA
Mam rację?
Jeśli używasz symbolu w swoim kodzie, to ten symbol musi znajdować się w pliku .so
Cała idea powiązania bibliotek współdzielonych (tj. Tworzenia interfejsów API) polega na tym, że symbole w głębszych warstwach są ukryte (pomyśl o obieraniu cebuli) i nie są używane. .. tj. nie odwołuj się do symA w twoim main.cpp, ale zamiast tego tylko do symB (i pozwól symB odnosić się tylko do symA).
źródło