CS360 Lecture notes -- Setjmp

  • Jim Plank
  • Directory: /blugreen/homes/plank/cs360/notes/Setjmp
  • Lecture notes: http://www.cs.utk.edu/~plank/plank/classes/cs360/360/notes/Setjmp/lecture.html

    setjmp()/longjmp()

    Setjmp() and longjmp() are subroutines that let you perform complex flow-of-control in C/Unix.

    One of the keys to understanding setjmp() and longjmp() is to understand machine layout, as described in the assembler and malloc lectures of the past few weeks. The state of a program depends completely on the contents of its memory (i.e. the code, globals, heap, and stack), and the contents of its registers. The contents of the registers includes the stack pointer (sp), frame pointer (fp), and program counter (pc). What setjmp() does is save the contents of the registers so that longjmp() can restore them later. In this way, longjmp() ``returns'' to the state of the program when setjmp() was called.

    Specifically:

    #include < setjmp.h >
    int setjmp(jmp_buf env);
    
    This says to save the current state of the registers into env. If you look in /usr/include/setjmp.h, you'll see that jmp_buf is defined as:
    #define _JBLEN  9
    typedef struct { int _jb[_JBLEN + 1]; } jmp_buf[1];
    
    This is an irritating way of saying that jmp_buf is an array of _JBLEN+1 integers.

    So, when you call setjmp(), you pass it the address of an array of integers, and it stores the value of the registers in that array. Setjmp() returns 0 when you call it in this way.

    longjmp(jmp_buf env, int val);
    
    Longjmp() resets the registers to the values saved in env. This includes the sp, fp and pc. What this means is that longjmp() doesn't return. Instead, when you call it, you return as if you have just called the setjmp() call that saved env. This is because the pc is restored along with the other registers. Setjmp() returns the val argument of longjmp(), which is not allowed to be zero (read the man page). Thus, you know when setjmp() returns a non-zero value that longjmp() was called, and is returning to setjmp().

    As an example, look at the following code (in sj1.c):


    #include < setjmp.h >
    
    main()
    {
      jmp_buf env;
      int i;
    
      i = setjmp(env);
      printf("i = %d\n", i);
    
      if (i != 0) exit(0);
    
      longjmp(env, 2);
      printf("Does this line get printed?\n");
    
    }
    

    When we run this, we get:
    UNIX> sj1
    i = 0
    i = 2
    UNIX>
    
    So, first, we call setjmp(), and it returns 0. Then we call longjmp() with a value of 2, which causes the code to return from setjmp() with a value of 2. That value is printed out, and the code exits.

    Setjmp() and longjmp() are usually used so that if an error is detected within a long string of procedure calls, the error may be dealt with gracefully by a high-level call. For example, look at sj2.c. It looks to be complicated, but really isn't. What happens is that there is a complicated series of procedure calls -- proc_1 through proc_4. If proc_4's argument is zero, then it flags the error by calling longjmp(). Otherwise, things proceed normally. As you can see, if you call sj2 with all positive arguments, then everything is ok. However, if you call it with all zeros, it will make the longjmp() call, and flag an error:

    UNIX> sj2 1 2 3 4
    proc_1(1, 2, 3, 4) = 4
    UNIX> sj2 0 0 0 0
    Error -- bad value of i (0), j (0), k (0), l (0)
    UNIX>
    

    Now, setjmp() saves all the registers, including the sp and fp. What this means is that if you return from a procedure that calls setjmp(), then the env buffer of that setjmp() will no longer be valid. Why? Because that env buffer contains the sp and fp of the calling procedure. If that procedure returns, then when you restore the sp and fp, the stack will be in a different state than before, and you will have an error. For example, look at sj3.c.

    When we execute it, we get the following:

    UNIX> sj3
    Setjmp() returned -- i = 0
    s = Jim
    In B: i=3.  Calling longjmp(env, i)
    Setjmp() returned -- i = 3
    Segmentation fault (core dumped)
    UNIX>
    
    So, exactly what is happening? When the main() routine is first called, the stack looks as follows:
                  Stack        
            |----------------|
            |                |
            |                |
            |                |
            |                |
            |                |
            |                | <-------- sp
            | env[0]         |
            | env[1]         |
            | env[2]         |               pc = main
            | env[3]         |
            | ....           |
            | env[8]         |
            | other stuff    | <------- fp
            |--------------- |
    
    Now, main() calls a(). First it pushes the arguments on the stack in reverse order, and then jsr is called, which pushes the return pc on the stack, and the old fp. The fp and sp are changed to make an empty stack frame for a():
                                         Stack        
                                   |----------------|
                                   |                |
                                   |                | <--------- sp, fp
                    /------------- | old fp in main |
                    |              | old pc in main |
                    |   "Jim" <--- | s = "Jim"      |
                    |         /--- | pointer to env | 
                    |         \--> | env[0]         |
                    |              | env[1]         |
                    |              | env[2]         |               pc = a
                    |              | env[3]         |
                    |              | ....           |
                    |              | env[8]         |
                    \------------> | other stuff    | 
                                   |--------------- |
    

    The first thing that a() does is allocate room its local variable i:

                                         Stack        
                                   |----------------|
                                   |                | <--------- sp
                                   |      i         | <--------- fp
                    /------------- | old fp in main |
                    |              | old pc in main |
                    |   "Jim" <--- | s = "Jim"      |
                    |         /--- | pointer to env | 
                    |         \--> | env[0]         |
                    |              | env[1]         |
                    |              | env[2]         |               pc = a
                    |              | env[3]         |
                    |              | ....           |
                    |              | env[8]         |
                    \------------> | other stuff    | 
                                   |--------------- |
    
    Then it calls setjmp(). This saves the current state of the registers. In other words, it saves the current values of sp, fp, and pc. Now, a() prints "i = 0", and "s = Jim", and then returns to main(). Now the stack looks as before, except that env is initialized to the state of the machine when a() was called:
                                         Stack        
                                   |----------------|
                                   |                |
                                   |                | 
                                   |                |
                                   |                |
                                   |                |
                                   |                | <----------- sp
                                   | env[0]         |
                                   | env[1]         |
                                   | env[2]         |               pc = main
                                   | env[3]         |
                                   | ....           |
                                   | env[8]         |
                                   | other stuff    | <------------ fp
                                   |--------------- |
    
    Now, main() calls b(), and the stack looks as follows:
                                         Stack        
                                   |----------------|
                                   |                |
                                   |                | <--------- sp, fp
                    /------------- | old fp in main |
                    |              | old pc in main |
                    |              | i = 3          |
                    |         /--- | pointer to env | 
                    |         \--> | env[0]         |
                    |              | env[1]         |
                    |              | env[2]         |               pc = b
                    |              | env[3]         |
                    |              | ....           |
                    |              | env[8]         |
                    \------------> | other stuff    | 
                                   |--------------- |
    
    Then longjmp() is called. The registers are restored to their values when a() called setjmp(), and the pc returns from setjmp() in a(). However, the values in the stack are the same as they were for b():
                                         Stack        
                                   |----------------|
                                   |                | <--------- sp
                                   | i = 2          | <--------- fp
                    /------------- | old fp in main |
                    |              | old pc in main |
                    |              | s??    = 3     |
                    |         /--- | pointer to env | 
                    |         \--> | env[0]         |
                    |              | env[1]         |
                    |              | env[2]         |               pc = a
                    |              | env[3]         |
                    |              | ....           |
                    |              | env[8]         |
                    \------------> | other stuff    | 
                                   |--------------- |
    
    You should see the problem. The stack is in a bad state. In particular, a() expects there to be a (char *) where s is, and instead, there is the integer value 3. Thus, when it tries to print out s, it tries to find a string at memory location 3, and dumps core.

    This is a very common bug with setjmp() and longjmp() -- to use them properly, you CANNOT RETURN FROM THE PROCEDURE THAT CALLS setjmp(). As you can see, this bug is subtle -- the stack frame for b() looks a lot like the stack frame for a(), and thus this bug might slip by unnoticed for a while.


    Setjmp() and signals

    One of the nice things about setjmp() and longjmp() is that you can longjmp() out of a signal handler, and back into your program. This lets you catch those signals again.

    Look at sh4.c:

    Note that this program longjmps out of alarm_handler after 8 seconds have passed, and then prints "Gave up". Be sure you can trace through this.


    png.c

    Ok -- in the program

    Look at png.c (which stands for "prime number generator"), there is some really cool stuff going on. Copy it and run it (you'll have to exit with CNTL-C), and then see if you can figure out what's going on. Note that this program may seg-fault. Can you figure out why? Unfortunately, I will not have time to go over this program, but it is a good exercise to see if you can understand it.

    Obviously, you'd do better to use threads to implement something like this.