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:
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 TheC 2011 Online Draftlongjmp
function
...
2 Thelongjmp
function restores the environment saved by the most recent invocation of
thesetjmp
macro in the same invocation of the program with the correspondingjmp_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 thesetjmp
macro has terminated execution248) in the interim, or if the invocation of thesetjmp
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 anotherlongjmp
call has caused a
transfer to asetjmp
invocation in a function earlier in the set of nested calls.
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 goto
s which (I think) provides sensible reasoning for why goto
s 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 itsjmp_buf
argument for later use by thelongjmp
function....
If the return is from a direct invocation, thesetjmp
macro returns the value zero. If the return is from a call to thelongjmp
function, thesetjmp
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 thelongjmp
function was called, except that the values of objects of automatic storage
duration that are local to the function containing the invocation of the correspondingsetjmp
macro
that do not have volatile-qualified type and have been changed between thesetjmp
invocation andlongjmp
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 andlongjmp
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 thesetjmp
macro had just returned the value specified byval
. ...
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 ofsetjmp()
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 linelongjmp
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 bylongjmp()
. The stack context will be invalidated if the function which calledsetjmp()
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
Profiling Arbitrary Cuda Applications
Git - Crlf Issue in Windows + Linux Dual Boot
File/Directory Permissions Trailing + ( Drwxr-Xr-X+ )
Logging Memory Access Footprint
Stty: Standard Input: Inappropriate Ioctl for Device
How to Identify Padding Scheme Used in Rsa Signature of a Certificate, Using Openssl
Mmap Flag Map_Uninitialized Not Defined
Linux Device Driver Unsafe Fxsave/Fxrstor Bug - Any Precedents
Mpc/Mpd on Linux: How to Play Local Wav File
Incorrect Rendering in Anaconda + Spyder (Wrong Colours in Text)
Curl Http Post File Upload Using Curl -Data in Linux Command Line
How to Highlight The Differences Between Subsequent Lines in a File
Hbase Does Not Run After ./Start-Hbase.Sh - Permission Denied
How to Change File Extension in Linux Shell Script
Linux + Find Word in File Under Directory But Quickly
Recursively List Files from a Given Directory in Bash
What Happens If There Are Too Many Files Under a Single Directory in Linux