Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement setjmp and longjmp functionality with ucontext

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

like image 506
kostas Avatar asked Nov 28 '25 17:11

kostas


1 Answers

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
like image 101
Craig Estey Avatar answered Dec 01 '25 09:12

Craig Estey



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!