Łatwe sprawdzenie nierozwiązanych symboli w bibliotekach współdzielonych?

83

Piszę dość dużą bibliotekę współdzielonych obiektów C ++ i napotkałem mały problem, który sprawia, że ​​debugowanie jest uciążliwe:

Jeśli zdefiniuję funkcję / metodę w pliku nagłówkowym i zapomnę utworzyć dla niej kod pośredniczący (podczas programowania), ponieważ tworzę jako współdzieloną bibliotekę obiektów, a nie plik wykonywalny, nie pojawiają się żadne błędy w czasie kompilacji, informujące mnie, że mam zapomniał zaimplementować tę funkcję. Jedyny sposób, w jaki dowiaduję się, że coś jest nie tak, to w czasie wykonywania, kiedy w końcu aplikacja łącząca się z tą biblioteką wypada z błędem „niezdefiniowany symbol”.

Szukam łatwego sposobu, aby sprawdzić, czy mam wszystkie potrzebne symbole w czasie kompilacji, być może coś, co mogę dodać do mojego pliku Makefile.

Jednym z rozwiązań, które wymyśliłem, jest uruchomienie skompilowanej biblioteki w nm -C -Ucelu uzyskania odszyfrowanej listy wszystkich niezdefiniowanych odniesień. Problem polega na tym, że pojawia się również lista wszystkich odniesień, które znajdują się w innych bibliotekach, takich jak GLibC, do których oczywiście zostanie dołączony link wraz z tą biblioteką podczas składania ostatecznej aplikacji. Byłoby możliwe użycie wyjścia nmdo grepprzez wszystkie moje pliki nagłówkowe i sprawdzenie, czy którakolwiek z nazw odpowiada… ale to wydaje się szalone. Z pewnością nie jest to rzadki problem i istnieje lepszy sposób jego rozwiązania?

David Claridge
źródło
3
nm -C -uuratował mnie wiele razy! (zwróć uwagę na małe litery -uw moim systemie). Pozostawię ten komentarz tutaj, abym mógł go znaleźć następnym razem, gdy będę go potrzebował.
dpritch

Odpowiedzi:

94

Sprawdź opcję konsolidatora -z defs/ --no-undefined. Podczas tworzenia obiektu współużytkowanego spowoduje to, że łącze nie powiedzie się, jeśli istnieją nierozwiązane symbole.

Jeśli używasz gcc do wywołania konsolidatora, użyjesz -Wlopcji kompilatora , aby przekazać opcję do konsolidatora:

gcc -shared ... -Wl,-z,defs

Jako przykład rozważ następujący plik:

#include <stdio.h>

void forgot_to_define(FILE *fp);

void doit(const char *filename)
{
    FILE *fp = fopen(filename, "r");
    if (fp != NULL)
    {
        forgot_to_define(fp);
        fclose(fp);
    }
}

Teraz, jeśli wbudujesz to we współdzielony obiekt, to się powiedzie:

> gcc -shared -fPIC -o libsilly.so silly.c && echo succeeded || echo failed
succeeded

Ale jeśli dodasz -z defs, łącze zawiedzie i poinformuje Cię o brakującym symbolu:

> gcc -shared -fPIC -o libsilly.so silly.c -Wl,-z,defs && echo succeeded || echo failed
/tmp/cccIwwbn.o: In function `doit':
silly.c:(.text+0x2c): undefined reference to `forgot_to_define'
collect2: ld returned 1 exit status
failed
R Samuel Klatchko
źródło
3
+1 ta odpowiedź może pomóc facetom pochodzącym z tła systemu Windows, w którym zewnętrzne symbole DLL muszą zostać rozwiązane w czasie kompilacji. Dobra droga do ładnego fragmentu kodu!
Shmil The Cat
@ShmilTheCat: Cześć, próbowałem umieścić -Wl, -z, defs w moim pliku make, nadal otrzymuję niezdefiniowany błąd symbolu podczas pracy, ale nie podczas kompilacji. Co mogę zrobić?. W moim scenariuszu mam dwa foldery jeden w drugim (powiedzmy, A / B, i, e B jest wewnątrz A) i widzę kilka symboli lub B w „libA.so”. Nie jestem pewien, czy każdy symbol w B jest dostępny w „libA.so”. Jak się tego upewnić?
Vinay,
@ShmilTheCat Aby uczynić rzeczy jeszcze bardziej podobnymi do biblioteki DLL systemu Windows, możesz dodać -Bsymbolicdo wiersza poleceń konsolidatora. Nawet jeśli forgot_to_defineteraz istnieje w bibliotece dzięki-z sprawdzeniu, plik wykonywalny może nadal przesłonić go swoją własną definicją, a własne definicje biblioteki przejdą do tego nadpisania; -Bsymbolicwymusza to, aby własne definicje biblioteki dotyczące jej funkcji trafiały do ​​jej funkcji.
Kaz
Działa tylko dla faktycznie używanych funkcji. Jeśli to zrobię, // forgot_to_define(fp);to zrobię nie zgłasza błędu
agentmith
19

