C++/clang: dlsym & shared lib options

Webseiten kenne ich leider keine besoders guten. Das Buch ist in Ordnung, aber wirklich alt, und der Fokus liegt stark auf der Implementierung. Wenn du keinen eigenen Linker bauen möchtest, oder wirklich die kompletten Interna nachollziehen willst, dann würde ich es mir nochmal überlegen, da ein Großteil der Sachen so extrem platformabhängig ist. Außerdem kommen ARM und MIPS garnicht vor, als RISC Instruction Set ist dafür SPARC vertreten. Ebenso ist amd64 außen vor, da es das damals erst gerade konzipiert wurde. Die behandelten Architekturen sind IBM 370, SPARC und x86. An Ausführungsformaten werden DOS COM (weil es so einfach ist), Unix a.out, COFF & ELF, Microsoft PE, IBM 360 und Intel OMF vorgestellt. Soweit ich informiert bin ist da auch alles tot bis auf ELF und PE.
Viele der Zusammenhänge sind durchaus interessant, aber da es eben an Autoren von Linkersoftware gerichtet ist geht es echt in die Details, wie Relocations usw. implementiert werden. Und da die Terminologie von Plattform zu Plattform variiert (Segmente: Speichersegmente der x86-Hardware, oder die Code-/Daten/...-Segmente des ELF-Formats? ELF auf x86? Argh!) ist es teilweise auch ziemlich verwirrend. Daher denke ich dass für Leute, die eher an konkreten Implementierungen (wie ELF auf FreeBSD) interessiert sind, andere Ressourcen interessanter sind, am ehesten die Linker-Doku[url], oder auch das [url=https://en.wikipedia.org/wiki/Executable_and_Linkable_Format]ELF Format.

Ich hab mir das Buch deshalb gekauft, weil ich sehr am Compilerbau interessiert bin. Ich hab es zwar immer noch nicht geschafft, mich am clang/LLVM-Projekt zu involvieren, aber das Thema fasziniert mich schon lange. Und wenn man in den Windows-Headern rumwühlt, dann findet man da so komische Dinge wie `__near` und `__far`, das wurde mir in Linkers & Loaders bisher mit am besten erklärt.
Das ist im Übrigen auch ein super Beispiel dafür, dass es in C "Die reine Lehre" garnicht gibt, weil der Standard auf einer abstrakten Maschine definiert wurde. Wirkliche Hardware hat eben auch Dinge wie Speichersegmente und um sowas abzubilden muss die Sprache dann vom Compiler-Hersteller erweitert werden.

Schlussendlich hat das Thema Linken auch eine emotionale Seite, heutzutage ist dynamisches Linken zwar quasi allgemein Anerkannt, aber gerade in den Anfangszeiten gab es viel Kritik an der zusätzlichen Komplexität und den damals noch relativ hohen Laufzeitkosten. Plan 9 hat es aus vielen Gründen nie geschafft, den Mainstream zu erreichen, aber die Entwickler waren vehemente Gegner von dynamischem Linken, was man heute wieder an der Programmiersprache Go sehen kann, die von vielen Ex-Plan 9 Entwicklern konzipiert wurde. Rob Pike hasst dynamisches Linken bis heute, wobei er dynamisches Laden akzeptiert, was aber auf FreeBSD nicht so gerne gesehen ist, wie wir in diesem Thread erfahren haben. In der GNU Welt wurde statisches Linken vom libc-Maintainer Ulrich Drepper verteufelt und technisch nahezu unmöglich gemacht, was zu einer Flut von Alternativimplementierungen auf GNU libc basierten Systemen (a.k.a. Linux) geführt hat, da es doch noch viele Anwendungsfälle für statisch gelinkte Programmdateien gibt (embedded Systeme, Rettungssysteme, ...). Als kleine Anekdote: Vor FreeBSD bin ich sehr viel in der suckless Community rumgehangen, wo statisches Linken auch bevorzugt wird. Kleine Programme sind statisch gegen die musl libc gelinkt kleiner als dynamisch gegen die GNU libc. Das hat zur Folge, dass sie Programme schneller von der Platte eingelesen sind, keine Symbole zur Laufzeit eingelesen werden müssen und evtl. entsprechende Bibliotheken eingelesen werden müssen usw., das führt vor allem auf alten Systemen zu einer unglaublichen Responsivität.[/url]
 
Das Buch ist in Ordnung, aber wirklich alt, und der Fokus liegt stark auf der Implementierung.
Ich denke das ist z.Z. nichts für mich. Das Linken wollen wir mal lieber anderen überlassen!:)
Ich hab mir das Buch deshalb gekauft, weil ich sehr am Compilerbau interessiert bin. Ich hab es zwar immer noch nicht geschafft, mich am clang/LLVM-Projekt zu involvieren, aber das Thema fasziniert mich schon lange.
Das ist auch für mich der Grund mich mit C++ und all dem zu beschäftigen. So etwas ähnliches wie ein Experimentier-Framework.
Schlussendlich hat das Thema Linken auch eine emotionale Seite, heutzutage ist dynamisches Linken zwar quasi allgemein Anerkannt, aber gerade in den Anfangszeiten gab es viel Kritik an der zusätzlichen Komplexität und den damals noch relativ hohen Laufzeitkosten.
Na ja, ich bin ja gerade an dabei eine Implementierung von dynamischem Laden zu hacken und frage mich auch ob sich das überhaupt lohnt und nicht andere Wege vozuziehen sind. Ich komme immerhin auf 5 Klassen die darin verwickelt sind: 2 davon wiederverwendbar wovon eine ein template ist, dann die jeweilige Factory, Interface und Implementierungsklasse für jede Lib. Alles nicht sehr umfangreich aber es beeinflußt doch das gesammte Design und hat natürlich auch einen gewissen runtime und space overhead. Und ich denke dass das Cache-Verhalten für die Laufzeitkosten höchst relevant sind.

