7 votes

Allocation d'une nouvelle pile d'appels

(Je pense qu'il y a de fortes chances que cette question soit dupliquée ou qu'elle ait déjà reçu une réponse, mais la recherche de la réponse est difficile en raison de l'interférence de l'"allocation de la pile" et des termes connexes).

J'ai travaillé sur un compilateur jouet pour un langage de script. Pour pouvoir interrompre l'exécution d'un script en cours et revenir au programme hôte, il possède sa propre pile : un simple bloc de mémoire avec une variable "pointeur de pile" qui s'incrémente en utilisant les opérations normales du code C pour ce genre de choses et ainsi de suite. Rien d'intéressant pour l'instant.

Pour l'instant, je compile en C. Mais je souhaite étudier la possibilité de compiler également en code machine - tout en conservant la pile secondaire et la possibilité de revenir au programme hôte à des points de contrôle prédéfinis.

Je suppose que l'utilisation des registres de pile conventionnels dans mon propre code ne devrait pas poser de problème, je suppose que ce qu'il advient des registres est mon affaire tant que tout est restauré quand c'est fait (corrigez-moi si je me trompe sur ce point). Mais ... si je veux que le code script fasse appel à un autre code de bibliothèque, est-il sûr de laisser le programme utiliser cette "pile virtuelle", ou est-il essentiel de lui rendre la pile d'origine à cette fin ?

Réponses comme celui-ci y celui-ci indiquent que la pile n'est pas un bloc de mémoire conventionnel, mais qu'elle repose sur un comportement spécial, propre au système, en ce qui concerne les défauts de page et autres.

Ainsi :

  • Est-il possible de déplacer les pointeurs de la pile dans une autre zone de la mémoire ? La mémoire de pile n'est pas "spéciale" ? Je pense que les bibliothèques de threading doivent faire quelque chose comme ça, puisqu'elles créent plus de piles...
  • En supposant que toute zone de mémoire puisse être manipulée en toute sécurité à l'aide des registres et des instructions de la pile, je ne vois aucune raison pour laquelle il serait problématique d'appeler n'importe quelle fonction avec une profondeur d'appel connue (c'est-à-dire sans récursivité, sans pointeur de fonction), tant que cette quantité est disponible sur la pile virtuelle. N'est-ce pas ?
  • Le débordement de pile est de toute façon un problème dans le code normal, mais un débordement dans un tel système aurait-il des conséquences plus désastreuses ?

Ce n'est évidemment pas nécessaire, puisque le simple fait de renvoyer les pointeurs vers la vraie pile serait parfaitement utilisable, ou d'ailleurs de ne pas en abuser et de se contenter de moins de registres, et je ne devrais probablement pas essayer de le faire du tout (notamment parce que je ne suis manifestement pas à la hauteur). Mais je suis toujours curieux de savoir ce qu'il en est. Je veux savoir comment ce genre de choses fonctionne.

EDITAR: Désolé bien sûr, j'aurais dû dire. Je travaille sur x86 (32 bits pour ma propre machine), Windows et Ubuntu. Rien d'exotique.

4voto

Mats Petersson Points 70074

Toutes ces réponses sont basées sur des "architectures de processeurs communes", et puisqu'il s'agit de générer du code assembleur, elles doivent être "spécifiques à la cible" - si vous décidez de faire cela sur le processeur X, qui a une gestion bizarre de la pile, ce qui suit ne vaut évidemment pas la surface d'écran sur laquelle il est écrit [substitut du papier]. Pour x86 en général, ce qui suit s'applique sauf indication contraire.

is it safe to move the stack pointers into some other area of memory?

La mémoire de pile n'est pas "spéciale" ? Je pense que les bibliothèques de filtrage doivent faire quelque chose comme ça, puisqu'elles créent plus de piles...

La mémoire en tant que telle n'est pas spéciale. Cela suppose toutefois qu'elle ne se trouve pas sur une architecture x86 où le segment de pile est utilisé pour limiter l'utilisation de la pile. Bien que cela soit possible, il est plutôt rare de le voir dans une implémentation. Je sais qu'il y a quelques années, Nokia avait un système d'exploitation spécial utilisant des segments en mode 32 bits. Pour autant que je puisse en juger à l'heure actuelle, c'est le seul système avec lequel j'ai été en contact qui utilise le segment de pile comme le décrit le mode de segmentation x86.

En supposant que n'importe quelle zone de la mémoire est sûre à manipuler de la pile et des instructions, je ne vois aucune raison pour laquelle il serait problème d'appeler n'importe quelle fonction avec une profondeur d'appel connue (c.-à-d. pas de pas de récursion, pas de pointeurs de fonction) tant que cette quantité est disponible sur la pile virtuelle. N'est-ce pas ?

Correct. Tant que vous ne vous attendez pas à pouvoir revenir à une autre fonction sans repasser par la pile d'origine. Un niveau limité de récursivité serait également acceptable, tant que la pile est suffisamment profonde [il y a certains types de problèmes qui sont définitivement difficiles à résoudre sans récursion - la recherche d'arbre binaire par exemple].

