embedded software boot camp

Effective C Tip #3 – Exiting an intentional trap

Tuesday, April 14th, 2009 by Nigel Jones

This is the third in a series of tips on writing what I call effective C. Today I’d like to give you a useful hint concerning traps. What exactly do I mean by a trap? Well while C++ has a ‘built in’ exception handler (try searching for ‘catch’ or ‘throw’), C does not (thanks to Uhmmmm for pointing this out). Instead, what I like to do when debugging code is to simply spin in an infinite loop when something unexpected happens. For example consider this code fragment:

switch (foo)
{
 case 0:
 ...
 break;

 case 1:
 ...
 break;

 ...

 default:
  trap();
 break;
}

My expectation is that the default case should never be taken. If it is, then I simply call the routine trap(). So what does trap() look like? Well the naive implementation looks something like this:

void trap(void)
{
 for(;;)
 {
 }
}

The idea is that when the system stops responding, stopping the debugger will show that something unexpected happened. However, while this mostly works, it has a number of significant shortcomings. The most important is that leaving code like this in a production release is definitely not a good idea, and so the first modification that needs to be made is to arrange to remove the infinite loop for a release build. This is usually done by defining NDEBUG. The code thus becomes:

void trap(void)
{
#ifndef NDEBUG
 for(;;)
 {
 }
#endif
}

The next problem with this trap function is that it would be ineffective in a system that executes most of its code under interrupt. As a result, it makes sense to disable interrupts when entering the trap. This is of course compiler / platform specific. However it will typically look something like this:

void trap(void)
{
#ifndef NDEBUG
 __disable_interrupts();
 for(;;)
 {
 }
#endif
}

The final major problem with this code is that it’s hard to tell what caused the trap. While you can of course examine the call stack and work backwards, it’s far easier if you instead do something like this:

static volatile bool Exit_Trap = false; 

void trap(void)
{
#ifndef NDEBUG
 __disable_interrupts();
 while (!Exit_Trap)
 {
 }
#endif
}

What I’ve done is declare a volatile variable called Exit_Trap and have initialized it to false. Thus when the trap occurs, the code spins in an infinite loop. However by setting Exit_Trap to true, I will cause the loop to be exited and I can then step the debugger and find out where the problem occurred.

Regular readers will perhaps have noticed that this isn’t the first time I’ve used volatile to achieve a useful result.

Incidentally I’m sure that many of you trap errors via the use of the assert macro. I do too – and I plan to write about how I do this at some point.

So does this meet the criteria for an effective C tip? I think so. It’s a very effective aid in debugging embedded systems. It’s highly portable and it’s easy to understand. That’s not a bad combination!

Next Effective C Tip
Previous Effective C Tip
Home

Tags:

10 Responses to “Effective C Tip #3 – Exiting an intentional trap”

  1. Uhmmmm says:

    When did C get exception handling? Last I heard, that was C++, not C.

  2. GregK says:

    What I am doing when only have uart in device:void SystemErrorHandler(const char * const File, const int Line){disable_interrupts();reset_uart(); /* clear pending characters, reset uart, bring back default baudrate */puts_software_version_and_other_details(); /* ie compiler ver. etc */puts_date_compilation();puts_file_name(); puts_line_number();/* */puts_apology(); while( /*…*/ ){ }}in header:#ifdef __DEBUG#define trap() SystemErrorHandler(__FILE__,__LINE__)#else…and it is ready for validations and testing by others, not only me.

  3. Nigel Jones says:

    “When did C get exception handling? Last I heard, that was C++, not C.”Sigh. The perils of working in two (similar) languages. Apologies on this folks. I’ll correct the posting.GregK – what you have done is essentially implement the assert() macro, while redirecting its output to your UART. I like the idea of outputting information on the compiler version, build date and so on as well as the usual file and line number.

  4. GregK says:

    Compilation date is just in case if you made change, but forgot change version of software, so __DATE__ and __TIME__ is your friend.

  5. Greg Nelson says:

    In my own code, I use something along the lines of:void trap(void) { breakpointable_function();}where breakpointable_function() is something that returns without any bad side effects. This could be an __asm("nop"); or something else, however, I chose the function call to protect myself from the optimizer; code likevolatile int i;i = i;can actually be (incorrectly) optimized out.By setting and leaving a debugger breakpoint on breakpointable_function(), and then ensuring that there is appropriate context-dependent error handling in the caller, I have an easy way to debug and to recover at run time. It also means I don't need to set an infinite number of breakpoints on every error condition; some debuggers are hardware-limited.The caller might be:if (x == 0) { trap(); return ERR_DIV_ZERO;} else { z = y/x;}If the problem occurs in the field, the application doesn't lock up, it just returns to the user without completing the current impossible mission, possibly with a meaningful error message on the screen if the product supports it.If the problem occurs in the lab, I can immediately track down where it occurred with a backtrace, and continue if I want to.I adopted this solution after getting bitten by a routine that checked ethernet packet length against the maximum the standard (and supposedly, the hardware) supported, and went into a while(1) loop if it didn't. Customers didn't like it when random network traffic locked up their systems…

  6. Gauthier says:

    Any good reason for not having Exit_trap as an auto variable inside the function?
    Even if it compiles to a register, you should be able to modify it, shouldn’t you?

    • Nigel Jones says:

      There is a reason – though I’ll let you judge if it’s good or not. Most debuggers differentiate between auto variables and non-auto variables by placing them in different ‘watch’ windows. I find that I use the non-auto variable window a lot more than the auto variable window, and as a result I keep the auto window normally closed. Thus I place Exit_Trap as shown such that it’s in the watch window that I keep normally open.

      • Gauthier says:

        Odd. The “auto” watch window in my environment (IAR Embedded Workbench for msp430) is not related to the variable’s storage class, it’s just that IAR is trying to automatically find for you the variables that are relevant (often poorly). That’s how I understood it, at least.

        It does not matter if the variable has auto or static storage, it can be shown in the regular Watch window (if it was not optimized away to a register, in which case you may need to check the assembly code to know what to change).
        I just tested your trap function with volatile bool exit = false; at function scope, and I was able to add the variable “exit” in the Watch window (the compiler allocated the variable on stack).

        • Nigel Jones says:

          That’s good to know. I recollect using IDE’s where that is not the case – so perhaps that’s where I got in the habit.

          It also occurred to me that there’s another reason why I do it this way – and that’s related to how well (poorly) a compiler handles volatile semantics. I’ve had times where a volatile auto variable isn’t handled correctly by the compiler, whereas a volatile static variable is handled correctly.

          Anyway, the bottom line is this. If making Exit_trap an automatic variable works in your environment, then I think it’s a change worth making. At the very least you save a byte of RAM!

  7. Frank says:

    I do something similar, except I add an explicit breakpoint. For example, in my current ARM project, I have the following:

    #define _BKPT __ASM(“bkpt 0”)

    I put _BKPT before the infinite loop. I use this same technique for asserts. Thus, I never need to set a breakpoint in the debugger on the error handlers, the debugger will just stop on them when they are called.

Leave a Reply

You must be logged in to post a comment.