W systemie Linux (którego prawdopodobnie używasz) ldd -r a.outpowinien dać ci dokładnie odpowiedź, której szukasz.

UPDATE: trywialny sposób tworzenia, a.outna podstawie którego można sprawdzić:

 echo "int main() { return 0; }" | g++ -xc++ - ./libMySharedLib.so
 ldd -r ./a.out
Zatrudniony Rosjanin
źródło
4
Robi to prawie dokładnie to samo, co nm -C -U, co zasugerowałem w oryginalnym poście. Problem polega na tym, że nie ma aplikacji „a.out”, o której można by mówić, jest to biblioteka współdzielona, ​​więc ldd -r mylibrary.so daje całą masę wyników, ponieważ używa symboli z różnych innych bibliotek dynamicznych. Jestem szczególnie zainteresowany w brakujących symbolach, które są zdefiniowane w moich plikach nagłówkowych, a nie w bibliotekach zewnętrznych.
David Claridge
2
ten należy zaakceptować, pytanie brzmi, jaki jest łatwy sposób sprawdzenia niezdefiniowanych symboli, a nie jak uniknąć niezdefiniowanych symboli
http8086
9

A co z zestawem testowym? Tworzysz pozorowane pliki wykonywalne, które odsyłają do potrzebnych symboli. Jeśli łączenie się nie powiedzie, oznacza to, że interfejs biblioteki jest niekompletny.

Stefano Borini
źródło
4

Miałem kiedyś ten sam problem. Pracowałem nad modelem komponentów w C ++ i oczywiście komponenty powinny ładować się dynamicznie w czasie wykonywania. Przychodzą mi na myśl trzy rozwiązania, które zastosowałem:

  1. Poświęć trochę czasu na zdefiniowanie systemu kompilacji, który będzie w stanie kompilować statycznie. Stracisz trochę czasu na inżynierię, ale zaoszczędzi ci to dużo czasu na łapanie tych irytujących błędów w czasie wykonywania.
  2. Pogrupuj swoje funkcje w dobrze znane i dobrze zrozumiałe sekcje, aby móc grupować funkcje / kody pośredniczące, aby mieć pewność, że każda odpowiadająca im funkcja ma swój kod pośredniczący. Jeśli poświęcisz trochę czasu na dobre udokumentowanie tego, być może możesz napisać skrypt, który sprawdzi definicje (na przykład poprzez jego komentarze doxygen) i sprawdzić odpowiedni plik .cpp.
  3. Wykonaj kilka testowych plików wykonywalnych, które ładują ten sam zestaw bibliotek i określ flagę RTLD_NOW na dlopen (jeśli jesteś pod * NIX). Będą sygnalizować brakujące symbole.

Mam nadzieję, że to pomoże.

Diego Sevilla
źródło
na liniach RTLD_NOW, czy istnieje zmienna env wymuszająca natychmiastowe (nie leniwe) wiązanie?
Stefano Borini
1
tak. tutaj jest to LD_BIND_NOW (libc5; glibc od 2.1.1) Jeśli ustawione na niepusty łańcuch, powoduje, że dynamiczny linker rozwiązuje wszystkie symbole podczas uruchamiania programu, zamiast odkładać wywołanie funkcji do punktu, w którym są one po raz pierwszy przywoływane. Jest to przydatne podczas korzystania z debugera.
Stefano Borini
Może to być również przydatne dla celów OP: LD_WARN (tylko ELF) (glibc od 2.1.3) Jeśli ustawione na niepusty łańcuch, ostrzega o nierozwiązanych symbolach.
Stefano Borini
Stefano: Tak, zgadza się. Te zmienne istnieją. Jeśli jednak odkładasz ładowanie dynamiczne na później (powiedzmy, kiedy użytkownik ładuje moduł), te zmienne nie są przydatne, chyba że faktycznie napiszesz program testujący, który ładuje zamierzone (przyszłe) biblioteki.
Diego Sevilla