Aber was wäre die Alternative? Vermutlich unterschiedliche, statisch gelinkte Programme. Aber dann bekommt man es evtl. mit IPC zu tun sofern man nicht die Datenübergabe über Dateinen macht, - auch ein Minenfeld wenn ich das richtig sehe.:rolleyes:
Plan 9 hat es aus vielen Gründen nie geschafft, den Mainstream zu erreichen, aber die Entwickler waren vehemente Gegner von dynamischem Linken, was man heute wieder an der Programmiersprache Go sehen kann, die von vielen Ex-Plan 9 Entwicklern konzipiert wurde. Rob Pike hasst dynamisches Linken bis heute, wobei er dynamisches Laden akzeptiert, was aber auf FreeBSD nicht so gerne gesehen ist, wie wir in diesem Thread erfahren haben. In der GNU Welt wurde statisches Linken vom libc-Maintainer Ulrich Drepper verteufelt und technisch nahezu unmöglich gemacht, was zu einer Flut von Alternativimplementierungen auf GNU libc basierten Systemen (a.k.a. Linux) geführt hat, da es doch noch viele Anwendungsfälle für statisch gelinkte Programmdateien gibt (embedded Systeme, Rettungssysteme, ...).
Noch einmal zur Terminologie:
Statisches linken = libname.a (compiletime)
Dynamisches linken = liname.so (compiletime)
Dynamische laden = libname.so (runtime)
Oder funktioniert dynamisches linken at loadtime was ich problematisch finden würde.
Vor FreeBSD bin ich sehr viel in der suckless Community rumgehangen, wo statisches Linken auch bevorzugt wird. Kleine Programme sind statisch gegen die musl libc gelinkt kleiner als dynamisch gegen die GNU libc. Das hat zur Folge, dass sie Programme schneller von der Platte eingelesen sind, keine Symbole zur Laufzeit eingelesen werden müssen und evtl. entsprechende Bibliotheken eingelesen werden müssen usw., das führt vor allem auf alten Systemen zu einer unglaublichen Responsivität.
Interessant! Als bekenendem Minimalisten ist mir das recht sympatisch, allerdings schätze ich inzwisschen einige Dinge an C++11 ohne die ich ungern leben würde: Namespaces, Smartpointer, Generics, Referencen und einiges mehr ...

Peter
 
Dynamisches Linken ist zur Compilezeit und zur Laufzeit gemischtes Linken. Abstrakt gesprochen - praktisch hängt es wieder sehr von der zugrunde liegenden Maschine, dem Betriebssystem und vor allem von dem verwendeten Binärformat ab - werden zur Compilezeit Referenzen zwischen den eigentlichen Programm und seinen Bibliotheken ermittelt und mit Symbolen verknüpft. Beim Programmstart löst der dynamische Linker diese Referenzen auf und setzt aus dem eigentlichen Programm und den Bibliotheken einen lauffähigen Prozess zusammen. Sowohl der Conpiler als auch der dynamische Linker müssen alle Symbole auflösen können. Sonst linkt und startet das Programm nicht.

