memoria - ¿Cómo detectar posibles/posibles problemas de desbordamiento de pila en el programa ac/c++?
desborde de memoria ram (9)
Algunos compiladores admiten la función stackavail (), que devuelve la cantidad de espacio libre restante de la pila. Puede usar esta función antes de llamar a funciones en sus programas que requieren mucho espacio de pila, para determinar si es seguro llamarlas
¿Existe una forma estándar de ver cuánto espacio de pila tiene tu aplicación y cuál es la marca de agua más alta para el uso de la pila durante una carrera?
También en el caso temido de desbordamiento real, ¿qué sucede?
¿Se bloquea, dispara una excepción o señal? ¿Hay un estándar o es diferente en todos los sistemas y compiladores?
Estoy buscando específicamente Windows, Linux y Macintosh.
El desbordamiento de la pila es probablemente el tipo de excepción más desagradable de manejar, porque su manejador de excepciones tiene que lidiar con una cantidad mínima de pila (por lo general, solo una página está reservada para este fin).
Para una discusión interesante sobre las dificultades para manejar este tipo de excepción, vea estas publicaciones en el blog: 1 y 2 de Chris Brumme, que se centran en el tema desde la perspectiva de .NET, especialmente el alojamiento del CLR.
En Windows se generará una excepción de desbordamiento de pila.
El siguiente código de Windows ilustra esto:
#include <stdio.h>
#include <windows.h>
void ()
{
CONTEXT context;
// we are interested control registers
context.ContextFlags = CONTEXT_CONTROL;
// get the details
GetThreadContext(GetCurrentThread(), &context);
// print the stack pointer
printf("Esp: %X/n", context.Esp);
// this will eventually overflow the stack
();
}
DWORD ExceptionFilter(EXCEPTION_POINTERS *pointers, DWORD dwException)
{
return EXCEPTION_EXECUTE_HANDLER;
}
void main()
{
CONTEXT context;
// we are interested control registers
context.ContextFlags = CONTEXT_CONTROL;
// get the details
GetThreadContext(GetCurrentThread(), &context);
// print the stack pointer
printf("Esp: %X/n", context.Esp);
__try
{
// cause a
();
}
__except(ExceptionFilter(GetExceptionInformation(), GetExceptionCode()))
{
printf("/n****** ExceptionFilter fired ******/n");
}
}
Cuando se ejecuta este exe, se genera el siguiente resultado:
Esp: 12FC4C
Esp: 12F96C
Esp: 12F68C
.....
Esp: 33D8C
Esp: 33AAC
Esp: 337CC
****** ExceptionFilter fired ******
En Linux obtienes un error de segmentación si tu código intenta escribir más allá de la pila.
El tamaño de la pila es una propiedad heredada entre procesos. Si puede leerlo o modificarlo en el shell usando comandos como ulimit -s
(en sh
, ksh
, zsh
) o limit stacksize
( tcsh
, zsh
).
Desde un programa, el tamaño de la pila puede leerse usando
#include <sys/resource.h>
#include <stdio.h>
struct rlimit l;
getrlimit(RLIMIT_STACK, &l);
printf("stack_size = %d/n", l.rlim_cur);
No sé de una forma estándar para obtener el tamaño de la pila disponible.
La pila comienza con argc
seguido de los contenidos de argv
y una copia del entorno, y luego sus variables. Sin embargo, dado que el núcleo puede aleatorizar la ubicación del inicio de la pila, y puede haber algunos valores ficticios por encima de argc
, sería incorrecto suponer que tiene l.rlim_cur
bytes disponibles debajo &argc
.
Una forma de recuperar la ubicación exacta de la pila es mirar el archivo /proc/1234/maps
(donde 1234
es la identificación del proceso de su programa). Una vez que conozca estos límites, puede calcular la cantidad de su pila que se utiliza mirando la dirección de la última variable local.
En Linux, la biblioteca libsigsegv de Gnu incluye la función _install_handler
, que puede detectar (y en algunos casos ayudar a recuperarse de) el desbordamiento de la pila.
En Windows, la pila (para un hilo específico) crece según demanda hasta que se alcanza el tamaño de pila especificado para este subproceso antes de su creación.
El crecimiento bajo demanda se impulsa usando páginas de guardia, en el sentido de que hay solo un fragmento de pila disponible inicialmente, seguido de una página de guardia, que, cuando se golpea, generará una excepción: esta excepción es especial y el sistema la maneja para usted - el manejo aumenta el espacio de pila disponible (¡también se verifica si se ha alcanzado un límite!) y se vuelve a intentar la operación de lectura.
Una vez que se alcanza el límite, no hay más crecimiento que da como resultado la excepción de desbordamiento de pila. La base y el límite de la pila actual se almacenan en un bloque de entorno de subprocesos, en una estructura llamada _NT_TIB
(bloque de información de subprocesos). Si tiene un depurador a mano, esto es lo que ve:
0:000> dt ntdll!_teb @$teb nttib.
+0x000 NtTib :
+0x000 ExceptionList : 0x0012e030 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : 0x00130000
+0x008 StackLimit : 0x0011e000
+0x00c SubSystemTib : (null)
+0x010 FiberData : 0x00001e00
+0x010 Version : 0x1e00
+0x014 ArbitraryUserPointer : (null)
+0x018 Self : 0x7ffdf000 _NT_TIB
El atributo StackLimit se actualizará según demanda. Si comprueba los atributos en este bloque de memoria, verá algo similar a eso:
0:000> !address 0x0011e000
00030000 : 0011e000 - 00012000
Type 00020000 MEM_PRIVATE
Protect 00000004 PAGE_READWRITE
State 00001000 MEM_COMMIT
Usage RegionUsageStack
Pid.Tid abc.560
Y al revisar una página al lado se revela el atributo de guardia:
0:000> !address 0x0011e000-1000
00030000 : 0011d000 - 00001000
Type 00020000 MEM_PRIVATE
Protect 00000104 PAGE_READWRITE | PAGE_GUARD
State 00001000 MEM_COMMIT
Usage RegionUsageStack
Pid.Tid abc.560
Espero eso ayude.
Es posible usar editbin en Visual Studio para cambiar el tamaño de la pila. La información se puede encontrar en msdn.microsoft.com/en-us/library/35yc2tc3.aspx .
Te sugiero que uses alterna-signal-stack si estás en Linux.
- En este caso, toda la señal se manejará sobre la pila alternativa.
- En caso de que se produzca un desbordamiento de la pila, el sistema genera una señal SEGV, que puede manejarse sobre una pila alternativa.
- Si no lo usa ... entonces es posible que no pueda manejar la señal, y su programa puede bloquearse sin manipulación / informes erróneos.
gcc coloca un bloque adicional de memoria entre la dirección de retorno y las variables normales en llamadas a funciones "inseguras", como (en este ejemplo, la función es prueba vacía () {char a [10]; b [20]}:
call stack:
-----------
return address
dummy
char b[10]
char a[20]
Si la función escribe 36 bytes en el puntero ''a'', el desbordamiento ''corromperá'' la dirección de retorno (posible infracción de seguridad). Pero también cambiará el valor del ''dummy'', es decir, entre el puntero y la dirección de retorno, por lo que el programa se bloqueará con una advertencia (puede deshabilitarlo con un -fno-stack-protector)