I am trying to grasp some key concepts in order to fully understand the ucontext and setjmp libraries.
On what concerns ucontext:
- User context, eg program counter and stack register.
- Is saved on a ucontext_t type when executing getcontext
- Activates a previously saved context via setcontext (this implies that the context was stored with getcontext)
- Swaps context with swapcontext by saving the current program state and starts executing by the 2nd context since we will load the previously stored context
Some sample code I tried:
#include <stdio.h>
#include <ucontext.h>
int main() {
// if you use the register storage identifier
// the variable will be stored at the CPU
// and since no change will be noticed it will be swaping forever
/*register*/ int done = 0;
ucontext_t one;
ucontext_t two;
getcontext(&one);
printf("hello \n");
if (!done) {
printf("about to swap \n");
done = 1;
swapcontext(&two, &one);
}
}
On what concerns setjmp and longjmp:
- Pretty much a global goto, the caveat is that since we will be jumping outside the current scope, we need to somehow store the state aka context when we jump back
- setjmp sets the point and returns twice, 0 when it's called from the current execution path and something else via longjmp
- longjmp practically signals to jump back to the point and set the new return code that setjmp will return.
Some sample code I tried
#include <setjmp.h>
#include <stdio.h>
jmp_buf buf;
void func() {
printf("about to jump back\n");
longjmp(buf, 1);
printf("unreachable\n");
}
int main() {
// Setup jump position using buf and return 0
if (setjmp(buf))
printf("This should be executed because longjmp returned 1 \n");
else {
printf("This should be executed because setjmp returned 0\n");
func();
}
return 0;
}
Please, if I have misunderstood something so far feel free to correct me.
On my actual question/problem now:
I am trying to implement my own setjmp and longjmp with ucontext.
Below is how I am trying to do so
#include <stdio.h>
#include <ucontext.h>
typedef struct jump_buffer {
ucontext_t ctx;
int is_set;
} jump_buffer_t;
jump_buffer_t buf;
int set_jumping_point(jump_buffer_t *uc_jmp_buf) {
getcontext(&uc_jmp_buf->ctx);
uc_jmp_buf->is_set = 0;
return uc_jmp_buf->is_set;
}
void jump_back(jump_buffer_t *uc_jmp_buf, int value) {
uc_jmp_buf->is_set = value;
setcontext(&uc_jmp_buf->ctx);
}
void func() {
printf("about to jump back\n");
jump_back(&buf, 1);
printf("unreachable\n");
}
int main() {
buf.is_set = 1;
// Setup jump position using buf and return 0
if (set_jumping_point(&buf))
printf("This should be executed because longjmp returned 1 \n");
else {
printf("This should be executed because setjmp returned 0\n");
func();
}
return 0;
}
Theoretically, my output should be
This should be executed because setjmp returned 0
about to jump back
This should be executed because longjmp returned 1
But my actual output is
This should be executed because setjmp returned 0
about to jump back
Segmentation fault (core dumped)
After debugging the program, I saw that the segfault occurs inside set_jumping_point
uc_jmp_buf->is_set = 0;
First question, why? I am assuming that since I set the context before entering function, by struct was initialized, so even when I am going back, the struct should not be corrupted, why the segfault?
I then tried commenting out that line but then the function returned normally, which means that it was able to read the struct and it is not supposed to be corrupted in that case? For the record, I am attaching the output of the code snippet with the segfaulty line commented out
This should be executed because longjmp returned 1
Can someone explain what am I missing in the bigger picture? It feels weird for that to be a segfault so I am assuming that I am misusing ucontext, but now sure where.
Edit : practically, setjmp does not return properly twice
There are several mistakes in your last set of code. Regarding the segmentation fault, the main issue is that when execution returns to the
getcontextcall withinsetjumpingpointafter callingsetcontext, the function stack values are not the same anymore. This is because the call togetcontextdoes not store the entire stack contents, sosetcontextdoesn't then restore them later. This is because, as shown in Wikipedia's setcontext article, we haveThe stack related information is stored in the
stack_tobject. As stated in this answer to Is the type `stack_t` no longer defined on linux?,In particular, although the size and stack pointer are stored, the entire stack contents are not. This means that, without using a call to
makecontextto use a different stack, the call tosetcontextwill basically just unwind the stack back to wheregetcontextwas last used. Thus, it should only be used to return to somewhere within the current function, or to one of the parent functions going up the call stack chain.To make this problem more explicit, although call stacks may be implemented differently depending on the machine and compiler, assume it's done as shown in the Structure section of Wikipedia's "Call stack" article. When
getcontextis called withinset_jumping_point, from the top-down we would then haveAfter that call finishes initially, the stack would unwind back. Then the call to
func, since it contains no parameters, would change the call stack toNote the uc_jmp_buf pointer has now been overwritten by the return address to main! The call to
jump_backwould add to the top of the stack, but not change any of the lower level values. Thus, the call tosetcontextwill, among other things, cause the stack pointer to return where it was when thegetcontextwas called, so then trying to execute the linewill cause the code to use the return value to
mainas the pointeruc_jmp_buf, thus causing the segmentation fault.Next, note that in your
set_jumping_pointfunction, the last 2 lines areThe member value of
is_setis unconditionally set to 0, and that value is returned in the next line. Thus,set_jumping_pointwill always return 0. There are several ways to change your code to do what you want, with I believe the simplest being to remove theuc_jmp_buf->is_set = 0;line and change thebuf.is_set = 1;line to bebuf.is_set = 0;instead. This way, the initial value returned fromset_jumping_pointwill be 0, while because the value ofis_setchanges to 1 in thejump_backfunction, the second return value fromset_jumping_pointwill then be 1.One final point is that, as stated in the setcontext article, regarding the
getcontextfunction,Thus, there's a chance that, depending on which compiler, and even which version, you're using that the program will not always work properly. As such, I suggest declaring the appropriate variable to be volatile.
Regarding how to fix the main issue of the segmentation fault occurring, if you're using a C++ compiler, then making the functions inline should usually work since then there's no explicit function calls, and thus changes to the stack, that would cause this problem to occur. However, the
inlinekeyword is only a suggestion, not a requirement, to the compiler, although your functions should not qualify for exclusion from being inlined. Alternative methods to avoid this stack problem is to have the function code be in themainfunction itself, or to use macros, as suggested and shown in Craig Estey's answer.Another option is to, such as shown in the Example section of Wikipedia's "setcontext" article, use
makecontextto have a separate stack be used instead during the calls to the function.