Statische Verknüpfung von libstdc ++: Fallstricke?

89

Ich muss eine C ++ - Anwendung, die auf Ubuntu 12.10 mit libstdc ++ von GCC 4.7 basiert, auf Systemen bereitstellen, auf denen Ubuntu 10.04 ausgeführt wird, das mit einer erheblich älteren Version von libstdc ++ geliefert wird.

Derzeit kompiliere ich mit -static-libstdc++ -static-libgcc, wie in diesem Blog-Beitrag vorgeschlagen: libstdc ++ statisch verknüpfen . Der Autor warnt davor, beim statischen Kompilieren von libstdc ++ dynamisch geladenen C ++ - Code zu verwenden, was ich noch nicht überprüft habe. Trotzdem scheint alles reibungslos zu laufen: Ich kann C ++ 11-Funktionen unter Ubuntu 10.04 nutzen, was ich auch wollte.

Ich stelle fest, dass dieser Artikel aus dem Jahr 2005 stammt und sich seitdem möglicherweise viel geändert hat. Ist der Rat noch aktuell? Gibt es irgendwelche lauernden Probleme, die ich beachten sollte?

Nick Hutchinson
quelle
Oh, das verstehe ich. Aber ist die Anbindung an libstdc ++ bedeutet statisch , dass ich sollte nicht zu irgendwelchen Shared Libraries? Sogar reine C?
Nick Hutchinson
Nein, eine statische Verknüpfung mit libstdc ++ bedeutet dies nicht. Wenn dies implizieren würde, hätte die -static-libstdc++Option keinen Sinn . Sie würden sie einfach verwenden-static
Jonathan Wakely,
@JonathanWakely -static wird kernel too oldin einem Ubuntu 1404-System einen Fehler erhalten. Die glibc.so ist wie kernel32.dllim Fenster, sie ist Teil der Betriebssystemschnittstelle, wir sollten sie nicht in unsere Binärdatei einbetten. Sie können verwenden, um objdump -T [binary path]zu sehen, ob es dynamisch geladen ist libstdc++.sooder nicht. Für Golang-Programmierer können Sie #cgo linux LDFLAGS: -static-libstdc++ -static-libgccvor dem Import "C"
Bronze Man
1
@NickHutchinson der verlinkte Blog-Beitrag ist weg. Diese SO-Frage ist ein beliebter Suchhit für die relevanten Begriffe hier. Können Sie die kritischen Informationen aus diesem Blog-Beitrag in Ihrer Frage reproduzieren oder einen neuen Link anbieten, wenn Sie wissen, wohin er verschoben wurde?
Brian Cain
1
@BrianCain Das Internetarchiv hat es: web.archive.org/web/20160313071116/http://www.trilithium.com/…
Rob Keniger

Antworten:

133

Dieser Blog-Beitrag ist ziemlich ungenau.

Soweit ich weiß, wurden C ++ ABI-Änderungen mit jeder Hauptversion von GCC eingeführt (dh mit unterschiedlichen Komponenten der ersten oder zweiten Versionsnummer).

Nicht wahr. Die einzigen seit GCC 3.4 eingeführten C ++ ABI-Änderungen waren abwärtskompatibel, was bedeutet, dass der C ++ ABI seit fast neun Jahren stabil ist.

Erschwerend kommt hinzu, dass die meisten großen Linux-Distributionen GCC-Snapshots verwenden und / oder ihre GCC-Versionen patchen, sodass es praktisch unmöglich ist, genau zu wissen, mit welchen GCC-Versionen Sie möglicherweise arbeiten, wenn Sie Binärdateien verteilen.

Die Unterschiede zwischen den gepatchten Versionen von GCC für Distributionen sind gering und ändern sich nicht an ABI. ZB Fedoras 4.6.3 20120306 (Red Hat 4.6.3-2) ist ABI-kompatibel mit den vorgelagerten FSF 4.6.x-Versionen und mit ziemlicher Sicherheit mit allen 4.6. x von jeder anderen Distribution.

Unter GNU / Linux verwenden die Laufzeitbibliotheken von GCC die ELF-Symbolversionierung, sodass Sie die von Objekten und Bibliotheken benötigten Symbolversionen leicht überprüfen können. Wenn Sie eine haben libstdc++.so, die diese Symbole bereitstellt, funktioniert dies, spielt es keine Rolle, ob es sich um eine etwas andere gepatchte Version handelt von einer anderen Version Ihrer Distribution.

