Hello! Welcome to the first lesson in our module on "Advanced Debugging and System Profiling."
In our last lesson, we designed a complex inter-processor communication system for an AMP architecture. While a solid design is the first step, the reality of embedded development is that even the best designs have bugs. When you're working on a bare-metal target without an operating system's conveniences, and printf is either too slow, unavailable, or alters system timing too much, you need a more powerful approach. This is where on-chip debugging becomes an indispensable skill.
This lesson introduces the foundational tool for deep, on-target analysis: the JTAG/SWD debugger. Mastering this tool is non-negotiable for anyone working at the hardware/software interface, and fluency with it is a key competency tested in interviews at top semiconductor companies.
Your learning outcome for this lesson is to: Use a JTAG/SWD debugger to set hardware/software breakpoints, inspect memory and CPU registers, and step through code on-target.
We'll dissect the entire debugging toolchain, from the software on your PC to the silicon on the target, and then walk through the essential commands for controlling and inspecting your embedded application.
1. The Debugging Ecosystem: From Host to Target
Before typing any commands, it's crucial to understand the components that make on-chip debugging possible. The system is a chain of hardware and software working in concert.
The complete toolchain typically looks like this:
- Host PC: Runs your development environment. This is where the GDB client runs.
- GDB Client: This is the program you interact with (e.g.,
arm-none-eabi-gdbor the debugger integrated into your IDE). It understands your source code, symbol information from the ELF file, and provides the command interface for debugging. - GDB Server: A program like OpenOCD (Open On-Chip Debugger) or SEGGER J-Link GDB Server. It acts as a translator, converting GDB's high-level commands (e.g., "set a breakpoint at
main") into low-level commands specific to the debug probe. - Debug Probe: The physical hardware (like an ST-Link or J-Link) that connects your PC's USB port to the target board. It translates the GDB server's commands into electrical signals for the JTAG or SWD interface.
- Target MCU: The microcontroller being debugged. It contains a dedicated On-Chip Debug (OCD) module (part of the ARM CoreSight architecture, as shown above) that listens for JTAG/SWD signals and can halt the CPU, access registers, and read/write memory.
To see this entire system explained, please start with the following video.
How to debug an embedded system - Part I
The video 'How to debug an embedded system - Part I' provides a great overview of on-chip debugging, the hardware involved, and the role of the debug probe and software stack.
Please watch the following segments: On-Chip Debuggers (06:34 - 07:54): Understand how the debug module is part of the microcontroller silicon and its ability to control the CPU. JTAG vs. SWD (08:07 - 10:00): Learn about the two primary physical interfaces, their pin requirements (JTAG: 4-5 pins, SWD: 2 pins), and their history. Debug Probe and Software (10:00 - 11:01): See how the debug probe connects to the PC and how software like OpenOCD and GDB fit together.
The article "Debugging with JTAG" also provides an excellent textual summary and diagram of this client-server architecture.
This article, 'Debugging with JTAG' from Actuated Robots, reinforces the communication flow between the GDB client, server, and debug probe.
Read the section 'From GDB via STLINK to TAP (Test Access Port)' and 'Tools for debugging discovery board'. Focus on the block diagram showing the relationship between the GDB Client, GDB Server, Debug probe, and microcontroller. This model is fundamental.
2. The GDB Session: A Practical Walkthrough
While IDEs provide a graphical interface for debugging, they are fundamentally just front-ends for GDB. Understanding the underlying GDB commands is a powerful skill, especially for automation, scripting, and situations where you only have command-line access.
The following video demonstrates setting up a command-line session using OpenOCD and GDB.
This Is 100% How You Should Be Debugging | How to Use OpenOCD to Debug Embedded Software with GDB
The video 'How to Use OpenOCD to Debug Embedded Software with GDB' by Low Level walks through the practical steps of starting a debug session from the terminal.
Watch the following segments to see the workflow in action: Configuring and Running OpenOCD (00:53 - 03:45): Observe how OpenOCD is launched with configuration files for the interface (the probe) and the target (the MCU). Note that it opens a TCP port (usually 3333) for GDB to connect to. Connecting GDB (03:45 - 05:05): See how gdb-multiarch is launched with the ELF file and then connected to the running OpenOCD server using the target extended-remote command.
With the session established, let's explore the core commands you'll use to debug your program.
Controlling Execution
Your primary tools for controlling the program flow are breakpoints and stepping commands.
Breakpoints vs. Watchpoints
- Breakpoint: Halts execution when the program counter (PC) reaches a specific address.
- Hardware Breakpoints: Implemented in dedicated hardware comparators inside the CPU. They are very fast and can be placed anywhere, including in read-only memory (ROM). Their number is limited (e.g., 4-8 on many Cortex-M cores).
- Software Breakpoints: Implemented by the debugger by replacing the instruction at the target address with a special
BKPTinstruction. When the CPU executesBKPT, it halts and enters debug mode. This requires the code to be in writable memory (RAM or Flash) and has a slight performance overhead. Debug probes like J-Link can manage "unlimited" software breakpoints by smartly swapping them in and out of flash memory.
- Watchpoint (or Data Breakpoint): Halts execution when a specific memory location is read or written. These are incredibly powerful for tracking down memory corruption bugs. They rely on hardware support and are usually very limited in number (e.g., 2-4).
Core GDB Commands:
The presentation slide below from Linaro provides a concise summary of essential GDB commands. We will refer to these as we proceed.
[PDF] Essential ARM Cortex-M Debugging with GDB
The 'Essential ARM Cortex-M Debugging with GDB' presentation provides an excellent, compact reference for common GDB commands. We'll use it as a guide.
Quickly review the slides titled 'Basic Navigation', 'Breakpoints (fast!)', and 'Watchpoints (Powerful, but sloooow)'. Don't worry about memorizing them; just get familiar with the command names.
-
Setting Breakpoints:
break mainorb main: Set a breakpoint at the entry of themainfunction.b my_file.c:42: Set a breakpoint at line 42 of a specific file.b my_function if x == 10: Set a conditional breakpoint that only triggers if the condition is true.watch my_variable: Set a watchpoint that triggers whenevermy_variableis written to.info breakpoints/info watchpoints: List all active breakpoints and watchpoints.
-
Running and Stepping:
continueorc: Resume execution until a breakpoint/watchpoint is hit or the program ends.nextorn: Execute the current line and move to the next line, stepping over function calls.stepors: Execute the current line, stepping into any function calls.finish: Continue execution until the current function returns.Ctrl+C: Halt program execution immediately.
Inspecting State
Once the program is halted, you can inspect its complete state.
-
Inspecting CPU Registers: This is critical for low-level debugging.
info registersori r: Display the values of all core registers (e.g.,r0-r12,sp,lr,pc,xpsr).p/x $pc: Print the value of a specific register (in this case, the program counter in hex).
-
Inspecting Memory and Variables:
print my_variableorp my_variable: Print the current value of a variable. You can use formatting, e.g.,p/x my_var(hex) orp/t my_var(binary).info locals/info args: Display the values of local variables and function arguments in the current stack frame.x/<n><f><u> <address>: The "examine" command is a power tool for viewing raw memory.n: Number of units to display.f: Format (xfor hex,dfor decimal,ifor instruction,cfor char).u: Unit size (bfor byte,hfor half-word (16-bit),wfor word (32-bit)).- Example:
x/32xw 0x20000000means "examine 32 words (w) in hexadecimal (x) starting at address 0x20000000".
3. Debugging Demonstration
Let's see these commands used in a practical context. The following video demonstrates a full debugging session in an IDE, which graphically exposes all the features we've just discussed with GDB commands.
How to debug an embedded system - Part I
Returning to 'How to debug an embedded system - Part I', let's watch the hands-on demonstration. Try to map the graphical actions to the GDB commands we've learned.
Watch the full demonstration carefully: Basic Debugging (13:06 - 18:04): Observe the use of breakpoints, viewing local/global variables (like p my_variable), inspecting CPU registers (i r), viewing peripheral registers (x at a specific address), and stepping through code (n and s). Advanced Breakpoints (18:04 - 22:27): This section shows how to set conditional breakpoints (b ... if ...) and data watchpoints (watch). The 'dynamic printf' feature is a probe-specific enhancement but achieves a similar goal to a non-halting conditional breakpoint.
Conclusion
In this lesson, we established the fundamental theory and practice of on-chip debugging. This is the bedrock upon which more advanced debugging techniques are built. Being able to confidently navigate a program's execution and inspect its state on-target is a mark of a senior embedded engineer.
Key Takeaways:
- The Debugging Stack: Debugging involves a client-server architecture: GDB Client ↔ GDB Server (OpenOCD) ↔ Debug Probe ↔ Target MCU.
- Physical Interfaces: JTAG and SWD are the primary electrical interfaces for connecting a debug probe to an ARM core's on-chip debug hardware.
- Execution Control: You can control program flow using breakpoints (halting on instruction execution) and watchpoints (halting on data access). You navigate code with commands like
continue,next,step, andfinish. - State Inspection: When halted, you have full visibility into the system state via commands like
info registersto see CPU registers,printto check variables, andexamine(x) to inspect raw memory.
Preview of the Next Lesson:
Now that you're equipped with the tools to halt a program and inspect its state, we can tackle a classic and critical interview topic: debugging a system crash. In the next lesson, we will focus on this outcome: Perform a post-mortem hard fault analysis by reading the stacked exception frame (LR, PC, PSR) to find the faulting instruction. We will use the GDB skills learned today to decode the processor state immediately following a crash to pinpoint its root cause.