What Is the Purpose of the _Chkstk() Function

What is the purpose of the _chkstk() function?

Windows pages in extra stack for your thread as it is used. At the end of the stack, there is one guard page mapped as inaccessible memory -- if the program accesses it (because it is trying to use more stack than is currently mapped), there's an access violation. The OS catches the fault, maps in another page of stack at the same address as the old guard page, creates a new guard page just beyond the old one, and resumes from the instruction that caused the violation.

If a function has more than one page of local variables, then the first address it accesses might be more than one page beyond the current end of the stack. Hence it would miss the guard page and trigger an access violation that the OS doesn't realise is because more stack is needed. If the total stack required is particularly huge, it could perhaps even reach beyond the guard page, beyond the end of the virtual address space assigned to stack, and into memory that's actually in use for something else.

So, _chkstk ensures that there is enough space for the local variables. You can imagine that it does this by touching the memory for the local variables at page-sized intervals, in increasing order, to ensure that it doesn't miss the guard page (so-called "stack probes"). I don't know whether it actually does that, though, possibly it takes a more direct route and instructs the OS to map in a certain amount of stack. Either way, if the total required is greater than the virtual address space available for stack, then the OS can complain about it instead of doing something undefined.

Where is __chkstk defined?

After a lot of back and forth, I've finally figured it out, in what I think is the correct way. I was using just Windows SDK, which is not enough to build on Windows, you also need Visual Studio, or at least just the Build Tools. So as part of my toolchain installation, I now install both of these, and use these parameters to the linker:

/defaultlib:C:\Program Files (x86)\Windows Kits\10\Lib\${sdkVersion}\ucrt\${targetDepth}\ucrt.lib

and add "msvcrt.lib" and "libcmt.lib", located in C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\${msvcVersion}\lib\

I do not distribute any of these files myself, rather, I just run the installers for Windows SDK and the MSVC Build Tools, and then expect the correct lib files to exist in those locations.

One good hint I got, was to look at the verbose output of the clang command, clang hello.c -o hello.exe -v which shows the full set of arguments being sent to the linker, and gives a good template to start from.

Suggestion for chkstk.asm stackoverflow exception in C++ with Visual Studio

Problem solved. Thanks to Kotti for supplying the code. I got the problem while comparing with that code. The problem was not about too much recursion. Actually I was working with a normal C++ array which was being stored on stack. Thus the problem ran out of stack space. I just changed it to a dynamically allocated array with the new/delete statements and it worked.

Linux process stack overrun by local variables (stack guarding)

_chkstk does stack probes to make sure each page is touched in order after a (potentially) large allocation, e.g. an alloca. Because Windows will only grow the stack one page at a time up to the stack size limit.

Touching that "guard page" triggers stack growth. It doesn't guard against stack overflow; I think you're misinterpreting the meaning of "guard page" in this usage.

The function name is also potentially misleading. _chkstk docs simply say: Called by the compiler when you have more than one page of local variables in your function. It doesn't truly check anything, it just makes sure that intervening pages have been touched before memory around esp/rsp gets used. i.e. the only possible effects are: nothing (possibly including a valid soft page fault) or an invalid page-fault on stack overflow (trying to touch a page that Windows refused to grow the stack to include.) It ensures that the stack pages are allocated by unconditionally writing them.

I guess you could look at this as checking for a stack clash by making sure you touch an unmappable page before continuing in the case of stack overflow.


Linux will grow the main-thread stack1 by any number of pages (up to the stack size limit set by ulimit -s; default 8MiB) when you touch memory below old stack pages if it's above the current stack pointer.

If you touch memory outside the growth limit, or don't move the stack pointer first, it will just segfault. Thus Linux doesn't need stack probes, merely to move the stack pointer by as many bytes as you want to reserve. Compilers know this and emit code accordingly.

See also How is Stack memory allocated when using 'push' or 'sub' x86 instructions? for more low-level details on what the Linux kernel does, and what glibc pthreads on Linux does.

