chapter six

6 Debugging, observability, and profiling

 

This chapter covers

  • Debugging fundamentals: the four broad techniques (print logging, interactive debug, tracing, profiling), the host-to-target debug pipeline, JTAG vs. SWD, and the probes (ST-Link, MCU-Link, J-Link, CMSIS-DAP) that connect them.
  • Printing debug messages in no_std Rust: semihosting, UART, ITM, and RTT, with their blocking behavior, hardware requirements, and host-side tooling.
  • Structured logging with defmt: binary log frames, transport backends, linker setup, and decoding output through probe-rs or cargo-embed.
  • Handling panics: panic-handler crates, conditional panic behavior via Cargo features, and recovering fault information from HardFault and the configurable Cortex-M faults.
  • Interactive debugging: choosing between GDB+OpenOCD and probe-rs, core GDB command groups, OpenOCD configuration, and automating a session with .gdbinit.
  • Profiling: cycle-accurate timing with the DWT counter, a SysTick-based fallback for cores without DWT, and platform notes spanning Cortex-M and RISC-V.

6.1 Debugging fundamentals: methods, interfaces, and protocols

6.1.1 Overview of debugging techniques

6.1.2 The debugging pipeline from host to target

6.1.3 Debug interfaces: JTAG vs. SWD

6.1.4 Debug probes: hardware interfaces

6.2 Printing debug messages in embedded Rust

6.2.1 Semihosting with hprintln!()

6.2.2 UART logging with writeln!()

6.2.3 ITM logging with iprintln!()

6.2.4 RTT logging with rtt-target

6.3 Structured logging with defmt

6.3.1 A minimal example

6.3.2 Transport backends

6.3.3 Linker setup

6.3.4 Host-side tooling

6.3.5 Configuring cargo-embed with Embed.toml

6.3.6 Comparative summary

6.4 Handling panics in embedded applications

6.4.1 When and why panics occur

6.4.2 Available panic handler crates

6.4.3 Minimal handler implementation