4

as per gcc documentation

-fstack-check

Generate code to verify that you do not go beyond the boundary of the stack. Note that this switch does not actually cause checking to be done; the operating system must do that. The switch causes generation of code to ensure that the operating system sees the stack being extended.

My assumption is that this extra code will generate exception to let OS know. When using C language I need to know what exception is being generated by the extra code.

Google is also not helping much. Close I came to know is that it generates Storage_Error exception in case of Ada language (Reference).

I am working on sort of small OS/scheduler where I need to catch this exception. I am using C/C++.

My GCC version 3.4.4

Gailu
  • 111
  • 1
  • 11

1 Answers1

6

It doesn't generate any exception directly. It generates code which, when the stack is enlarged by more than one page, generates a read-write access to each page in the newly allocated region. That's all it does. Example:

extern void bar(char *);
void foo(void)
{
    char buf[4096 * 8];
    bar(buf);
}

compiles (with gcc 4.9, on x86-64, at -O2) to:

foo:
        pushq   %rbp
        movq    $-32768, %r11
        movq    %rsp, %rbp
        subq    $4128, %rsp
        addq    %rsp, %r11
.LPSRL0:
        cmpq    %r11, %rsp
        je      .LPSRE0
        subq    $4096, %rsp
        orq     $0, (%rsp)
        jmp     .LPSRL0
.LPSRE0:
        addq    $4128, %rsp
        leaq    -32768(%rbp), %rdi
        call    bar
        leave
        ret

