Empiler
Une pile, dans ce contexte, est le dernier tampon entré, premier sorti dans lequel vous placez des données pendant l'exécution de votre programme. Last in, first out (LIFO) signifie que la dernière chose que vous mettez est toujours la première chose que vous retirez - si vous poussez 2 objets sur la pile, `` A '' puis `` B '', alors la première chose que vous faites apparaître hors de la pile sera «B», et la chose suivante est «A».
Lorsque vous appelez une fonction dans votre code, l'instruction suivante après l'appel de fonction est stockée sur la pile, ainsi que tout espace de stockage susceptible d'être écrasé par l'appel de fonction. La fonction que vous appelez peut utiliser plus de pile pour ses propres variables locales. Une fois terminé, il libère l'espace de pile des variables locales qu'il a utilisé, puis revient à la fonction précédente.
Débordement de pile
Un débordement de pile se produit lorsque vous avez utilisé plus de mémoire pour la pile que votre programme était censé utiliser. Dans les systèmes embarqués, vous pouvez n'avoir que 256 octets pour la pile, et si chaque fonction occupe 32 octets, vous ne pouvez avoir que des appels de fonction 8 profonds - la fonction 1 appelle la fonction 2 qui appelle la fonction 3 qui appelle la fonction 4 .... qui appelle la fonction 8 qui appelle la fonction 9, mais la fonction 9 écrase la mémoire en dehors de la pile. Cela pourrait écraser la mémoire, le code, etc.
De nombreux programmeurs font cette erreur en appelant la fonction A qui appelle ensuite la fonction B, qui appelle ensuite la fonction C, qui appelle ensuite la fonction A. Cela peut fonctionner la plupart du temps, mais une seule fois la mauvaise entrée la fera entrer dans ce cercle pour toujours jusqu'à ce que l'ordinateur reconnaisse que la pile est exagérée.
Les fonctions récursives en sont également une cause, mais si vous écrivez de manière récursive (c'est-à-dire que votre fonction s'appelle elle-même), vous devez en être conscient et utiliser des variables statiques / globales pour éviter une récursivité infinie.
En général, le système d'exploitation et le langage de programmation que vous utilisez gèrent la pile, et c'est hors de votre contrôle. Vous devriez regarder votre graphe d'appel (une structure arborescente qui montre de votre main ce que chaque fonction appelle) pour voir jusqu'où vont vos appels de fonction, et pour détecter les cycles et récursions qui ne sont pas prévus. Les cycles intentionnels et la récursivité doivent être vérifiés artificiellement pour détecter une erreur s'ils s'appellent trop souvent.
Au-delà des bonnes pratiques de programmation, des tests statiques et dynamiques, vous ne pouvez pas faire grand-chose sur ces systèmes de haut niveau.
Systèmes embarqués
Dans le monde embarqué, en particulier dans le code à haute fiabilité (automobile, aéronautique, spatial), vous effectuez des révisions et des vérifications approfondies du code, mais vous effectuez également les opérations suivantes:
- Interdire la récursivité et les cycles - mis en application par la stratégie et les tests
- Gardez le code et la pile éloignés (code en flash, pile en RAM, et jamais le twain ne se rencontrera)
- Placez des bandes de garde autour de la pile - une zone de mémoire vide que vous remplissez d'un nombre magique (généralement une instruction d'interruption logicielle, mais il y a de nombreuses options ici), et des centaines ou des milliers de fois par seconde vous regardez les bandes de garde pour vous assurer ils n'ont pas été écrasés.
- Utilisez la protection de la mémoire (c.-à-d. Pas d'exécution sur la pile, pas de lecture ou d'écriture juste à l'extérieur de la pile)
- Les interruptions n'appellent pas de fonctions secondaires - elles définissent des indicateurs, copient les données et laissent l'application se charger de les traiter (sinon vous pourriez obtenir 8 au plus profond de votre arbre d'appels de fonction, avoir une interruption, puis sortir quelques autres fonctions dans le interrompre, provoquant l'éruption). Vous disposez de plusieurs arborescences d'appels - une pour les processus principaux et une pour chaque interruption. Si vos interruptions peuvent s'interrompre ... eh bien, il y a des dragons ...
Langages et systèmes de haut niveau
Mais dans les langages de haut niveau exécutés sur les systèmes d'exploitation:
- Réduisez votre stockage de variables locales (les variables locales sont stockées sur la pile - bien que les compilateurs soient assez intelligents à ce sujet et mettent parfois de gros locaux sur le tas si votre arbre d'appels est peu profond)
- Éviter ou limiter strictement la récursivité
- Ne divisez pas trop vos programmes en fonctions de plus en plus petites - même sans compter les variables locales, chaque appel de fonction consomme jusqu'à 64 octets sur la pile (processeur 32 bits, sauvegarde de la moitié des registres du processeur, des indicateurs, etc.)
- Gardez votre arbre d'appel peu profond (similaire à la déclaration ci-dessus)
Serveurs Web
Cela dépend du `` bac à sable '' que vous avez, que vous puissiez contrôler ou même voir la pile. Il y a de fortes chances que vous puissiez traiter les serveurs Web comme vous le feriez pour n'importe quel autre langage et système d'exploitation de haut niveau - c'est en grande partie hors de votre contrôle, mais vérifiez la langue et la pile de serveurs que vous utilisez. Il est possible de faire sauter la pile sur votre serveur SQL, par exemple.
-Adam
Habituellement, un débordement de pile est le résultat d'un appel récursif infini (étant donné la quantité habituelle de mémoire dans les ordinateurs standard de nos jours).
Lorsque vous appelez une méthode, une fonction ou une procédure, la manière «standard» de faire l'appel consiste à:
Donc, généralement, cela prend quelques octets en fonction du nombre et du type des paramètres ainsi que de l'architecture de la machine.
Vous verrez alors que si vous commencez à faire des appels récursifs, la pile commencera à croître. Maintenant, la pile est généralement réservée en mémoire de telle manière qu'elle croît dans la direction opposée au tas, donc, étant donné un grand nombre d'appels sans "revenir", la pile commence à se remplir.
Maintenant, à une époque plus ancienne, un débordement de pile pouvait se produire simplement parce que vous épuisiez toute la mémoire disponible, comme ça. Avec le modèle de mémoire virtuelle (jusqu'à 4 Go sur un système X86) qui était hors de portée, donc généralement, si vous obtenez une erreur de débordement de pile, recherchez un appel récursif infini.
la source
Quoi? Personne n'aime ceux qui sont enfermés dans une boucle infinie?
la source
Outre la forme de débordement de pile que vous obtenez à partir d'une récursivité directe (par exemple
Fibonacci(1000000)
), une forme plus subtile de celui-ci que j'ai expérimentée à plusieurs reprises est une récursivité indirecte, où une fonction appelle une autre fonction, qui en appelle une autre, puis l'une des ces fonctions appelle à nouveau la première.Cela peut généralement se produire dans les fonctions qui sont appelées en réponse à des événements mais qui peuvent elles-mêmes générer de nouveaux événements, par exemple:
Dans ce cas, l'appel à
ResizeWindow
peut provoquer leWindowSizeChanged()
déclenchement à nouveau du rappel, qui appelle àResizeWindow
nouveau, jusqu'à ce que vous soyez à court de pile. Dans de telles situations, vous devez souvent reporter la réponse à l'événement jusqu'au retour de la trame de pile, par exemple en publiant un message.la source
Considérant que cela a été étiqueté avec "piratage", je soupçonne que le "débordement de pile" auquel il fait référence est un débordement de pile d'appels, plutôt qu'un débordement de pile de plus haut niveau comme ceux référencés dans la plupart des autres réponses ici. Cela ne s'applique pas vraiment aux environnements gérés ou interprétés tels que .NET, Java, Python, Perl, PHP, etc., dans lesquels les applications Web sont généralement écrites, de sorte que votre seul risque est le serveur Web lui-même, qui est probablement écrit en C ou C ++.
Découvrez ce fil:
/programming/7308/what-is-a-good-starting-point-for-learning-buffer-overflow
la source
J'ai recréé le problème de débordement de pile tout en obtenant un nombre de Fibonacci le plus courant, c'est-à-dire 1, 1, 2, 3, 5 ..... donc calcul pour fib (1) = 1 ou fib (3) = 2 .. fib (n ) = ??.
pour n, disons que nous serons intéressés - et si n = 100 000 alors quel sera le nombre de Fibonacci correspondant ??
L'approche à une boucle est comme ci-dessous -
c'est assez simple et le résultat est -
Maintenant, une autre approche que j'ai appliquée est celle de Divide and Concur via récursivité
c'est-à-dire Fib (n) = fib (n-1) + Fib (n-2) et ensuite récursion supplémentaire pour n-1 & n-2 ..... jusqu'à 2 & 1. qui est programmé comme -
Quand j'ai exécuté le code pour n = 100 000, le résultat est comme ci-dessous -
Ci-dessus, vous pouvez voir que StackOverflowError est créé. Maintenant, la raison en est trop de récursivité car -
Ainsi, chaque entrée dans la pile crée 2 entrées supplémentaires et ainsi de suite ... qui est représentée par -
Finalement, autant d'entrées seront créées que le système ne pourra pas gérer dans la pile et StackOverflowError sera renvoyé.
Pour la prévention: Pour l'exemple de perspective ci-dessus - 1. Évitez d'utiliser l'approche de récursivité ou réduisez / limitez la récursion par une division de niveau encore une fois, comme si n est trop grand, puis divisez le n pour que le système puisse gérer sa limite. 2. Utilisez une autre approche, comme l'approche en boucle que j'ai utilisée dans le premier exemple de code. (Je n'ai pas du tout l'intention de dégrader Divide & Concur ou Recursion car ce sont des approches légendaires dans de nombreux algorithmes les plus célèbres ... mon intention est de limiter ou de rester à l'écart de la récursivité si je soupçonne des problèmes de dépassement de pile)
la source