Determinación del espacio de pila con Visual Studio

6 minutos de lectura

avatar de usuario
JXG

Estoy programando en C en Visual Studio 2005. Tengo un programa de subprocesos múltiples, pero eso no es especialmente importante aquí.

¿Cómo puedo determinar (aproximadamente) cuánto espacio de pila usan mis subprocesos?

La técnica que planeaba usar es configurar la memoria de la pila en un valor predeterminado, digamos 0xDEADBEEF, ejecutar el programa durante mucho tiempo, pausar el programa e investigar la pila.

¿Cómo leo y escribo la memoria de la pila con Visual Studio?

EDITAR: consulte, por ejemplo, “Cómo determinar el uso máximo de la pila”. Esa pregunta habla de un sistema integrado, pero aquí estoy tratando de determinar la respuesta en una PC normal.

Windows no compromete la memoria de la pila inmediatamente; en cambio, reserva el espacio de direcciones para él y lo confirma página por página cuando se accede a él. Leer esta página para más información.

Como resultado, el espacio de direcciones de la pila consta de tres regiones contiguas:

  • Memoria reservada pero no comprometida que se puede usar para el crecimiento de la pila (pero nunca se accedió a ella todavía);
  • Guard page, a la que nunca se accedió todavía, y sirve para desencadenar el crecimiento de la pila cuando se accede;
  • Memoria comprometida, es decir, memoria de pila a la que alguna vez accedió el subproceso.

Esto nos permite construir una función que obtiene el tamaño de la pila (con granularidad de tamaño de página):

static size_t GetStackUsage()
{
    MEMORY_BASIC_INFORMATION mbi;
    VirtualQuery(&mbi, &mbi, sizeof(mbi));
    // now mbi.AllocationBase = reserved stack memory base address

    VirtualQuery(mbi.AllocationBase, &mbi, sizeof(mbi));
    // now (mbi.BaseAddress, mbi.RegionSize) describe reserved (uncommitted) portion of the stack
    // skip it

    VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi));
    // now (mbi.BaseAddress, mbi.RegionSize) describe the guard page
    // skip it

    VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi));
    // now (mbi.BaseAddress, mbi.RegionSize) describe the committed (i.e. accessed) portion of the stack

    return mbi.RegionSize;
}

Una cosa a considerar: CreateThread permite especificar el tamaño de compromiso de la pila inicial (a través de dwStackSize parámetro, cuando STACK_SIZE_PARAM_IS_A_RESERVATION la bandera no está configurada). Si este parámetro es distinto de cero, nuestra función devolverá el valor correcto solo cuando el uso de la pila sea mayor que dwStackSize valor.

  • ¿La pila no crece? ¿Por qué está agregando RegionSize a la dirección base en lugar de restarlo?

    – Felipe

    22 de septiembre de 2011 a las 22:06

  • @Philip: la pila crece hacia abajo (en x86, al menos). Estoy agregando porque VirtualQuery devuelve la dirección base de la región de asignación de memoria: la dirección del último byte utilizable (teóricamente) de una pila que crece hacia abajo. En una plataforma con una pila que crece hacia arriba, la primera VirtualQuery llamada habría dado el resultado necesario. Supongo que podría ilustrarlo con una imagen; Probablemente incluso lo haré más tarde cuando tenga más tiempo.

    – atzz

    23 de septiembre de 2011 a las 13:28


  • @atzz Tengo una ligera preocupación sobre esta solución (que es bastante útil). ¿Cómo sabemos que mientras ejecutamos esta función, o una de las llamadas de VirtualQuery que realiza, no nos encontramos con la página de protección y, por lo tanto, hacemos que el estado real de la pila cambie debajo de nosotros? ¿No podría moverse la página de guardia?

    – ac

    13 oct 2015 a las 14:38

  • @acm No puede (si está dispuesto a aceptar algunas suposiciones razonables sobre VirtualQuery internos y la generación de código del compilador, el crecimiento de la pila debe terminar por el primero VirtualQuery llamada). Aunque podría llamar a esto fn twise (o norte veces) y tome el último resultado para estar más seguro. (Pero tampoco es 100%; por ejemplo, otro proceso puede infligir un WriteProcessMemory nosotros y estaríamos jodidos :)). De todos modos, el concepto de uso de la pila solo tiene significado para el monitoreo de la salud o la depuración, por lo que el fn debería estar bien como está.

    – atzz

    15/10/2015 a las 15:47

Puede hacer uso de la información en el Bloque de información de subprocesos Win32

Cuando desee en un hilo averiguar cuánto espacio de pila utiliza, puede hacer algo como esto:

#include <windows.h>
#include <winnt.h>
#include <intrin.h>

inline NT_TIB* getTib()
{
    return (NT_TIB*)__readfsdword( 0x18 );
}
inline size_t get_allocated_stack_size()
{
    return (size_t)getTib()->StackBase - (size_t)getTib()->StackLimit;
}

void somewhere_in_your_thread()
{
    // ...
    size_t sp_value = 0;
    _asm { mov [sp_value], esp }
    size_t used_stack_size = (size_t)getTib()->StackBase - sp_value;

    printf("Number of bytes on stack used by this thread: %u\n", 
           used_stack_size);
    printf("Number of allocated bytes on stack for this thread : %u\n",
           get_allocated_stack_size());    
    // ...
}

La pila tampoco funciona de la manera que esperas. La pila es una secuencia lineal de páginas, la última (superior) de las cuales está marcada con un bit de protección de página. Cuando se toca esta página, se elimina el bit de protección y se puede usar la página. Para un mayor crecimiento, se asigna una nueva página de protección.

Por lo tanto, la respuesta que desea es dónde se asigna la página de guardia. Pero la técnica que propone tocaría la página en cuestión y, como resultado, invalidaría lo que está tratando de medir.

La forma no invasiva de determinar si una página (pila) tiene el bit de protección es a través de VirtualQuery().

  • Tu comentario no es exactamente cierto. Tocar la página en cuestión está bien, de verdad. La técnica es escribir toda la memoria relevante con un valor específico y luego, después de un largo tiempo de operación, ver cuánta memoria ya no tiene ese valor allí.

    – JXG

    16 de noviembre de 2009 a las 15:20

  • Qupting Microsoft: “Un intento de leer o escribir en una página de protección hace que el sistema genere una excepción STATUS_ACCESS_VIOLATION y apague el estado de la página de protección. Por lo tanto, las páginas de protección actúan como una alarma de acceso única”. No, la lectura no está exenta.

    – MSalters

    17 de noviembre de 2009 a las 9:08

  • Creo que estamos hablando entre nosotros.

    – JXG

    17 de noviembre de 2009 a las 13:04

  • Pero si te entiendo bien, tu solución solo tiene resolución de página. Su respuesta es útil, pero no me da una respuesta tan específica como esperaba.

    – JXG

    17 de noviembre de 2009 a las 15:25

  • En realidad, es la respuesta correcta, porque una página asignada a una pila se asigna exclusivamente a esa pila y subproceso. Por lo tanto, el tamaño de la pila es siempre como un número de páginas. Consulte también las opciones del compilador de MSVC: las opciones como “espacio de pila inicial” se especifican en múltiplos del tamaño de la página.

    – MSalters

    17 de noviembre de 2009 a las 15:35

Puede usar la función GetThreadContext() para determinar el puntero de pila actual del hilo. Luego use VirtualQuery() para encontrar la base de la pila para este puntero. Restar esos dos punteros le dará el tamaño de la pila para el hilo dado.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad