3 Gordon Drive, P.O.Box 1347 Rockland, Maine 04841 U.S.A.
Find Tools for Your Chip


 

© 2004 Avocet Systems, Inc.
Call Us Today at 207-596-7766 ("Picton Press")
Avocet Systems, Inc. : The Complete Solution for Embedded Systems Development Tools
Hints
Single Stepping in ISRs

Abstract
Single stepping in Interrupt Service Routines (ISRs) can cause all sorts of trouble. Here's our perspective on the matter.

Are you willing to give up most of your debugging resources just when you need them most - in debugging the real time code inside of an interrupt service routine (ISR)? Many emulators cannot breakpoint or single step inside an ISR, forcing you to debug by guess and by golly in these, the most difficult to troubleshoot sections of your code.

Softaid's emulators support all forms of debugging in an ISR, including single stepping and breakpointing. We feel that heavy interrupt usage is a characteristic of most embedded systems, and that our users often purchase their emulators to deal with just these sorts of problems.

Though this discussion uses 80188 example code, it holds true for all of the processors Softaid supports.

Debugging interrupts can be a challenge if you are not aware of your system's behavior when stopped at a breakpoint. Consider the following timer ISR:



Most ISRs reenable interrupts just before the return, as shown in this example. Every processor defers the actual interrupt reenable until the instruction after the STI (Enable Interrupt) or IRET executes, so interrupts don't come back on until the ISR has completed and has returned to the main line code.

Code like this is easy to debug. Any Softaid emulator will merrily single step and breakpoint all day long without problem.

Suppose we modify the code a little as follows:



Now interrupts come back on long before the ISR completes. Single step through this code and you'll face an interesting problem: if the timer that invokes the code has a periodic rate of, say, 1 millisecond, when you are stopped at a breakpoint in the ISR after the STI instruction the service routine will be restarted by the timer issuing another interrupt. That is, two or more versions of the ISR may be active at a time.

Emulators single step by placing a temporary breakpoint at the next instruction that will execute, and then letting the code run at full speed. A pending interrupt will preempt the execution path and restart the ISR (if interrupts are on, of course). Thus, in this case, if the program counter was pointing to label "a" and you press STEP, the emulator will follow the pending interrupt, restart the ISR, and halt at the instruction after "a", stopping in a new incarnation of Timer_ISR. The previous one will have been suspended - the stack will have additional pushes from the routine's new invocation.

One company we work with wrote code to count timer ticks in a register as shown in the example at label "a". He simply incremented register BX each time the ISR ran. Mainline code watched BX to track time. While single stepping though the code he noticed that BX seemed to magically change after each step. The explanation is simple - while the emulator was stopped waiting for him to initiate the next single step another timer interrupt became valid. Pressing STEP allowed the program to run, but it followed the interrupting path, eventually breakpointing on the next instruction but only after finishing another interrupt.

Though the emulator was running correctly (it indeed followed the flow of the target code - the fact that the real time nature of the system had been modified by stopping at a breakpoint caused additional incarnations of the ISR to run), this can make debugging harder. Several solutions exist.

The best is to never reenable interrupts until the ISR returns. Admittedly this is not always feasible but it is the best design practice as it eliminates the possibility of non-reentrant code slipping in after the STI and causing hard-to-find bugs.

Another solution is to write your ISRs with handshaking, so only one version of each can be active at a time. We can recode the example above as follows:



The added complexity costs little in execution time or program space, yet greatly eases debugging. It's also a sensible precaution - it's awfully foolish to run multiple copies of a routine when even one is possibly not bug-free (that's why you were single stepping in the first place - right?).

Note that this entire discussion is relevant only if your interrupts can re-occur while you are stepping through a routine, and only if interrupts come back on before the ISR terminates. Write your code as shown in the first example, use a Softaid emulator, and you can ignore the rest of this paper.

Copyright 1994, Softaid, Inc.