Quel appel système est utilisé pour charger les bibliothèques sous Linux?

23

Dans les stracesorties, les chemins d'accès aux bibliothèques que les exécutables appellent sont dans les appels open(). S'agit-il de l'appel système utilisé par les exécutables liés dynamiquement? Et alors dlopen()? open()n'est pas un appel que j'aurais deviné jouerait un rôle dans l'exécution des programmes.

Melab
la source

Réponses:

33

dlopenn'est pas un appel système, c'est une fonction de bibliothèque dans la bibliothèque libdl . Seuls les appels système s'affichent strace.

Sous Linux et sur de nombreuses autres plates-formes (en particulier celles qui utilisent le format ELF pour les exécutables), dlopenest implémentée en ouvrant la bibliothèque cible avec open()et en la mappant en mémoire avec mmap(). mmap()est vraiment la partie critique ici, c'est ce qui intègre la bibliothèque dans l'espace d'adressage du processus, afin que le CPU puisse exécuter son code. Mais vous devez avoir open()le dossier avant de pouvoir le mmap()faire!

Celada
la source
2
"mmap () est vraiment la partie critique": Et puis l'éditeur de liens dynamique doit faire les relocalisations, l'initialisation et ainsi de suite (mais cela n'est pas vu au niveau de l'appel système).
ysdx
1
Étant donné que le chargement des bibliothèques est effectué par une fonction de bibliothèque, je pense qu'il est pertinent d'ajouter que l'exécutable lui-même et ld-linuxsont mappés par le noyau dans le cadre de l' execveappel système.
kasperd
mmap selon cette réponse. Notez également qu'après avoir "ouvert" chaque bibliothèque, certains (832) octets sont lus avant l'appel mmap, je présume de vérifier que la bibliothèque est valide.
Johan
@kasperd Le noyau Linux connaît-il donc le chargeur dynamique? L'appelle-t-il lorsque l'application est exécutée? Ou l'application elle-même fait-elle cela? Dans ce dernier cas, comment un autre exécutable a-t-il accès à la mémoire de l'application?
Melab
@Melab Oui, le noyau est au courant de l'éditeur de liens dynamique. Le noyau lira le chemin vers l'éditeur de liens dynamique depuis l'en-tête de l'exécutable. Et le noyau mappera les deux en mémoire. Je ne sais pas si le point d'entrée vers lequel le contrôle de transfert du noyau est d'abord dans l'éditeur de liens ou exécutable. Si je l'implémentais, j'aurais probablement le contrôle de transfert du noyau vers un point d'entrée dans l'éditeur de liens avec une adresse de retour sur la pile pointant vers le point d'entrée de l'exécutable.
kasperd
5

dlopen n'a rien à voir avec les bibliothèques partagées comme vous les pensez. Il existe deux méthodes de chargement d'un objet partagé:

  1. Vous dites à l'éditeur de liens au moment de la compilation (ld, bien qu'il soit généralement appelé via le compilateur) que vous souhaitez utiliser les fonctions d'une bibliothèque partagée particulière. Avec cette approche, vous devez connaître le nom de la bibliothèque lors de l'exécution de l'éditeur de liens au moment de la compilation, mais vous pouvez appeler les fonctions de la bibliothèque comme si elles étaient liées statiquement dans votre programme. Lorsque l'application est exécutée, l'éditeur de liens dynamique d'exécution (ld.so) sera appelé juste avant l'appel de la mainfonction et configurera l'espace de processus de l'application de sorte que l'application trouve les fonctions de la bibliothèque. Cela implique d' open()ingérer le lubrary, puis de l'intégrer, mmap()suivi de la configuration de certaines tables de recherche.
  2. Vous dites l'éditeur de liens de compilation que vous voulez faire le lien avec libdl, à partir de laquelle vous ensuite ( en utilisant la première méthode) peut appeler dlopen()etdlsym()les fonctions. Avec dlopen, vous obtenez un handle vers la bibliothèque, que vous pouvez ensuite utiliser avec dlsym pour recevoir un pointeur de fonction vers une fonction particulière. Cette méthode est beaucoup plus compliquée pour le programmeur que la première méthode (puisque vous devez faire l'installation manuellement, plutôt que l'éditeur de liens le fait automatiquement pour vous), et elle est également plus fragile (puisque vous n'obtenez pas la compilation -time vérifie que vous appelez des fonctions avec les types d'arguments corrects lors de la première méthode), mais l'avantage est que vous pouvez décider quel objet partagé charger à l'exécution (ou même le charger), ce qui rend c'est une interface destinée à la fonctionnalité de type plugin. Enfin, l'interface dlopen est également moins portable que dans l'autre sens, car sa mécanique dépend de l'implémentation exacte de l'éditeur de liens dynamique (d'où libtool'slibltdl, qui tente de résumer ces différences).
