Einfache Suche nach ungelösten Symbolen in gemeinsam genutzten Bibliotheken?

82

Ich schreibe eine ziemlich große C ++ - Bibliothek für gemeinsam genutzte Objekte und bin auf ein kleines Problem gestoßen, das das Debuggen zu einem Problem macht:

Wenn ich eine Funktion / Methode in einer Header-Datei definiere und vergesse, einen Stub dafür zu erstellen (während der Entwicklung), werden beim Kompilieren keine Fehler angezeigt, da ich als gemeinsam genutzte Objektbibliothek und nicht als ausführbare Datei baue vergessen, diese Funktion zu implementieren. Der einzige Weg, um herauszufinden, dass etwas nicht stimmt, ist zur Laufzeit, wenn eine Anwendung, die mit dieser Bibliothek verknüpft ist, schließlich mit einem Fehler "undefiniertes Symbol" umkippt.

Ich suche nach einer einfachen Möglichkeit, um zu überprüfen, ob ich alle Symbole habe, die ich zum Zeitpunkt der Kompilierung benötige, vielleicht etwas, das ich meinem Makefile hinzufügen kann.

Eine Lösung, die ich mir nm -C -Uausgedacht habe, besteht darin, die kompilierte Bibliothek zu durchlaufen , um eine entwirrte Liste aller undefinierten Referenzen zu erhalten. Das Problem ist, dass auch die Liste aller Referenzen in anderen Bibliotheken wie GLibC angezeigt wird, mit denen natürlich zusammen mit dieser Bibliothek verknüpft wird, wenn die endgültige Anwendung zusammengestellt wird. Es wäre möglich, die Ausgabe von nmto grepdurch alle meine Header-Dateien zu verwenden und zu prüfen, ob einer der Namen übereinstimmt. Dies scheint jedoch verrückt zu sein. Sicher ist dies kein ungewöhnliches Problem und es gibt einen besseren Weg, es zu lösen?

David Claridge
quelle
3
nm -C -uhat mich mehrfach gerettet! (Beachten Sie die Kleinbuchstaben -uauf meinem System.) Lassen Sie diesen Kommentar hier, damit ich ihn beim nächsten Mal finden kann, wenn ich ihn brauche.
dpritch

Antworten:

93

Überprüfen Sie die Linker-Option -z defs/ --no-undefined. Wenn Sie ein freigegebenes Objekt erstellen, schlägt die Verknüpfung fehl, wenn nicht aufgelöste Symbole vorhanden sind.

Wenn Sie den Linker mit gcc aufrufen, verwenden Sie die Compiler- -WlOption, um die Option an den Linker zu übergeben:

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

Betrachten Sie als Beispiel die folgende Datei:

#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);
    }
}

Wenn Sie dies in ein freigegebenes Objekt einbauen, ist dies erfolgreich:

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

Wenn Sie jedoch hinzufügen -z defs, schlägt der Link fehl und informiert Sie über Ihr fehlendes Symbol:

> 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
quelle
3
+1 Diese Antwort könnte Leuten helfen, die aus dem Windows-Hintergrund stammen und in denen externe DLL- Symbole während der Kompilierungszeit aufgelöst werden müssen. Gut gemacht für das nette Code-Snippet!
Shmil The Cat
@ShmilTheCat: Hallo, ich habe versucht, -Wl, -z, defs in meine make-Datei einzufügen. Trotzdem wird beim Ausführen ein undefinierter Symbolfehler angezeigt, aber nicht beim Kompilieren. Was kann ich tun?. In meinem Szenario habe ich zwei Ordner ineinander (z. B. A / B, i, e B befindet sich in A), und in "libA.so" werden nur wenige Symbole oder B angezeigt. Ich bin nicht sicher, ob jedes Symbol in B in "libA.so" verfügbar ist. Wie stelle ich das sicher?
Vinay
@ShmilTheCat Um die Dinge noch mehr wie eine Windows-DLL zu gestalten, können Sie -Bsymbolicsie der Linker-Befehlszeile hinzufügen . Selbst wenn forgot_to_definees dank der -zPrüfung jetzt in der Bibliothek vorhanden ist , kann die ausführbare Datei diese mit ihrer eigenen Definition überschreiben, und die eigenen Definitionen der Bibliothek werden zu dieser Überschreibung verschoben. -Bsymbolicerzwingt Dinge, so dass die eigenen Definitionen der Bibliothek zu ihren Funktionen zu ihren Funktionen gehen.
Kaz
Funktioniert nur für tatsächlich verwendete Funktionen. Wenn ich // forgot_to_define(fp);es tue , meldet es keinen Fehler
Agentenschmied
17

