How can we poll the stack status - unused (availab

2019-04-29 08:26发布

How can we get this information? I guess it is OS dependent, and I'm running Windows so my question refers to the windows API.

Is there any function that can do that for us - get the remaining stack memory for the calling thread?

Alternatively, if we could find out the following details, we will be able to calculate that on our own:

  1. Get the thread stack base address. There must be some function that takes a thread identifier as a parameter, and returns some information about it (Such as... Stack base address?)
  2. Get the thread stack size. If the thread was started by us, we can know it (Since we specified it when calling CreateThread). But if it is the main thread, which was started by the OS for our program, or any other thread we did not start explicitly, how do we find it?
  3. Get the current stack pointer. Well, that's easier. we can either check it with esp, or take the address of a local variable, to get a close location.

This is for educational purposes, but I guess it could be used to stop a recursive algorithm from causing a stack overflow - Instead of using any max-depth limiting function.

4条回答
Bombasti
2楼-- · 2019-04-29 09:08

You can use NtCurrentTeb(), which gets you a pointer to the TEB. This has NT_TIB as its first member:

typedef struct _NT_TIB
{
     PEXCEPTION_REGISTRATION_RECORD ExceptionList;
     PVOID StackBase;
     PVOID StackLimit;
     PVOID SubSystemTib;
     // ....
} NT_TIB, *PNT_TIB;
查看更多
Lonely孤独者°
3楼-- · 2019-04-29 09:12
  1. Get the thread stack base address: As wj32 showed, use the StackBase of the thread information block.

  2. Get the thread stack size: Determine a threads reserved stack size (which is it's maximum size) is different. What the StackLimit shows is the lowest commited address, which can show how large the stack has ever grown, not it's limit. Also not that the stack size you pass to CreateThread is the initial commit size, not reserve size unless you pass the STACK_SIZE_PARAM_IS_A_RESERVATION flag. Your program's stack size is specified by a linker parameter and defaults to 1MB if you don't specify. So most likely all of threads have a 1MB stack reservation.

    Since last page of the stack is a guard page you could conceivably start from the StackPage and check each lower stack page VirtualQuery to find the gaurd page that would be the end of the stack. This is of course completely relying on implementation defined behavior.

  3. Get the current stack pointer: You could use the StackLimit to get the maximum commited size of your stack, but that's not the same as the current pointer. esp is obviously the current stack position and could be higher than StackLimit.

Note on reserved vs commited. In Windows reserved means that virtual addresses have been reserved for use and can't be taken for something else. Reserved addresses do not consume any physical or virtual memory. Once it is commited it the address will be mapped to physical or virtual memory and can be used. Windows user threads have a fixed stack reserve size - address space is reserved for the stack and cannot be increased and a variable commit size - the stack will only use (commit) memory as it needs it.

Edit

My thoughts on checking the gaurd page won't work. I wrote a test program and the guard page is set at the commit limit, so that doesn't work. But I did find that running a VirtualQuery anywhere on the stack will give the AllocationBase of the lowest address on the stack, since the reserve size was allocated at once. The following example shows this in action:

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

This outputs:

1048576 1048576 65536

As it should.

查看更多
Lonely孤独者°
4楼-- · 2019-04-29 09:13

Not directly answering the OP's question, but referring to the idea mentioned at it's end: "... it could be used to stop a recursive algorithm from causing a stack overflow - Instead of using any max-depth limiting function."

The Windows API offers the method SetThreadStackGuarantee() which allows to define a minimum stack size to keep available when throwing a stack overflow exception. This method together with the VC runtime library method _resetstkoflw() may make it possible to recover from stack overflows.

See this on MSDN for details.

查看更多
等我变得足够好
5楼-- · 2019-04-29 09:18

edit: This is an outstandingly excellent question for educational purposes! Have an upvote for that. Stack space is fixed edit: at the point that a process or thread begins execution; I think you must mean the heap, from which memory is dynamically allocated (by malloc( ), for example. There's a good discussion of this question over here on MSDN. I don't see the exact API call you're looking for: you'll have to poke around for that; it can't be too far away.

HTH

查看更多
登录 后发表回答