Arduino Zero

Well, you know how you can use "ISP programming" to read and write the flash memory and fuses of an AVR?
It's a short jump from that to being able to use "some protocol" to also be able to read and write RAM, and all the other internal registers of the chip. And you can sort-of picture one of those registers meaning "stop executing code and sit there while I look at you." And an instruction "breakpoint" that means "go to the 'stop executing and let the debugger look at you' state. And maybe a comparison register so that when you access certain memory locations in a certain way, it also goes to that state.

At that point, you can watch the chip execute as slowly as you might want, and keep track of everything that happens. "This number is supposed to be between 1 and 10 - why is it actually 250 ?" "Wait; why are we back at setup() instead of continuing to loop?" "Oh look! It's going to one of the null interrupt vectors that does nothing but infinite loop."

Naturally, at this level everything is in machine code, with numbers (well, bit patterns) for variable addresses and such. You need (well, "want") a piece of software that will interpret the raw numbers and translate them back into something that looks like your sketch with its meaningful function and variable names. But that just a SMOP.

Note that unlike putting Serial.print() all over your code, this sort of debugging does not consume memory of any kind, or interfere with interrupts (like trying to use print() in an ISR.) With a bit of cooperation and care, most of your program can run at full speed, or near full speed even if the debugger is sampling information every now and then.

There are a couple of common concepts:

  1. Breakpoints - give control to the debugger when the code reaches a particular point in the program.
  2. Watchpoints (aka "data breakpoints") - give control to the debugger when a particular data variable is modified. Also, show the value of these variable periodically (whenever the debugger has control of the chip, at least.)
  3. Stepping - execute one instruction, or one line, or one function of the program and go back to the debugger. (also: "finish the current subroutine.")