A sufficiently large alloca on Linux can move the stack all the way past the bottom of the stack growth region, beyond the guard pages below that, and into another mapping; this is a Stack Clash. https://blog.qualys.com/securitylabs/2017/06/19/the-stack-clash It of course requires that the program uses a potentially-huge size for alloca, dependent on user input. The mitigation for CVE-2017-1000364 is to leave a 1MiB guard region, requiring a much larger alloca than normal to get past the guard pages.

This 1MiB guard region is below the ulimit -s (8MiB) growth limit, not below the current stack pointer. It's separate from Linux's normal stack growth mechanism.


gcc -fstack-check

The effect of gcc -fstack-check is essentially the same as what's always needed on Windows (which MSVC does by calling _chkstk): touch stack pages in between previous and new stack pointer when moving it by a large or runtime-variable amount.

But the purpose / benefit of these probes is different on Linux; it's never needed for correctness in a bug-free program on GNU/Linux. It "only" defends against stack-clash bugs/exploits.

On x86-64 GNU/Linux, gcc -fstack-check will (for functions with a VLA or large fixe-size array) add a loop that does stack probes with or qword ptr [rsp], 0 along with sub rsp,4096. For known fixed array sizes, it can be just a single probe. The code-gen doesn't look very efficient; it's normally never used on this target. (Godbolt compiler explorer example that passes a stack array to a non-inline function.)

https://gcc.gnu.org/onlinedocs/gccint/Stack-Checking.html describes some GCC internal parameters that control what -fstack-check does.

If you want absolute safety against stack-clash attacks, this should do it. It's not needed for normal operation, though, and a 1MiB guard page is enough for most people.


Note that -fstack-protector-strong is completely different, and guards against overwrite of the return address by buffer overruns on local arrays. Nothing to do with stack clashes, and the attack is against stuff already on the stack above a small local array, not against other regions of memory by moving the stack a lot.


Footnote 1: Thread stacks on Linux (for threads other than the initial one) have to be fully allocated up front because the magic growth feature doesn't work. Only the initial aka main thread of a process can have that.

