Определение пространства стека с помощью Visual Studio

Я программирую на C в Visual Studio 2005. У меня многопоточная программа, но это не особенно важно здесь.

Как определить (приблизительно), сколько пространства стека используют мои потоки?

Метод, который я планировал использовать, - установить память стека на некоторое предопределенное значение, скажем 0xDEADBEEF, долгое время запускать программу, приостанавливать программу и исследовать стек.

Как читать и записывать стек с Visual Studio?

EDIT: см., например, "Как определить максимальное использование стека" . Этот вопрос говорит о встроенной системе, но здесь я пытаюсь определить ответ на обычный ПК.

Ответ 1

Windows не фиксирует память стека сразу; вместо этого он резервирует для него адресное пространство и записывает его по страницам, когда к нему обращаются. Подробнее читайте эту страницу.

В результате адресное пространство стека состоит из трех смежных областей:

  • Зарезервированная, но незафиксированная память, которая может использоваться для роста стека (но еще не была доступна);
  • Страница Guard, к которой еще никогда не обращался, и служит для запуска роста стека при доступе;
  • Committed memory, т.е. стек памяти, к которому всегда обращался поток.

Это позволяет нам построить функцию, которая получает размер стека (с гранулярностью размера страницы):

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;
}

Одна вещь, которую следует учитывать: CreateThread позволяет указать начальный размер фиксации стека (с помощью параметра dwStackSize, когда флаг STACK_SIZE_PARAM_IS_A_RESERVATION не установлен). Если этот параметр отличен от нуля, наша функция вернет правильное значение только тогда, когда использование стека станет больше значения dwStackSize.

Ответ 2

Вы можете использовать информацию в разделе информации о потоке Win32

Если вы хотите в потоке узнать, сколько стекового пространства оно использует, вы можете сделать что-то вроде этого:

#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());    
    // ...
}

Ответ 3

Стек не работает так, как вы ожидаете. Стек представляет собой линейную последовательность страниц, последняя (верхняя), одна из которых отмечена битом защиты страницы. Когда эта страница затрагивается, защитный бит удаляется, и страница может использоваться. Для дальнейшего роста выделяется новая страница охраны.

Следовательно, ответ, который вы хотите, - это место, где выделяется страница gaurd. Но метод, который вы предлагаете, коснется страницы, о которой идет речь, и в результате это сделает недействительным то, что вы пытаетесь измерить.

Неинвазивный способ определения, имеет ли страница (стек) бит защиты через VirtualQuery().

Ответ 4

Вы можете использовать функцию GetThreadContext() для определения указателя стека текущего потока. Затем используйте VirtualQuery(), чтобы найти базу стека для этого указателя. Выделение этих двух указателей даст вам размер стека для заданного потока.