Wie werden dyn. Libraries eingebunden?

Gast #7420201
Lesenswert?

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.

#7420386
Lesenswert?

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)

#7420425
Lesenswert?

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).

#7420705
Lesenswert?

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)
#7420744
Lesenswert?

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
}
#7422918
Lesenswert?

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.

#7424795
Lesenswert?

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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren