web-dev-qa-db-fra.com

Un débordement de pile peut-il entraîner autre chose qu'une erreur de segmentation?

Dans un programme compilé (disons C ou C++, mais je suppose que cette question pourrait s'étendre à n'importe quel langage non-VM-ish avec une pile d'appels) - très souvent lorsque vous débordez votre pile, vous obtenez une erreur de segmentation :

Le débordement de pile est [a] une cause, un défaut de segmentation en est le résultat.

Mais est-ce toujours le cas? Un débordement de pile peut-il entraîner d'autres types de comportement de programme/système d'exploitation?

Je pose également des questions sur les systèmes d'exploitation non Linux, non Windows et non X86. (Bien sûr, si vous n'avez pas de protection matérielle de la mémoire ou de prise en charge du système d'exploitation (par exemple MS-DOS), il n'y a rien de tel qu'une erreur de segmentation; je pose des questions sur les cas où vous pourrait obtenir une erreur de segmentation mais quelque chose d'autre se produit).

Remarque: Supposons qu'à part le débordement de pile, le programme est valide et n'essaie pas d'accéder aux tableaux au-delà de leurs limites, de déréférencer des pointeurs invalides, etc.

36
einpoklum

Oui, même sur un système d'exploitation standard (Linux) et du matériel standard (x86).

void f(void) {
    char arr[BIG_NUMBER];
    arr[0] = 0; // stack overflow
}

Notez que sur x86, la pile se développe, donc nous affectons au début du tableau pour déclencher le débordement. Les avertissements habituels s'appliquent ... le comportement exact dépend de plus de facteurs que ceux abordés dans cette réponse, y compris les détails de votre compilateur C.

Si le BIG_NUMBER est à peine assez grand pour déborder, vous rencontrerez le garde de pile et obtiendrez une erreur de segmentation. C'est pour cela que la protection de pile est là, et elle peut être aussi petite qu'une seule page de 4 Ko (mais pas plus petite, et cette taille de 4 Ko est utilisée avant Linux 4.12) ou elle peut être plus grande (1 Mio par défaut sur Linux 4.12 , voir mm: grand espace de garde de pile ), mais il s'agit toujours d'une taille particulière.

Si BIG_NUMBER est suffisamment grand, le débordement peut ignorer le garde de pile et atterrir sur une autre pièce de mémoire, potentiellement de la mémoire valide. Cela peut entraîner un comportement incorrect de votre programme mais pas un plantage, ce qui est fondamentalement le pire des cas: nous voulons que nos programmes se bloquent lorsqu'ils sont incorrects plutôt que de faire quelque chose d'inattendu.

31
Dietrich Epp

Une chose est ce qui se passe lors de l'exécution lorsque vous débordez la pile, ce qui peut être beaucoup de choses. Y compris, mais sans s'y limiter; erreur de segmentation, écrasement des variables suivant ce que vous débordez, provoquant une instruction illégale, rien du tout et bien plus encore. Le "vieux" papier classique Smashing The Stack For Fun And Profit décrit de nombreuses façons de s'amuser avec ce genre de choses.

Une autre chose est ce qui peut arriver au moment de la compilation. En C et C++, l'écriture au-delà d'un tableau ou le dépassement de la taille de la pile est un comportement indéfini et lorsqu'un programme contient UB n'importe où le compilateur est fondamentalement gratuit faire ce qu'il veut pour n'importe quelle partie de votre programme. Et les compilateurs modernes deviennent très agressifs dans l'exploitation de l'UB à des fins d'optimisation - souvent en supposant que l'UB ne se produit jamais, ce qui les conduit à supprimer simplement le code contenant UB ou à faire en sorte qu'une branche soit toujours ou jamais prise car l'alternative provoquerait UB. Parfois, le compilateur introduira voyage dans le temps ou appelera une fonction qui n'a jamais été appelée dans le code source et bien d'autres choses qui peuvent provoquer un comportement d'exécution vraiment déroutant.

Voir également:

