Option GCC -fPIC

437

J'ai lu au sujet des options de GCC pour les conventions de génération de code , mais je n'ai pas pu comprendre ce que fait "Générer un code indépendant de la position (PIC)". Veuillez donner un exemple pour m'expliquer ce que cela signifie.

Narek
la source
25
Clang utilise également -fPIC.
disparu le
3
Connexes: -fpie: stackoverflow.com/questions/2463150/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

526

Position Independent Code signifie que le code machine généré ne dépend pas d'être situé à une adresse spécifique pour fonctionner.

Par exemple, les sauts seraient générés comme relatifs plutôt qu'absolus.

Pseudo-assemblage:

PIC: Cela fonctionnerait si le code était à l'adresse 100 ou 1000

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP

Non-PIC: cela ne fonctionnera que si le code est à l'adresse 100

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP

EDIT: En réponse au commentaire.

Si votre code est compilé avec -fPIC, il peut être inclus dans une bibliothèque - la bibliothèque doit pouvoir être déplacée de son emplacement préféré en mémoire vers une autre adresse, il pourrait y avoir une autre bibliothèque déjà chargée à l'adresse que votre bibliothèque préfère.

Erik
la source
36
Cet exemple est clair, mais en tant qu'utilisateur, quelle sera la différence si je crée un fichier labrary partagé (.so) sans l'option? Y a-t-il des cas où sans -fPIC ma lib sera invalide?
Narek
16
Oui, la création d'une bibliothèque partagée qui n'est pas PIC pourrait être une erreur.
John Zwinck
92
Pour être plus précis, la bibliothèque partagée est censée être partagée entre les processus, mais il n'est pas toujours possible de charger la bibliothèque à la même adresse dans les deux. Si le code n'était pas indépendant de la position, chaque processus nécessiterait sa propre copie.
Simon Richter
19
@Narek: l'erreur se produit si un processus souhaite charger plus d'une bibliothèque partagée à la même adresse virtuelle. Étant donné que les bibliothèques ne peuvent pas prédire quelles autres bibliothèques pourraient être chargées, ce problème est inévitable avec le concept traditionnel de bibliothèque partagée. L'espace d'adressage virtuel n'aide pas ici.
Philipp
6
Vous pouvez omettre -fPIClors de la compilation d'un programme ou d'une bibliothèque statique, car un seul programme principal existera dans un processus, donc aucune relocalisation d'exécution n'est jamais nécessaire. Sur certains systèmes, les programmes sont toujours indépendants de leur position pour une sécurité renforcée.
Simon Richter
61

Je vais essayer d'expliquer ce qui a déjà été dit de manière plus simple.

Chaque fois qu'une bibliothèque partagée est chargée, le chargeur (le code sur le système d'exploitation qui charge n'importe quel programme que vous exécutez) change certaines adresses dans le code en fonction de l'endroit où l'objet a été chargé.

Dans l'exemple ci-dessus, le "111" dans le code non PIC est écrit par le chargeur la première fois qu'il a été chargé.

Pour les objets non partagés, vous voudrez peut-être que ce soit comme ça parce que le compilateur peut faire quelques optimisations sur ce code.

Pour un objet partagé, si un autre processus veut «se lier» à ce code, il doit le lire aux mêmes adresses virtuelles ou le «111» n'aura aucun sens. mais cet espace virtuel peut déjà être utilisé dans le deuxième processus.

