Les adresses absolues 32 bits ne sont plus autorisées dans x86-64 Linux?

64 bits Linux utilise le petit modèle de mémoire par défaut, qui place tout le code et les données statiques en dessous de la limite d'adresse de 2 Go. Cela permet de s'assurer que vous pouvez utiliser 32 bits d'adresses absolues. Les anciennes versions de gcc utilisent des adresses absolues 32 bits pour les tableaux statiques afin d'enregistrer une instruction supplémentaire pour le calcul d'adresse relative. Toutefois, cela ne fonctionne plus. Si j'essaie de faire une adresse absolue de 32 bits dans assembly, j'obtiens l'erreur de linker: la "délocalisation R_X86_64_32S contre".les données " ne peut pas être utilisé lors de la création d'un objet partagé; recompiler avec-fPIC". Ce message d'erreur est trompeur, bien sûr, parce que je ne fais pas un objet partagé et-fPIC n'aide pas. Ce que j'ai découvert jusqu'à présent est ceci: gcc version 4.8.5 utilise des adresses absolues 32 bits pour les tableaux statiques, gcc version 6.3.0 ne le fait pas. la version 5 non plus. Le linker dans binutils 2.24 permet des adresses absolues 32 bits, contrairement à la version 2.28.

La conséquence de ce changement est que les anciennes bibliothèques doivent être recompilés et le code d'assemblage de l'héritage est cassé.

maintenant je veux demander: quand ce changement a-t-il été fait? Est-il documenté quelque part? Et y a-t-il une option linker qui lui permet d'accepter les adresses absolues 32 bits?

10
demandé sur Peter Cordes 2017-04-12 13:48:28

1 réponses

votre distro a configuré gcc avec --enable-default-pie , de sorte qu'il crée des exécutables indépendants de la position par défaut, (permettant L'ASLR de l'exécutable ainsi que des bibliothèques). La plupart des distributions font que, ces jours-ci.

vous êtes en fait sont faire un objet partagé: les exécutables PIE sont en quelque sorte un piratage en utilisant un objet partagé avec un point d'entrée. Le linker dynamique a déjà pris en charge cela, et ASLR est agréable pour la sécurité, donc c'était le plus facile façon de mettre en œuvre ASLR pour les exécutables.

le déplacement absolu de 32 bits n'est pas autorisé dans un objet partagé ELF; cela les empêcherait d'être chargés à l'extérieur du 2gib bas (pour les adresses étendues de signe 32 bits). Les adresses absolues 64 bits sont autorisées, mais généralement vous voulez seulement que pour les tables de saut ou d'autres données statiques, pas dans le cadre des instructions. 1

la partie recompile with -fPIC du message d'erreur est fausse pour écrit à la main en asm; il est écrit pour le cas de personnes compilant avec gcc -c et ensuite essayer de lier avec gcc -shared -o foo.so *.o , avec un gcc où -fPIE est et non le défaut. Le message d'erreur devrait probablement changer parce que beaucoup de gens sont en train de courir dans cette erreur lors de la liaison main-écrite asm.


utilisez gcc -fno-pie -no-pie pour annuler ce retour à l'ancien comportement. -no-pie est le l'éditeur de liens option, -fno-pie est le code d'option-gen . Avec seulement -fno-pie , gcc va faire du code comme mov eax, offset .LC0 qui ne se lie pas avec le encore-activé -pie .

( clang peut avoir les TARTE activé par défaut: clang -fno-pie -nopie . Un juillet 2017 patch fait -no-pie un alias pour -nopie , pour compat avec gcc, mais clang4.0.1 il.)


avec seulement -no-pie , (mais toujours -fpie ) le code généré par le compilateur (à partir de sources C ou C++) sera légèrement plus lent et plus grand que nécessaire , mais sera toujours lié dans un exécutable dépendant de la position qui ne bénéficiera pas D'ASLR. "Trop de TARTE est mauvais pour la performance" , rapporte un ralentissement moyen de 3% pour x86-64 sur SPEC CPU2006 (je n'ai pas avoir une copie du papier afin de savoir quel matériel qui était sur :/). Mais en code 32 bits, le ralentissement moyen est de 10%, dans le pire des cas de 25% (sur la spécification CPU2006).

la pénalité pour les exécutables PIE est principalement pour des choses comme l'indexation des tableaux statiques, comme Agner décrit dans la question, Où l'utilisation d'une adresse statique comme un immédiat de 32 bits ou dans le cadre d'une [disp32 + index*4] l'adressage sauve des instructions de mode et des registres par rapport à une LEA RIP-relative pour obtenir une adresse dans un registre. Aussi 5 octets mov r32, imm32 au lieu de 7 octet lea r64, [rel symbol] pour obtenir une adresse statique dans un registre plus sympa pour passer l'adresse d'une chaîne littérale ou d'autres données statiques à une fonction.

