21

From the question:

Is it good programming practice to use setjmp and longjmp in C?

Two of the comments left said:

"You can't throw an exception in a signal handler, but you can do a longjmp safely -- as long as you know what you are doing. – Dietrich Epp Aug 31 at 19:57 @Dietrich: +1 to your comment. This is a little-known and completely-under-appreciated fact. There are a number of problems that cannot be solved (nasty race conditions) without using longjmp out of signal handlers. Asynchronous interruption of blocking syscalls is the classic example."

I was under the impression that signal handlers were called by the kernel when it encountered an exceptional condition (e.g. divide by 0). Also, that they're only called if you specifically register them.

This would seem to imply (to me) that they aren't called through your normal code.

Moving on with that thought... setjmp and longjmp as I understand them are for collapsing up the stack to a previous point and state. I don't understand how you can collapse up a stack when a signal handler is called since its called from the Kernel as a one-off circumstance rather than from your own code. What's the next thing up the stack from a signal handler!?

Community
  • 1
  • 1
John Humphreys
  • 37,047
  • 37
  • 155
  • 255

6 Answers6

24

The way the kernel "calls" a signal handler is by interrupting the thread, saving the signal mask and processor state in a ucontext_t structure on the stack just beyond (below, on grows-down implementations) the interrupted code's stack pointer, and restarting execution at the address of the signal handler. The kernel does not need to keep track of any "this process is in a signal handler" state; that's entirely a consequence of the new call frame that was created.

If the interrupted thread was in the middle of a system call, the kernel will back out of the kernelspace code and adjust the return address to repeat the system call (if SA_RESTART is set for the signal and the system call is a restartable one) or put EINTR in the return code (if not restartable).

It should be noted that longjmp is async-signal-unsafe. This means it invokes undefined behavior if you call it from a signal handler if the signal interrupted another async-signal-unsafe function. But as long as the interrupted code is not using library functions, or only using library functions that are marked async-signal-safe, it's legal to call longjmp from a signal handler.

Finally, my answer is based on POSIX since the question is tagged unix. If the question were just about pure C, I suspect the answer is somewhat different, but signals are rather useless without POSIX anyway...

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
15

longjmp does not perform normal stack unwinding. Instead, the stack pointer is simply restored from the context saved by setjmp.

Here is an illustration on how this can bite you with non-async-safe critical parts in your code. It is advisable to e.g. mask the offending signal during critical code.

makes
  • 6,438
  • 3
  • 40
  • 58
  • Can you name an implementation of longjmp that does *not* perform stack unwinding? The libc implementation sure doesn't, but C++ is free to substitute an implementation that does, and that's what happens on a number of popular platforms. – zneak Jun 15 '17 at 23:28
1

worth reading this: http://man7.org/linux/man-pages/man2/sigreturn.2.html in regard to how Linux handles signal handler invocation, and in this case how it manages signal handler exit, my reading of this suggests that executing a longjmp() from a signal handler (resulting in no call of sigreturn()) might be at best "undefined"... also have to take into account on which thread (and thus user stack) the setjmp() was called, and on which thread (and thus user stack) longjmp() in subsequently called also!

1

This doesn't answer the question of whether or not it is "good" to do this, but this is how to do it. In my application, I have a complicated interaction between custom hardware, huge page, shared memory, NUMA lock memory, etc, and it is possible to have memory that seems to be decently allocated but when you touch it (write in this case), it throws a BUS error or SEGV fault in the middle of the application. I wanted to come up with a way of testing memory addresses to make sure that the shared memory wasn't node locked to a node that didn't have enough memory, so that the program would fail early with graceful error messages. So these signal handlers are ONLY used for this one piece of code (a small memcpy of 5 bytes) and not used to rescue the app while it is in use. I think it is safe here.

Apologies if this is not "correct". Please comment and I'll fix it up. I cobbled it together based on hints and some sample code that didn't work.

#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

sigjmp_buf  JumpBuffer;

void handler(int);

int count = 0;

int main(void)
{
    struct sigaction sa;
    sa.sa_handler = handler;
    sigemptyset(&(sa.sa_mask));
    sigaddset(&(sa.sa_mask), SIGSEGV);
    sigaction(SIGSEGV, &sa, NULL);

    while (1) {
        int r = sigsetjmp(JumpBuffer,1);
        if (r == 0) {
            printf("Ready for memcpy, count=%d\n",count);
            usleep(1000000);
            char buffer[10];
#if 1
            char* dst = buffer; // this won't do bad
#else
            char* dst = nullptr; // this will cause a segfault
#endif
            memcpy(dst,"12345",5); // trigger seg fault here
            longjmp(JumpBuffer,2);
        }
        else if (r == 1)
        {
            printf("SEGV. count %d\n",count);
        }
        else if (r == 2)
        {
            printf("No segv. count %d\n",count);
        }
    }
    return 0;
}