Unter Linux (das Sie anscheinend verwenden) ldd -r a.outsollten Sie genau die Antwort erhalten, nach der Sie suchen.

UPDATE: Eine einfache Methode zum Erstellen a.out, anhand derer überprüft werden kann:

 echo "int main() { return 0; }" | g++ -xc++ - ./libMySharedLib.so
 ldd -r ./a.out
Angestellt Russisch
quelle
4
Das macht fast genau das Gleiche wie nm-C-U, was ich im ursprünglichen Beitrag vorgeschlagen habe. Das Problem ist, dass es keine nennenswerte 'a.out'-Anwendung gibt, sondern eine gemeinsam genutzte Bibliothek. Daher gibt ldd -r mylibrary.so einen ganzen Haufen Ausgabe aus, da Symbole aus verschiedenen anderen dynamischen Bibliotheken verwendet werden. Ich bin besonders interessiert in fehlenden Symbolen, die in meinen Header-Dateien definiert sind , und nicht in externen Bibliotheken.
David Claridge
1
dieses akzeptiert werden sollte, ist die Frage , was der einfache Weg ist undefinierte Symbole zu überprüfen, nicht , wie undefinierte Symbole zu vermeiden
workplaylifecycle
8

Was ist mit einer Testsuite? Sie erstellen nachgebildete Scheindateien, die mit den benötigten Symbolen verknüpft sind. Wenn die Verknüpfung fehlschlägt, bedeutet dies, dass Ihre Bibliotheksschnittstelle unvollständig ist.

Stefano Borini
quelle
3

Ich hatte einmal das gleiche Problem. Ich habe ein Komponentenmodell in C ++ entwickelt, und natürlich sollten Komponenten zur Laufzeit dynamisch geladen werden. Mir fallen drei Lösungen ein, die ich angewendet habe:

  1. Nehmen Sie sich etwas Zeit, um ein Build-System zu definieren, das statisch kompiliert werden kann. Sie werden einige Zeit verlieren, um es zu entwickeln, aber es wird Ihnen viel Zeit sparen, diese lästigen Laufzeitfehler zu erkennen.
  2. Gruppieren Sie Ihre Funktionen in bekannten und verständlichen Abschnitten, damit Sie Funktionen / Stubs gruppieren können, um sicherzustellen, dass jede entsprechende Funktion ihren Stub hat. Wenn Sie sich die Zeit nehmen, es gut zu dokumentieren, können Sie möglicherweise ein Skript schreiben, das die Definitionen überprüft (z. B. über seine Sauerstoffkommentare) und die entsprechende CPP-Datei darauf überprüft.
  3. Führen Sie mehrere ausführbare Testdateien durch, die denselben Satz von Bibliotheken laden, und geben Sie das RTLD_NOW-Flag für dlopen an (wenn Sie sich unter * NIX befinden). Sie signalisieren die fehlenden Symbole.

Hoffentlich hilft das.

Diego Sevilla
quelle
Gibt es in den Zeilen von RTLD_NOW eine env var, um eine sofortige (nicht faule) Bindung zu erzwingen?
Stefano Borini
1
Jep. hier ist es LD_BIND_NOW (libc5; glibc seit 2.1.1) Wenn es auf eine nicht leere Zeichenfolge gesetzt ist, löst der dynamische Linker alle Symbole beim Programmstart auf, anstatt die Auflösung von Funktionsaufrufen bis zu dem Punkt zu verschieben, an dem sie zum ersten Mal referenziert werden. Dies ist nützlich, wenn Sie einen Debugger verwenden.
Stefano Borini
Dies kann auch für den Zweck des OP nützlich sein: LD_WARN (nur ELF) (glibc seit 2.1.3) Wenn auf eine nicht leere Zeichenfolge gesetzt, warnen Sie vor nicht aufgelösten Symbolen.
Stefano Borini
Stefano: Ja, das stimmt. Diese Variablen existieren. Wenn Sie jedoch das dynamische Laden auf einen späteren Zeitpunkt verschieben (z. B. wenn der Benutzer ein Modul lädt), sind diese Variablen nur dann nützlich, wenn Sie tatsächlich das Testprogramm schreiben, das die beabsichtigten (zukünftigen) Bibliotheken lädt.
Diego Sevilla