Setjmp and Longjmp - Understanding with Examples

Practical usage of setjmp and longjmp in C

Error handling
Suppose there is an error deep down in a function nested in many other functions and error handling makes sense only in the top level function.

It would be very tedious and awkward if all the functions in between had to return normally and evaluate return values or a global error variable to determine that further processing doesn't make sense or even would be bad.

That's a situation where setjmp/longjmp makes sense.
Those situations are similar to situation where exception in other langages (C++, Java) make sense.

Coroutines
Besides error handling, I can think also of another situation where you need setjmp/longjmp in C:

It is the case when you need to implement coroutines.

Here is a little demo example.
I hope it satisfies the request from Sivaprasad Palas for some example code and answers the question of TheBlastOne how setjmp/longjmp supports the implementation of corroutines (as much as I see it doesn't base on any non-standard or new behaviour).

EDIT:
It could be that it actually is undefined behaviour to do a longjmp down the callstack (see comment of MikeMB; though I have not yet had opportunity to verify that).

#include <stdio.h>
#include <setjmp.h>

jmp_buf bufferA, bufferB;

void routineB(); // forward declaration

void routineA()
{
int r ;

printf("(A1)\n");

r = setjmp(bufferA);
if (r == 0) routineB();

printf("(A2) r=%d\n",r);

r = setjmp(bufferA);
if (r == 0) longjmp(bufferB, 20001);

printf("(A3) r=%d\n",r);

r = setjmp(bufferA);
if (r == 0) longjmp(bufferB, 20002);

printf("(A4) r=%d\n",r);
}

void routineB()
{
int r;

printf("(B1)\n");

r = setjmp(bufferB);
if (r == 0) longjmp(bufferA, 10001);

printf("(B2) r=%d\n", r);

r = setjmp(bufferB);
if (r == 0) longjmp(bufferA, 10002);

printf("(B3) r=%d\n", r);

r = setjmp(bufferB);
if (r == 0) longjmp(bufferA, 10003);
}

int main(int argc, char **argv)
{
routineA();
return 0;
}

Following figure shows the flow of execution:

flow of execution

Warning note
When using setjmp/longjmp be aware that they have an effect on the validity of local variables often not considered.

Cf. my question about this topic.

setjmp and longjmp - understanding with examples

When calling longjmp(), all those registers are restored automatically, and execution continues at the corresponding call to setjmp(), but this time setjmp() has a different return value (similar to how fork() has different return values in parent and child).

setjmp()/longjmp() save only a limited environment. In particular, they just save the stack pointer, not the full stack, so you can only return to the same function or to a calling function. POSIX has setcontext(), which allows to switch between stacks, making it more immediately useful for implementing things like userspace threads (fibrils, green-threads, ...).

Why does setjmp/longjmp

TL/DR - you can't jump back into a function you jumped out of.

7.13.2.1 The longjmp function
...

2     The longjmp function restores the environment saved by the most recent invocation of
the setjmp macro in the same invocation of the program with the corresponding
jmp_buf argument. If there has been no such invocation, or if the invocation was from
another thread of execution, or if the function containing the invocation of the setjmp
macro has terminated execution
248) in the interim
, or if the invocation of the setjmp
macro was within the scope of an identifier with variably modified type and execution has
left that scope in the interim, the behavior is undefined.

248) For example, by executing a return statement or because another longjmp call has caused a
transfer to a setjmp invocation in a function earlier in the set of nested calls.

C 2011 Online Draft

When you execute longjmp(jump_body, 1); in func, you invalidate jump_ret.

longjmp isn't bidirectional - it unwinds the stack as though any of the function calls between the setjmp and longjmp never happened.

About setjmp/longjmp

The stack pointer marks the division between the "used" and "unused" portions of the stack. When you call setjmp, all current call frames are on the "used" side, and any calls that take place after setjmp, but before the function which called setjmp returns, have their call frames on the "unused" side of the saved stack pointer. Note that calling longjmp after the function which called setjmp has returned invokes undefined behavior, so that case does not need to be considered.