orq $0, (%rsp) has no effect on the contents of the memory at (%rsp), but the CPU treats it as a read-write access to that address anyway. (I don't know why GCC offsets %rsp by 4128 bytes during the loop, or why it thinks a frame pointer is necessary.) The theory is that the OS can notice these accesses and do something appropriate if the stack has become too large. For a POSIX-compliant operating system, that would be delivery of a SIGSEGV signal.

You may be wondering how the OS can notice such a thing. The hardware allows the OS to designate pages of address space as completely inaccessible; any attempt to read or write memory in those pages triggers a hardware fault which the OS can process as it sees fit (again, for a POSIX-compliant OS, delivery of SIGSEGV). This can be used to place a "guard area" immediately past the end of the space reserved for the stack. That's why one access per page is sufficient.

What -fstack-check is meant to protect you from, to be clear, is the situation where the "guard area" is very small - perhaps just one page - so allocating a large buffer on the stack moves the stack pointer past that area and into another region of accessible RAM. If the program then happens never to touch memory within the guard area, you won't get a prompt crash, but you will scribble on whatever that other region is, causing a delayed-action malfunction.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • +1. Tis such a shame that POSIX provides no safe way to handle stack overflow conditions :( – Billy ONeal Oct 14 '14 at 22:01
  • @BillyONeal: Or, for that matter, that there's no defined mechanism by which recursive code can refrain from making calls that *might* blow the stack. Of course, if one looks at what the C standards actually mandate, it would be very difficult to write a useful program with standards-mandated behavior, since the standard doesn't even require a stack capable of handling eight-deep nesting. A rather bizarre omission, IMHO. – supercat Oct 14 '14 at 22:45
  • @supercat: We're stuck with platform specific hacks it seems. Makes sense for the C standard to not define something, but POSIX could easily have had a `SIGSTACK` or similar. At least on NT you can handle `EXCEPTION_STACK_OVERFLOW` safely. – Billy ONeal Oct 14 '14 at 22:53
  • @BillyONeal: Trying to recover from a stack overflow is apt to be problematic for a variety of reasons. On the other hand, I would see no problem with the standard specifying that implementations must provide a method `int __stack_near_limit(int n, int t)` which would be required to exist, and should when practical return a negative value if the system would likely be able to support `n` nested calls using `t` bytes of local storage, a positive if it might crash in such a case, and 0 if it really doesn't know. – supercat Oct 14 '14 at 23:18
  • @BillyONeal: If zero would always be considered a legitimate (albeit unhelpful) return value, no platform should have any difficulty providing a conformant implementation of that method. Of course, with regard to portability I think there's an even bigger need to separate C into "byte-based-integer C" and "unusual-int C", and work to define integer types with usable semantics at least for byte-based systems. I find it ludicrous that given `uint32_t a=0xFFFFFFFFu;` there's no clean portable way to compute `a*a` without risking Undefined Behavior. – supercat Oct 14 '14 at 23:24
  • @BillyONeal: Can't a SIGSEGV be caught on POSIX systems? – mafso Oct 14 '14 at 23:27
  • @BillyONeal One can use `sigaltstack` to take delivery of that `SIGSEGV` on an "alternate stack" -- basically a special block of emergency stack space reserved for signal handlers -- and then, using the information in the `ucontext_t` passed to the signal handler, unwind the original stack. GCC's Java-compiled-to-machine-code runtime does this for `NullPointerException`. I don't know if it also does it for stack overflow, but it *could*. To distinguish stack overflow from other types of `SIGSEGV` you would inspect `siginfo_t.si_addr` and compare it to the stack pointer in the `ucontext_t`. – zwol Oct 15 '14 at 00:53
  • @supercat or just throw an exception. Let the unwind process free stack space. To my understanding that's what Boost.Regex does. – Billy ONeal Oct 15 '14 at 04:12
  • @mafso handling SIGSEGV is generally undesirable because most occurrences of such an exception are bugs and should tear down the process. – Billy ONeal Oct 15 '14 at 04:14
  • @BillyONeal: A problem with using exceptions for stack overflow is that it's entirely possible that the stack unwinding after a piece of code executes will require more stack space than has been needed up to that point. If a stack overflow during exception handling is a fatal error, the system still crashes, and if it simply abandons the earlier exception there's no way the exception handling code can tell what other serious problems may exist. Letting code bow out before the stack overflows avoids those problems. – supercat Oct 15 '14 at 13:30
  • @BillyONeal A library cannot do this, because the signal handler is a global setting, so only the application can safely touch it. (GCJ gets away with it because it's a language runtime for an all-encompassing environment; you're expected to adapt any C you want to link in.) Windows' lexically-scoped handlers for hardware faults are definitely a superior design, at least in the abstract; in practice the semantics differ just enough from C++ language exceptions to make it a giant headache. – zwol Oct 15 '14 at 14:07
  • @BillyONeal However, I *think* you as application author could install a `SIGSEGV` handler, on an alternate stack, that invokes [`_Unwind_RaiseException`](http://refspecs.linuxfoundation.org/LSB_3.1.1/LSB-Core-S390/LSB-Core-S390/baselib--unwind-raiseexception.html) with a designated "stack overflow" exception class, and then compile everything with `-fnon-call-exceptions -fstack-check`. And I do mean *everything*, from the C library on up. Let us know how it goes! – zwol Oct 15 '14 at 14:09
  • @Zack: What do you think of the idea of having a standard means by which an application could ask whether continued nested calls should be considered "dangerous"? Most implementations would want to add some more controls to it (e.g. allow a per-thread control of the "low water" mark where recursive code try to exit gracefully) but averting stack overflows seems much easier and cleaner than trying to handle them safely. – supercat Oct 15 '14 at 16:59
  • @supercat It doesn't seem hard to implement, and could coexist with other mechanisms. The tricky part might be figuring out what to do if the API reports that you have to stop recursing. – zwol Oct 15 '14 at 17:05
  • @Zack: Typically report that whatever one was trying to parse or process is either corrupt, or is too complex for the method to handle given the resources available to it. If one is trying to parse data from a file which could contain nested data structures that are generally not complicated, but have no fixed limit to their complexity, recursive parsing would often be the most practical way to accomplish that, but for the fact that unless one imposes an arbitrary limit on depth it may be difficult or impossible to ensure that an attempt to parse a corrupt file won't enter the Land of UB. – supercat Oct 15 '14 at 18:06
  • @Zack: It may well be that the proper course of action would be for the parser to throw an exception, but there's a huge difference between an exception which says that it was necessary to *cleanly refuse* an operation because it seemed dangerous, versus one that says that the system caused an operation to be abruptly abandoned without warning, and with unknown consequences. – supercat Oct 15 '14 at 18:10
  • Please see the **libsigsegv** library which can add handlers on stack overflows on supporting architectures. https://www.gnu.org/software/libsigsegv/ Here is a list of architectures which do support stack overflow handlers: https://github.com/rurban/libsigsegv/blob/master/PORTING – rurban Jun 21 '17 at 08:31