Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can we poll the stack status - unused (available) memory

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.

like image 857
Jong Avatar asked Dec 30 '11 19:12

Jong


People also ask

How much memory is available on the stack?

It depends on your operating system. On Windows, the typical maximum size for a stack is 1MB, whereas it is 8MB on a typical modern Linux, although those values are adjustable in various ways.

Is stack memory limited?

Yes, stack is always limited. In several languages/compilers you can set the requested size.

What happens if you call free on stack memory?

This is a bug. free() frees the memory space pointed to by ptr, which must have been returned by a previous call to malloc(), calloc() or realloc(). Otherwise, or if free(ptr) has already been called before, undefined behaviour occurs. If ptr is NULL, no operation is performed.

Why is stack memory limited?

Because all threads in a process share the same address space, they have to divide it between them. And after the operating system has taken its part, there is "only" 2-3 GB left for an application. And that size is the limit for both the physical and the virtual memory, because there just aren't any more addresses.


3 Answers

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;
like image 177
wj32 Avatar answered Oct 23 '22 05:10

wj32


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.

like image 43
alk Avatar answered Oct 23 '22 03:10

alk


  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.

like image 44
shf301 Avatar answered Oct 23 '22 03:10

shf301