ALINK="#FF0000"> << Prev  |  TOC  |  Front Page  |  Talkback  |  FAQ  |  Next >>
LINUX GAZETTE
...making Linux just a little more fun!
setjmp/longjmp Illustrated
By Raghu J Menon

The setjmp/longjmp set of macros implemented in the C programming language provide the perfect platform to perform complex flow-control, but make sure that you have gained adequate knowledge about them, before you actually use them, or else  your programs could become so complex that it would be impossible to discern them.

What do they do?

The setjmp function saves the state of a program. The state of a program to be precise are the values of sp (stack pointer), fp (frame pointer), pc (program counter). A program state is completely defined by these set of registers and the contents of the memory, which includes the heap and the stack. The next obvious question would be, why do i need to save the state for? Well simple to restore it later through longjmp. So these functions hunt in pairs i.e. setjmp saves the state, longjmp restores it.

The syntax....

The syntax is quite simple. setjmp stores the state of the program in a variable of type jmp_buf (defined in the header file setjmp.h). Always include the header file while working with these functions.

int setjmp (jmp_buf env);

int longjmp(jmp_buf env , int val);

The longjmp function restores the state of the program that is stored in env. The purpose of the parameter val will be explained later. So what does all this add upto? Simply that the longjmp function never returns (another one after exec). Before encountering a longjmp there has to be a setjmp which saves the state in env and returns a value 0. When you encounter longjmp next the state stored in env is restored and the program execution resumes at the instruction after setjmp. It is as though the longjmp returned through setjmp. This return should yield a value though and that value is what is specified through the parameter val.

i = setjmp (env);//Stores the state in env and returns 0

...........      //Resume execution at this point after the longjmp call as though the setjmp call

.......         //returned.

 

longjmp(env,val)

As a last point, try printing the value of i. You would get 2 values, the first one is that obtained when setjmp saves the state and will be 0 always. The second one will be the value that you pass to longjmp through the val parameter. So the code after setjmp seems to get executed more than once. That calls for some exploration. We therefore have our first code and an interesting one too. if-else.c

 Compile and run it. I hope you noticed it, both the if and else part of the condition are executed! Now, that is not how if-else condition is supposed to work. Looks like fork() (parent executing the if part and child executing the else or vice-versa). Well in fork we have two different threads of execution, that is not the case here. The setjmp call saves the state in env and returns 0. The if condition evaluates to true and you get the first message. Now later in the code when longjmp is executed the state is restored and you return to the statement following setjmp with a return value 2.

This return value is specified in the longjmp call. Now you see why the if condition failed and the else was executed. In addition the program showed disparity by not executing the last line. Well as i said earlier longjmp never returns and so it is quite obvious why the last line is not executed. If you take out the exit statement the code falls into a never ending loop alternating between the else part and the longjmp call.

Some thing more useful please.....

