Как мы можем опросить статус стека - неиспользованная (доступная) память - PullRequest
5 голосов
/ 30 декабря 2011

Как мы можем получить эту информацию?Я предполагаю, что это зависит от ОС, и я использую Windows, поэтому мой вопрос относится к Windows API.

Есть ли какая-нибудь функция, которая может сделать это для нас - получить оставшуюся память стека для вызывающего потока?

В качестве альтернативы, если мы сможем выяснить следующие детали, мы сможем рассчитать это самостоятельно:

  1. Получить базовый адрес стека потоков .Должна быть какая-то функция, которая принимает идентификатор потока в качестве параметра и возвращает некоторую информацию о нем (например, ... Базовый адрес стека?)
  2. Получить размер стека потока .Если поток был запущен нами, мы можем знать его (так как мы указали его при вызове CreateThread).Но если это основной поток, который был запущен ОС для нашей программы, или любой другой поток, который мы не запустили явно, как мы его найдем?
  3. Получить текущий указатель стека .Ну так прощемы можем либо проверить его с помощью esp, либо взять адрес локальной переменной, чтобы получить близкое местоположение.

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

Ответы [ 4 ]

5 голосов
/ 31 декабря 2011

Вы можете использовать NtCurrentTeb (), который получает указатель на TEB.NT_TIB имеет первый член:

typedef struct _NT_TIB
{
     PEXCEPTION_REGISTRATION_RECORD ExceptionList;
     PVOID StackBase;
     PVOID StackLimit;
     PVOID SubSystemTib;
     // ....
} NT_TIB, *PNT_TIB;
1 голос
/ 31 декабря 2011
  1. Получите базовый адрес стека потока : Как wj32 показал , используйте StackBase блока информации о потоке.

  2. Получить размер стека потока : Определить, что зарезервированный размер стека потока (который является его максимальным размером) отличается. StackLimit показывает самый низкий зафиксированный адрес , который может показать, насколько велик размер стека, а не его предел. Кроме того, размер стека, который вы передаете CreateThread, не является начальным размером коммита, а не резервным, если вы не пропустите флаг STACK_SIZE_PARAM_IS_A_RESERVATION. Размер стека вашей программы определяется параметром компоновщика и по умолчанию равен 1 МБ, если вы не укажете. Поэтому, скорее всего, все потоки имеют резервирование стека в 1 МБ.

    Поскольку последняя страница стека является защитной страницей , вы можете начать с StackPage и проверить каждую нижнюю страницу стека VirtualQuery, чтобы найти страницу gaurd, которая будет концом стека. , Это, конечно, полностью полагается на поведение, определяемое реализацией.

  3. Получить текущий указатель стека : Вы можете использовать StackLimit, чтобы получить максимальный зафиксированный размер вашего стека, но это не то же самое, что текущий указатель. esp, очевидно, является текущей позицией стека и может быть выше, чем StackLimit.

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

Редактировать

Мои мысли о проверке страницы гаурда не будут работать. Я написал тестовую программу, и для страницы защиты установлено ограничение на принятие, так что это не работает. Но я обнаружил, что выполнение VirtualQuery в любом месте стека даст AllocationBase самого низкого адреса в стеке, поскольку резервный размер был выделен сразу. Следующий пример показывает это в действии:

#include <windows.h>
#include <WinNT.h>
#include <stdio.h>

DWORD GetThreadStackSize()
{
    SYSTEM_INFO systemInfo = {0};
    GetSystemInfo(&systemInfo);

    NT_TIB *tib = (NT_TIB*)NtCurrentTeb();
    DWORD_PTR stackBase = (DWORD_PTR)tib->StackBase;

    MEMORY_BASIC_INFORMATION mbi = {0};
    if (VirtualQuery((LPCVOID)(stackBase - systemInfo.dwPageSize), &mbi, sizeof(MEMORY_BASIC_INFORMATION)) != 0)
    {
        DWORD_PTR allocationStart = (DWORD_PTR)mbi.AllocationBase;
        return stackBase - allocationStart;
    }
    return 0;
}

DWORD WINAPI ThreadRtn(LPVOID param)
{
    DWORD stackSize = GetThreadStackSize();
    printf("%d\n", stackSize);
    return 0;
}

int main()
{
    ThreadRtn(NULL);
    HANDLE thread1 = CreateThread(NULL, 65535, ThreadRtn, NULL, 0, NULL);
    WaitForSingleObject(thread1, -1);
    HANDLE thread2 = CreateThread(NULL, 65535, ThreadRtn, NULL, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
    WaitForSingleObject(thread2, -1);

    return 0;
}

Это выводит:

1048576 1048576 65536

Как и должно быть.

1 голос
/ 31 декабря 2011

Непосредственно не отвечая на вопрос ОП, но ссылаясь на идею, упомянутую в его конце: "... его можно использовать, чтобы остановить рекурсивный алгоритм от переполнения стека - вместо использования какого-либо ограничения максимальной глубиныfunction. "

Windows API предлагает метод SetThreadStackGuarantee(), который позволяет определить минимальный размер стека, доступный при создании исключения переполнения стека.Этот метод вместе с методом библиотеки времени выполнения VC _resetstkoflw() может сделать возможным восстановление после переполнения стека.

Подробнее см. this в MSDN.

0 голосов
/ 30 декабря 2011

edit: Это превосходный вопрос для образовательных целей! Иметь голос за это. Пространство стека фиксировано edit: в тот момент, когда процесс или поток начинает выполнение; Я думаю, вы должны иметь в виду кучу, из которой динамически выделяется память (например, с помощью malloc (). Здесь есть хорошее обсуждение этого вопроса на MSDN . Точный вызов API, который вам нужен, вам не нужен: вам придется искать его, он не может быть слишком далеко.

HTH

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...