Reading MCU variables without touching your firmware
How ELF/DWARF symbol information plus SWD memory access make it possible to observe live variables in a running target without adding a single line of instrumentation.
Most embedded debugging still comes down to the same trade-off: to see what a
variable is doing over time, you have to change the firmware that owns it. You
add a printf, you push it out over UART, and now the thing you are measuring
is not quite the thing that ships.
SWD Tracer takes a different route. The information needed to find a variable in memory is already sitting in the build artifacts, and the SWD port can read that memory while the core keeps running.
The ELF file already knows where everything is
When your toolchain compiles firmware, it emits an ELF binary containing DWARF debug information. That data describes every global and static object in the program:
- its name, as written in the source
- its address in the target’s address space
- its type, size and byte layout
- the source file and line where it was declared
This is the same information a debugger uses when you hover a variable and see its value. Nothing about it is exotic — it just usually goes unused outside a breakpoint-driven workflow.
// Nothing here needs to change to trace `motor_current`.
static float motor_current;
void control_loop(void) {
motor_current = read_current_sensor();
update_pid(motor_current);
}
SWD reads memory while the core runs
The Serial Wire Debug port on an ARM Cortex-M target exposes an access port to the system bus. Reads through it do not require halting the core: the debug hardware arbitrates bus access independently of instruction execution.
So the loop is simple:
- Parse the ELF to resolve
motor_currentto an address and a type. - Read those bytes over SWD on a fixed interval.
- Decode the bytes according to the DWARF type and plot the result.
The firmware never learns it is being watched. No hooks, no ring buffer, no serial port budget.
What this changes in practice
The interesting part is not any single read — it is that observation becomes free of the edit-compile-flash cycle. You can decide after flashing that a variable is worth watching, and you can watch it for an hour without the overhead a logging path would add to a tight control loop.
Timing stays honest: a 1 kHz control loop keeps running at 1 kHz while you watch its internals.
Where the limits are
Sampling over SWD is polled, not synchronous with your code. You observe values at the sampling interval, not at every write, so this is not a substitute for a trace buffer when you need every transition of a fast signal. What it is very good at is the long-running, “what is this system actually doing” question that UART logging answers badly and breakpoints cannot answer at all.