web-dev-qa-db-fra.com

Pourquoi ld a-t-il besoin de -rpath-link pour lier un exécutable à un so qui en a besoin d'un autre?

Je suis juste curieux ici. J'ai créé un objet partagé:

gcc -o liba.so -fPIC -shared liba.c

Et un autre objet partagé, qui se relie à l'ancien:

gcc -o libb.so -fPIC -shared libb.c liba.so

Maintenant, lors de la création d'un exécutable qui établit un lien avec libb.so, Je vais devoir spécifier -rpath-link vers ld pour qu'il puisse trouver liba.so Lorsqu'il découvre que libb.so En dépend:

gcc -o test -Wl,-rpath-link,./ test.c libb.so

sinon ld se plaindra.

Pourquoi est-ce que ld DOIT être capable de localiser liba.so Lors de la liaison de test? Parce que pour moi, il ne semble pas que ld fasse autre chose que de confirmer l'existence de liba.so. Par exemple, exécuter readelf --dynamic ./test Ne répertorie que libb.so Selon les besoins, donc je suppose que l'éditeur de liens dynamique doit découvrir la dépendance libb.so -> liba.so Par lui-même et faire sa propre recherche de liba.so.

Je suis sur une plate-forme GNU/Linux x86-64, et la routine main () dans test appelle une fonction dans libb.so Qui à son tour appelle une fonction dans liba.so .

34
Troels Folke

Pourquoi est-ce que ld DOIT être capable de localiser liba.so Lors de la liaison de test? Parce que pour moi, il ne semble pas que ld fasse autre chose que de confirmer l'existence de liba.so. Par exemple, exécuter readelf --dynamic ./test Ne répertorie que libb.so Selon les besoins, donc je suppose que l'éditeur de liens dynamique doit découvrir la dépendance libb.so -> liba.so Par lui-même et faire sa propre recherche de liba.so.

Eh bien, si je comprends bien le processus de liaison, ld n'a en fait pas besoin de localiser même libb.so. Il pourrait simplement ignorer toutes les références non résolues dans test en espérant que l'éditeur de liens dynamiques les résoudrait lors du chargement de libb.so Au moment de l'exécution. Mais si ld fonctionnait de cette manière, de nombreuses erreurs de "référence non définie" ne seraient pas détectées au moment de la liaison, mais seraient trouvées lors de la tentative de chargement de test en exécution. Donc ld vérifie simplement que tous les symboles non trouvés dans test lui-même peuvent vraiment être trouvés dans les bibliothèques partagées dont test dépend. Donc, si le programme test a une erreur de "référence non définie" (une variable ou une fonction non trouvée dans test lui-même et ni dans libb.so), Cela devient évident au moment du lien, pas seulement lors de l'exécution. Par conséquent, un tel comportement n'est qu'un contrôle d'esprit supplémentaire.

Mais ld va encore plus loin. Lorsque vous liez test, ld vérifie également que toutes les références non résolues dans libb.so Se trouvent dans les bibliothèques partagées dont libb.so Dépend (dans notre cas libb.so dépend de liba.so, il nécessite donc que liba.so soit localisé au moment du lien). Eh bien, en fait ld a déjà fait cette vérification, quand il liait libb.so. Pourquoi fait-il cette vérification une deuxième fois ... Peut-être que les développeurs de ld ont trouvé cette double vérification utile pour détecter les dépendances brisées lorsque vous essayez de lier votre programme à une bibliothèque obsolète qui pourrait être chargée à l'époque quand il était lié, mais maintenant il ne peut pas être chargé car les bibliothèques dont il dépend sont mises à jour (par exemple, liba.so a été retravaillé plus tard et une partie de la fonction en a été supprimée).