Now, it's possible that local variables in some of the existing call frames are modified after setjmp, either by the calling function or through pointers, and this is one reason why it's necessary to use volatile in many cases...

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

Essentially, you're right in your assertion that jmp-style propagation is essentially the same thing as goto. Read Dijkstra's (famous and controversial) paper about gotos which (I think) provides sensible reasoning for why gotos should rarely be used. Unless you know exactly why you're doing what you're doing (or you're working in very specific fields -- such as embedded programming), you should not touch either goto or longjmp.

Using setjmp and longjmp with a local jmp_buf

TL;DR Since the standard isn't clear, it is better to treat the value of a local jmp_buf as indeterminate after a local longjmp.

ISO/IEC 9899:2018 §17.13.1.1 ¶2 describes the behavior of setjmp, and ¶3 describes what happens on return.

The setjmp macro saves its calling environment in its jmp_buf argument for later use by the longjmp function.

...


If the return is from a direct invocation, the setjmp macro returns the value zero. If the return is from a call to the longjmp function, the setjmp macro returns a nonzero value.

We infer that a successful return from setjmp results in an initialized jmp_buf argument. However, there is no mention if the initialization takes into account of the jmp_buf itself having automatic storage duration (and so, itself could be represented by registers rather than by memory).

ISO/IEC 9899:2018 §7.13.2.1 ¶3 describes the behavior of longjmp, and is worded the same as the 2011 text cited by Marko:

All accessible objects have values, and all other components of the abstract machine254) have state,
as of the time the longjmp function was called, except that the values of objects of automatic storage
duration that are local to the function containing the invocation of the corresponding setjmp macro
that do not have volatile-qualified type and have been changed between the setjmp invocation and
longjmp call are indeterminate.


254)This includes, but is not limited to, the floating-point status flags and the state of open files.

However, the meaning of the word between is somewhat elusive. The standard could have explicitly specified the context of between to mean after setjmp completed. For example, the wording could have stated:

... changed between the setjmp return and longjmp call are indeterminate.

The current wording suggests that one should include the invocation of setjmp itself as something that may trigger the indeterminate condition.

There is a possibility that the semantics of the return of longjmp covers for this problem, however. ISO/IEC 9899:2018 §17.13.2.1 ¶4 states:

After longjmp is completed, thread execution continues as if the corresponding invocation of the
setjmp macro had just returned the value specified by val. ...

This sentence could be interpreted to mean that the invocation semantics of setjmp is the same whether it returns from direct invocation or returns from a longjmp function. That is, the return of setjmp means the jmp_buf argument is initialized and can be used by another longjmp. But again, this is not clear. In the most limiting interpretation, the as if clause only speaks to the value returned by setjmp, and not the invocation itself.

Since the semantics are ambiguous, it is proper to treat the jmp_buf object value as indeterminate upon return from longjmp.

How to use longjmp from main function to other functions?

Your usage of longjmp() is invalid, take a look at the documentation:

longjmp() restores the environment saved by the last call of setjmp()

You need to call setjmp() first in order to "set" where to jump before calling longjump(). That's why your code doesn't work. It's undefined behavior at best.

In other words, you cannot just use longjmp() as a simple "jump to global label". It has a different purpose.



after putting test(); call before the line longjmp it works and print it twice but why does it still crashes?

Let's again take a look at the documentation:

setjmp() saves the stack context/environment in env for later use by longjmp(). The stack context will be invalidated if the function which called setjmp() returns.

You cannot longjmp() to a buffer that was previously set by a function that has now returned. It's invalid. Your usage of setjmp()/longjmp() is really not what those functions are meant for.


Jumping out to another function isn't really the purpose of setjmp()/longjmp(), their purpose is to save the context and "jump back" to a function that has still not returned (see below example). It's not simple to come up with a meaningful example usage of such functions, since they are meant for advanced usage.

Here's a correct usage example (while still not that meaningful) from Wikipedia:

#include <stdio.h>
#include <setjmp.h>

static jmp_buf buf;