Problematisch... Die Welt ist nicht schwarz-weiß. Für "richtige" Computer, also vergleichsweise leistungsstarke Desktops und Server gesprochen, hat sich dynamisches Linken de facto durchgesetzt und vollständig statisches Linken wurde bis auf einige Randbereiche wie Rescuesysteme oder außerhalb des Paketmanagements zu verteilende Anwendungen weitgehend verdrängt. Die Vorteile überwiegen einfach. Einfaches austauschen von Bibliotheken, anstatt diverse Programme zu ersetzen. Die meisten großen Systeme (FreeBSD, Linux, etc.) verbinden den dynamischen Linker direkt oder indirekt mit der virtuellen Speicherverwaltung, wodurch gleiche und in diversen Prozessen genutzte Bibliotheken nur einmal im physischen RAM gehalten werden müssen. Und nicht zuletzt Platzersparnis auf der Festplatte. Das ist durch immer billigeren Speicherplatz zwar weniger wichtig geworden, aber schaue dir z.B. mal Qt an. Statisch gelinkt hätte jedes Qt-Programm eine Größe im locker zweistelligen Megabytebereich.

Umgekehrt würde ein Anhänger statisches Ladens nun wahrscheinlich genau in die andere Richtung argumentieren. :)
 
Die Vorteile überwiegen einfach. Einfaches austauschen von Bibliotheken, anstatt diverse Programme zu ersetzen. Die meisten großen Systeme (FreeBSD, Linux, etc.) verbinden den dynamischen Linker direkt oder indirekt mit der virtuellen Speicherverwaltung, wodurch gleiche und in diversen Prozessen genutzte Bibliotheken nur einmal im physischen RAM gehalten werden müssen. Und nicht zuletzt Platzersparnis auf der Festplatte. Das ist durch immer billigeren Speicherplatz zwar weniger wichtig geworden, aber schaue dir z.B. mal Qt an. Statisch gelinkt hätte jedes Qt-Programm eine Größe im locker zweistelligen Megabytebereich.
Wenn ich das richtig verstehe heißt dynamisches linken also (meist) dass der Linkprozess zur start-time erfolgt. Was heißt das aber praktisch. Der Linker muss die Lib ja finden. Deshalb bekommt er im Makefile ja einen Pfad mit. Das klappt jetzt natürlich prima. Wenn ich aber das Programm verschiebe (nur das Programm), dann stimmt der evtl. ja nicht mehr. Das heißt dann ja wohl - nehme ich an - dass die Lib irgendwo auf einem der Standart-Pfade liegen muss oder wie wird das praktisch gehandhabt?

Beim dynamischen laden kann ich dem ja einen - möglicherweise zur Laufzeit ermittelten (config etc) - Pfad mitgeben so dass das keine Problem ist.

Der Overhead die Lib zu finden entsteht also so oder so: Beim dynamischen linken, macht das der linker, beim dynamischen laden, erledigt das halt das Programm. Oder täusche ich mich da?

Watt'n komplexes Ding! Und was ist mit der dll-hell? (Oder ist das nur Polemik?)
 
ldconfig(8) hätte ich schon mal früherlesen müssen. Ich werde mir das mal in Ruhe genauer anschauen.

Aber da entsteht schon eine neue Frage. :rolleyes:

Meiner Application besteht ja aus einer CoreLib und einer kleinen Zahl zusätzlicher DynLibs. Vermerke ich dann die CoreLib einfach als Abhängigkeit inm Makefile der DynLibs damit die die Symbole importiert bekommen oder muss ich da noch mehr tun?

Peter

PS: Das dynamische laden funktioniert jetzt! :) Ist aber noch einiges an Feinarbeit notwendig...
 
Du kannst deiner Anwendung beim Linken auch noch einen RPATH setzen, in dem der dynamische Linker beim Programmstart zusätzlich zu seinen systemweiten Verzeichnissen nach Bibliotheken sucht. Das nutzen vor allem kommerzielle Anwendungen, die vorpaketiert kommen. Wikipedia hat da was zu: https://en.wikipedia.org/wiki/Rpath
 
OK, dann kann man damit also /usr/local/myapp o.Ä. setzen. Macht Sinn! Evtl. auch ~/.myapp ? Ich werde erst einmal das dynamische Laden fertig machen, dann kann ich mich um das linken kümmern und ausprobieren. Zuviele Baustellen auf einmal sind Käse!
 
