Skip to main content

Debugging and Testing

On-target debugging, fault forensics, instrumentation that does not change the bug, and testing firmware off the hardware.

šŸ“„ļøSWD, JTAG, and GDB

The thing that surprises people about on-chip debugging is how little of it is software. GDB does not single-step your firmware; the processor single-steps it, because Armv7-M defines a halting-debug mode with its own registers, comparator hardware for breakpoints, and comparator hardware for watchpoints. GDB is a client. OpenOCD is a translator. The debug capability is silicon that was on the die before you wrote anything, and it is finite in a way that software breakpoints on a hosted system are not.

šŸ“„ļøPostmortem Debugging

Every technique in HardFault Forensics assumes something that is only true on your bench: a debugger attached at the moment of the fault, watching CFSR before anything clears it, holding the stack frame before the stack is reused. A device in the field has none of that. It faults, and unless you decided in advance what to do about it, the only evidence is a b . loop nobody is watching, or a reset that erases everything and starts the firmware running again as if nothing happened.

šŸ“„ļøThe Oscilloscope for Firmware Engineers

A logic analyzer and an oscilloscope look like they answer the same question — "what happened on this wire" — and firmware engineers who own one tend to reach for it for everything, because a protocol decode is easier to read than a wobbly trace. They are not the same instrument. A logic analyzer's front end does one thing: at every sample instant, compare the voltage to a threshold and emit a 0 or a 1. Everything about how the signal got to that voltage — how fast, how far past it, whether it wobbled on the way — is discarded before the decoder ever sees a bit. An oscilloscope keeps that information. It is not a better logic analyzer; it answers a different class of question, and it is the only instrument in this folder that can.

šŸ“„ļøInstruction and Event Tracing

There is a category of bug that a breakpoint destroys by looking at it, and a log line destroys almost as thoroughly. A race between two interrupts, an occasional priority inversion, a state machine that takes a wrong branch once in ten thousand iterations — halting the core to inspect any of these changes exactly the timing that produced them, and a printf line inserted to catch them costs enough cycles to close the window it was supposed to observe. The Debug Toolbox's perturbation table already ranks every instrument in this folder by how much it disturbs the thing it is measuring; trace is the answer at the bottom of that table, the one built specifically to be close to zero.

šŸ“„ļøUnit Testing Firmware

The ordinary embedded development loop is: change a line, build, flash, power-cycle or reset, and watch a UART or an LED to see whether the change did what you meant. On a fast board with a fast probe that loop is a few seconds; on a slow one, or one shared with a bench full of other work, it is tens of seconds, and every one of those seconds is spent re-verifying code paths that had nothing to do with the line you just changed. A state machine with a dozen transitions and a handful of edge cases does not get thoroughly exercised at that cadence — it gets exercised until the one case someone thought to try passes, and the rest ride along untested until a customer finds them.

šŸ“„ļøMocking Hardware

Unit Testing Firmware drew a clean line around the logic that needs nothing from the hardware at all — a parser, a state machine, a checksum — and pointed at this page for the harder case: logic whose whole job is to talk to a peripheral. A driver cannot be tested by giving it inputs and checking outputs the way a parser can, because its inputs and outputs are register writes and register reads, and there is no register to read from on a laptop. Testing it on the host requires something on the other end of the bus that behaves like the real device without being it.

šŸ“„ļøStatic Analysis and Sanitizers

There is a tool that finds real defects, requires no new install, no configuration file, and no CI integration beyond a flag already accepted by the compiler you are already invoking on every build — and most projects run it in a mode that throws its findings away. That tool is the compiler itself. arm-none-eabi-gcc already builds a fully type-checked internal representation of your program to generate code from; a warning is analysis it already did, offered back to you, and the default build configuration in a great many embedded projects is -Wall at best, or nothing at all, which means the cheapest static analysis available is routinely left switched off.

šŸ“„ļøSimulation and Emulation

Every technique in the rest of this folder needs a board on the desk, or at minimum a physical binary running somewhere real. Unit Testing Firmware and Mocking Hardware sidestep that by pulling logic out from behind a seam and running it on the host — a deliberate, narrow substitute for one driver's register interface. Simulation is a different move entirely: instead of extracting the logic, it models the chip, and runs the actual, unmodified cross-compiled firmware.elf against that model — the same instruction stream, the same startup code, the same register pokes, executing against a virtual STM32 instead of a real one.