void second() {
printf("second\n"); // prints
longjmp(buf,1); // jumps back to where setjmp was called - making setjmp now return 1
}

void first() {
second();
printf("first\n"); // does not print
}

int main() {
if (!setjmp(buf))
first(); // when executed, setjmp returned 0
else // when longjmp jumps back, setjmp returns 1
printf("main\n"); // prints

return 0;
}

setjmp longjmp with Stack

I can't see anything obviously wrong with your code, but it is not an MVCE, so it is tough to tell -- there may be something off in your scheduler or your push and pop functions.

One thing that seems questionable is the tests in ult_yield and ult_read:

if(runqueue->start == NULL && blockedqueue->start == NULL) ...

These should both be:

if (runqueue->start == NULL) {
printf("Scheduler queue corrupted");
abort(); }

since when these functions are called, runqueue->start MUST point at the current thread's tcb/queue node.

Your valgrind error looks like it is trying to longjmp through an invalid jmp_buf, so try to backtrack to see where it came from and how it got into that state.

You should also probably unset the signal handler at the end of ult_spawn (or use SA_RESETHAND), lest a spurious SIGUSR1 from somewhere cause corruption of things.

Special treatment of setjmp/longjmp by compilers

The C language defines setjmp to be a macro and places strict limitations on context in which it may appear without invoking undefined behavior. It is not a normal function: you cannot take its address and expect a call via the resulting pointer to behave as a proper setjmp invocation.

In particular, it is not true in general that assembly code invoked by setjmp obeys the same calling conventions as normal functions. SPARC on Linux and Solaris provides a counterexample: its setjmp does not restore all call-preserved registers (nor does vfork). It took GCC by surprise as recently as 2018 (gcc-patches thread, bugzilla entry).

But even considering "compiler-friendly" platforms where setjmp entrypoint obeys the usual conventions, it is still necessary to recognize it as a function that "returns twice". GCC recognizes setjmp-like functions (including vfork) by name, and offers __attribute__((returns_twice)) for annotating such functions in custom code.

The reason for that is longjmp'ing back to setjmp can transfer control from a point where some variable or temporary appears dead (and the compiler reused its storage for something unrelated) back to where it was live (but its storage is "clobbered" now, oops).

Constructing an example that demonstrates how this happens is a bit tricky: the clobbered storage cannot be a register, because if it's call-clobbered it wouldn't be in use at the point of setjmp, and if it is call-saved longjmp would restore it (SPARC exception aside). So it needs to be forced to stack without making addresses of both variables exposed in a way that makes their lifetimes overlap, preventing reuse of stack slots, and without making one of them go out of scope before longjmp.

With a bit of luck I managed to arrive at the following testcase, which when compiled with -O2 -mtune-ctrl=^inter_unit_moves_from_vec (view on Compiler Explorer):

//__attribute__((returns_twice))
int my_setjmp(void);

__attribute__((noreturn))
void my_longjmp(int);

static inline
int float_as_int(float x)
{
return (union{float f; int i;}){x}.i;
}

float f(void);

int g(void)
{
int ret = float_as_int(f());

if (__builtin_expect(my_setjmp(), 1)) {
int tmp = float_as_int(f());
my_longjmp(tmp);
}
return ret;
}

produces the following assembly:

g:
sub rsp, 24
call f
movss DWORD PTR [rsp+12], xmm0
call my_setjmp
test eax, eax
je .L2
call f
movss DWORD PTR [rsp+12], xmm0
mov edi, DWORD PTR [rsp+12]
call my_longjmp
.L2:
mov eax, DWORD PTR [rsp+12]
add rsp, 24
ret

The -mtune-ctrl=^inter_unit_moves_from_vec flag causes GCC to implement SSE-to-gpr moves via stack, and both moves use the same stack slot, because as far as the compiler can tell, there's no conflict (computing 'tmp' leads to a noreturn function, so temporary used for computing 'ret' is no longer needed). However, if my_longjmp transfers control back to my_setjmp, after branching to label .L2 we try to read the value of 'ret' from the overwritten slot.



Related Topics



Leave a reply



Submit