Services and Modeling for Embedded Software Development
Embecosm divider strip
Prev  Next

4.1.2.  Implementing the setjmp and longjmp functions.

setjmp and longjmp are a pair of C function facilitating cross-procedure transfer of control. Typically they are used to allow resumption of execution at a known good point after an error.

Both take as first argument a buffer, which is used to hold the machine state at the jump destination. When setjmp is called it populates that buffer with the current location state (which includes stack and frame pointers and the return address for the call to setjmp, and returns zero.

longjmp takes a buffer previously populated by setjmp. It also takes a (non-zero) second argument, which will ultimately be the result of the function call. longjmp restores the machine state from the buffer. It then jumps to the return address it has just restored, passing its second argument as the result. That return address is the return address from the original call to setjmp, so the effect will be as if setjmp has just returned with a non-zero argument.

setjmp and longjmp are typically used in a top level function in the following way.

#include <setjmp.h>

...

  jmp_buf  buf;

  if (0 == setjmp (buf))
    {
      normal processing passing in buf
    }
  else
    {
      error handling code
    }

...
	  

During normal processing if an error is found, the state held in buf can be used to return control back to the top level using longjmp.

#include <setjmp.h>

...

  if (error detected)
    {
      longjmp (buf, 1);
    }

...
	  

The program will behave as though the original call to setjmp had just returned with result 1.

It will be appreciated that this is behavior that cannot usually be written in C. The OpenRISC 1000  implementation is given as an example. This processor has 32 registers, r0 through r31, each of 32-bits. r0 is always tied to zero, so need not be saved. r11 is the function result register, which is always set by setjmp and longjmp, so also need not be saved. In addition we should save and restore the machine's 32-bit supervision register, which holds the branch flag.

Thus we need the buffer to be 31 32-bit words long. This is defined in the setjmp header (see Section 4.2.2).

In the Application Binary Interface (ABI) for the OpenRISC 1000 , function arguments are passed in registers r3 through r8 and the function return address is in r9.

When defining these two functions, in assembler, be aware of any prefix conventions used by the C compiler. It is common for symbols defined in C to have an underscore prepended (this is the case for the OpenRISC 1000 ). Thus in this case the assembler should define _setjmp and _longjmp.

This is the implementation of setjmp.

        .global _setjmp
_setjmp:
        l.sw    4(r3),r1                /* Slot 0 saved for flag in future */
        l.sw    8(r3),r2
        l.sw    12(r3),r3
        l.sw    16(r3),r4
        l.sw    20(r3),r5
        l.sw    24(r3),r6
        l.sw    28(r3),r7
        l.sw    32(r3),r8
        l.sw    36(r3),r9
        l.sw    40(r3),r10              /* Skip r11 */
        l.sw    44(r3),r12
        l.sw    48(r3),r13
        l.sw    52(r3),r14
        l.sw    56(r3),r15
        l.sw    60(r3),r16
        l.sw    64(r3),r17
        l.sw    68(r3),r18
        l.sw    72(r3),r19
        l.sw    76(r3),r20
        l.sw    80(r3),r21
        l.sw    84(r3),r22
        l.sw    88(r3),r23
        l.sw    92(r3),r24
        l.sw    96(r3),r25
        l.sw    100(r3),r26
        l.sw    104(r3),r27
        l.sw    108(r3),r28
        l.sw    112(r3),r29
        l.sw    116(r3),r30
        l.sw    120(r3),r31

        l.jr    r9
        l.addi  r11,r0,0                /* Zero result */
	  

In this simplified implementation, the status flags are not saved—that is a potential future enhancement. All the general registers, with the exception of r0 (always zero) and r11 (result register) are saved in the buffer, which, being the first argument, is pointed to by r3.

Finally the result register, r11 is set to zero and the function returns using r9 (the OpenRISC 1000  has delayed branches, so the setting of r11 is placed after the branch to return.).

The implementation of longjmp is slightly more complex, since the second argument will be returned as the effective result from setjmp, unless the second argument is zero in which case 1 is used.

The result must be dealt with first and placed in the result register, r11, because the second argument, in r4 will be subsequently overwritten when the machine state is restored. Similarly we must ensure that r3, which holds the first argument pointing to the restore buffer must itself be the last register restored.

        .global _longjmp
_longjmp:
        /* Sort out the return value */
        l.sfne  r4,r0
        l.bf    1f
        l.nop

        l.j     2f
        l.addi  r11,r0,1                /* 1 as result */

1:      l.addi  r11,r4,0                /* val as result */

        /* Restore all the other registers, leaving r3 to last. */
2:      l.lwz   r31,120(r3)
        l.lwz   r30,116(r3)
        l.lwz   r29,112(r3)
        l.lwz   r28,108(r3)
        l.lwz   r27,104(r3)
        l.lwz   r26,100(r3)
        l.lwz   r25,96(r3)
        l.lwz   r24,92(r3)
        l.lwz   r23,88(r3)
        l.lwz   r22,84(r3)
        l.lwz   r21,80(r3)
        l.lwz   r20,76(r3)
        l.lwz   r19,72(r3)
        l.lwz   r18,68(r3)
        l.lwz   r17,64(r3)
        l.lwz   r16,60(r3)
        l.lwz   r15,56(r3)
        l.lwz   r14,52(r3)
        l.lwz   r13,48(r3)
        l.lwz   r12,44(r3)
        l.lwz   r10,40(r3)              /* Omit r11 */
        l.lwz   r9,36(r3)
        l.lwz   r8,32(r3)
        l.lwz   r7,28(r3)
        l.lwz   r6,24(r3)
        l.lwz   r5,20(r3)
        l.lwz   r4,16(r3)
        l.lwz   r2,8(r3)                /* Skip r3 */
        l.lwz   r1,4(r3)                /* Slot 0 saved for flag in future */
        l.lwz   r3,12(r3)               /* Now safe */
        
        /* Result is already in r11. Having restored r9, it will appear as
           though we have returned from the earlier call to _setjmp. The
           non-zero result gives it away though. */
        l.jr    r9
        l.nop
	  

The return address, stack pointer and frame pointer having been restored, the return from the function, will place the execution point immediately after the original call to setjmp.

The following is a simple test program, which can be used to verify that setjmp and longjmp are working correctly.

#include <setjmp.h>
#include <stdio.h>


void
testit (jmp_buf  env,
        int      prev_res)
{
  int  res = (0 == prev_res) ? prev_res : prev_res + 1;

  printf ("Long jumping with result %d\n", res);
  longjmp (env, res);

}       /* testit () */


int
main (int   argc,
      char *argv[])
{
  jmp_buf  env;

  int  res = setjmp (env);

  printf ("res = 0x%08x\n", res);

  if (res > 1)
    {
      return  0;
    }

  testit (env, res);

  return  256;                  /* We should never actually get here */

}       /* main () */
	  
Embecosm divider strip