Wenn ich ein Programm starte das von dynamischen Bibliotheken abhängt (*.dll bzw *.so), wer findet diese Dateien und lädt sie in den Speicher? Der Betriebssystem-Kern, das Anwendungsprogramm selbst oder ist da noch eine Instanz zwischen? Das interessiert mich für Linux und Windows.
Klaus W. schrieb: > Unter Linux ist das Stichwort ld.so bzw. ld-linux.so. Aber wer lädt die SO-Dateien in den Arbeitspeiche? Das Programm selbst, wenn dieses die dyn. Library benötigt?
Steve van de Grens schrieb: > Wenn ich ein Programm starte das von dynamischen Bibliotheken abhängt > (*.dll bzw *.so), wer findet diese Dateien und lädt sie in den Speicher? > Der Betriebssystem-Kern, das Anwendungsprogramm selbst oder ist da noch > eine Instanz zwischen? > > Das interessiert mich für Linux und Windows. Also bei Windows ist das so: Es gibt zwei verschiedene Möglichkeiten, eine DLL einzubinden, nämlich "quasi-statisch" und wirklich "dynamisch". Davon hängt ab, was genau die DLL lädt. Im ersteren Fall macht's der Exe-Loader des OS, im zweiten Fall muss es die Anwendung zu Fuss selber erledigen. Letztlich greifen aber beide Varianten natürlich wiederum auf Routinen zurück, die das OS bereitstellt. Warum schnappst du dir nicht einfach einen brauchbaren Debugger und spielst das selber durch? Da kannst du dir im Detail anschauen, was da passiert. Brauchst nur eine wenig Ausdauer. ;o)
C-hater schrieb: > Warum schnappst du dir nicht einfach einen brauchbaren Debugger und > spielst das selber durch? Da kannst du dir im Detail anschauen, was da > passiert. Brauchst nur eine wenig Ausdauer. ;o) Auf https://learn.microsoft.com/de-de/sysinternals/downloads/sysinternals-suite gibt es die dafür notwenigen Tools.
Linux unterstützt mehrere Dateiformate (/usr/src/linux/fs/binfmt_xxx.c). Bei ELF-Dateien wird die Datei vom Kernel in den Speicher geladen. Im Header der Datei steht ein "Interpreter" (objdump -sj.interp /bin/echo, normalerweise "ld-linux.so.X", ein statisches Library). Dieser "Interpreter" wird auch noch geladen und dann gestartet (nicht das Programm selbst). Der kümmert sich dann um den Rest (man ld.so). Dessen Arbeit kann man mit "strace /bin/echo" beobachten. Das Laden der abhängigen Libraries findet also außerhalb des Kernels statt. Dieser Loader steht der Anwendung selbst auch zur Verfügung (man dlopen). Damit kann man bei Bedarf weitere Libraries nachladen (Beispiel in der man-page).
:
Bearbeitet durch User
Foobar schrieb: > Dieser "Interpreter" wird auch noch geladen und dann gestartet (nicht das > Programm selbst). Den kann man übrigens auch manuell aufrufen:
1 | $ /lib64/ld-linux-x86-64.so.2 --help |
2 | Usage: /lib64/ld-linux-x86-64.so.2 [OPTION]... EXECUTABLE-FILE [ARGS-FOR-PROGRAM...] |
3 | You have invoked 'ld.so', the program interpreter for dynamically-linked |
4 | ELF programs. Usually, the program interpreter is invoked automatically |
5 | when a dynamically-linked executable is started. |
6 | |
7 | You may invoke the program interpreter program directly from the command |
8 | line to load and run an ELF executable file; this is like executing that |
9 | file itself, but always uses the program interpreter you invoked, |
10 | instead of the program interpreter specified in the executable file you |
11 | run. Invoking the program interpreter directly provides access to |
12 | additional diagnostics, and changing the dynamic linker behavior without |
13 | setting environment variables (which would be inherited by subprocesses). |
14 | |
15 | --list list all dependencies and how they are resolved |
16 | --verify verify that given object really is a dynamically linked |
17 | object we can handle |
18 | --inhibit-cache Do not use /etc/ld.so.cache |
19 | --library-path PATH use given PATH instead of content of the environment |
20 | variable LD_LIBRARY_PATH |
21 | --glibc-hwcaps-prepend LIST |
22 | search glibc-hwcaps subdirectories in LIST |
23 | --glibc-hwcaps-mask LIST |
24 | only search built-in subdirectories if in LIST |
25 | --inhibit-rpath LIST ignore RUNPATH and RPATH information in object names |
26 | in LIST |
27 | --audit LIST use objects named in LIST as auditors |
28 | --preload LIST preload objects named in LIST |
29 | --argv0 STRING set argv[0] to STRING before running |
30 | --list-tunables list all tunables with minimum and maximum values |
31 | --list-diagnostics list diagnostics information |
32 | --help display this help and exit |
33 | --version output version information and exit |
34 | |
35 | This program interpreter self-identifies as: /lib64/ld-linux-x86-64.so.2 |
36 | |
37 | Shared library search path: |
38 | (libraries located via /etc/ld.so.cache) |
39 | /lib/x86_64-linux-gnu (system search path) |
40 | /usr/lib/x86_64-linux-gnu (system search path) |
41 | /lib (system search path) |
42 | /usr/lib (system search path) |
43 | |
44 | Subdirectories of glibc-hwcaps directories, in priority order: |
45 | x86-64-v4 |
46 | x86-64-v3 (supported, searched) |
47 | x86-64-v2 (supported, searched) |
48 | |
49 | Legacy HWCAP subdirectories under library search path directories: |
50 | haswell (AT_PLATFORM; supported, searched) |
51 | tls (supported, searched) |
52 | avx512_1 |
53 | x86_64 (supported, searched) |
Mit der Funktion **dlopen**() kann vom Programm aus eine dynamische Library geladen werden. https://man7.org/linux/man-pages/man3/dlopen.3.html Codebeispiel:
1 | #include <stdio.h> |
2 | #include <dlfcn.h> |
3 | |
4 | int main() { |
5 | void *library_handle; |
6 | int (*add)(int, int); |
7 | int (*subtract)(int, int); |
8 | |
9 | // Dynamische Bibliothek laden
|
10 | library_handle = dlopen("./libmath.so", RTLD_LAZY); |
11 | if (!library_handle) { |
12 | fprintf(stderr, "Fehler beim Laden der Bibliothek: %s\n", dlerror()); |
13 | return 1; |
14 | }
|
15 | |
16 | // Symbole auflösen
|
17 | add = dlsym(library_handle, "add"); |
18 | subtract = dlsym(library_handle, "subtract"); |
19 | if (!add || !subtract) { |
20 | fprintf(stderr, "Fehler beim Auflösen der Symbole: %s\n", dlerror()); |
21 | dlclose(library_handle); |
22 | return 1; |
23 | }
|
24 | |
25 | // Funktionen aufrufen
|
26 | int result = add(5, 3); |
27 | printf("Ergebnis der Addition: %d\n", result); |
28 | |
29 | result = subtract(10, 4); |
30 | printf("Ergebnis der Subtraktion: %d\n", result); |
31 | |
32 | // Dynamische Bibliothek entladen
|
33 | dlclose(library_handle); |
34 | |
35 | return 0; |
36 | }
|
:
Bearbeitet durch User
Dankeschön. So detaillierte Antworten hatte ich gar nicht erwartet.
Der Vollständigkeit halber: Unter Windows werden DLLs, die als Importreferenzen im Programmheader des *.exe-Files aufgelistet werden, vom zum Betriebssystem gehörenden Programmlader geladen. Das ist das, was "c-hater" als "quasi-statisch" bezeichnet hat. Im Programmheader stehen die zu verwendenden DLLs und die daraus zu verwendenen Funktionen ("Prozedureinsprungspunkte" im schönen Microsoft-Deutsch). Wird so eine DLL nicht gefunden oder wird eine Funktion in der referenzierten DLLs nicht gefunden, gibt es eine allseits bekannte und verhasste Fehlermeldung. Erst wenn alle im Programmheader aufgelisteten Referenzen aufgelöst werden können, wird der eigentliche Programmcode ausgeführt. Mit Tools wie dem zu manchen Compilern gehörendenden "dumpbin" lassen sich die Importreferenzen auflisten, der "dependency walker" zeigt auch noch Abhängigkeiten von DLLs untereinander auf. Die Bezeichnung "quasi-statisch", die "c-hater" verwendete, rührt daher, daß Programme, die ihre DLL-Referenzen vom Programmlader aufgelöst haben möchten, so geschrieben werden, als würden sie herkömmliche statische Libraries verwenden. Beim Linken aber werden dem Programm sogenannte "import-Libraries" untergeschoben, die dafür sorgen, daß die verwendeten Symbole als Importreferenzen im Programmheader eingetragen werden. Der Gegenentwurf ist dynamisches Laden unter Kontrolle des bereits laufenden Programmes, das erfolgt mit der Win32-Funktion LoadLibrary. Zum Bestimmen von Funktionspointern dient die Win32-Funktion GetProcAddress. Damit lassen sich Funktionalitäten wie "Plugins" realisieren, d.h. zur Laufzeit (und nur bei Vorhandensein) geladener Code, der ein Programm erweitern kann, aber nicht muss. Das von Rolf beschriebene Verfahren mit ld.so entstammt in seinen Ursprüngen SunOS 4.0 (1988), und ist damit ähnlich alt wie das unter Windows verwendete Verfahren, das seine Wurzeln tief in den 16-Bit-Zeiten noch vor Windows 3.x hat. SunOS war schon immer ein 32-Bit-System, und konnte daher auf mehr und leistungsfähigere Ressourcen zurückgreifen als das ursprünglich 16-bittige Windows, das bis einschließlich Version 3.0 auf 8088-Systemen wie dem IBM PC/XT laufen konnte, d.h. mit gerade mal 640 kiB RAM ... Das erklärt die unterschiedlich flexiblen Ansätze des Programmladers.
Harald K. schrieb: > Der Vollständigkeit halber: Der weiteren Vollständigkeit halber: Auch unter Windows lassen sich dll's zur Laufzeit laden. Der Ablauf entspricht dabei im Prinzip dem von Linux: dll laden, Symbol auflösen, benutzen, wenn nicht mehr gebraucht, alles wieder schließen (und unloaden), fertig. Oliver
Oliver S. schrieb: > Der weiteren Vollständigkeit halber: > > Auch unter Windows lassen sich dll's zur Laufzeit laden. Das hat er doch bereits sehr konkret beschrieben: Harald K. schrieb: > Der Gegenentwurf ist dynamisches Laden unter Kontrolle des bereits > laufenden Programmes, das erfolgt mit der Win32-Funktion LoadLibrary. > Zum Bestimmen von Funktionspointern dient die Win32-Funktion > GetProcAddress.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.