Es darf jedoch kein C ++ - Code (oder Code, der die C ++ - Laufzeitunterstützung verwendet) dynamisch verknüpft werden, wenn dies funktionieren soll.

Dies ist auch nicht wahr.

Das statische Verknüpfen mit libstdc++.aist jedoch eine Option für Sie.

Der Grund, warum es möglicherweise nicht funktioniert, wenn Sie eine Bibliothek dynamisch laden (mit dlopen), ist, dass libstdc ++ - Symbole, von denen es abhängt, von Ihrer Anwendung möglicherweise nicht benötigt wurden, als Sie sie (statisch) verknüpft haben, sodass diese Symbole in Ihrer ausführbaren Datei nicht vorhanden sind. Dies kann durch dynamisches Verknüpfen der gemeinsam genutzten Bibliothek mit libstdc++.so(was ohnehin das Richtige ist, wenn es davon abhängt) gelöst werden . ELF-Symbol-Interposition bedeutet, dass Symbole, die in Ihrer ausführbaren Datei vorhanden sind, von der gemeinsam genutzten Bibliothek verwendet werden, andere jedoch nicht Das in Ihrer ausführbaren Datei vorhandene Element befindet sich in dem libstdc++.soLink, auf den es verweist. Wenn Ihre Anwendung nicht verwendet wird, müssen dlopenSie sich nicht darum kümmern.

Eine andere Option (und die, die ich bevorzuge) besteht darin, die neuere libstdc++.soneben Ihrer Anwendung bereitzustellen und sicherzustellen, dass sie vor dem Standardsystem gefunden wird. Dies libstdc++.sokann erreicht werden, indem der dynamische Linker gezwungen wird, an der richtigen Stelle zu suchen, entweder mithilfe der $LD_LIBRARY_PATHUmgebungsvariablen bei run-. Zeit oder durch Festlegen einer RPATHin der ausführbaren Datei zur Verbindungszeit. Ich bevorzuge die Verwendung, RPATHda dies nicht davon abhängt, dass die Umgebung richtig eingestellt ist, damit die Anwendung funktioniert. Wenn Sie mit Ihrer Anwendung verknüpfen '-Wl,-rpath,$ORIGIN'( man beachte die einfachen Anführungszeichen um die Schale zu verhindern versuchen , zu erweitern $ORIGIN) die ausführbare Datei wird dann eine hat RPATHvon $ORIGINdenen erzählt die dynamischen Linker Look für gemeinsam genutzte Bibliotheken im selben Verzeichnis wie die ausführbaren Datei selbst. Wenn Sie die neuere setzenlibstdc++.soIm selben Verzeichnis wie die ausführbare Datei befindet sie sich zur Laufzeit. Das Problem wurde behoben. (Eine andere Möglichkeit besteht darin, die ausführbare Datei in /some/path/bin/und die neuere libstdc ++ einzufügen. Also in /some/path/lib/und mit '-Wl,-rpath,$ORIGIN/../lib'einem anderen festen Speicherort in Bezug auf die ausführbare Datei zu verknüpfen und den RPATH in Bezug auf zu setzen. $ORIGIN)

