This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Problems using setjmp and lngjmp on CC2540

Other Parts Discussed in Thread: CC2540, CC2541

Has anyone been able to use this successfully? I am trying to use it in the typical exception handling pattern but the code doesn't return to the setjmp location on a lngjmp. 

  • Hi Joshua,
    What are you trying to achieve with those commands?
    Best Regards
    Joakim
  • I have incorporated a large, very badly structured library in to my codebase. There are bad failures that can occur way down in the function chain in this library and rather than restructure the library to propagate error codes back up to the level where I can appropriately handle the failure, I want to implement a basic exception handling mechanism. I have read setjmp/longjmp are a way to do this. It's also true that setjmp/longjmp compile for the CC2540. Unfortunately, the code goes off in to the weeds and crashes when I try to use it. 

    Do you have a suggestion for how to build an exception handling mechanism?

  • After a weekend of experimenting and figuring out the 8051's function calling / bank switching mechanism, I think it's manageable to write my own setjmp/longjmp. It requires a bit of link file hacking (my favorite activity) and then some assembly but my test code so far appears to do what I require. I'll post my solution here if I can get it working. Unless someone posts a simple fix first!
  • Hi Joshua,
    To be honest, it's a bit out of my experience to hack around with setjmp/longjmp the way you do. I'll check internally to see if someone has done something similar. Feel free to post any progress during your adventure.

    Best Regards
    Joakim
  • Here's how I got this to work.

    First, why do we want setjmp/longjmp?

    Many modern languages have exception handling mechanisms built in. This allows the following pattern:

    try {
    //code to execute that could potentially throw an exception
    }
    catch {
    //deal with a potentially thrown exception, but this code is not executed if
    //the try-block code completes successfully (i.e, throws no exceptions)
    }

    The thrown exception mechanism allows the programmer to specify a toplevel location to handle errors that could occur much farther down in the function callstack. In C this mechanism doesn't exist as a first-class citizen, so most libraries are structured to propagate error codes back up the function callstack. If the library is structured this way, each intermediate function in the callstack must check the errorCode from any function it invokes and decide to proceed, handle the error locally, or return the error code to its calling function. This can add a lot of messy error handling code at many points in a deeply nested callstack. In addition, not every library writer writes codes like this. Mine sure didn't.

    The setjmp/longjmp mechanism in C addresses this shortcoming. It is used in the following manner (very similar to the try-catch block):

    uint8 returnVal = setjmp(); //always returns 0 when called normally
    
    if (0 == returnVal) {
    //because returnVal is always 0 when invoked normally, this code will always be executed.
    TopLevelFunction();
    }
    else {
    //this is the code to handle a nonzero returnVal
    }

    The if-block will always be executed, because returnVal always returns 0 when invoked normally (seriousy, it's got a return 0; in it). However, if anytime before TopLevelFunction returns normally a longjmp() is called, execution will jump to the location of the return of setjmp but this time it will act as if returnVal is the returnCode passed to longjmp(). Voila, error handling can now occur at the top-level of the stack.

    Implementing this for the 8051-based CC2540/CC2541 kind of sucks, because of the weird addressing modes for code and memory. Also, there is no program counter register that can be simply read (doubleplus ungood).

    Second, here's one way to do this:

    NB: My approach probably sucks. It has only one virtue: It works.

    * I have to specify particular locations in IXDATA for my variables so that the compiler/linker don't use them and so I know their address when I'm writing assembly.
    * I don't (know how to) constrain the return mechanism used by the compiler, so if the compiler in its wisdom decides to change from the way I use to the other mechanism (using the intermediate relay) this code will break. Because I don't know the addresses of these functions (supplied by linker, and I dont want to mess with this too much) I tend to let the compiler supply the code addresses, but it means I am dependent on a predictable compiler. The compiler is unpredictable at various optimization levels. Caveat emptor.
    * Lots of assembly, because we have to hack the stack.

    1. Create some known locations in IXDATA for placing variables by editing the link file.

    //JMP_SPACE
    //Im reserving 8 spaces, so I use 0x07 (because I get the last one as well)
    //_XDATA_END is at 0x1EFF, so this reserves 0x1EF8-0x1EFF.
    -D_JMP_SPACE_END=(_XDATA_END)
    -D_JMP_SPACE_START=(_JMP_SPACE_END-0x07) 
    -Z(XDATA)JMP_SPACE=_JMP_SPACE_START-_JMP_SPACE_END

    2. Declare the corresponding variable in mysetjmp.c.

    typedef struct {
    uint8 stackPtr; //0x1EF8
    uint8 codeBank; //0x1EF9
    uint8 pcL; //0x1EFA
    uint8 pcH; //0x1EFB
    uint8 retCode; //0x1EFC
    uint8 intRetL; //0x1EFD
    uint8 intRetH; //0x1EFE (FE FF)
    } MyJumpStructT;
    
    #pragma location="JMP_SPACE"
    static __no_init MyJumpStructT s_jmp;

    NB: C doesn't guarantee byte ordering in structs, so it would be *marginally* more portable (haha, this is insanely hardware dependent) to use a uint8 buffer, but this improves readability.

    3. In setjmp, preserve the invoking functions stack pointer, program counter, and code bank.

    The stack pointer is obvious: We wish to reset the stack when we return to setjmp from a longjmp. The program counter is how we force the executing code to jump back out to setjmp from longjmp - basically we rewrite the stack, then call return.

    One complication: code banks. The 8051 uses code banks to extend the effective codesize from 65K to 256K using a 24-bit address, where 16 bits is the pointer to the currently addressable code and the third byte is used to select one of eight possible banks of 32K.

    Additional complication: The 8051 doesn't have a dedicated PC (program counter) register. It's an apparently a well-known 8051 trick to examine the stack for the return address of the invoking function, which is my approach. Here's the code:

    uint8 mysetjmp(void) {
    
    //Step 1: Preserve SP
    asm("MOV A, SP");
    //The stack holds 3 bytes for this function call, so subtract 3.
    asm("DEC A"); asm("DEC A"); asm("DEC A"); 
    asm("MOV R1, A");
    asm("MOV DPTR, #0x1EF8");
    asm("MOV A, R1");
    asm("MOVX @DPTR, A");
    
    //Step 2: Preserve code bank
    //This is currently at the top of the stack, so store the value there.
    asm("MOV A, SP");
    asm("MOV R1, A");
    asm("MOV DPTR, #0x1EF9"); //code bank
    asm("MOV A, @R1");
    asm("MOVX @DPTR, A");
    
    //Step 3: Preserve PCL of calling function
    asm("MOV A, SP");
    asm("DEC A"); asm("DEC A"); //Move down two spaces on the stack
    asm("MOV R1, A");
    asm("MOV DPTR, #0x1EFA"); //store to s_jmp.pcL
    asm("MOV A, @R1");
    asm("MOVX @DPTR, A");
    
    //Step 4: Preserve PCH of calling function
    asm("MOV A, SP");
    asm("DEC A"); //Move down one space on the stack
    asm("MOV R1, A");
    asm("MOV DPTR, #0x1EFB"); //store to s_jmp.pcH
    asm("MOV A, @R1");
    asm("MOVX @DPTR, A");
    
    return 0; //normal invocation, always return 0
    }

    4. In longjmp, wipe the stack, rewrite the stack, then place the returnode in the right spot.

    One complication: It's possible that the compiler could change the return mechanism of this function. I don't yet understand why this happens, but the compiler can use an intermediate function, the relay, to return, in which case there are often two additional bytes on the stack. My compiler is not using this for this function so this code currently works, but it is brittle.

    void mylongjmp(uint8 returnCode) {
    
    //Step 0: Preserve returnCode
    asm("MOV DPTR, #0x1EFC");
    asm("MOV A, R1");
    asm("MOVX @DPTR, A");
    
    //Step 1: Restore old stack pointer (i.e, obliterate current stack)
    asm("MOV DPTR, #0x1EF8"); //retrieve the SP
    asm("MOVX A, @DPTR");
    asm("MOV SP, A"); //sets the SP to the old value
    
    //Step 2: Push return values PCL, PCH, CBANK
    asm("MOV DPTR, #0x1EFA"); //PCL
    asm("MOVX A, @DPTR");
    asm("PUSH A");
    
    asm("MOV DPTR, #0x1EFB"); //PCH
    asm("MOVX A, @DPTR");
    asm("PUSH A");
    
    asm("MOV DPTR, #0x1EF9"); //CBANK
    asm("MOVX A, @DPTR");
    asm("PUSH A");
    
    //Step 3: Put retCode back in to accumulators
    asm("MOV DPTR, #0x1EFC");
    asm("MOVX A, @DPTR");
    
    //Step 4: Back to C-land, if there are multiple return conventions
    //(there are) then this is brittle as hell
    return;
    }

    I hope this technique is useful to others on the forums. If any experts have additional insight commentary would be much appreciated.

  • Hi Joshua,

    Thanks for an interesting and educating read!

    I've notified IAR about this, since I think setjmp.h is a part of the DLIB/CLIB they provide, and I would have expected it to work.

    Best regards,
    Aslak

  • Good idea Aslak. Please post here if you get any information from IAR. 

    Thank you-

    Josh

  • Hi Josh,

    This is confirmed by IAR to be a bug. There will be a service pack for v 9.10, and additionally, a fixed iar_setjmp_longjmp.s51 will be released for earlier versions.

    Apparently the problem is that ?CBANK isn't saved correctly when calling set/jmp from different banks. I'll let you know when I receive a fix.

    BR,
    Aslak

  • Thanks for following up on that Aslak. I've spent some more time with my implementation and although it works in specific cases, there are function stacks for which it doesn't work. Some functions that I try to jump out of have much more elaborate assembly at their return, and jumping rather than exiting normally causes the same bad crash I saw with the IAR implementation. Im eager to get their patch for this problem.

    -Josh

  • Hi Josh,

    A service pack has been released for 9.10 called 9.10.3.

    Find the fix from IAR attached to this post.

    iar_setjmp_longjmp.s51

    Best regards,
    Aslak