[~ # ~] upd [~ # ~]

Je viens de faire quelques expériences. Il semble que mon hypothèse "en fait ld a déjà fait cette vérification, quand il liait libb.so" est faux.

Supposons que le liba.c Ait le contenu suivant:

int liba_func(int i)
{
    return i + 1;
}

et libb.c a le suivant:

int liba_func(int i);
int liba_nonexistent_func(int i);

int libb_func(int i)
{
    return liba_func(i + 1) + liba_nonexistent_func(i + 2);
}

et test.c

#include <stdio.h>

int libb_func(int i);

int main(int argc, char *argv[])
{
    fprintf(stdout, "%d\n", libb_func(argc));
    return 0;
}

Lors de la liaison de libb.so:

gcc -o libb.so -fPIC -shared libb.c liba.so

l'éditeur de liens ne génère aucun message d'erreur que liba_nonexistent_func ne peut pas être résolu, mais génère simplement en silence la bibliothèque partagée cassée libb.so. Le comportement est le même que pour une bibliothèque statique (libb.a) Avec ar qui ne résout pas non plus les symboles de la bibliothèque générée.

Mais lorsque vous essayez de lier test:

gcc -o test -Wl,-rpath-link=./ test.c libb.so

vous obtenez l'erreur:

libb.so: undefined reference to `liba_nonexistent_func'
collect2: ld returned 1 exit status

La détection d'une telle erreur ne serait pas possible si ld n'a pas analysé récursivement toutes les bibliothèques partagées. Il semble donc que la réponse à la question soit la même que celle que j'ai dit ci-dessus: ld a besoin rpath-link afin de s'assurer que l'exécutable lié peut être chargé plus tard par dynamique chargé. Juste une vérification de santé mentale.

UPD2

Il serait logique de vérifier les références non résolues le plus tôt possible (lors de la liaison de libb.so), Mais ld pour certaines raisons ne le fait pas. C'est probablement pour permettre de faire des dépendances cycliques pour les bibliothèques partagées.

liba.c Peut avoir l'implémentation suivante:

int libb_func(int i);

int liba_func(int i)
{
    int (*func_ptr)(int) = libb_func;
    return i + (int)func_ptr;
}

Donc liba.so Utilise libb.so Et libb.so Utilise liba.so (Mieux vaut ne jamais faire une telle chose). Cela compile et fonctionne avec succès:

$ gcc -o liba.so -fPIC -shared liba.c
$ gcc -o libb.so -fPIC -shared libb.c liba.so
$ gcc -o test test.c -Wl,-rpath=./ libb.so
$ ./test
-1217026998

Bien que readelf indique que liba.so N'a pas besoin de libb.so:

$ readelf -d liba.so | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
$ readelf -d libb.so | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [liba.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]

Si ld vérifiait les symboles non résolus lors de la liaison d'une bibliothèque partagée, la liaison de liba.so Ne serait pas possible.

Notez que j'ai utilisé - rpath clé au lieu de - rpath-link . La différence est que rpath-link est utilisé au moment de la liaison uniquement pour vérifier que tous les symboles de l'exécutable final peuvent être résolus, alors que rpath intègre réellement le chemin que vous spécifiez comme paramètre dans l'ELF:

$ readelf -d test | grep RPATH
 0x0000000f (RPATH)                      Library rpath: [./]

Il est donc désormais possible d'exécuter test si les bibliothèques partagées (liba.so Et libb.so) Se trouvent dans votre répertoire de travail actuel (./). Si vous venez d'utiliser rpath-link il n'y aurait pas une telle entrée dans test ELF, et vous devriez ajouter le chemin d'accès aux bibliothèques partagées au /etc/ld.so.conf ou à la variable d'environnement LD_LIBRARY_PATH.

UPD3

Il est en fait possible de vérifier les symboles non résolus lors de la liaison de la bibliothèque partagée, l'option --no-undefined Doit être utilisée pour cela:

$ gcc -Wl,--no-undefined -o libb.so -fPIC -shared libb.c liba.so
/tmp/cc1D6uiS.o: In function `libb_func':
libb.c:(.text+0x2d): undefined reference to `liba_nonexistent_func'
collect2: ld returned 1 exit status

J'ai également trouvé un bon article qui clarifie de nombreux aspects de la liaison de bibliothèques partagées qui dépendent d'autres bibliothèques partagées: Mieux comprendre la résolution des dépendances secondaires Linux avec des exemples .

25
anton_rh

Vous système, par ld.so.conf, ld.so.conf.d, et l'environnement système, LD_LIBRARY_PATH, etc., fournit les chemins de recherche de bibliothèque à l'échelle du système qui sont complétés par les bibliothèques installées via pkg-config informations et autres lorsque vous construisez avec des bibliothèques standard. Lorsqu'une bibliothèque réside dans un chemin de recherche défini, les chemins de recherche de bibliothèque standard sont suivis automatiquement permettant de trouver toutes les bibliothèques requises.

Il n'y a pas de standard chemin de recherche de bibliothèque d'exécution pour les bibliothèques partagées personnalisées que vous créez vous-même. Vous spécifiez le chemin de recherche de vos bibliothèques via le -L/path/to/lib désignation pendant la compilation et la liaison. Pour les bibliothèques situées dans des emplacements non standard, le chemin de recherche de bibliothèque peut être éventuellement placé dans l'en-tête de votre exécutable (en-tête ELF) au moment de la compilation afin que votre exécutable puisse trouver les bibliothèques nécessaires.

rpath fournit un moyen d'incorporer votre chemin de recherche de bibliothèque d'exécution personnalisé dans l'en-tête ELF afin que vos bibliothèques personnalisées puissent également être trouvées sans avoir à spécifier le chemin de recherche chaque fois qu'il est utilisé. Cela s'applique également aux bibliothèques qui dépendent des bibliothèques. Comme vous l'avez constaté, non seulement l'ordre dans lequel vous spécifiez les bibliothèques sur la ligne de commande est important, mais vous devez également fournir le chemin de recherche de bibliothèque au moment de l'exécution, ou rpath, des informations pour chaque bibliothèque dépendante avec laquelle vous effectuez la liaison, de sorte que l'en-tête contient l'emplacement des bibliothèques all nécessaires à l'exécution.

Addemdum from Comments

Ma question est principalement pourquoi ld doit "essayer automatiquement de localiser la bibliothèque partagée" (liba.so) et "l'inclure dans le lien".

C'est simplement la façon dont ld fonctionne. De man ld "L'option -rpath est également utilisée lors de la localisation objets partagés nécessaires aux objets partagés explicitement inclus dans le lien ... Si -rpath n'est pas utilisé lors de la liaison d'un exécutable ELF, le contenu de la variable d'environnement "LD_RUN_PATH" sera utilisée si elle est définie. " Dans votre cas, liba ne se trouve pas dans le LD_RUN_PATH so ld aura besoin d'un moyen de localiser liba pendant la compilation de votre exécutable, soit avec rpath (décrit ci-dessus), soit en lui fournissant un chemin de recherche explicite.

Secondairement ce que "l'inclure dans le lien" signifie vraiment. Pour moi, il semble que cela signifie simplement: "confirmer son existence" (de liba.so), car les en-têtes ELF de libb.so ne sont pas modifiés (ils avaient déjà une balise NEEDED contre liba.so), et les en-têtes de l'exécutable ne déclarent que libb. afin que BESOIN. Pourquoi ld se soucie-t-il de trouver liba.so, ne peut-il pas simplement laisser la tâche à l'éditeur de liens au moment de l'exécution?

Non, revenons à la sémantique de ld. Afin de produire un "bon lien", ld doit être capable de localiser toutes les bibliothèques dépendantes. ld ne peut pas assurer un bon lien sinon. L'éditeur de liens d'exécution doit trouver et charger, pas seulement trouver les bibliothèques partagées nécessaires à un programme. ld ne peut garantir que cela se produira à moins que ld lui-même ne puisse localiser toutes les bibliothèques partagées nécessaires au moment où le programme est lié.

7
David C. Rankin

Je suppose que vous devez savoir quand utiliser -rpath option et -rpath-link option. Je cite d'abord ce que man ld spécifié:

  1. La différence entre -rpath et -rpath-link est que les répertoires spécifiés par les options -rpath sont inclus dans l'exécutable et utilisés au moment de l'exécution, tandis que l'option -rpath-link n'est effective qu'au moment de la liaison. La recherche de -rpath de cette manière n'est prise en charge que par les éditeurs de liens natifs et croisés qui ont été configurés avec l'option --with-sysroot.

Vous devez faire la distinction entre le temps de liaison et le temps d'exécution. Selon la réponse de votre anton_rh acceptée, la vérification des symboles non définis n'est pas activée lors de la compilation et de la liaison des bibliothèques partagées ou des bibliothèques statiques, mais ACTIVÉE lors de la compilation et de la liaison des exécutables. (Cependant, veuillez noter qu'il existe des fichiers qui sont des bibliothèques partagées ainsi que des exécutables, par exemple, ld.so. Tapez man ld.so pour explorer cela, et je ne sais pas si la vérification des symboles non définis est activée lors de la compilation de ces fichiers de types "doubles").

Alors -rpath-link est utilisé dans la vérification du temps de liaison, et -rpath est utilisé pour la liaison et l'exécution car rpath est incorporé dans les en-têtes ELF. Mais vous devez faire attention à ce que -rpath-link l'option remplacera -rpath option pendant la liaison si les deux sont spécifiés.

Mais encore, pourquoi -rpath-option et -rpath option? Je pense qu'ils sont utilisés pour éliminer les "chevauchements". Voir ceci Mieux comprendre la résolution des dépendances secondaires Linux avec des exemples. , utilisez simplement ctrl + F pour accéder aux contenus liés au "chevauchement". Vous devez vous concentrer sur les raisons pour lesquelles le "chevauchement" est mauvais, et en raison de la méthode que nous adoptons pour éviter le "chevauchement", l'existence de ld options -rpath-link et -rpath est raisonnable: nous omettons délibérément certaines bibliothèques dans les commandes de compilation et de liaison pour éviter le "chevauchement", et à cause de l'omission, ld need -rpath-link ou -rpath pour localiser ces bibliothèques omises.

5
Han XIAO

Vous ne dites pas réellement à ld (lorsque vous liez libb contre liba) liba est - seulement que c'est une dépendance. Un rapide ldd libb.so vous montrera qu'il ne peut pas trouver liba.

Étant donné que ces bibliothèques ne sont probablement pas dans votre chemin de recherche de l'éditeur de liens, vous obtiendrez une erreur de l'éditeur de liens lorsque vous liez l'exécutable. Gardez à l'esprit que lorsque vous liez la liba elle-même, la fonction dans libb est toujours non résolue, mais le comportement par défaut de ld est de ne pas se soucier des symboles non résolus dans les DSO jusqu'à ce que vous liez l'exécutable final.

1
Mark Nunberg