-fPIE ne suppose toujours aucun symbole-interposition pour les variables / fonctions globales, contrairement à -fPIC pour les bibliothèques partagées qui doivent passer par le GOT pour accéder aux globals (ce qui est encore une autre raison d'utiliser static pour toutes les variables qui peuvent être limitées à la portée du fichier au lieu de global). Voir L'état lamentable des bibliothèques dynamiques sur Linux .

donc -fPIE est beaucoup moins mauvais que -fPIC pour le code 64 bits, mais toujours mauvais pour 32 bits parce que L'adressage relatif N'est pas disponible . Voir quelques exemples sur le compilateur Godbolt explorer . En moyenne, -fPIE a une très petite performance / Taille de code EN code 64 bits. Le pire des cas pour une boucle spécifique pourrait être seulement quelques %. Mais la tarte 32-bit peut être bien pire.

aucune de ces options -f code-gen font une différence lors de la simple liaison, ou lors de l'assemblage .S écrit à la main asm. gcc -fno-pie -no-pie -O3 main.c nasm_output.o est un cas où vous voulez les deux options.


si votre GCC a été configuré de cette façon, gcc -v |& grep -o -e '[^ ]*pie' imprime --enable-default-pie . Le Support de cette option de configuration était ajouté à gcc dans début 2015 . Ubuntu l'a activé en 16.10, et Debian à peu près au même moment dans gcc 6.2.0-7 (conduisant à des erreurs de compilation du noyau: https://lkml.org/lkml/2016/10/21/904 ).

apparenté: Build compressed x86 kernels as PIE a également été affecté par le changement de défaut.

pourquoi Linux ne randomise-t-il pas l'adresse du code exécutable segment? est une question plus ancienne sur la raison pour laquelle il n'était pas par défaut plus tôt, ou n'était activé que pour quelques paquets sur Ubuntu plus ancien avant qu'il ne soit activé de manière générale.


notez que ld lui-même n'a pas changé son défaut . Il fonctionne encore normalement (au moins sur Arch Linux avec binutils 2.28). Le changement est que gcc par défaut à passer -pie comme une option de linker, à moins que vous utilisez explicitement -static ou -no-pie .

dans un fichier source NASM, j'ai utilisé a32 mov eax, [abs buf] pour obtenir une adresse absolue. (J'étais en train de tester si la méthode 6-byte pour encoder de petites adresses absolues (address-size + mov eax,moffs: 67 a1 40 f1 60 00 ) a un décrochage LCP sur les processeurs Intel. Il ne .)

nasm -felf64 -Worphan-labels -g -Fdwarf testloop.asm &&
ld -o testloop testloop.o              # works: static executable

gcc -v -nostdlib testloop.o            # doesn't work
...
..../collect2  ... -pie ...
/usr/bin/ld: testloop.o: relocation R_X86_64_32 against `.bss' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Nonrepresentable section on output
collect2: error: ld returned 1 exit status

gcc -v -no-pie -nostdlib testloop.o    # works
gcc -v -static -nostdlib testloop.o    # also works: -static implies -no-pie

related: construire des exécutables statiques / dynamiques avec / sans libc, définissant _start or main .


vérification si un exécutable existant est PIE ou non

file et readelf dire que les Tartes sont des "objets partagés", pas exécutables ELF. Les exécutables statiques ne peuvent pas être une tarte.

$ gcc -fno-pie  -no-pie -O3 hello.c
$ file a.out
a.out: ELF 64-bit LSB executable, ...

$ gcc -O3 hello.c
$ file a.out
a.out: ELF 64-bit LSB shared object, ...

cela a également été demandé à: Comment tester si un binaire Linux a été compilé comme code indépendant de la position?


Semi-lié (mais pas vraiment): une autre caractéristique récente de gcc est gcc -fno-plt . Enfin les appels dans les bibliothèques partagées peuvent être juste call [rip + symbol@GOTPCREL] (AT&T call *puts@GOTPCREL(%rip) ), sans trampoline PLT.

Distros va avec un peu de chance commencer à l'activer bientôt, parce qu'il évite également d'avoir besoin de pages mémoire exécutables + écrites. C'est un raccourci significatif pour les programmes qui font beaucoup d'appels de bibliothèques partagées, par exemple x86-64 clang -O2 -g compilant tramp3d va de 41,6 s à 36,8 s sur n'importe quel matériel l'auteur du patch testé sur . (clang est peut-être le pire des scénarios pour les appels de bibliothèques.)

il ne nécessite liaison précoce au lieu de liaison dynamique paresseuse, de sorte qu'il est plus lent pour les grands programmes qui sortent tout de suite. (par exemple clang --version ou hello.c ). Ce ralentissement pourrait être réduit avec prelink, apparemment.

ce n'est pas cependant, supprimez les frais généraux GOT pour les variables externes dans le code PIC de la bibliothèque partagée. (Voir le lien godbolt ci-dessus).


notes de bas de page 1

les adresses absolues 64 bits sont en fait autorisées dans les objets partagés Elf Linux, avec les relocalisations de texte pour permettre le chargement à différentes adresses (ASLR et bibliothèques partagées). Cela vous permet d'avoir des tables de saut dans section .rodata , ou static const int *foo = &bar; sans initialiseur d'exécution.

So mov rdi, qword msg works (NASM/YASM syntaxe for 10-byte mov r64, imm64 , aka AT & T syntaxe movabs , la seule instruction qui peut utiliser un 64-bit immédiat). Mais c'est plus grand et généralement plus lent que lea rdi, [rel msg] , ce qui est ce que vous devez utiliser si vous décidez de ne pas désactiver -pie . Un 64-bit immédiat est plus lent à récupérer à partir du cache uop sur Sandybridge-famille de Processeurs, selon Agner de la Brume microarch pdf . (Oui, la même personne qui a posé cette question. :)

vous pouvez utiliser le default rel de NASM au lieu de le spécifier dans chaque mode d'adressage [rel symbol] . Voir aussi le format Mach-O 64 bits ne supporte pas les adresses absolues 32 bits. MSNA Accéder Tableau pour un peu plus de description d'éviter de 32 bits adressage absolu. OS X ne peut pas utiliser d'adresses 32 bits à tous, donc RIP-l'adressage relatif est le meilleur moyen, trop.

en code dépendant de la position ( -no-pie ), vous devez utiliser mov edi, msg quand vous voulez une adresse dans un registre; 5-byte mov r32, imm32 est encore plus petit que RIP-relative LEA, et plus de ports d'exécution peuvent l'exécuter.

16
répondu Peter Cordes 2018-05-16 03:22:42