Wouter Verhelst
la source
intéressant; donc les bibliothèques chargées dynamiquement sont mieux appelées bibliothèques liées dynamiquement, car le chargement de fichiers binaires en mémoire n'est pas la partie difficile, ce qui donne un sens aux adresses utilisées. Lorsque je demande de charger une bibliothèque dynamique, je demande en fait de lier (ou de dissocier) la bibliothèque dans (ou hors) de mon espace d'adressage.
Dmitry
4

Aujourd'hui, la plupart des systèmes d'exploitation utilisent la méthode des bibliothèques partagées introduite fin 1987 par SunOS-4.0. Cette méthode est basée sur le mappage de la mémoire via mmap ().

Étant donné qu'au début des années 1990, Sun a même fait don de l'ancien code basé sur a.out (Solaris à l'époque était déjà basé sur ELF) aux gens de FreeBSD et que ce code a ensuite été transféré à de nombreux autres systèmes (y compris Linux) , vous comprendrez peut-être pourquoi il n'y a pas de grande différence entre les plates-formes.

schily
la source
3

ltrace -Sl'analyse d'un exemple minimal montre qu'il mmapest utilisé dans la glibc 2.23

Dans la glibc 2.23, Ubuntu 16.04, fonctionnant latrace -Ssur un programme minimal qui utilise dlopenavec:

ltrace -S ./dlopen.out

spectacles:

dlopen("libcirosantilli_ab.so", 1 <unfinished ...>
SYS_open("./x86_64/libcirosantilli_ab.so", 524288, 06267650550)      = -2
SYS_open("./libcirosantilli_ab.so", 524288, 06267650550)             = 3
SYS_read(3, "\177ELF\002\001\001", 832)                              = 832
SYS_brk(0)                                                           = 0x244c000
SYS_brk(0x246d000)                                                   = 0x246d000
SYS_fstat(3, 0x7fff42f9ce30)                                         = 0
SYS_getcwd("/home/ciro/bak/git/cpp-cheat"..., 128)                   = 54
SYS_mmap(0, 0x201028, 5, 2050)                                       = 0x7f1c323fe000
SYS_mprotect(0x7f1c323ff000, 2093056, 0)                             = 0
SYS_mmap(0x7f1c325fe000, 8192, 3, 2066)                              = 0x7f1c325fe000
SYS_close(3)                                                         = 0
SYS_mprotect(0x7f1c325fe000, 4096, 1)                                = 0

nous voyons donc immédiatement que dlopenappelle open+ mmap.

L' ltraceoutil génial trace à la fois les appels de bibliothèque et les appels système, et est donc parfait pour examiner ce qui se passe dans ce cas.

Une analyse plus approfondie montre que openrenvoie le descripteur de fichier 3(libre suivant après stdin, out et err).

readutilise ensuite ce descripteur de fichier, mais TODO pourquoi mmaples arguments sont limités à quatre, et nous ne pouvons pas voir quel fd a été utilisé car il s'agit du 5ème argument . straceconfirme comme prévu celui 3-là, et l'ordre de l'univers est rétabli.

Les âmes courageuses peuvent également s'aventurer dans le code glibc, mais je n'ai pas pu trouver l' mmapafter après une grep rapide et je suis paresseux.

Testé avec cet exemple minimal avec build passe-partout sur GitHub .

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
la source
2

stracerapports sur les appels système (c'est-à-dire les fonctions implémentées directement par le noyau). Les bibliothèques dynamiques ne sont pas une fonction du noyau; dlopenfait partie de la bibliothèque C, pas du noyau. L'implémentation de dlopenwill call open(qui est un appel système) pour ouvrir le fichier de bibliothèque afin qu'il puisse être lu.

cjm
la source
5
Les appels de bibliothèque peuvent être vus en utilisant ltrace.
kasperd
@kasperd ltrace -Sest parfait pour analyser cela car il montre également les appels système: unix.stackexchange.com/a/462710/32558
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件