How can we poll the stack status – unused (available) memory-Collection of common programming errors
-
Get the thread stack base address: As wj32 showed, use the
StackBaseof the thread information block. -
Get the thread stack size: Determine a threads reserved stack size (which is it’s maximum size) is different. What the
StackLimitshows 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 toCreateThreadis the initial commit size, not reserve size unless you pass theSTACK_SIZE_PARAM_IS_A_RESERVATIONflag. 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
StackPageand check each lower stack pageVirtualQueryto find the gaurd page that would be the end of the stack. This is of course completely relying on implementation defined behavior. -
Get the current stack pointer: You could use the
StackLimitto get the maximum commited size of your stack, but that’s not the same as the current pointer.espis obviously the current stack position and could be higher thanStackLimit.
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
#include
#include
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.