Le dépassement de pile est manifestement un problème en nor mais y aurait-il des conséquences extra-désastreuses à un débordement dans dans un tel système ?

En effet, il s'agit d'un problème difficile à résoudre si l'on est un peu malchanceux.

Je vous suggère d'utiliser un appel à VirtualProtect() (Windows) ou mprotect() (Linux, etc.) pour marquer la "fin de la pile" comme illisible et non inscriptible, de sorte que si votre code sort accidentellement de la pile, il se plante correctement plutôt que d'avoir un autre comportement indéfini plus subtil [parce qu'il n'est pas garanti que la mémoire juste en dessous (adresse inférieure) soit indisponible, de sorte que vous pourriez écraser d'autres choses utiles si le code sortait de la pile, ce qui provoquerait des bogues très difficiles à déboguer].

Ajouter un bout de code qui vérifie de temps en temps la profondeur de la pile (vous savez où commence et où finit votre pile, il ne devrait donc pas être difficile de vérifier si une valeur particulière de la pile est "en dehors de la plage" [si vous vous donnez un "espace tampon supplémentaire" entre le sommet de la pile et la zone "nous sommes morts" que vous avez protégée - une "zone d'effritement" comme ils l'appelleraient s'il s'agissait d'une voiture dans un accident]. Vous pouvez également remplir toute la pile d'un motif reconnaissable et vérifier la part qui reste "intacte".

2voto

Alexey Frunze Points 37651

En général, sur x86, vous pouvez utiliser la pile existante sans problème à condition que :

  • vous ne le débordez pas
  • vous n'incrémentez pas le registre du pointeur de pile (avec pop o add esp, positive_value / sub esp, negative_value ) au-delà de ce que votre code commence (si vous le faites, les interruptions ou les rappels asynchrones (signaux) ou toute autre activité utilisant la pile en détruiront le contenu).
  • vous ne provoquez pas d'exception au niveau du processeur (si c'est le cas, le code de gestion des exceptions peut ne pas être en mesure de dérouler la pile jusqu'au point le plus proche où l'exception peut être gérée)

Il en va de même pour l'utilisation d'un bloc de mémoire différent pour une pile temporaire et le fait de pointer esp jusqu'à son terme.

Le problème de la gestion des exceptions et du déroulement de la pile est lié au fait que votre code C et C++ compilé contient certaines structures de données liées à la gestion des exceptions, telles que les plages de eip avec les liens vers leurs gestionnaires d'exception respectifs (cela indique où se trouve le gestionnaire d'exception le plus proche pour chaque morceau de code) et il y a également des informations relatives à l'identification de la fonction appelante (c'est-à-dire où se trouve l'adresse de retour sur la pile, etc), de sorte que vous puissiez faire remonter les exceptions. Si vous vous contentez d'insérer du code machine brut dans ce "cadre", vous n'étendrez pas correctement ces structures de données de gestion des exceptions, et si les choses tournent mal, elles tourneront probablement très mal (l'ensemble du processus peut se bloquer ou être endommagé, malgré la présence de gestionnaires d'exceptions autour du code généré).

Donc, oui, si vous êtes prudent, vous pouvez jouer avec des piles.

2voto

Ira Baxter Points 48153

Vous pouvez utiliser n'importe quelle région pour la pile du processeur (modulo les protections de la mémoire).

Pour l'essentiel, il suffit de charger le registre ESP ("MOV ESP, ...") avec un pointeur vers la nouvelle zone, quelle que soit la manière dont vous l'avez allouée.

Vous devez en avoir assez pour votre et tout ce qu'il peut appeler (par exemple, une API du système d'exploitation Windows), ainsi que les comportements bizarres du système d'exploitation. Vous pourrez peut-être déterminer l'espace dont votre code a besoin ; un bon compilateur peut facilement le faire. Il est plus difficile de déterminer l'espace dont Windows a besoin ; vous pouvez toujours allouer "beaucoup trop", ce que les programmes Windows ont tendance à faire.

Si vous décidez de gérer cet espace de manière rigoureuse, vous devrez probablement changer de pile pour appeler les fonctions Windows. Cela ne suffira pas ; vous serez probablement victime de diverses surprises de la part de Windows. J'en décris une ici Windows : éviter de pousser le contexte x86 complet sur la pile . J'ai des solutions médiocres, mais pas de bonnes solutions.

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