As programmers you might have written code by dividing it  into functions or subroutines (If not learn the art of functional programming. I started off by writing a C program as one big main function, gradually though i was able to split my program into functions. Why?  It is easier to debug, that's why.). In implementing your program as functions there are bound to be function calls that are nested, that have pretty complex flow as well. Whenever an error occurs you need to find the function that caused it. This way it is easier to debug the program. The code below illustrates the use of setjmp/longjmp pair in debugging such programs. nest.c

Well the program does not do anything useful other than serve the purpose of illustrating graceful error handling. The code defines 4 functions, each one of them apart from accepting specified number of integer parameters, also has env as its parameter. The env holds the state of the program saved by the setjmp call in the main function. The failure in executing each of the function is specified in the if condition. Compile the program and execute it. Enter the following sets of values for l, m, n.

Enter values (integer) for l m and n please

1

4

7

The functions executed normally


Enter values (integer) for l m and n please

0

0

0

There is an error in function 1 exiting..
 

Enter values (integer) for l m and n please

1

1

2

There is an error in function 2 exiting..
 

Enter values (integer) for l m and n please

0

1

2

There is an error in function 3 exiting..


Enter values (integer) for l m and n please

1

2

3

There is an error in function 4 exiting..
 

 Well that was useful i suppose. The error message could tell you where the error occurred. Let us trace the code. The setjmp in the main function saves the state of the program and returns 0. The if condition equates to false and therefore is not executed. The next statement calls the function fun1 with parameters env, l, m, n, fun1 in turn calls fun2 and so on. Whenever an error occurs in any of these functions a longjmp is executed, the val parameter being the function number where the longjmp was executed. The program returns to the main function (to the statement after setjmp) whenever a longjmp is executed. The value in s now is either 1, 2, 3, 4, depending on where the longjmp was made from. The if condition now equates to truth value and therefore an appropriate error message is flagged indicating the function in which the error occurred. If no error occurred during the execution of the program the functions return normally and the last statement of the main function is executed. Why don't i just use the goto statement to make a jump during an error? Try compiling the code below goto.c. The error flagged is because goto can be used only for local jumps. The jumps in the previous program made by longjmp where non-local ones, goto searches for local labels and hence cannot make non-local jumps.

 

Vulnerability Of The Programmer........

There is a subtle bug in setjmp/longjmp, not in its implementation, but in the way we use it. Most of us are quite unaware and rightly so of the stack state when we write a program. It is when there is an error we try and trace it by inspecting the stack (through gdb). Whenever there is a function call the stack is manipulated. First the arguments to the called function are pushed in the reverse order. Next the JSR is called to push the return address (pc) and then the fp, fp and sp are then emptied to make a new stack frame for the called function. The called function immediately on entry creates space on the stack for the local variables that might have been declared in the function. Now that you have an idea of the stack structure, try running the code below seg.c.  It compiles fine, but alas it fails to run completely and faults. Could you find the reason?

        Let us trace the code. The main function calls  me_first with 2 arguments,  the arguments are pushed onto the stack env followed by the string "IC-Labs", the JSR then pushes the pc and fp values on the stack. On entry  the function creates a local variable i on the stack. This is followed by a call to the setjmp function which saves the current state, that of me_first function. The local variable now contains the value 0, value returned by setjmp. After returning from me_first the  stack is returned to the original state, one in which it left the main function. The i_follow function is called next with a value 3 and the env variable. The stack is modified as above (when me_first was called). In the function the state stored in env is restored by longjmp. The values in the stack remain the same i.e. as they where during the execution of function  i_follow . The state though is that of function me_first. The stack frame of this state has a variable of type (char * )  which previously had a string "IC-Labs". Now after the state has been restored the value that variable s holds is 3 (the value that i_follow was passed from the main). As a result of the longjmp the statement following setjmp in me_first is executed.  In executing the statement after setjmp (printf), there is an illegal memory access since in trying to print out s the program tries to find a string at memory location 0x3 which causes a memory protection error and causes the program to fault. This bug is very subtle and often goes unnoticed, this is because the stack frame of both the functions look almost the same. In cases were the stack frames are the same there is no such error. Try replacing the argument "char *" with one of type int, and rerun it. Did it fault?

Signal Handling........

One of the beauties of these functions is that you can longjmp from a signal handler and return to your program and catch those signals again. Check out the program below sig.c

The main function installs a signal handler using the signal system call, parameters are the signo(SIGALRM), which indicates the signal for which you are setting up the handler and the handler routine which is executed when the signal occurs. The alarm call sends the   SIGALRM signal to the program every second. The alarm_handler basically longjmps out after 8 seconds have passed.   

 

[BIO] I am almost through with my graduate studies in computer science and engineering. I hail from Trichur (a small town in god's own country, Kerala). Any constructive criticism with regards to the style and content are welcome. Please feel free to contact me via e-mail.


Copyright © 2003, Raghu J Menon. Copying license http://www.linuxgazette.net/copying.html
Published in Issue 90 of Linux Gazette, May 2003

<< Prev  |  TOC  |  Front Page  |  Talkback  |  FAQ  |  Next >>