I just wrestled with this. You cannot effectively handle stack overflow situations if you use the system stack on Linux, because stack segments are in magical "MAP_GROWSDOWN" regions. These regions are managed by the kernel itself. There is a base address and a size, and the kernel initially only maps some small number of pages near the upper end (high addresses). Faulting accesses to addresses that are not mapped, but in the region, just below what is currently mapped cause the kernel to map in new pages automatically, "growing down".

The customary way to handle stack overflow gracefully in userland is to install a SIGSEGV handler and check the faulting address to see if it is near the stack segment end, handling the signal on an alternate stack. Except that determining if the access was in the stack segment requires knowing the stack segment start. The signal API doesn't allow querying that; you'd have to query the process's mappings by other means. It's also not robust because a program with big frames might just stride too far and confuse the kernel's automatic growing, causing spurious SIGSEGVs when in fact, there is stack remaining.

I found it was just easier to allocate my own stack segment and immediately switch to it and just leave the loader-provided stack segment alone. Virgil does this in the first 50 or so instructions upon entry.

This also makes stack overflow very deterministic (only depends on the program) and controllable--there is a compiler option to set the stack size in KiB. There's no need for the compiler to insert strided accesses either, as the whole segment is mapped, and you can have more than a single guard page at the beginning (think: 1MiB guard region).

Stack segments in C (and Linux) are madness if you use very much and want anything to be robust to overflow.

What is Virgil?