Roee Gavirel
la source
Whenever a shared lib is loaded, the loader changes some addresses in the code depending on where the object was loaded to.Je pense que ce n'est pas correct s'il est compilé avec -fpic et la raison pour laquelle -fpic existe, c'est-à-dire pour des raisons de performances ou parce que vous avez un chargeur qui n'est pas en mesure de se déplacer ou parce que vous avez besoin de plusieurs copies à différents endroits ou pour de nombreuses autres raisons.
robsn
Pourquoi ne pas toujours utiliser -fpic?
Jay
1
@Jay - car il faudra un calcul supplémentaire (l'adresse de la fonction) pour chaque appel de fonction. Donc, en termes de performances, s'il n'est pas nécessaire, il vaut mieux ne pas l'utiliser.
Roee Gavirel
45

Le code intégré aux bibliothèques partagées doit normalement être un code indépendant de la position, afin que la bibliothèque partagée puisse être facilement chargée à (plus ou moins) n'importe quelle adresse en mémoire. L' -fPICoption garantit que GCC produit un tel code.

Jonathan Leffler
la source
Pourquoi une bibliothèque partagée ne serait-elle pas chargée à une adresse en mémoire sans avoir le -fPICdrapeau activé? n'est-il pas lié au programme? lorsque le programme est en cours d'exécution, le système d'exploitation le télécharge en mémoire. Suis-je en train de manquer quelque chose?
Tony Tannous
1
L' -fPICindicateur est-il utilisé pour garantir que cette bibliothèque peut être chargée sur n'importe quelle adresse virtuelle dans le processus qui la relie? désolé pour les doubles commentaires 5 minutes écoulées ne peuvent pas modifier le précédent.
Tony Tannous
1
Distinguer entre la création de la bibliothèque partagée (création libwotnot.so) et la liaison avec elle ( -lwotnot). Lors de la liaison, vous n'avez pas besoin de vous inquiéter -fPIC. Auparavant, lors de la création de la bibliothèque partagée, vous deviez vous assurer -fPICque tous les fichiers objets devaient être intégrés à la bibliothèque partagée. Les règles peuvent avoir changé parce que les compilateurs construisent avec du code PIC par défaut, de nos jours. Donc, ce qui était critique il y a 20 ans, et qui aurait pu être important il y a 7 ans, l'est moins de nos jours, je crois. Les adresses en dehors du noyau o / s sont «toujours» des adresses virtuelles ».
Jonathan Leffler
Donc, auparavant, vous deviez ajouter le -fPIC. Sans passer cet indicateur, le code généré lors de la construction du .so doit être chargé sur des adresses virtuelles spécifiques qui pourraient être utilisées?
Tony Tannous
1
Oui, car si vous n'avez pas utilisé le drapeau PIC, le code n'était pas relocalisable de manière fiable. Des choses comme ASLR (randomisation de la disposition de l'espace d'adressage) ne sont pas possibles si le code n'est pas PIC (ou, au moins, sont si difficiles à atteindre qu'elles sont effectivement impossibles).
Jonathan Leffler
21

Plus loin ...

Chaque processus a le même espace d'adressage virtuel (si la randomisation de l'adresse virtuelle est arrêtée à l'aide d'un indicateur dans le système d'exploitation Linux) (pour plus de détails, désactivez et réactivez la randomisation de la disposition de l'espace d'adressage uniquement pour moi )

Donc, si c'est un exe sans lien partagé (scénario hypothétique), nous pouvons toujours donner la même adresse virtuelle à la même instruction asm sans aucun dommage.

Mais lorsque nous voulons lier un objet partagé à l'exe, nous ne sommes pas sûrs de l'adresse de début affectée à l'objet partagé car cela dépendra de l'ordre dans lequel les objets partagés ont été liés. Cela étant dit, l'instruction asm à l'intérieur de .so aura toujours adresse virtuelle différente selon le processus auquel elle est liée.

Ainsi, un processus peut donner l'adresse de début à .so comme 0x45678910 dans son propre espace virtuel et un autre processus peut donner en même temps l'adresse de début de 0x12131415 et s'ils n'utilisent pas l'adressage relatif, .so ne fonctionnera pas du tout.

Ils doivent donc toujours utiliser le mode d'adressage relatif et donc l'option fpic.

Ritesh
la source
1
Merci pour l'explication de l'adr virtuel.
Hot.PxL
2
Quelqu'un peut-il expliquer comment ce n'est pas un problème avec une bibliothèque statique, pourquoi vous n'avez pas besoin d'utiliser -fPIC sur une bibliothèque statique? Je comprends que la liaison est effectuée au moment de la compilation (ou juste après), mais si vous avez 2 bibliothèques statiques avec du code dépendant de la position, comment vont-elles être liées?
Michael P
3
Le fichier objet @MichaelP a une table d'étiquettes dépendantes de la position et lorsqu'un fichier obj particulier est lié, toutes les étiquettes sont mises à jour en conséquence. Cela ne peut pas être fait pour la bibliothèque partagée.
Slava
16

