我们如何轮询堆栈状态 - 未使用(可用)内存

时间:2011-12-30 19:40:24

标签: c windows winapi stack

我们如何获得这些信息?我想这是依赖于操作系统的,我正在运行Windows,所以我的问题是指Windows API。

是否有任何函数可以为我们做这些 - 获取调用线程的剩余堆栈内存?

或者,如果我们能够找到以下详细信息,我们将能够自行计算:

  1. 获取线程堆栈基址。必须有一些函数将线程标识符作为参数,并返回一些关于它的信息(例如...堆栈基址?)
  2. 获取线程堆栈大小。如果线程是由我们启动的,我们就可以知道它(因为我们在调用CreateThread时指定了它)。但是如果它是由我们的程序的操作系统启动的主线程,或者我们没有明确启动的任何其他线程,我们如何找到它?
  3. 获取当前堆栈指针。嗯,那更容易。我们可以使用esp进行检查,或者使用本地变量的地址来获取一个位置。
  4. 这是出于教育目的,但我想它可以用来阻止递归算法导致堆栈溢出 - 而不是使用任何最大深度限制函数。

4 个答案:

答案 0 :(得分:5)

你可以使用NtCurrentTeb(),它可以获得指向TEB的指针。这有NT_TIB作为其第一个成员:

typedef struct _NT_TIB
{
     PEXCEPTION_REGISTRATION_RECORD ExceptionList;
     PVOID StackBase;
     PVOID StackLimit;
     PVOID SubSystemTib;
     // ....
} NT_TIB, *PNT_TIB;

答案 1 :(得分:1)

没有直接回答OP的问题,而是提到它最后提到的想法:“......它可以用来阻止递归算法导致堆栈溢出 - 而不是使用任何最大深度限制功能“。

Windows API提供方法SetThreadStackGuarantee(),允许定义最小堆栈大小,以便在抛出堆栈溢出异常时保持可用。此方法与VC运行时库方法_resetstkoflw()一起使用可以从堆栈溢出中恢复。

有关详细信息,请参阅MSDN上的this

答案 2 :(得分:1)

  1. 获取线程堆栈基址:作为wj32 showed,使用线程信息块的StackBase

  2. 获取线程堆栈大小:确定线程保留堆栈大小(它的最大大小)是不同的。 StackLimit显示的是lowest commited address,它可以显示堆栈有多大,而不是它的限制。除了传递CreateThread标志之外,传递给STACK_SIZE_PARAM_IS_A_RESERVATION的堆栈大小也不是初始提交大小,而不是保留大小。您的程序的堆栈大小由linker parameter指定,如果未指定,则默认为1MB。所以很可能所有线程都有1MB的堆栈预留。

    stack is a guard page的最后一页开始,您可以想象从StackPage开始,并检查每个下层堆栈页面VirtualQuery,以找到将成为堆栈末尾的gaurd页面。这当然完全依赖于实现定义的行为。

  3. 获取当前堆栈指针:您可以使用StackLimit来获取堆栈的最大提交大小,但这与当前指针不同。 esp显然是当前的堆叠位置,可能高于StackLimit

  4. 关于reserved vs commited的注释。在Windows中,保留意味着虚拟地址已保留供使用,不能用于其他内容。保留地址不消耗任何物理或虚拟内存。提交后,地址将映射到物理或虚拟内存并可以使用。 Windows用户线程具有固定的堆栈保留大小 - 为堆栈保留地址空间并且不能增加和变量提交大小 - 堆栈将仅在需要时使用(提交)内存。

    修改

    我对检查gaurd页面的想法是行不通的。我编写了一个测试程序,并且保护页面设置为提交限制,因此不起作用。但我确实发现在堆栈上的任何地方运行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

    应该如此。

答案 3 :(得分:-1)

编辑:这是一个非常出色的教育用途问题!有一个upvote为此。在进程或线程开始执行时,堆栈空间是固定的编辑:;我认为你必须指的是动态分配内存的堆(例如,通过malloc()。这里有一个很好的讨论这个问题 on MSDN。我没有看到确切的API调用你是寻找:你将不得不四处寻找;它不会太远。

HTH