void handler(int  sig)
{
    count++;
    siglongjmp(JumpBuffer, 1);
}

References

Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
0

In most systems a signal handler has it's own stack, separate from the main stack. That's why you could longjmp out of a handler. I think it's not a wise thing to do though.

cyco130
  • 4,654
  • 25
  • 34
  • 3
    It does not have its own stack unless you use `sigaltstack`. It merely grows on top of the stack of the interrupted code. Any stack memory beyond (below, on common grows-down implementations) the stack pointer is considered unused, so the kernel is free to clobber it (with the necessary arguments for the signal handler and return address to the main program) and adjust the stack pointer for the signal handler code to run. – R.. GitHub STOP HELPING ICE Sep 07 '11 at 13:52
  • 1
    In that case it's even safer to use a longjmp if you're sure that the handler was called _after_ setjmp. – cyco130 Sep 07 '11 at 13:58
  • It's always impossible to use `longjmp` without a valid `jmp_buf` created by `setjmp`... – R.. GitHub STOP HELPING ICE Sep 07 '11 at 14:00
  • @R.. I think the intended meaning of "after" here was "lower down the call stack", not the conventional time-wise meaning. (So "before" would be, for example, a `jmp_buf` variable declared in `main`, with a pointer to it passed down to some subroutine which in turn calls `setjmp` to set another *local* `jmp_buf`, but then `memcpy`s the contents from that `jmp_buf` into the original `jmp_buf`, and then later in `main`, a call to `longjmp` with that original `jmp_buf`.) I think that's technically undefined behavior, but that's the impression of intended meaning I get here. – mtraceur Feb 15 '19 at 21:08
  • Correction: That is definitely undefined behavior, I just wasn't able to verify that within the duration of the comment edit window. – mtraceur Feb 15 '19 at 21:14
-3

You can't use longjmp to get out of a signal handler.

The reason for this is that setjmp only saves the resources (process registers) etc. that the calling-convention specifies that should be saved over a plain function call.

When an interrupt occurs, the function being interrupted may have a much larger state, and it will not be restored correctly by longjmp.

Lindydancer
  • 25,428
  • 4
  • 49
  • 68
  • 2
    That's not relevant. The same is true whenever you `longjmp`, regardless of whether it's from a signal handler or not. It's true that the *signal mask* won't be restored when you `longjmp` out of a signal handler (you can use `sigsetjmp` and `siglongjmp` if you want it to be restored) but this might even be desirable, and it's always possible to save and restore it yourself too if needed. – R.. GitHub STOP HELPING ICE Sep 07 '11 at 13:57
  • Think of this from the point of view of a compiler. It can do any kind of optimization, like caching the value of a global variable, as long as it is saved back whenever the value could be used again. One assumption that the compiler makes is that a `longjmp` only can occur is at function calls. If an asynchronous signal occurs and control is passed back using `longjmp`, this assumption is broken. Hence, *you can't use `longjmp` to return control from a signal handler*. (Btw, I've spent the majority of my profession career writing C compilers...) – Lindydancer Sep 07 '11 at 14:24
  • I agree you can't rely on the consistency of data that was being modified in the interrupted code, just like you can't rely on the consistency of that data from within the signal handler context. This is not very relevant however if the interrupted code was only modifying automatic storage objects and possibly kernel-level resources (e.g. calling async-signal-safe functions like `sigprocmask`, `read`, etc.). – R.. GitHub STOP HELPING ICE Sep 07 '11 at 14:36
  • Whatever the interrupt routine does is irrelevant. The fact is that if normal code would be interrupted and control restored using `longjmp`, then the normal function and normal global data could end up in an inconsistent state. – Lindydancer Sep 07 '11 at 14:46
  • If global data is not being modified, it could not end up in an inconsistent state. 5.1.2.3 also specifies the behavior for volatile global data with signals; at most one value (between sequence points) may be inconsistent, and if the implementation does not have padding bits (or if you're only using character types), there is no possibility of a trap representation and thus no danger. It also allows implementations (or higher-level standards like POSIX) to make further guarantees. – R.. GitHub STOP HELPING ICE Sep 07 '11 at 14:54
  • ...and POSIX allows a lot to be done from a signal handler: any async-signal-safe functions, as well as unsafe ones if an unsafe one is not interrupted. Thus, as POSIX does not explicitly forbid `longjmp` from a signal handler, and even mentions this usage in the rationale in least one place if I'm not mistaken, I'm inclined to believe POSIX requires an implementation to support this usage or at least implies that a good implementation should support this usage. – R.. GitHub STOP HELPING ICE Sep 07 '11 at 14:55
  • Yup. Just be sure to not invoke any unsafe functions while the handler is set. – Demi Sep 07 '13 at 01:04