Embedded Systems · Embedded Tooling

How a Probe Reads Memory Without the CPU: Debug Ports and Access Ports

The transaction sequence a debug probe uses over SWD or JTAG to halt a core and touch memory, and why some reads return stale data even though the transfer succeeded.

Reading a variable with a debugger looks like reading memory, and on a host that is what it is: ptrace hands back bytes. On a target, the core may be running at full speed, may be asleep, or may be in a fault handler, and none of those states allow an external device to simply read the bus. The mechanism that makes it possible is a separate hardware path into the chip, with its own protocol, its own address space, and its own failure modes that have nothing to do with the program under test.

Two transports, one model

JTAG is defined by IEEE 1149.1 as a boundary-scan standard for testing solder joints. It defines a five-signal interface — clock, mode select, data in, data out, and reset — and a state machine driven by the mode select line that sequences the test logic through capture and shift phases. Debugging over JTAG is a use the standard did not originally define; the debug logic sits on the scan chain as another device and is accessed by shifting instruction and data registers.

SWD is a two-wire protocol, clock and a bidirectional data line, defined by Arm alongside the debug architecture. It carries packets rather than shifting a chain: an eight-bit request holding a start bit, a bit that selects the debug port or an access port, a read/write bit, two address bits naming a register, a parity bit, a stop bit and a park bit; then a three-bit acknowledge; then data with its own parity bit. Because it is packet-oriented, the probe knows whether a transfer succeeded and does not need to know the topology of a scan chain.

The important difference for tooling is that SWD has no notion of multiple devices in series. A JTAG chain with several parts on it requires the probe to be told the instruction register length of each, which is why a JTAG connection to an unfamiliar board often fails at the identification step with a plausible-looking but wrong IDCODE: the probe is reading bits shifted through the wrong device. SWD avoids the problem and uses two pins, which is why it displaced JTAG on small packages.

The access port hierarchy

Both transports reach the same internal structure. The debug port is the transport endpoint: it holds identification, control and status, and a selector register that chooses which access port subsequent transactions target. An access port is an adapter that converts a debug transaction into a bus transaction. A memory access port appears as a small register window — control/status, transfer address, and data read/write — through which an arbitrary 32-bit address is reached.

A memory read is therefore not one transaction but several. The probe writes the transfer address register with the target address, sets the size and increment behaviour in the control register, then reads the data register. Reads are posted: the data register read returns the result of the previous transfer rather than of the one just requested, because the bus access is pipelined. The first read after an address write primes the pipeline and the second returns the value that the first requested, so a tool that treats each data register read as the answer to the address it just wrote displays every word one transfer behind its label. The correction is either one extra data register read at the end of a block, or a read of the debug port’s read buffer register, which returns the value of the last completed access port transfer and forces any outstanding one to finish.

For a block transfer, the address register is set once and the control register enables address increment, after which successive data register accesses walk through memory. This is how a probe uploads a flash algorithm: a few hundred bytes written by repeated data register accesses, each a handful of clocked packets rather than a full address setup.

Halting the core

A running core cannot be halted by a bus transaction; the request has to go to the core’s own debug logic. On the Cortex-M profile this is a register in the system control space holding a halt bit and a status field, and it is write-protected by a 16-bit key in the upper half of the word, 0xA05F, which must be written in the same access. A write without the key is acknowledged on the bus and ignored by the core, which is worse than a rejection: the tool sees a successful transfer and no change in state.

The status field reports whether the core is halted and whether a debug event is pending. The reason for the halt lives in a separate fault status register, which records whether the stop came from a breakpoint, a watchpoint, a vector catch, an external request, or a debug-step. That distinction is what allows a debugger to decide whether stopping is expected. Vector catch is the mechanism that stops the core before a handler executes. A debug exception and monitor control register holds one enable bit per fault class plus one for reset, so a tool can stop on a hard fault, a bus fault or a reset without an address comparator and without spending any of the breakpoint budget. Catching one particular interrupt is a separate arrangement: the vector fetch is a data read, so a watchpoint comparator pointed at that interrupt’s slot in the vector table stops the core as the handler address is loaded.

While halted, the core stops retiring instructions but its clock may keep running, and the bus remains accessible through the access port. This is what makes memory-mapped peripheral inspection work: the probe reads a peripheral register through the same bus the core would have used, so the value is the real one. It also means the read has side effects. Reading a status register that clears on read clears it. Reading a data register of a FIFO pops an entry, and a debugger that polls that address in a live watch window drains the FIFO while the program runs, which is a failure that appears as dropped bytes and is blamed on the peripheral driver.

Access while the core runs

Bus access does not require a halt on most implementations, so a debugger can read memory and set breakpoints on a running target. Three problems follow.

The first is coherency with the core’s own caches and write buffers. A read through the debug port bypasses the data cache on cores that have one, so it may return a stale value that the core wrote but has not yet evicted, or a stale value that the core has cached but never wrote back. The mitigation is to make the read non-cacheable where the architecture provides a way, or to accept that the value is a snapshot of memory rather than of the program’s view.

The second is that a multi-word structure read as separate transactions is not atomic with respect to the running core. A linked list traversed while the program modifies it yields a mixed state, and a debugger that follows a next pointer to an object that has just been freed will read unmapped or reused memory. This is the ordinary explanation for a crash inside a debugger’s variable printer on a target that is behaving correctly.

The third is power management. A part that enters a deep sleep mode may gate the clock to the debug logic, and the connection drops. Recovery usually requires a connect-under-reset sequence: the probe holds the reset line asserted, attaches to the debug port while the core is held, and only then releases reset, so the debug logic is configured before any application code runs. That sequence is also the only way to attach to a target whose startup code disables the debug port or reconfigures its pins for another function.

Error reporting and the sticky bits

The protocol distinguishes three responses to a request: acknowledged, wait, and fault. A wait response means the target needs more time — typically because a previous transfer is still completing or because the bus is clocked slowly — and the probe retries. A fault response means the transfer failed, and the reason is recorded in a sticky error field in the access port’s control/status register.

Sticky means the bits stay set until written as one to clear. A probe that does not clear them accumulates a history of errors, and a single early failure makes every subsequent transfer report a fault. Recovering requires clearing the field and, for some error classes, reinitialising the access port. Tools that hide the distinction report a generic inability to access the target, which is why a wrong address, a disabled clock, and a bus held in reset all look identical from the debugger’s interface.

One error class deserves separate mention: accessing a peripheral whose clock is disabled returns a fault on the bus, not zeros. The core would fault the same way, and the peripheral’s registers read as an error rather than as a defined value. A debugger showing 0x00000000 for a register in an unclocked peripheral is showing the tool’s interpretation, not the hardware’s answer.

Where the chain continues

Everything above describes how a probe reaches an address. What it does with the address — stopping the program there, watching for a write, sampling the program counter, streaming a trace of executed instructions — is a separate layer, built on the same access ports but implemented by different hardware blocks. The stopping case is covered in hardware breakpoints and their limits, and the streaming case in instruction trace and the trace funnel.

References

  1. CoreSight Architecture Specification — Arm Ltd, IHI 0029
  2. Arm Debug Interface Architecture Specification (ADIv5) — Arm Ltd, IHI 0031
  3. IEEE 1149.1-2013: Standard for Test Access Port and Boundary-Scan Architecture — IEEE
  4. ARMv7-M Architecture Reference Manual — Arm Ltd, DDI 0403