Problematisch... Die Welt ist nicht schwarz-weiß. Für "richtige" Computer, also vergleichsweise leistungsstarke Desktops und Server gesprochen, hat sich dynamisches Linken de facto durchgesetzt und vollständig statisches Linken wurde bis auf einige Randbereiche wie Rescuesysteme oder außerhalb des Paketmanagements zu verteilende Anwendungen weitgehend verdrängt. Die Vorteile überwiegen einfach. Einfaches austauschen von Bibliotheken, anstatt diverse Programme zu ersetzen. Die meisten großen Systeme (FreeBSD, Linux, etc.) verbinden den dynamischen Linker direkt oder indirekt mit der virtuellen Speicherverwaltung, wodurch gleiche und in diversen Prozessen genutzte Bibliotheken nur einmal im physischen RAM gehalten werden müssen. Und nicht zuletzt Platzersparnis auf der Festplatte. Das ist durch immer billigeren Speicherplatz zwar weniger wichtig geworden, aber schaue dir z.B. mal Qt an. Statisch gelinkt hätte jedes Qt-Programm eine Größe im locker zweistelligen Megabytebereich.

Natürlich ist die Welt nicht schwarz-weiß und große C++-Frameworks wie Qt würde ich auch im Leben nicht statisch linken wollen. Es kommt immer auf die Menge geteilten Codes an, die ist im klassischen Userland eben nicht so hoch wie bei großen GUI-Programmen, daher finde ich ein statisch gelinktes Userlan schöner, als nebenbei dann auch noch /rescue zur Verfügung zu stellen. Aber ich wollte eben darlegen, dass es nicht *nur* Vorteile hat, so wie das oft dargestellt wird (Ulrich Drepper *hüstel*). DLL-Hell ist ein gutes Stichwort, und solib-Versionierung bereitet auch ab und zu Probleme. Es gibt halt wie immer kein Licht ohne Schatten.

Wenn ich das richtig verstehe heißt dynamisches linken also (meist) dass der Linkprozess zur start-time erfolgt. Was heißt das aber praktisch. Der Linker muss die Lib ja finden. Deshalb bekommt er im Makefile ja einen Pfad mit. Das klappt jetzt natürlich prima. Wenn ich aber das Programm verschiebe (nur das Programm), dann stimmt der evtl. ja nicht mehr. Das heißt dann ja wohl - nehme ich an - dass die Lib irgendwo auf einem der Standart-Pfade liegen muss oder wie wird das praktisch gehandhabt?

Beim dynamischen laden kann ich dem ja einen - möglicherweise zur Laufzeit ermittelten (config etc) - Pfad mitgeben so dass das keine Problem ist.

Der Overhead die Lib zu finden entsteht also so oder so: Beim dynamischen linken, macht das der linker, beim dynamischen laden, erledigt das halt das Programm. Oder täusche ich mich da?

Watt'n komplexes Ding! Und was ist mit der dll-hell? (Oder ist das nur Polemik?)

Wenn du dynamische Bibliotheken verwendest, dann wird der Code nur einmal in den Speicher geladen und das Betriebssystem macht diesen allen Programmen zugänglich, die ihn benutzen wollen. Das bringt mehrere Problemstellungen mit sich: In jedem Programm liegt der Code an einer anderen Stelle im Speicher, also müssen alle aboluten Adressen angepasst werden. Das kann zur Linkzeit festgelegt werden, allerdings muss die Version dann immer genau die gleiche sein, damit sich die Adressen von Funktionen und Symbolen nicht ändern. Alternativ kann es dafür eine Lookup-Table geben, die zur Laufzeit angelegt wird, jedoch ist das Overhead beim Laden und mehr Indirektion bei jedem Funktionsaufruf. Aber das entspricht ungefähr dem, was du von Hand machst wenn du dynamisch lädst, vor allem wenn du Yamagis Vorschlag mit dem struct umsetzt. Der Vorteil ist jedoch, dass die Bibliothek aktualisiert werden kann, ohne dass alle Programme neu gelinkt werden müssen, nur weil ein bisschen Code verändert hat, aber sich dafür alle Offsets verschoben haben.

Was das Finden von Bibliotheken angeht hast du unter FreeBSD ldconfig(8), wie double-p ja schon erwähnt hat. Das hat mich aber auch schon gebissen, als ich meinen eigenen QtCreator mit eigenem Qt kompiliert habe, da waren dann die Pfade kaputt. Als Alternative kann man auch mit -rpath kompilieren, da gibst du dem Programm einen eigenen Suchpfad mit. Oder du setzt die Umgebundsvariable LD_PRELOAD_PATH. Wenn du den Benutzer aber zur Laufzeit eine Plugindatei angeben lassen möchtest, dann kommst du meiner Meinung nach nicht um dynamisches Laden herum.