Le lien vers une fonction dans une bibliothèque dynamique est résolu lorsque la bibliothèque est chargée ou au moment de l'exécution. Par conséquent, le fichier exécutable et la bibliothèque dynamique sont chargés en mémoire lors de l'exécution du programme. L'adresse mémoire à laquelle une bibliothèque dynamique est chargée ne peut pas être déterminée à l'avance, car une adresse fixe peut entrer en conflit avec une autre bibliothèque dynamique nécessitant la même adresse.


Il existe deux méthodes couramment utilisées pour résoudre ce problème:

1.Relocation. Tous les pointeurs et adresses du code sont modifiés, si nécessaire, pour correspondre à l'adresse de charge réelle. La relocalisation est effectuée par l'éditeur de liens et le chargeur.

2. Code indépendant de la position. Toutes les adresses dans le code sont relatives à la position actuelle. Les objets partagés dans les systèmes de type Unix utilisent par défaut du code indépendant de la position. C'est moins efficace que la relocalisation si le programme s'exécute pendant une longue période, en particulier en mode 32 bits.


Le nom " code indépendant de la position " implique en fait ce qui suit:

  • La section de code ne contient aucune adresse absolue nécessitant une relocalisation, mais uniquement des adresses auto-relatives. Par conséquent, la section de code peut être chargée à une adresse mémoire arbitraire et partagée entre plusieurs processus.

  • La section de données n'est pas partagée entre plusieurs processus car elle contient souvent des données accessibles en écriture. Par conséquent, la section de données peut contenir des pointeurs ou des adresses qui nécessitent une relocalisation.

  • Toutes les fonctions et données publiques peuvent être remplacées sous Linux. Si une fonction dans l'exécutable principal a le même nom qu'une fonction dans un objet partagé, la version dans main aura la priorité, non seulement lorsqu'elle est appelée depuis main, mais également lorsqu'elle est appelée depuis l'objet partagé. De même, lorsqu'une variable globale dans main a le même nom qu'une variable globale dans l'objet partagé, alors l'instance dans main sera utilisée, même lorsqu'elle est accessible depuis l'objet partagé.


Cette soi-disant interposition de symboles est destinée à imiter le comportement des bibliothèques statiques.

Un objet partagé a une table de pointeurs vers ses fonctions, appelée table de liaison de procédure (PLT) et une table de pointeurs vers ses variables appelée table de décalage globale (GOT) afin de mettre en œuvre cette fonctionnalité de "remplacement". Tous les accès aux fonctions et aux variables publiques passent par ces tableaux.

ps Lorsque la liaison dynamique ne peut pas être évitée, il existe différentes façons d'éviter les fonctionnalités chronophages du code indépendant de la position.

Vous pouvez lire plus de cet article: http://www.agner.org/optimize/optimizing_cpp.pdf

bruziuz
la source
9

Un ajout mineur aux réponses déjà publiées: les fichiers objets non compilés pour être indépendants de la position sont déplaçables; ils contiennent des entrées de table de relocalisation.

Ces entrées permettent au chargeur (ce bit de code qui charge un programme en mémoire) de réécrire les adresses absolues pour s'adapter à l'adresse de chargement réelle dans l'espace d'adressage virtuel.

Un système d'exploitation essaiera de partager une seule copie d'une "bibliothèque d'objets partagés" chargée en mémoire avec tous les programmes liés à cette même bibliothèque d'objets partagés.

Étant donné que l'espace d'adressage du code (contrairement aux sections de l'espace de données) n'a pas besoin d'être contigu, et parce que la plupart des programmes qui se lient à une bibliothèque spécifique ont un arbre de dépendance de bibliothèque assez fixe, cela réussit la plupart du temps. Dans les rares cas où il y a une divergence, oui, il peut être nécessaire d'avoir en mémoire deux copies ou plus d'une bibliothèque d'objets partagée.

De toute évidence, toute tentative de randomiser l'adresse de chargement d'une bibliothèque entre des programmes et / ou des instances de programme (afin de réduire la possibilité de créer un modèle exploitable) rendra de tels cas courants, pas rares, donc lorsqu'un système a activé cette capacité, il faut faire tous les efforts pour compiler toutes les bibliothèques d'objets partagées pour qu'elles soient indépendantes de la position.

Étant donné que les appels dans ces bibliothèques à partir du corps du programme principal seront également rendus déplaçables, cela rend beaucoup moins probable qu'une bibliothèque partagée devra être copiée.

user1016759
la source