Jonathan Wakely
quelle
7
Diese Erklärung, insbesondere über RPATH, ist herrlich.
Nilweed
3
Der Versand von libstdc ++ mit Ihrer App unter Linux ist ein schlechter Rat. Google für "Steam Libstdc ++", um all das Drama zu sehen, das dies bringt. Kurz gesagt, wenn Ihre Exe externe Bibliotheken (wie opengl) lädt, die libstdc ++ erneut öffnen möchten (wie Radeon-Treiber), verwenden diese Bibliotheken Ihre libstdc ++, da sie bereits geladen sind, anstatt ihrer eigenen, was sie benötigen und erwarten von. Sie sind also wieder auf dem ersten Platz.
7
@cap, das OP fragt speziell nach der Bereitstellung in einer Distribution, in der das System libstdc ++ älter ist. Das Steam-Problem ist, dass sie ein libstdc ++ gebündelt haben, so dass es älter als das System war (vermutlich war es zu dem Zeitpunkt, als sie es gebündelt haben, neuer, aber die Distributionen sind zu noch neueren übergegangen). Dies kann gelöst werden, indem der RPATH auf ein Verzeichnis verweist, das einen libstdc++.so.6Symlink enthält, der bei der Installation so eingestellt ist, dass er auf die mitgelieferte Bibliothek oder auf die Systembibliothek verweist, wenn diese neuer ist. Es gibt kompliziertere Modelle mit gemischten Verknüpfungen, wie sie von Red Hat DTS verwendet werden, aber sie sind schwer selbst zu erstellen.
Jonathan Wakely
5
Hey Mann, es tut mir leid, wenn ich nicht möchte, dass mein Modell für den Versand abwärtskompatibler Binärdateien "Vertrauen in andere Personen, um libstdc ++ ABI-kompatibel zu halten" oder "bedingte Verknüpfung von libstdc ++ zur Laufzeit" enthält ... wenn dies hier einige Federn zerzaust und dort, was kann ich tun, meine ich keine Respektlosigkeit. Und wenn Sie sich an das Drama memcpy@GLIBC_2.14 erinnern, können Sie mir nicht wirklich vorwerfen, dass ich Vertrauensprobleme damit habe :)
6
Ich musste '-Wl, -rpath, $ ORIGIN' verwenden (beachte das '-' vor rpath). Ich kann die Antwort nicht bearbeiten, da die Änderungen mindestens 6 Zeichen umfassen müssen ....
user368507
11

Eine Ergänzung zu Jonathan Wakelys hervorragender Antwort, warum dlopen () problematisch ist:

Aufgrund des neuen Ausnahmebehandlungspools in GCC 5 (siehe PR 64535 und PR 65434 ) tritt jedes Mal ein Speicherverlust (des Poolobjekts ) auf, wenn Sie eine Bibliothek öffnen und schließen, die statisch mit libstdc ++ verknüpft ist. Wenn es also eine Chance gibt, dass Sie jemals dlopen verwenden, scheint es eine wirklich schlechte Idee zu sein, libstdc ++ statisch zu verknüpfen. Beachten Sie, dass dies ein echtes Leck ist, im Gegensatz zu dem in PR 65434 erwähnten gutartigen .

Emil Styrke
quelle
1
Die Funktion __gnu_cxx::__freeres()scheint zumindest einige Hilfe bei diesem Problem zu bieten, da sie den internen Puffer des Poolobjekts freigibt. Für mich ist jedoch ziemlich unklar, welche Auswirkungen ein Aufruf dieser Funktion auf Ausnahmen hat, die versehentlich danach ausgelöst wurden.
Phlipsy
2

Möglicherweise müssen Sie auch sicherstellen, dass Sie nicht vom dynamischen glibc abhängig sind. Führen lddSie die resultierende ausführbare Datei aus und notieren Sie sich alle dynamischen Abhängigkeiten (libc / libm / libpthread sind übliche Verdächtige).

Eine zusätzliche Übung wäre, eine Reihe von beteiligten C ++ 11-Beispielen mit dieser Methode zu erstellen und die resultierenden Binärdateien tatsächlich auf einem echten 10.04-System auszuprobieren. In den meisten Fällen wissen Sie sofort, ob das Programm funktioniert oder abstürzt, es sei denn, Sie tun etwas Seltsames mit dynamischem Laden.

Alexander L. Belikoff
quelle
1
Was ist das Problem in Abhängigkeit von der dynamischen Glibc?
Nick Hutchinson
Ich glaube, dass libstdc ++ vor einiger Zeit eine Abhängigkeit von glibc implizierte. Ich bin mir nicht sicher, wo die Dinge heute stehen.
Alexander L. Belikoff
9
libstdc ++ hängt von glibc ab (z. B. werden iostreams in Bezug auf implementiert printf), aber solange das glibc unter Ubuntu 10.04 alle Funktionen bietet, die von dem neueren libstdc ++ benötigt werden, gibt es kein Problem damit, abhängig vom dynamischen glibc, niemals zu verlinken statisch zu glibc
Jonathan Wakely
2

Add-on zu Jonathan Wakelys Antwort bezüglich des RPATH:

