35 votes

Pourquoi l'adresse 0x400000 est choisie comme début de segment de texte dans l'ABI x86_64 ?

En ce à la page 27, il est dit que le segment de texte commence à 0x400000. Pourquoi cette adresse particulière a-t-elle été choisie ? Y a-t-il une raison à cela ? La même adresse est choisie dans GNU ld en Linux :

$ ld -verbose | grep -i text-segment
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;

C'est surprenant car cette adresse est plus grand dans les exécutables x86 32 bits :

$ ld -verbose | grep -i text-segment
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x08048000)); . = SEGMENT_START("text-segment", 0x08048000) + SIZEOF_HEADERS;

Je lis cette question qui discute pourquoi l'adresse 0x080xxxxx a été choisie pour i386 mais n'explique pas un changement dans x86_64. Il est difficile de trouver une explication à ce sujet. Quelqu'un a-t-il un indice ?

0 votes

uclibc.org/docs/psABI-x86_64.pdf est la dernière version (0.99.7), selon le Wiki OSDev .

0 votes

0x400000 représente 4MiB, ce qui pourrait avoir un rapport avec la prise en charge d'énormes pages. La section 3.3.3 n'autorise la taille des pages que jusqu'à 64KiB.

0 votes

@ivan_pozdeev : github.com/hjl-tools/x86-psABI/wiki/X86-psABI a des liens vers des PDFs construits à partir de la révision git HEAD des sources de LaTeX. Les tables de pages x86-64 peuvent utiliser des hugepages de 2MB (et même 1GiB), et Linux le fait de manière transparente/opportuniste pour la mémoire anonyme, mais pas pour le mappage par fichier. Je pense que 4MB est suffisamment éloigné de 0 pour qu'un déréférencement par pointeur NULL indexé ne soit pas indexé dans des pages valides. Heh, je vois que vous avez dit la même chose dans votre réponse.

35voto

ivan_pozdeev Points 2233

En résumé : certaines limites techniques qui amd64 a dans l'utilisation de grandes adresses suggèrent de consacrer la partie inférieure 2GiB de l'espace d'adressage au code et aux données pour plus d'efficacité. La pile a donc été déplacée hors de cette plage.


