Dlaczego ld potrzebuje -rpath-link podczas łączenia pliku wykonywalnego z so, który potrzebuje innego so?

Jestem tylko ciekawa. Stworzyłem obiekt współdzielony:

gcc -o liba.so -fPIC -shared liba.c

I jeszcze jeden wspólny obiekt, który łączy się z poprzednim:

gcc -o libb.so -fPIC -shared libb.c liba.so

Teraz, podczas tworzenia pliku wykonywalnego, który łączy się z libb.so, będę musiał podać -rpath-link do ld, aby mógł znaleźć liba.so podczas odkrywania, że libb.so zależy od niego:

gcc -o test -Wl,-rpath-link,./ test.c libb.so

W przeciwnym razie ld będzie narzekać.

Dlaczego ld musi być w stanie zlokalizować liba.so podczas łączenia test? Bo dla mnie to nie wygląda na ld robi coś więcej niż potwierdzenie istnienia liba.so. Na przykład, uruchamianie readelf --dynamic ./test tylko List libb.so w razie potrzeby, więc myślę, że linker dynamiczny musi odkryć zależność libb.so -> liba.so od siebie i samodzielnie wyszukać liba.so.

Jestem na platformie GNU/Linux x86 - 64 i funkcja main()-w test wywołuje funkcję w libb.so, która z kolei wywołuje funkcję w liba.so.

Author: Troels Folke, 2014-07-06

4 answers

Dlaczego ld musi być w stanie zlokalizować liba.so podczas łączenia test? Bo dla mnie to nie wygląda na to, że ld robi coś innego niż potwierdzenie istnienia liba.so. Na przykład, uruchamianie readelf --dynamic ./test tylko list libb.so W razie potrzeby, więc myślę, że linker dynamiczny musi odkryć zależność libb.so -> liba.so od siebie i samodzielnie wyszukać liba.so.

Cóż, jeśli dobrze rozumiem proces łączenia, ld w rzeczywistości nie musi zlokalizować nawet libb.so. Może po prostu Ignoruj wszystkie nierozwiązane odwołania w test mając nadzieję, że dynamiczny linker rozwiąże je podczas ładowania libb.so w czasie wykonywania. Jeśli jednak ld postępowałyby w ten sposób, wiele błędów "undefined reference" nie zostałoby wykrytych w czasie łącza, zamiast tego zostałyby znalezione podczas próby załadowania test w trybie runtime. Tak więc ld robi tylko dodatkowe sprawdzenie, czy wszystkie symbole nie Znalezione w test mogą być rzeczywiście Znalezione w bibliotekach dzielonych, od których zależy test. Więc jeśli test program ma " undefined reference " błąd (jakaś zmienna lub funkcja nie znaleziona w samej test i ani w libb.so), staje się to oczywiste w czasie łącza, a nie tylko w czasie wykonywania. Tak więc takie zachowanie jest tylko dodatkowym sprawdzeniem zdrowego rozsądku.

Ale ld idzie jeszcze dalej. Gdy łączysz test, ld sprawdza również, czy wszystkie nierozwiązane odniesienia w libb.so znajdują się w bibliotekach dzielonych, od których zależy libb.so (w naszym przypadku libb.so zależy od liba.so, więc wymaga, aby liba.so znajdowały się w czasie połączenia). Cóż, właściwie ld już to sprawdził, kiedy łączył libb.so. Dlaczego robi to sprawdzanie po raz drugi... Być może Programiści ld uznali to podwójne sprawdzanie za przydatne do wykrywania uszkodzonych zależności, gdy próbujesz połączyć program z przestarzałą biblioteką, która mogła być załadowana w czasach, gdy była połączona, ale teraz nie można jej załadować, ponieważ biblioteki, od których zależy, są aktualizowane (na przykład liba.so została później przerobiona i część funkcji została usunięta od niego).

UPD

