I am trying to grasp some key concepts in order to fully understand the ucontext and setjmp libraries.
On what concerns ucontext:
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:
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
The issue is that we're calling getcontext from a child function (i.e. set_jumping_point).
getcontext saves the stack frame/pointer for the function that called it. Here, this is the stack frame for set_jumping_point. This disappears (and becomes invalid) when set_jumping_point returns.
So, when we call setcontext, it sets the stack pointer to point to the now invalid/non-existent stack frame for set_jumping_point. This is UB (undefined behavior) and, in this situation, causes a segfault.
We need to save the stack context for the caller function (i.e. main) that persists until we call setcontext.
We can't do this when set_jumping_point is a function. We need to convert it to a macro.
In the code below, I've used a gcc extension to make the macro "pretty". Or, [a bit messier] we just physically cut-and-paste the code from set_jumping_point into main.
Also, we should set is_set before calling getcontext and we should guarantee that the compiler won't reorder the calls, so we should add a barrier.
There would be a similar issue for setjmp if we put it into a separate child function. In your example, main calls setjmp directly, so this is okay. If we put the setjmp into a subfunction (e.g. set_jumping_point_setjmp) and we did: main --> set_jumping_point_setjmp --> setjmp, it would have a similar problem.
Here is the corrected code:
#include <stdio.h>
#include <ucontext.h>
typedef struct jump_buffer {
ucontext_t ctx;
int is_set;
} jump_buffer_t;
jump_buffer_t buf;
#define barrier \
__asm__ __volatile__("" ::: "memory" )
#if 0
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;
}
#else
#define set_jumping_point(_bufp) \
({ \
(_bufp)->is_set = 0; \
barrier; \
getcontext(&(_bufp)->ctx); \
(_bufp)->is_set; \
})
#endif
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;
}
Here is the output:
This should be executed because setjmp returned 0
about to jump back
This should be executed because longjmp returned 1
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With