En i386 ABI 1

  • La pile est située avant le code, et passe d'un peu moins d'un million d'euros à un peu plus d'un million d'euros. 0x8048000 vers le bas. Ce qui permet "un peu plus de 128 Mo pour la pile et environ 2 GB pour le texte et les données" (p. 3-22).
  • Les segments dynamiques commencent à 0x80000000 (2GiB),
  • et le noyau occupe la "zone réservée" en haut, que la spécification autorise à aller jusqu'à 1GiB à partir d'au moins 0xC0000000 (p. 3-21) ( ce qui est ce qu'il fait généralement ).
  • Il n'est pas nécessaire que le programme principal soit indépendant de la position.
  • Une implémentation n'est pas tenue d'intercepter l'accès aux pointeurs nuls (p. 3-21) mais il est raisonnable de s'attendre à ce qu'une partie de l'espace de la pile au-dessus de 128MiB (qui est 288KiB ) sera réservé à cet effet.

amd64 ( dont l'ABI est formulé comme un amendement à la i386 one (p. 9)) dispose d'un espace d'adressage beaucoup plus grand (48 bits), mais la plupart des instructions n'acceptent que des opérandes immédiats de 32 bits (qui incluent les adresses directes et les offsets dans les instructions de saut), ce qui demande plus de travail et un code moins efficace (surtout si l'on tient compte de l'interdépendance des instructions) pour traiter des valeurs plus grandes. Les mesures visant à contourner ces limitations sont résumées par les auteurs en présentant quelques "modèles de code" qu'ils recommandent d'utiliser pour "permettre au compilateur de générer un meilleur code". (p. 33)

  • Plus précisément, le premier d'entre eux, "Small code model", suggère d'utiliser des adresses "dans la fourchette de 0 à 2 31 -2 24 -1 ou de 0x00000000 a 0x7effffff " qui permet des références relatives et des itérations de tableaux très efficaces. C'est 1.98GiB ce qui est plus que suffisant pour de nombreux programmes.
  • Le "modèle de code moyen" est basé sur le modèle précédent, fendant les données en une partie "rapide" sous la frontière ci-dessus et la partie restante "plus lente" qui nécessite une instruction spéciale pour y accéder. Le code, quant à lui, reste sous la frontière.
  • Et seul le modèle "large" ne fait aucune hypothèse sur les tailles, ce qui oblige le compilateur à "pour utiliser le movabs instruction, comme dans le modèle de code même pour traiter les adresses à l'intérieur de la section texte. De plus, les branchements indirects sont nécessaires lors du branchement à des adresses dont le dont le décalage par rapport au pointeur d'instruction courant est inconnu". Ils suggèrent ensuite de diviser la base de code en plusieurs bibliothèques partagées, car ces mesures ne s'appliquent pas aux références relatives dont les décalages sont connus pour être dans les limites (comme indiqué dans "Small position independent code model").

Ainsi, la pile a été déplacée sous l'espace de la bibliothèque partagée ( 0x80000000000 , 128GiB ) parce que ses adresses ne sont jamais des opérandes immédiats, toujours référencées soit indirectement, soit par l'intermédiaire de lea / mov d'une autre référence, donc seules les limitations de décalage relatif s'appliquent.


Ce qui précède explique pourquoi l'adresse de chargement a été déplacée vers une adresse inférieure. Maintenant, pourquoi a-t-elle été déplacée à exactement 0x400000 ( 4MiB ) ? Ici, je suis venu vide, donc, en résumant ce que j'ai lu dans les spécifications de l'ABI, je ne peux que deviner que c'était "juste comme il faut" :

  • Il est suffisamment grand pour prendre en compte tout décalage de structure probablement incorrect, ce qui permet d'utiliser des unités de données plus grandes que celles de l'UE. amd64 mais suffisamment petite pour ne pas gaspiller une grande partie de la précieuse capacité de démarrage. 2GiB de l'espace d'adressage.
  • Elle est égale à la plus grande taille de page pratique à ce jour et est un multiple de toutes les autres tailles d'unités de mémoire virtuelle auxquelles on peut penser.

1 Notez que les Linux x32 actuels s'écartent de cette disposition. <a href="https://stackoverflow.com/questions/2187484/why-is-the-elf-execution-entry-point-virtual-address-of-the-form-0x80xxxxx-and-n#">plus </a>y <a href="https://stackoverflow.com/questions/7187981/whats-the-memory-before-0x08048000-used-for-in-32-bit-machine">plus </a>au fil du temps. Mais nous parlons ici de la spécification ABI depuis que la <code>amd64</code> l'une d'elles est formellement basée sur elle plutôt que sur une disposition dérivée (voir son paragraphe pour la citation).

0voto

Peter Cordes Points 1375

Le code/données statiques aux adresses basses, la pile aux adresses hautes, est le modèle traditionnel . x86-64 suit cela ; i386 était le plus inhabituel. (Avec "le tas" au milieu, même si ce n'est pas une chose réelle dans asm ; il y a .data/.bss au-dessus de .text, brk ajoutant plus d'espace juste après .bss, et mmap choisissant des adresses aléatoires entre les deux).

La disposition de l'i386 laissait de la place pour placer la pile sous le code, mais les Linux modernes ne le font pas de toute façon. Vous avez toujours des adresses de pile comme 0xffffe000 en code 32 bits ( par exemple, sous un noyau 64 bits ). Je ne suis pas sûr de l'endroit où une construction moderne d'un noyau 32 bits placerait les piles de l'espace utilisateur. Bien sûr, cela ne concerne que la pile du thread principal ; les piles des nouveaux threads doivent être allouées manuellement, généralement avec mmap.


Pourquoi 0x400000 (4 MiB) spécifiquement pour le programme ld l'adresse de base par défaut ?

Suffisamment élevé pour éviter mmap_min_addr (par défaut 64k) et laisser un espace pour que la déréférence NULL soit toujours susceptible d'échouer bruyamment, au lieu de lire silencieusement le code. Même si c'est comme ptr[i] avec quelques grands i . Mais sinon, le bas de l'espace d'adressage virtuel est un bon endroit,

Aussi pour optimiser les tables de pages : ils sont un arbre radix clairsemé (diagramme en cette réponse ). Idéalement, les pages utilisées partagent autant de niveaux supérieurs de l'arbre que possible, de sorte que les niveaux supérieurs de l'arbre ont principalement des entrées "non présentes". Le noyau a moins à allouer et à gérer, et le marcheur de table de pages HW peut mettre en cache interne les entrées de niveau supérieur (cache PDE) pour accélérer les manques TLB dans les pages 4k lorsqu'elles sont dans la même région de 2M, 1G ou 512G. Et le(s) page-walker(s) accède à la mémoire par le biais du cache Par conséquent, des tables de pages plus petites signifient également une réduction de l'encombrement du cache pour ces accès.

0x400000 = 4MiB. C'est le début d'un groupe de pages de 2MiB près du début de l'espace d'adressage virtuel de 1GiB. Ainsi, un exécutable avec un code plus important et/ou des données statiques qui ont besoin de plusieurs pages les aura toutes dans le même sous-arbre des tables de pages, touchant aussi peu que possible les différentes régions de 1G et 2M.

Bien, presque le moins de régions 1G possible : à partir de 0x40000000 (1 GiB) l'aurait placé au tout début d'une région de 1GiB, sans sauter les deux premières grandes pages de 2MiB de celle-ci. Mais cela n'a d'importance que si la taille de vos données statiques était juste en dessous de 1GiB, sinon vous tenez toujours dans la première région de 1GiB d'énorme page, ou vous vous étendez dans la 2ème de toute façon.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X