Pourquoi gcc force-t-il PIC pour les bibliothèques partagées x64?

Essayer de comstackr du code non-PIC dans une bibliothèque partagée sur x64 avec gcc génère une erreur, par exemple:

 /usr/bin/ld: /tmp/ccQ2ttcT.o: relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recomstack with -fPIC 

Cette question concerne la raison pour laquelle c’est le cas. Je sais que le x64 a un adressage RIP-relatif conçu pour rendre le code PIC plus efficace. Cependant, cela ne signifie pas que la relocalisation du temps de chargement ne peut pas (en théorie) être appliquée à un tel code.

Certaines sources en ligne, y compris celle-ci (qui est largement citée dans ce numéro), affirment qu’il existe une limitation inhérente interdisant le code non-PIC dans les bibliothèques partagées, en raison de l’adressage relatif au protocole RIP. Je ne comprends pas pourquoi c’est vrai.

Considérons “ancien x86” – une instruction d’ call a également un opérande relatif à IP. Et pourtant, le code x86 avec call in comstack bien dans une librairie partagée sans PIC, mais en utilisant la relocalisation du temps de chargement R_386_PC32 . Ne peut-on pas faire la même chose pour l’adressage relatif au RIP de données dans x64?

Notez que je comprends parfaitement les avantages du code PIC, et que la pénalité de performance liée à l’adressage RIP permet d’alléger. Cependant, je suis curieux de savoir pourquoi il est interdit d’utiliser un code non PIC. Existe-t-il un véritable raisonnement technique ou est-ce simplement pour encourager l’écriture de code PIC?

Voici la meilleure explication que j’ai lue dans un article de comp.unix.programmer :

Les bibliothèques partagées ont besoin de PIC sur x86-64, ou plus précisément, le code déplaçable doit être PIC. Cela est dû au fait qu’un opérande d’adresse immédiate de 32 bits utilisé dans le code peut nécessiter plus de 32 bits après la relocalisation. Si cela se produit, il n’y a nulle part où écrire la nouvelle valeur.

Dites simplement quelque chose de plus.

Dans l’ URL fournie dans la question , il est mentionné que vous pouvez transmettre -mcmodel=large à gcc pour indiquer au compilateur de générer un opérande d’adresse immédiate de 64 bits pour votre code.

Ainsi, gcc -mcmodel=large -shared ac -partagé avec un grand partage va générer un object partagé non-PIC.

Démos:

ac:

 #include  void foo(void) { printf("%p\n", main); } 

L’opérande d’adresse immédiate de 32 bits vous empêche de générer un object non-PIC.

 xiami@gentoo ~ $ cc -shared -o a.so ac /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.4/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/cck3FWeL.o: relocation R_X86_64_32 against `main' can not be used when making a shared object; recomstack with -fPIC /tmp/cck3FWeL.o: error adding symbols: Bad value collect2: error: ld returned 1 exit status 

Utilisez -mcmodel=large pour le résoudre. (Les avertissements n’apparaissent que sur mon système car la modification de .text est interdite par mon kernel PaX.)

 xiami@gentoo ~ $ cc -mcmodel=large -shared -o a.so ac /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.4/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/ccZ3b9Xk.o: warning: relocation in readonly section `.text'. /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.4/../../../../x86_64-pc-linux-gnu/bin/ld: warning: creating a DT_TEXTREL in object. 

Vous pouvez maintenant voir que le type de l’entrée de relocalisation est R_X86_64_64 au lieu de R_X86_64_32, R_X86_64_PLT32, R_X86_64_PLTOFF64.

 xiami@gentoo ~ $ objdump -R a.so a.so: file format elf64-x86-64 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE ... 0000000000000758 R_X86_64_64 printf ... 

Et sur mon système, liez cet object partagé à un code normal et exécutez le programme émettra des erreurs telles que: ./a.out: error while loading shared libraries: ./a.so: cannot make segment writable for relocation: Permission denied

Cela prouve que le chargeur dynamic est en train de faire des relocalisations sur .text, ce qui n’est pas le cas de la bibliothèque PIC.

Le fait est que le code PIC et non-PIC est toujours différent.

C source:

 extern int x; void func(void) { x += 1; } 

Assemblée, pas PIC:

 addl $1, x(%rip) 

Assemblée, avec PIC:

 movq x@GOTPCREL(%rip), %rax addl $1, (%rax) 

Il semble donc que le code PIC doit passer par une table de relocalisation pour accéder aux variables globales. En fait, il doit faire la même chose pour les fonctions, mais il peut effectuer des fonctions via des stubs créés au moment de la liaison. Ceci est transparent au niveau de l’assemblage, tandis que l’access aux globals ne l’est pas. (Si vous avez besoin de l’ adresse d’une fonction, alors PIC et non-PIC sont différents, tout comme les globals.) Notez que si vous modifiez le code comme suit:

 __atsortingbute__((visibility("hidden"))) extern int x; 

Dans ce cas, puisque GCC sait que le symbole doit résider dans le même object que le code, il émet le même code que la version non PIC.