¿Cómo se produce un desbordamiento de pila y cuáles son las mejores formas de asegurarse de que no suceda, o formas de evitarlo, especialmente en servidores web, pero también serían interesantes otros ejemplos?
fuente
¿Cómo se produce un desbordamiento de pila y cuáles son las mejores formas de asegurarse de que no suceda, o formas de evitarlo, especialmente en servidores web, pero también serían interesantes otros ejemplos?
Una pila, en este contexto, es el último búfer en entrar, primero en salir que coloca los datos mientras se ejecuta el programa. Último en entrar, primero en salir (LIFO) significa que lo último que pones es siempre lo primero que vuelves a salir: si empujas 2 elementos en la pila, 'A' y luego 'B', entonces lo primero que sacas fuera de la pila será 'B', y lo siguiente es 'A'.
Cuando llama a una función en su código, la siguiente instrucción después de la llamada a la función se almacena en la pila y cualquier espacio de almacenamiento que pueda ser sobrescrito por la llamada a la función. La función a la que llama podría usar más pila para sus propias variables locales. Cuando termina, libera el espacio de pila de variables locales que usó y luego regresa a la función anterior.
Un desbordamiento de pila es cuando ha usado más memoria para la pila de la que se suponía que debía usar su programa. En los sistemas embebidos, es posible que solo tenga 256 bytes para la pila, y si cada función ocupa 32 bytes, entonces solo puede tener llamadas de función 8 en profundidad: la función 1 llama a la función 2 quién llama a la función 3 quién llama a la función 4 ... quién llama función 8 que llama a la función 9, pero la función 9 sobrescribe la memoria fuera de la pila. Esto podría sobrescribir la memoria, el código, etc.
Muchos programadores cometen este error al llamar a la función A que luego llama a la función B, que luego llama a la función C, que luego llama a la función A. Podría funcionar la mayor parte del tiempo, pero solo una vez, la entrada incorrecta hará que entre en ese círculo para siempre. hasta que la computadora reconozca que la pila está exagerada.
Las funciones recursivas también son una causa de esto, pero si está escribiendo de forma recursiva (es decir, su función se llama a sí misma), debe ser consciente de esto y usar variables estáticas / globales para evitar la recursividad infinita.
Generalmente, el sistema operativo y el lenguaje de programación que está utilizando administran la pila, y está fuera de sus manos. Debe mirar su gráfico de llamadas (una estructura de árbol que muestra desde su principal lo que llama cada función) para ver qué tan profundas son las llamadas a sus funciones y para detectar ciclos y recursiones que no están previstos. Los ciclos intencionales y la recursividad deben comprobarse artificialmente para detectar errores si se llaman entre sí demasiadas veces.
Más allá de las buenas prácticas de programación, las pruebas estáticas y dinámicas, no hay mucho que pueda hacer en estos sistemas de alto nivel.
En el mundo integrado, especialmente en el código de alta confiabilidad (automotriz, aeronave, espacio), realiza revisiones y verificaciones de código extensas, pero también hace lo siguiente:
Pero en lenguajes de alto nivel se ejecutan en sistemas operativos:
Depende de la 'caja de arena' que tenga si puede controlar o incluso ver la pila. Es muy probable que pueda tratar los servidores web como lo haría con cualquier otro lenguaje y sistema operativo de alto nivel; en gran medida está fuera de sus manos, pero compruebe el idioma y la pila de servidores que está utilizando. Que es posible soplar la pila en su servidor SQL, por ejemplo.
-Adán
Un desbordamiento de pila en código real ocurre muy raramente. La mayoría de situaciones en las que ocurre son recursiones en las que se ha olvidado la terminación. Sin embargo, podría ocurrir raramente en estructuras muy anidadas, por ejemplo, documentos XML particularmente grandes. La única ayuda real aquí es refactorizar el código para usar un objeto de pila explícito en lugar de la pila de llamadas.
La mayoría de la gente le dirá que se produce un desbordamiento de pila con recursividad sin una ruta de salida, aunque en su mayoría es cierto, si trabaja con estructuras de datos lo suficientemente grandes, incluso una ruta de salida de recursión adecuada no lo ayudará.
Algunas opciones en este caso:
Se produce un desbordamiento de pila cuando Jeff y Joel quieren brindar al mundo un lugar mejor para obtener respuestas a preguntas técnicas. Es demasiado tarde para evitar este desbordamiento de pila. Ese "otro sitio" podría haberlo evitado si no fuera scuzzy. ;)
La recursividad infinita es una forma común de obtener un error de desbordamiento de pila. Para prevenir, asegúrese siempre de que haya una ruta de salida que pueda ser golpeada. :-)
Otra forma de obtener un desbordamiento de pila (en C / C ++, al menos) es declarar alguna variable enorme en la pila.
char hugeArray[100000000];
Eso lo hará.
Por lo general, un desbordamiento de pila es el resultado de una llamada recursiva infinita (dada la cantidad habitual de memoria en las computadoras estándar hoy en día).
Cuando realiza una llamada a un método, función o procedimiento, la forma "estándar" o la llamada consiste en:
Por lo tanto, esto suele llevar unos pocos bytes dependiendo del número y tipo de parámetros, así como de la arquitectura de la máquina.
Verá entonces que si comienza a hacer llamadas recursivas, la pila comenzará a crecer. Ahora, la pila generalmente se reserva en la memoria de tal manera que crece en dirección opuesta a la pila, por lo que, dada una gran cantidad de llamadas sin "regresar", la pila comienza a llenarse.
Ahora, en tiempos más antiguos, el desbordamiento de pila podría ocurrir simplemente porque agotó toda la memoria disponible, así como así. Con el modelo de memoria virtual (hasta 4 GB en un sistema X86) que estaba fuera del alcance, por lo general, si obtiene un error de desbordamiento de pila, busque una llamada recursiva infinita.
fuente
¿Qué? ¿Nadie siente amor por aquellos encerrados en un bucle infinito?
fuente
Aparte de la forma de desbordamiento de pila que obtiene de una recursividad directa (por ejemplo
Fibonacci(1000000)
), una forma más sutil de la misma que he experimentado muchas veces es una recursividad indirecta, donde una función llama a otra función, que llama a otra, y luego a una de esas funciones vuelven a llamar a la primera.Esto puede ocurrir comúnmente en funciones que se llaman en respuesta a eventos pero que por sí mismas pueden generar nuevos eventos, por ejemplo:
En este caso, la llamada a
ResizeWindow
puede hacer que laWindowSizeChanged()
devolución de llamada se active de nuevo, lo que llamaResizeWindow
vuelve a , hasta que se agote la pila. En situaciones como estas, a menudo es necesario posponer la respuesta al evento hasta que el marco de la pila haya regresado, por ejemplo, publicando un mensaje.fuente
Teniendo en cuenta que esto fue etiquetado con "piratería", sospecho que el "desbordamiento de pila" al que se refiere es un desbordamiento de pila de llamadas, en lugar de un desbordamiento de pila de nivel superior como los que se mencionan en la mayoría de las otras respuestas aquí. Realmente no se aplica a ningún entorno administrado o interpretado como .NET, Java, Python, Perl, PHP, etc., en los que las aplicaciones web suelen estar escritas, por lo que su único riesgo es el servidor web en sí, que probablemente esté escrito en C o C ++.
Mira este hilo:
/programming/7308/what-is-a-good-starting-point-for-learning-buffer-overflow
fuente
He recreado el problema de desbordamiento de pila mientras obtenía un número de Fibonacci más común, es decir, 1, 1, 2, 3, 5 ..... así que el cálculo para fib (1) = 1 o fib (3) = 2 .. fib (n ) = ??.
para n, digamos que nos interesará - ¿y si n = 100.000, entonces cuál será el número de Fibonacci correspondiente?
El enfoque de un bucle es el siguiente:
esto es bastante sencillo y el resultado es:
Ahora, otro enfoque que he aplicado es a través de dividir y concurrir a través de la recursividad.
es decir, Fib (n) = fib (n-1) + Fib (n-2) y luego más recursividad para n-1 & n-2 ..... hasta 2 & 1. que está programado como -
Cuando ejecuté el código para n = 100,000, el resultado es el siguiente:
Arriba puede ver que se crea StackOverflowError. Ahora, la razón de esto es demasiada recursividad como:
Entonces, cada entrada en la pila crea 2 entradas más y así sucesivamente ... que se representa como -
Eventualmente se crearán tantas entradas que el sistema no podrá manejar en la pila y se lanzará StackOverflowError.
Para la prevención: Para la perspectiva del ejemplo anterior: 1. Evite el uso del enfoque de recursividad o reduzca / limite la recursividad nuevamente en una división de nivel, como si n es demasiado grande, entonces divida la n para que el sistema pueda manejar con su límite. 2. Utilice otro enfoque, como el enfoque de bucle que he usado en el primer ejemplo de código. (No tengo la intención de degradar Divide & Concur o Recursion, ya que son enfoques legendarios en muchos de los algoritmos más famosos ... mi intención es limitar o mantenerme alejado de la recursividad si sospecho que hay problemas de desbordamiento de pila)
fuente