J'ai lu sur Options de GCC pour les conventions de génération de code , mais je ne comprenais pas ce que "Générer du code indépendant de la position (PIC)" fait. S'il vous plaît, donnez un exemple pour m'expliquer ce que cela signifie.
Le code indépendant de la position signifie que le code machine généré ne dépend pas de sa localisation à une adresse spécifique pour fonctionner.
Par exemple. les sauts seraient générés de manière relative plutôt qu'absolue.
Pseudo-Assemblée:
PIC: Cela fonctionnerait que le code soit à 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 convient de l'inclure 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.
Je vais essayer d'expliquer ce qui a déjà été dit d'une 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 le programme que vous exécutez) modifie certaines adresses dans le code en fonction du lieu de chargement de l'objet.
Dans l'exemple ci-dessus, le "111" du code non-PIC est écrit par le chargeur lors de son premier chargement.
Pour les objets non partagés, vous voudrez peut-être que ce soit comme cela, car le compilateur peut effectuer certaines optimisations sur ce code.
Pour un objet partagé, si un autre processus souhaite "lier" ce code, il doit le lire aux mêmes adresses virtuelles, sinon le "111" n'aura aucun sens. mais cet espace virtuel est peut-être déjà utilisé dans le deuxième processus.
Le code intégré aux bibliothèques partagées doit normalement être indépendant de la position, de sorte que la bibliothèque partagée puisse être chargée facilement (plus ou moins) à n’importe quelle adresse en mémoire. L'option -fPIC
garantit que GCC produit ce code.
Ajouter plus loin ...
Chaque processus a le même espace d'adressage virtuel (Si la randomisation d'adresse virtuelle est arrêtée à l'aide d'un indicateur sous Linux) (Pour plus de détails Désactiver et réactiver la randomisation de la disposition d'espace d'adressage uniquement pour moi-même )
Donc, si c'est un exe sans lien partagé (scénario hypothétique), alors 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 certains de l'adresse de départ attribuée à l'objet partagé, car cela dépend de l'ordre dans lequel les objets partagés ont été liés. Cela dit, l'instruction asm dans .so aura toujours adresse virtuelle différente en fonction du processus auquel elle est liée.
Ainsi, un processus peut attribuer une adresse de démarrage à .so sous la forme 0x45678910 dans son propre espace virtuel et un autre processus simultanément peut attribuer une adresse de démarrage à 0x12131415. S'ils n'utilisent pas d'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 tous deux 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 s’adapter à l’adresse de chargement 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 du code sont relatives à la position actuelle. Les objets partagés dans les systèmes de type Unix utilisent par défaut un code indépendant de la position. Cela est moins efficace que la relocalisation si le programme est exécuté pendant une longue période, en particulier en mode 32 bits.
Le nom " code indépendant de la position " implique en réalité 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 inscriptibles. Par conséquent, la section de données peut contenir des pointeurs ou des adresses nécessitant une relocalisation.
Toutes les fonctions et données publiques peuvent être écrasées sous Linux. Si une fonction de l'exécutable principal porte le même nom qu'une fonction dans un objet partagé, la version dans main est prioritaire, non seulement lorsqu'elle est appelée depuis la commande principale, 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é, l'instance dans main est utilisée, même en cas d'accès depuis l'objet partagé.
Cette soi-disant interposition de symboles est destinée à imiter le comportement des bibliothèques statiques.
Un objet partagé possède 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 global (GOT) afin de mettre en œuvre cette fonctionnalité de "substitution". Tous les accès aux fonctions et aux variables publiques passent par ces tables.
p.s. Lorsque la liaison dynamique ne peut pas être évitée, il existe différents moyens d'éviter les fonctionnalités fastidieuses du code indépendant de la position.
Vous pouvez lire plus de cet article: http://www.agner.org/optimize/optimizing_cpp.pdf
Ajout mineur aux réponses déjà postées: les fichiers objet non compilés pour être indépendants du poste sont transférables; 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 à ajuster pour l’adresse de chargement réelle dans l’espace d’adresse virtuel.
Un système d'exploitation essaiera de partager une copie unique 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.
Etant donné que l'espace d'adressage du code (contrairement aux sections de l'espace de données) ne doit pas nécessairement être contigu, et que la plupart des programmes liés à une bibliothèque spécifique ont un arbre de dépendance de bibliothèque relativement fixe, cela aboutit la plupart du temps. Dans les rares cas de divergence, il peut être nécessaire d’avoir deux copies ou plus d’une bibliothèque d’objets partagés en mémoire.
Évidemment, toute tentative de randomiser l'adresse de chargement d'une bibliothèque entre des programmes et/ou des instances de programmes (afin de réduire les possibilités de création d'un modèle exploitable) rendra ces cas communs, pas rares, ainsi lorsqu'un système a activé cette capacité, il faut essayer de compiler toutes les bibliothèques d'objets partagés pour qu'ils soient indépendants de la position.
Étant donné que les appels dans ces bibliothèques à partir du corps du programme principal seront également rendus transférables, il est donc beaucoup moins probable qu'une bibliothèque partagée doive être copiée.