(There's an mmap(MAP_GROWSDOWN) feature but it's not safe because there's no limit, and because nothing stops other dynamic allocations from randomly picking a page close below the current stack, limiting future growth to a tiny size before a stack clash. Also because it only grows if you touch the guard page, so it would need stack probes. For these showstopper reasons, MAP_GROWSDOWN is not used for thread stacks. The internal mechanism for the main stack relies on different magic in the kernel which does prevent other allocations from stealing space.)

stackoverflow error at the beginning of the program in function main

If you decleared a fixed size array and if its size is too much, you may have this error.

int fixedarray[1000000000];

Try to decrease the length or create it on the heap.

int * array = new int[1000000000];

Do not forget to delete it later.

delete[] array;

But it is better to use std::vector instead of pointers even in a C function,

//...
int Old_C_Func(int * ptrs, unsigned len_);
//...
std::vector<int> intvec(1000000000);
int * intptr = &intvec[0];
int result = Old_C_Func(intptr,intvec.size());

assuming 32bit compilation.

stack overflow on stepping into a function

Turns out that _chkstk() throws a stack overflow when you have exceeded the declared maximum stack size declared in an .exe build. Your solution then? Increase it. Though also consider removing the bit of redundancy in your code:

First, consider that upon entering the function, we need to ensure that there is enough space for all local variables of your function on the stack. Let's look at what those are:

void CGUITaskRequest::DecodeAndDeserializeSettings(const std::string& sEncodedSettingsString)
{
/* two variables here */
auto sDecoded = string_functions::base64_decode(sEncodedSettingsString);
std::stringstream ss(sDecoded);

if (m_eType == ETaskTypes::FILE && m_eSubtype == ETaskSubtypes::OPEN) {
/* two more variables here */
auto pSettings = std::make_shared<CModSettingsFileImport>();
cereal::XMLInputArchive arSettingsObject(ss, pSettings->XML_TAG);
pSettings->load(arSettingsObject);
m_ptrSettings = pSettings;
}
else if (m_eType == ETaskTypes::META && m_eSubtype == ETaskSubtypes::STATUS) {
/* and a final pair */
auto pSettings = std::make_shared<SMetaStatusSettings>();
cereal::XMLInputArchive arSettingsObject(ss, pSettings->XML_TAG); // <- line I comment out to run successfully
pSettings->load(arSettingsObject);
m_ptrSettings = pSettings;
}
}

Now consider that you find the _chkstk() variable on the call stack to begin with. This implies that this function allocates a lot of memory! That commenting out a single declaration resolves the problem points to the greedy memory culprit. But wait, you have two of these, and that you can get away with one implies that consolidating your duplicated declaration might pay dividends:

void CGUITaskRequest::DecodeAndDeserializeSettings(const std::string& sEncodedSettingsString)
{
auto sDecoded = string_functions::base64_decode(sEncodedSettingsString);
std::stringstream ss(sDecoded);
/* single declaration*/
cereal::XMLInputArchive arSettingsObject;
if (m_eType == ETaskTypes::FILE && m_eSubtype == ETaskSubtypes::OPEN) {
auto pSettings = std::make_shared<CModSettingsFileImport>();
arSettingsObject = cereal::XMLInputArchive(ss, pSettings->XML_TAG);
pSettings->load(arSettingsObject);
m_ptrSettings = pSettings;
}
else if (m_eType == ETaskTypes::META && m_eSubtype == ETaskSubtypes::STATUS) {
auto pSettings = std::make_shared<SMetaStatusSettings>();
arSettingsObject = cereal::XMLInputArchive(ss, pSettings->XML_TAG); // <- line I comment out to run successfully
pSettings->load(arSettingsObject);
m_ptrSettings = pSettings;
}
}

Although this changes the scope on arSettingsObject, it isn't a problem because the function terminates after the if/else statement and all return paths from where it is declared require it.

How to solve -------undefined reference to `__chkstk_ms'-------on mingw

The orig problem seems solved regarding linking against old libs, but I have opposite case.
I need to work with older GCC 3.4 version, so I installed gcc-v3-core package.
Then orig errors immediately appear when linking a trivial source.
I found out that MinGW GCC 3.4 cannot work with mingwrt > 3.20 and the problem is that gcc-v3-core contains incorrect mingwrt spec, allowing newest vesions.

The solution is simple:

mingw-get upgrade "mingwrt=3.20.*"

!Update!
Actually with mingwrt-3.20 linker reports same errors for C++ code:

g++ hello.cpp

BAM!

Solution 2:

mingw-get upgrade "mingwrt=3.18.*"

the last one that seems to work with gcc-v3-g++.

Intel Fortran - chkstk.asm not found

A problem has occurred in your program and your debugger (Visual Studio in this case) has been notified. Your debugger is then looking for the source code associated with the location of the problem. It cannot find that source code, most likely because you do not have it installed (it is part of the underlying C runtime library).

But you don't particularly need (or want) that source code to understand the nature of the problem.

As per its name, the code in chkstk.asm checks/probes the stack to ensure that there is sufficient space committed on the stack for a stack allocation. Chances are that check failed because there was insufficient stack space. You may be able to use the call stack window to identify the location in your Fortran source code related to the stack allocation - it may be associated with invocation of a procedure (creating storage for the local variables of the function) or an expression (creating storage for temporaries for function results and the like).

Parallel code tends to make much heavier use of the stack because that is a easy way of making storage specific to a particular thread of execution. You generally need to increase the amount of memory reserved for each stack (in the properties for the relevant executable project see under Linker > System > Stack Reserve Size, the default is only 1MB or so, try making it 10MB). You may also want to tell the compiler to use heap based allocations rather than stack based (in the project properties under Fortran > Optimization, set Heap Arrays to 0).

There are numerous posts and articles on the Intel forums for this sort of issue.



Related Topics



Leave a reply



Submit