views:

429

answers:

2

Hi,

The following code summarizes the problem I have at the moment. My current execution flow is as follows and a I'm running in GCC 4.3.

jmp_buf a_buf;
jmp_buf b_buf;

void b_helper()
{
    printf("entering b_helper");
    if(setjmp(b_buf) == 0)
    {
     printf("longjmping to a_buf");
     longjmp(a_buf, 1);
    }
    printf("returning from b_helper");
    return; //segfaults right here
}
void b()
{
    b_helper();
}
void a()
{
    printf("setjmping a_buf");
    if(setjmp(a_buf) == 0)
    {
     printf("calling b");
     b();
    }
    printf("longjmping to b_buf");
    longjmp(b_buf, 1);
}
int main()
{
    a();
}

The above execution flow creates a segfault right after the return in b_helper. It's almost as if only the b_helper stack frame is valid, and the stacks below it are erased.

Can anyone explain why this is happening? I'm guessing it's a GCC optimization that's erasing unused stack frames or something.

Thanks.

+6  A: 

You can only longjmp() back up the call stack. The call to longjmp(b_buf, 1) is where things start to go wrong, because the stack frame referenced by b_buf no longer exists after the longjmp(a_buf).

From the documentation for longjmp:

The longjmp() routines may not be called after the routine which called the setjmp() routines returns.

This includes "returning" through a longjmp() out of the function.

Greg Hewgill
Is there a way to longjmp down the stack frame? Is it possible to copy the stack from b to b_helper into the heap and executing from there?Also, why is the stack frame referenced by b_buf no longer valid after jumping up the call stack?
jameszhao00
Once a part of the stack is released, it's completely invalid (other function calls, interrupts or whatever might overwrite the memory).
Michael Burr
You can think of a `longjmp()` as an "extended return". A successful `longjmp()` works like a series of successive returns, unwinding the call stack until it reaches the corresponding `setjmp()`. Once the call stack frames are unwound, they are no longer valid. This is in contrast to implementations of coroutines (eg. Modula-2) or continuations (eg. Scheme) where the call stack remains valid after jumping somewhere else. C and C++ only support a single linear call stack, *unless* you use threads where you create multiple independent call stacks.
Greg Hewgill
@jameszhao00 - I think that to have any hope of doing something like what it seems you're after will require dropping into assembly code. Whether it's a good idea to pursue is another question altogether.
Michael Burr
Ah. So basically I can do a GCC force-inline of b_helper() and at the end of b_helper() longjmp back to a?
jameszhao00
@jameszhao00: but if you are looking to do coroutines in C, take a look at http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html whcih describes using state variables and switch statements to get a similar effect.
Michael Burr
Also, I don't get why after I longjmp back to a from b_helper() the b_helper() frame is still valid. According to the docs it should've been unwound.
jameszhao00
The point is the b_helper() frame is *not* valid, and `b_buf` refers to an invalid frame. The fact that execution continues to the end of `b_helper()` is an accident.
Greg Hewgill
Ah I see. Thanks.
jameszhao00
+2  A: 

The standard says this about longjmp() (7.13.2.1 The longjmp function):

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 function containing the invocation of the setjmp macro has terminated execution in the interim

with a footnote that clarifies this a bit:

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.

So you can't longjmp() back & forth across nested setjmp/longjmp sets.

Michael Burr