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.
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.
Réponses:
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.
-fPIC
lors 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.
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.
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.
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' -fPIC
option garantit que GCC produit un tel code.
-fPIC
drapeau 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?
-fPIC
indicateur 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.
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 -fPIC
que 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 ».
-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?
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.
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
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.