RPATH funktioniert nur, wenn das betreffende RPATH das RPATH der laufenden Anwendung ist . Wenn Sie über eine Bibliothek verfügen, die über ein eigenes RPATH dynamisch mit einer Bibliothek verknüpft ist, wird das RPATH der Bibliothek vom RPATH der Anwendung überschrieben, die es lädt. Dies ist ein Problem, wenn Sie nicht garantieren können, dass der RPATH der Anwendung mit dem Ihrer Bibliothek identisch ist, z. B. wenn Sie erwarten, dass sich Ihre Abhängigkeiten in einem bestimmten Verzeichnis befinden, dieses Verzeichnis jedoch nicht Teil des RPATH der Anwendung ist.

Angenommen, Sie haben eine Anwendung App.exe, die eine dynamisch verknüpfte Abhängigkeit von libstdc ++. So.x für GCC 4.9 aufweist. Die App.exe hat diese Abhängigkeit durch das RPATH aufgelöst, dh

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

Angenommen, es gibt eine weitere Bibliothek Dependency.so, die eine dynamisch verknüpfte Abhängigkeit von libstdc ++. So.y für GCC 5.5 aufweist. Die Abhängigkeit wird hier durch das RPATH der Bibliothek aufgelöst, dh

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

Wenn App.exe Dependency.so lädt, wird das RPATH der Bibliothek weder angehängt noch vorangestellt . Es konsultiert es überhaupt nicht. Das einzige RPATH, das berücksichtigt wird, ist das der laufenden Anwendung oder in diesem Beispiel App.exe. Das heißt, wenn die Bibliothek auf Symbolen basiert, die sich in gcc5_5 / libstdc ++. So.y, aber nicht in gcc4_9 / libstdc ++. So.x befinden, kann die Bibliothek nicht geladen werden.

Dies ist nur ein Wort der Warnung, da ich in der Vergangenheit selbst auf diese Probleme gestoßen bin. RPATH ist ein sehr nützliches Tool, aber seine Implementierung hat noch einige Fallstricke.

Jonathan McDevitt
quelle
RPATH für gemeinsam genutzte Bibliotheken ist also irgendwie sinnlos! Und ich hatte gehofft, dass sie Linux in den letzten 2 Jahrzehnten in dieser Hinsicht ein wenig verbessert haben ...
Frank Puck
0

Ich möchte Jonathan Wakelys Antwort Folgendes hinzufügen.

Herumspielen -static-libstdc++auf Linux, habe ich das Problem mit konfrontiert dlclose(). Angenommen, wir haben eine statisch verknüpfte Anwendung 'A', die zur Laufzeit libstdc++dynamisch mit dem libstdc++Plugin 'P' verknüpft geladen wird. Das ist gut. Wenn 'A' jedoch 'P' entlädt, tritt ein Segmentierungsfehler auf. Ich gehe davon aus libstdc++.so, dass 'A' nach dem Entladen keine Symbole mehr verwenden kann, die sich auf beziehen libstdc++. Beachten Sie, dass libstdc++das Problem nicht auftritt , wenn sowohl 'A' als auch 'P' statisch verknüpft sind oder wenn 'A' dynamisch und 'P' statisch verknüpft sind.

Zusammenfassung: Wenn Ihre Anwendung Plugins lädt / entlädt, mit denen möglicherweise eine dynamische Verknüpfung hergestellt wird, libstdc++muss die App auch dynamisch mit ihr verknüpft werden. Dies ist nur meine Beobachtung und ich würde gerne Ihre Kommentare erhalten.

Fedorov7890
quelle
1
Dies ähnelt wahrscheinlich dem Mischen von libc-Implementierungen (z. B. dynamisches Verknüpfen mit einem Plugin, das wiederum glibc dynamisch verknüpft, während die Anwendung selbst statisch mit musl-libc verknüpft ist). Rich Felker, Autor von musl-libc, behauptet, dass das Problem in einem solchen Szenario darin besteht, dass die Glibc-Speicherverwaltung (unter Verwendung sbrk) bestimmte Annahmen trifft und ziemlich erwartet, innerhalb eines Prozesses allein zu sein ... nicht sicher, ob dies auf a beschränkt ist bestimmte glibc version oder was auch immer.
0xC0000022L
und die Leute sehen immer noch nicht die Vorteile der Windows-Heap-Oberfläche, die mehrere unabhängige Kopien von libc ++ / libc in einem einzigen Prozess verarbeiten kann. Solche Leute sollten keine Software entwerfen.
Frank Puck