Und die DLL-Hell ist vornehmlich ein Windows-Problem, weil es dort keinen zentralen Platz für Bibliotheken wie /usr/lib (oder /usr/local/lib) gibt, und sowohl Programme und Bibliotheken zuerst im Arbeitsverzeichnis gesucht werden. Da verschiedene Programme dann die gleichen Bibliotheken in verschiedenen Versionen (mit verschiedenen ABIs) brauchen kann es passieren, dass die falsche Bibliothek geladen wird. Um das zu umgehen bringen heute die meisten Windows-Programme ihre eigenen Bibliotheken mit und machen somit alle Vorteile des dynamischen Linkens zunichte. Und mit Windows Vista oder so haben sie ein unglaublich kompliziertes System eingeführt, wo Bibliotheken nicht mehr durch ihren Namen identifiziert werden, sondern durch globale IDs. Wie das genau funktioniert weiß ich aber nicht, ich weiß nur dass das der Grund ist, warum moderne Windows-Installationen so viel Speicherplatz brauchen: Es gibt einfach jede Bibliothek in jeder Variante, falls irgendwer das braucht. Speicher ist doch so billig...
 
Das ist ja eine hoch interessante Diskussion! Vielen Dank dafür!

Für mich heißt dass u.A. dass die SharedLibs das ganze Design beeinflussen. Ich werde wohl kräftig in den Makefiles rumrühren müssen (platform/versioning) :( Ich denke ich werde sowohl .so als auch .a generieren dann bleiben alle Optionen offen. Mal sehen.

Interessant finde ich das auch hinsichtlich Language Design. Da werden doch einige Design-Entscheidungen plausibler.

Ich weiß nicht recht ob ich das Alles für bewundernswert oder für ziemlich verrückt halten soll! :rolleyes:

Peter
 
Passt lose zur Diskussion: ein Brandaktuelles Problem mit dynamischem Linken:
Code:
$ grep ssl *.ldd
cmake.ldd:      libssl.so.8 => /usr/local/lib/libssl.so.8 (0x804159000)
cmake.ldd:      libssl.so.7 => /usr/lib/libssl.so.7 (0x805271000)
xelatex.ldd:    libssl.so.8 => /usr/local/lib/libssl.so.8 (0x807dcd000)
xelatex.ldd:    libssl.so.7 => /usr/lib/libssl.so.7 (0x8090d0000)
Beide Programme linken gegen /usr/lib/libfetch.so.6, welches wiederrum gegen /usr/lib/libssl.so.7 linkt.
Aber da ich für Ports eigentlich die aktuellere libssl aus den Ports verwenden möchte (war auch ein Tipp vom Baeren), habe ich `WITH_OPENSSL_PORT=yes` in der /etc/make.conf.
Da beide Ports auch direkt gegen libssl linken, linken sie so eben auch gegen /usr/local/lib/libssl.so.8.
Beide haben zwar die gleichen Funktionen, aber anscheinend funktioniert das unterschiedlich, weswegen ich von CMake und xelatex Fehlermeldungen bekomme, dass die Environment-Variablen kaputt sind. Tatsächlich wird bei der letzten Variable im Environment ein Teil abgeschnitten.
*Wie* genau das funktioniert weiß ich jetzt noch nicht, aber mehrmals gegen die gleiche Bibliothek aber in verschiedenen Versionen zu linken kann nicht gut sein.
 
Um hier nicht allzu viele dumme Fragen zu stellen habe ich nach einem geeigneten Buch bzgl. der hier dikutierten Themen gesucht und bin auf Milan Stevanovic: Advanced C and C++ Compiling gestoßen.

Eine erste Durchsicht zeigt dass wohl allle wichtigen Themen zu Compiling, Linking und Loading behandelt werden (s.a. Inhaltsverzeichnis bei Amazon). Das Buch ist in gut lesbarem Englisch geschrieben und der Text wird an vielen Stellen durch gut verständliche Grafiken und eine Menge an Listings und Code sowie Tools und Libs ergänzt. Besonders gefallen hat es mir dass der Verfasser an einigen Stellen auf mögliche Anfängerfehler hinweißt. Behandelt wird ausschließlich X86 unter Linux (GCC) und Windows (VC), - was ja beides kein Nachteil ist. Da das Buch von 2014 ist, ist es auch halbwegs aktuell. Ich vermute dass das als Grundlage ausreichend ist um sich andere Platformen erschließen zu können, insbesondere also auch BSDs.

In der Sache bin ich ein ganzes Stück weiter und das dynamische Laden funktioniert. Allerdings werde ich es noch verallgemeinern müssen sowie das ganze Design umstrukturieren was mir mit manuellem gmake zu aufwendig wird. Mit Cmake bin ich furchtbar gescheitert und habe nicht das einfachste Beispiel zum laufen bekommen (Bin ich doof oder die docus ? Vermutlich ich! :rolleyes:). Erste Versuche mit Premake4 stimmen mich hoffnungsvoll.

Peter
 
Zurück
Oben