Ce que tout programmeur C devrait savoir sur le comportement indéfini # 1/

Ce que tout programmeur C devrait savoir sur le comportement indéfini # 2/

Ce que tout programmeur C devrait savoir sur le comportement indéfini # 3/

n guide du comportement indéfini en C et C++, partie 1

n guide du comportement indéfini en C et C++, partie 2

n guide du comportement indéfini en C et C++, partie

7
Jesper Juhl

D'autres réponses ont assez bien couvert le côté PC. Je vais aborder certains des problèmes du monde intégré.

Le code intégré a quelque chose de similaire à un défaut de segmentation. Le code est stocké dans une sorte de stockage non volatile (généralement flash ces jours-ci, mais une sorte de ROM ou PROM dans le passé)). nécessite des opérations spéciales pour le configurer; les accès à la mémoire normaux peuvent lire à partir de celui-ci mais pas y écrire. En outre, les processeurs intégrés présentent généralement de grandes lacunes dans leurs mappages de mémoire. Si le processeur reçoit une demande d'écriture pour la mémoire en lecture seule, ou s'il obtient une demande de lecture ou d'écriture pour une adresse qui n'existe pas physiquement, le processeur lèvera généralement une exception matérielle. Si vous avez un débogueur connecté, vous pouvez vérifier l'état du système pour trouver ce qui ne va pas, comme avec un core dump.

Il n'y a cependant aucune garantie que cela se produira pour un débordement de pile. La pile peut être placée n'importe où dans la RAM, et ce sera généralement aux côtés d'autres variables. Le résultat d'un débordement de pile sera généralement de corrompre ces variables.

Si votre application utilise également le segment de mémoire (allocation dynamique), il est courant d'affecter une section de mémoire où la pile commence au bas de cette section et se développe vers le haut, et le segment de mémoire commence en haut de cette section et se développe vers le bas. De toute évidence, cela signifie que les données attribuées dynamiquement seront la première victime.

Si vous n'avez pas de chance, vous ne remarquerez peut-être même pas quand cela se produit, puis vous devez comprendre pourquoi votre code ne se comporte pas correctement. Dans le cas le plus ironique, si les données en cours d'écrasement sont un pointeur, vous pouvez toujours obtenir une exception matérielle lorsque le pointeur essaie d'accéder à une mémoire invalide - mais cela se produira quelque temps après le débordement de la pile et l'hypothèse naturelle sera généralement que c'est un bug dans votre code.

Le code intégré a un modèle commun pour gérer cela, qui consiste à "filigraner" la pile en initialisant chaque octet à une valeur connue. Parfois, le compilateur peut le faire; ou parfois vous devrez peut-être l'implémenter vous-même dans le code de démarrage avant main (). Vous pouvez regarder en arrière depuis la fin de la pile pour trouver où elle n'est plus définie sur cette valeur, moment auquel vous connaissez le high-mark-mark pour l'utilisation de la pile; ou si tout est incorrect, vous savez que vous avez un débordement. Il est courant (et une bonne pratique) pour les applications intégrées de l'interroger en continu en tant qu'opération d'arrière-plan et de pouvoir le signaler à des fins de diagnostic.

Ayant permis de suivre l'utilisation de la pile, la plupart des entreprises fixeront une marge acceptable dans le pire des cas pour éviter les débordements. Cela se situe généralement entre 75% et 90%, mais il y en aura toujours. Non seulement cela permet la possibilité qu'il y ait un pire cas pire que vous n'ayez pas encore vu, mais cela rend également la vie plus facile pour le développement futur lorsqu'un nouveau code doit être ajouté qui utilise plus de pile.

6
Graham

Stackoverflow est l'une des nombreuses raisons pour comportement indéfini d'un programme. Dans ce cas, vous pouvez obtenir un résultat attendu ou une erreur de segmentation ou votre disque dur pourrait être effacé, etc. N'attendez aucun comportement défini car il s'agit d'un comportement non défini.

3
haccks