8

I'm programming in C in Visual Studio 2005. I have a multi-threaded program, but that's not especially important here.

How can I determine (approximately) how much stack space my threads use?

The technique I was planning to use is setting the stack memory to some predetermined value, say 0xDEADBEEF, running the program for a long time, pausing the program, and investigating the stack.

How do I read and write stack memory with Visual Studio?

EDIT: See, for example, "How to determine maximum stack usage." That question talks about an embedded system, but here I'm trying to determine the answer on a regular PC.

Community
  • 1
  • 1
JXG
  • 7,263
  • 7
  • 32
  • 63

4 Answers4

16

Windows does not commit the stack memory immediately; instead, it reserves the address space for it, and commits it page-by-page when it is accessed. Read this page for more info.

As a result, stack address space consists of three contiguous regions:

  • Reserved but uncommitted memory which can be used for stack growth (but was never accessed yet);
  • Guard page, which was never accessed yet too, and serves to trigger stack growth when accessed;
  • Committed memory, i.e. stack memory which was ever accessed by the thread.

This allows us to construct a function that obtains stack size (with page size granularity):

static size_t GetStackUsage()
{
    MEMORY_BASIC_INFORMATION mbi;
    VirtualQuery(&mbi, &mbi, sizeof(mbi));
    // now mbi.AllocationBase = reserved stack memory base address

    VirtualQuery(mbi.AllocationBase, &mbi, sizeof(mbi));
    // now (mbi.BaseAddress, mbi.RegionSize) describe reserved (uncommitted) portion of the stack
    // skip it

    VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi));
    // now (mbi.BaseAddress, mbi.RegionSize) describe the guard page
    // skip it

    VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi));
    // now (mbi.BaseAddress, mbi.RegionSize) describe the committed (i.e. accessed) portion of the stack

    return mbi.RegionSize;
}

One thing to consider: CreateThread allows to specify initial stack commit size (via dwStackSize parameter, when STACK_SIZE_PARAM_IS_A_RESERVATION flag is not set). If this parameter is nonzero, our function will return correct value only when stack usage becomes greater than dwStackSize value.

atzz
  • 17,507
  • 3
  • 35
  • 35
  • Doesn't the stack grow down? Why are you adding the RegionSize to the base address instead of subtracting it? – Philip Sep 22 '11 at 22:06
  • 2
    @Philip - The stack does grow down (on x86, at least). I'm adding because `VirtualQuery` returns the base address of memory allocation region - the address of the last (theoretically) usable byte of a downward-growing stack. On a platform with upward-growing stack, the first `VirtualQuery` call would have given the needed result. I guess I could illustrate it with a picture; I'll probably even do it later when I have more time. – atzz Sep 23 '11 at 13:28
  • @atzz I have a slight worry about this solution (which is quite helpful). How do we know that while executing this function, or one of the VirtualQuery calls that it makes, that we don't run into the guard page and therefore cause the actual stack state to change underneath us? Couldn't the guard page move? – acm Oct 13 '15 at 14:38
  • @acm It can't (if you're willing to accept some reasonable assumptions about `VirtualQuery` internals and compiler code generation, stack growth should be finished by the first `VirtualQuery` call). Though you could call this fn twise (or *n* times) and take the last result to be extra sure. (But it's not 100% too; e.g. another process can inflict a `WriteProcessMemory` on us and we'd be screwed :) ). The concept of stack usage has meaning only for health monitoring or debugging anyway, so the fn should be ok as is. – atzz Oct 15 '15 at 15:47
8

You can make use of information in the Win32 Thread Information Block

When you want in a thread to find out how much stack space it uses you can do something like this:

#include <windows.h>
#include <winnt.h>
#include <intrin.h>

inline NT_TIB* getTib()
{
    return (NT_TIB*)__readfsdword( 0x18 );
}
inline size_t get_allocated_stack_size()
{
    return (size_t)getTib()->StackBase - (size_t)getTib()->StackLimit;
}

void somewhere_in_your_thread()
{
    // ...
    size_t sp_value = 0;
    _asm { mov [sp_value], esp }
    size_t used_stack_size = (size_t)getTib()->StackBase - sp_value;

    printf("Number of bytes on stack used by this thread: %u\n", 
           used_stack_size);
    printf("Number of allocated bytes on stack for this thread : %u\n",
           get_allocated_stack_size());    
    // ...
}
1

The stack doesn't work the way you expect it too. The stack is a linear sequence of pages, the last (top) one of which is marked with a page guard bit. When this page is touched, the guard bit is removed, and the page can be used. For further growth, a new guard page is allocated.

Hence, the answer you want is where the gaurd page is allocated. But the technique you propose would touch the page in question, and as a result it would invalidate the very thing you're trying to measure.

The non-invasive way to determine if a (stack) page has the guard bit is via VirtualQuery().

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 1
    Your comment is not exactly true. Touching the page in question is OK, really. The technique is to write all of relevant memory with a specific value, and then after a long while of operation, see how much memory doesn't have that value there anymore. – JXG Nov 16 '09 at 15:20
  • Qupting Microsoft: "An attempt to read from or write to a guard page causes the system to raise a STATUS_ACCESS_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-shot access alarm.". No, reading is not exempt. – MSalters Nov 17 '09 at 09:08
  • I think we are talking past each other. – JXG Nov 17 '09 at 13:04
  • Yup, you have a solution firmly in mind and want to know how to do it. I've got an alternative that's (1) in line with the actual OS implementation of the stack and (2) where I know how to do it - just check how much the guardpage moves. – MSalters Nov 17 '09 at 13:31
  • 1
    But if I understand you correctly, your solution only has page resolution. Your answer is helpful, but it doesn't give me as specific an answer as I was hoping for. – JXG Nov 17 '09 at 15:25
  • 3
    Actually, it is the correct answer, because a page allocated to a stack is exclusively allocated to that stack and thread. Hence, stack size is always as a number of pages. See also MSVC compiler options - options like "initial stack space" are specified in multiples of the page size. – MSalters Nov 17 '09 at 15:35
0

You can use GetThreadContext() function to determine thread's current stack pointer. Then use VirtualQuery() to find stack base for this pointer. Substracting those two pointers will give you stack size for given thread.

denisenkom
  • 414
  • 2
  • 3