views:

517

answers:

5

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.

A: 

You don't necessarily want to let the program run for a long time, then pause it, to determine stack size. The stack isn't something that grows and grows (like the heap can, if you don't release memory appropriately). The stack grows by having lots of levels of function calls that have lots of and/or big parameters to those functions.

Look for that type of point in your program to decide where to debug.

Here's the Microsoft answer to debugging stack size.

Eric J.
Well, it doesn't grow and grow indefinitely, but after running for a long time it should have reached its maximum. What I want to do is see how far the high tide reaches, if I can use a metaphor. Also, as you may have guessed, this isn't a small program I wrote; it's a behemoth I have to maintain. I don't even have all the source code.
JXG
OK I see what you mean... even though the stack itself will shrink, the pages allocated by the OS will not since the reserved pages are allocated but not released by default. Here's some code that will show you free and used pages in your stack space. There's also a method presented to shrink the stack: http://www.codeproject.com/KB/cpp/StackShrink.aspx?msg=1329352
Eric J.
A: 

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
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
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
I think we are talking past each other.
JXG
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
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
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
+2  A: 

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());    
    // ...
}
skwllsp
+1  A: 

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
A: 

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