Zrobiłem tylko kilka eksperymentów. Wygląda na to, że moje założenie " w rzeczywistości ld już to sprawdzało, kiedy łączyło libb.so" jest źle.

Załóżmy, że liba.c ma następującą treść:

int liba_func(int i)
{
    return i + 1;
}

I libb.c ma następne:

int liba_func(int i);
int liba_nonexistent_func(int i);

int libb_func(int i)
{
    return liba_func(i + 1) + liba_nonexistent_func(i + 2);
}

I test.c

#include <stdio.h>

int libb_func(int i);

int main(int argc, char *argv[])
{
    fprintf(stdout, "%d\n", libb_func(argc));
    return 0;
}

Przy linkowaniu libb.so:

gcc -o libb.so -fPIC -shared libb.c liba.so

Linker nie generuje żadnych komunikatów o błędach, których nie można rozwiązać, zamiast tego po prostu cicho generuje uszkodzoną bibliotekę współdzieloną libb.so. Zachowanie jest takie samo, jak w przypadku statycznej biblioteki (libb.a) z ar , która nie rozwiązuje również symboli Wygenerowanej biblioteki.

Ale gdy spróbujesz połączyć test:

gcc -o test -Wl,-rpath-link=./ test.c libb.so

Pojawia się błąd:

libb.so: undefined reference to `liba_nonexistent_func'
collect2: ld returned 1 exit status

Wykrycie takiego błędu nie byłoby możliwe, gdyby ld Nie skanowało rekurencyjnie wszystkich bibliotek współdzielonych. Wydaje się więc, że odpowiedź na pytanie jest taka sama, jak powiedziałem powyżej: LD potrzebuje - rpath-link w celu upewnienia się, że połączony plik wykonywalny może być później załadowany dynamicznie. Tylko kontrola zdrowia psychicznego.

UPD2

Sensowne byłoby jak najwcześniejsze sprawdzenie nierozwiązanych odniesień( przy linkowaniu libb.so), ale ld z pewnych powodów tego nie robi. Prawdopodobnie służy do tworzenia cyklicznych zależności dla bibliotek współdzielonych.

liba.c może mieć następującą implementację:

int libb_func(int i);

int liba_func(int i)
{
    int (*func_ptr)(int) = libb_func;
    return i + (int)func_ptr;
}

Więc liba.so używa libb.so i libb.so używa liba.so (lepiej nigdy czegoś takiego nie robić). To z powodzeniem kompiluje i działa:

$ gcc -o liba.so -fPIC -shared liba.c
$ gcc -o libb.so -fPIC -shared libb.c liba.so
$ gcc -o test test.c -Wl,-rpath=./ libb.so
$ ./test
-1217026998

Choć readelf mówi, że liba.so nie potrzebuje libb.so:

$ readelf -d liba.so | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
$ readelf -d libb.so | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [liba.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]

Jeśli ld sprawdza nierozwiązane symbole podczas łączenia biblioteki współdzielonej, łączenie liba.so nie byłoby możliwe.

Zauważ, że użyłem klucza -rpath zamiast -rpath-link . Różnica polega na tym, że -rpath-link jest używany w czasie łączenia tylko w celu sprawdzenia, czy wszystkie symbole w finalnym pliku wykonywalnym mogą być rozwiązane, podczas gdy -rpath faktycznie osadza ścieżkę podaną jako parametr w ELF:

$ readelf -d test | grep RPATH
 0x0000000f (RPATH)                      Library rpath: [./]

Jest więc teraz możliwe uruchomienie test, jeśli biblioteki współdzielone (liba.so i libb.so) znajdują się w bieżącym katalogu roboczym (./). Jeśli użyjesz -rpath-link nie będzie takiego wpisu w test ELF i będziesz musiał dodać ścieżkę do bibliotek współdzielonych do pliku /etc/ld.so.conf lub do LD_LIBRARY_PATH zmienna środowiskowa.

UPD3

Istnieje możliwość sprawdzenia nierozwiązanych symboli podczas łączenia biblioteki współdzielonej, w tym celu należy użyć opcji --no-undefined:
$ gcc -Wl,--no-undefined -o libb.so -fPIC -shared libb.c liba.so
/tmp/cc1D6uiS.o: In function `libb_func':
libb.c:(.text+0x2d): undefined reference to `liba_nonexistent_func'
collect2: ld returned 1 exit status

Znalazłem również dobry artykuł, który wyjaśnia wiele aspektów łączenia bibliotek współdzielonych, które zależą od innych bibliotek współdzielonych: lepsze zrozumienie drugorzędnych zależności Linuksa za pomocą przykładów.

 20
Author: anton_rh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-24 12:37:22

You system, through ld.so.conf, ld.so.conf.d, i środowiska systemowego, LD_LIBRARY_PATH, itp.., dostarcza ścieżki przeszukiwania bibliotek dla całego systemu, które są uzupełniane przez zainstalowane biblioteki poprzez informacje pkg-config i tym podobne, gdy budujesz je w oparciu o standardowe biblioteki. Gdy Biblioteka znajduje się w zdefiniowanej ścieżce wyszukiwania, ścieżki wyszukiwania bibliotek standardowych są śledzone automatycznie, umożliwiając odnalezienie wszystkich wymaganych bibliotek.

Nie ma standardowego przeszukiwania biblioteki w czasie rzeczywistym path dla niestandardowych bibliotek współdzielonych tworzysz sam. Możesz określić ścieżkę wyszukiwania do swoich bibliotek poprzez oznaczenie -L/path/to/lib podczas kompilacji i linku. W przypadku bibliotek w niestandardowych lokalizacjach ścieżka wyszukiwania bibliotek może być opcjonalnie umieszczona w nagłówku pliku wykonywalnego (nagłówek ELF) podczas kompilacji, aby plik wykonywalny mógł znaleźć potrzebne biblioteki.

rpath umożliwia osadzenie niestandardowej ścieżki przeszukiwania biblioteki w nagłówku ELF, aby Twoja niestandardowa biblioteki można również znaleźć bez konieczności określania ścieżki wyszukiwania za każdym razem, gdy są używane. Dotyczy to również bibliotek, które zależą od bibliotek. Jak już zauważyłeś, ważna jest nie tylko kolejność, którą podajesz w wierszu poleceń, ale także musisz podać ścieżkę przeszukiwania biblioteki w czasie wykonywania (run-time library search path) lub ścieżkę rpath, informacje dla każdej zależnej biblioteki, z którą się łączysz, tak aby nagłówek zawierał lokalizację wszystkich bibliotek potrzebnych do uciekaj.

Addemdum from Comments

Moje pytanie brzmi przede wszystkim dlaczego ld musi " automatycznie próbować zlokalizować biblioteka współdzielona" (liba.so) i "załącz to w linku".

Tak po prostu działa ld. From man ld " opcja-rpath jest również używana podczas lokalizowania obiektów współdzielonych, które są potrzebne przez obiekty współdzielone jawnie zawarte w łączu ... Jeśli -rpath nie jest używane podczas łączenia pliku wykonywalnego ELF, zawartość zmienna środowiskowa "LD_RUN_PATH" zostanie użyta, jeśli zostanie zdefiniowana."W Twoim przypadku liba nie znajduje się w LD_RUN_PATH, więc ld będzie potrzebował sposobu lokalizowania liba podczas kompilacji Twojego pliku wykonywalnego, albo za pomocą rpath (opisanego powyżej), albo przez podanie jawnej ścieżki wyszukiwania do niego.

Po drugie, co tak naprawdę oznacza "załącz to w linku". Wydaje mi się że oznacza tylko: "potwierdź swoje istnienie" (liba.so ' s), ponieważ libb.so nagłówki ELF nie są modyfikowane (miały już Potrzebny tag przeciw liba.so), a nagłówki exec deklarują tylko libb.so jako Potrzebne. Dlaczego ld zależy na znalezieniu liba.so, can it not just leave zadanie do linkera?

Nie, wróćmy do semantyki ld. W celu wytworzenia "dobrego linku", ld musi być w stanie zlokalizować wszystkie zależne biblioteki. ld w przeciwnym razie nie można ubezpieczyć dobrego łącza. Linker runtime musi znaleźć i załadować , a nie tylko znaleźć biblioteki współdzielone potrzebne przez program . ld nie może zagwarantować, że tak się stanie, chyba że ld sama może zlokalizować wszystkie potrzebne biblioteki współdzielone w momencie połączenia progam.

 6
Author: David C. Rankin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-29 05:00:56

Myślę, że musisz wiedzieć, kiedy użyć opcji -rpath i opcji -rpath-link. Najpierw cytuję co man ld:

  1. różnica między-rpath i-rpath-link jest taka, że katalogi określone przez-rpath opcje są zawarte w pliku wykonywalnym i używane w czasie wykonywania, natomiast opcja-rpath-link jest skuteczna tylko w czasie połączenia. Szukanie -rpath w ten sposób jest wspierany tylko przez natywne linkery i cross linkery, które zostały skonfigurowane z -- z opcją-sysroot.

Musisz odróżnić link-time od runtime. Zgodnie z zaakceptowaną przez Ciebie odpowiedzią anton_rh, sprawdzanie niezdefiniowanych symboli nie jest włączone podczas kompilowania i łączenia bibliotek współdzielonych lub bibliotek statycznych, ale włączone podczas kompilowania i łączenia plików wykonywalnych. (Należy jednak pamiętać, że istnieją pewne pliki, które są bibliotekami współdzielonymi, a także plikami wykonywalnymi, na przykład ld.so. Wpisz man ld.so, Aby to zbadać, i nie wiem, czy sprawdzanie niezdefiniowanych symboli jest włączone przy kompilacji tych plików typu "dual").

Tak więc -rpath-link jest używany do sprawdzania czasu łącza, a -rpath jest używany do sprawdzania czasu łącza i czasu działania, ponieważ rpath jest wbudowany w nagłówki ELF. Należy jednak uważać, aby opcja -rpath-link nadpisała opcję -rpath w czasie łącza, jeśli obie z nich są podane.

Ale dlaczego -rpath-option i -rpath opcja? Myślę, że są one używane do wyeliminowania "overlinking". Zobacz to lepsze zrozumienie Linuksa rozwiązywanie drugorzędnych zależności z przykładami., po prostu użyj ctrl + F, aby przejść do treści związanych z"overlinking". Powinieneś skupić się na tym, dlaczego "overlinking" jest zły, a ze względu na metodę, którą przyjmujemy, aby uniknąć "overlinkowania", istnienie ld opcji -rpath-link i -rpath jest rozsądne: celowo pomijamy niektóre biblioteki w poleceniach kompilowania i łączenia, aby uniknąć "overlinkowania", a z powodu pominięcia, ld potrzebujemy -rpath-link lub -rpath, aby zlokalizować te pominięte biblioteki.

 1
Author: Han XIAO,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-04 11:31:12

Nie mówisz ld (przy linkowaniu libb przeciwko liba) gdzie liba jest-tylko tyle, że to zależność. Szybki ldd libb.so pokaże ci, że nie może znaleźć liba.

Ponieważ prawdopodobnie te biblioteki nie znajdują się w ścieżce wyszukiwania linkera, podczas łączenia pliku wykonywalnego pojawi się błąd linkera. Należy pamiętać, że gdy połączysz samą libę, funkcja w libb jest nadal nierozwiązana, ale domyślnym zachowaniem ld nie jest dbanie o nierozwiązane symbole w DSO dopóki nie połączysz finalnego pliku wykonywalnego.

 0
Author: Mark Nunberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-06 23:58:29