So we understand now what we don’t know (see The Stack … so misunderstood, part 1). We know how to set a minimum limit for the Stack size. We know how to verify how much space the linker has allocated for a project. We DON’T know how much Stack space our project is actually using!
If we have the luxury of being able to simulate the entire application in a complete and realistic scenario, nothing beats the power of MPLAB SIM. We can open the Watch window and keep an eye on WRGE15 and SPLIM. Opening the Stack window we can watch the stack grow and see each function parameters as they are passed along.
But as the complexity of a project grows, full simulations became less likely… and we want to find a way to identify Stack issues at run time when debugging in circuit.
Quite empirically, if a program is not crashing (as in experiencing inexplicable resets) chances are we have not reached the stack limit (yet). But what if we do experience those resets… in a complex application things can go wrong in many ways … it would be nice to be able to pin-point the issue and make a clear determination when we fail because of the stack.
Here is an example project with a (perverse) recursive function (call()) that will exercise the stack available for as many nested calls as the parameter passed: stack.c
Make the parameter large enough or allocate a large enough Heap (8,000?) and sooner rather than later it will produce a Stack Error trap.
By replacing the default trap handler for the _StackError vector, we could perhaps place a function (with an infinite loop) in there to capture and retain control of the application when things go wrong. Placing a breakpoint inside our custom handler we would be immediately alerted as soon as our program reaches the top of the stack space. Here is an example: stack2.c
Unfortunately the new handler, as any C function, is going to need to use the stack itself. Even if we declare it with the no_auto_psv attribute (to avoid saving and restoring the PSV registers values) and even if we don’t use any local variable.
It seems like a catch 22 condition. We cannot write a handler for a Stack Error condition in C because by definition each C function uses at least a small amount of Stack space.
We might be tempted to artificially lower the stack limit (SPLIM) to allow for some extra room for the handler function so to “break the loop”. But this is just an illusion… We cannot know before hand by how much the stack will be exceeded when the trap occurs. In other words, think of a case where the stack is almost all used up, the stack pointer is just one word below the limit and then a function call is made to a routine that allocates 100 bytes for local variables… To get away with it, we should really lower the stack limit for the worst possible case (the function that allocates the largest amount of stack space) but this is exactly the type of complex and manual analysis of a project we were determined NOT to have to go through in the first place (besides a possible big waste of space).
Fortunately to our rescue comes one powerful option of the MPLAB C30 compiler, that allows us to insert custom code before the prologue of an interrupt handler, that is before the stack is touched! It is called the interrupt preprologue. Here is an example of how we would use it, combined with one instruction of inline assembly to achieve a blocking interrupt handler that does not rely on the stack: stack3.c
void __attribute__((no_auto_psv, interrupt( preprologue(“1: bra 1b”))))
where the “1: bra 1b” is a single instruction infinite loop that we insert before the handler prologue.
But by the time we do this and we try to set a breakpoint on the line, we realize that we have gone full circle. This is not C code anymore… and to set the breakpoint we need to switch to the disassembly window…
If we surrender to the evidence, we get a cleaner, less cryptic and overall more elegant solution by using the original code stack.c and including in our project a simple (three lines long) assembly source code:
(Note the double underscore before the label StackError)
Save it as stackhandler.s and add it to the project.
Now we can set a breakpoint inside the new handler and we can have our real time debugger to halt and notify us whenever a Stack Error trap is triggered at run time!
Perhaps we learned a good lesson today. Certain things are just meant to be done in assembly! …although there are not that many left anymore!