The Debugger

Motivation

When a bug pops up in code, what is your first instinct?

  • Some choose to use print statements

  • Some choose to remove code until it works again

  • Some choose to use asserts and see where a program aborts

While these methods may be good for simple debugging, they rely on having an interface that you can communicate the data out with. In an embedded system, a simple print statement cannot be taken for granted as you may not have a UART interface. Sometimes, you just want a way to pause your code on a specific line and look at what’s happening.

 

Note: For the purposes of this guide, I [Sahil] will be using the STM32CubeIDE debugger to demonstrate the concepts. Additionally, some points are specific to using a debugger in an embedded context (such as limited breakpoints & watchpoints).

Theory of operation

The STM32 supports 2 protocols for debugging: Serial Wire Debug (SWD), and JTAG. For this guide, we will be using SWD as it is an ARM-specific protocol.

Most debuggers use GDB (the GNU debugger) under the hood. On an embedded project, you have a limited number of watchpoints and breakpoints. Be aware of this when setting them.

How to use

  1. Open up STM32CubeIDE

  2. Click on the green “bug” in the top toolbar

    Toolbar with the green bug highlighted

Your screen should now look like this:

Debug Context Screen

There are a few things to explain. Let’s break it down.

Breakpoints

‘Breaking’ is the iconic aspect of a debugger - the ability to stop at points in your code on command.

Notice the tiny dot to the left of line 37. This means the function will pause on this line if it is ever called

To set a breakpoint, simply double click in the tiny empty space immediately to the left of the line number. It should make a tiny dot.

Toolbar

Basic debugger controls

From left to right, the options are:

  • Terminate and relaunch

  • Continue (or as I call it, “Play”)

  • Pause

    • This will stop and break wherever the processor is right now

  • Stop

  • Disconnect

  • Step Into

    • This will perform the action on the line. Note that if the line involves a function call, it will go into the function call and spiral deeper, hence why it is referred to as “into”. Use this to go deeper into functions.

  • Step Over

    • This will execute the actions on the line, but it will not spiral deeper. Use this to essentially ‘run’ a line.

  • Step Return

    • This option is only available in functions. Pressing this option will mean that the debugger will execute the program normally and break where the function returns.

 

Watchpoints

Watchpoints allow you to ‘watch’ certain points in your code. Unlike breakpoints, they do not refer to physical points in your code. To add a new ‘thing’ to watch, simply click on the green plus sign at the bottom and type what you want to watch.

Watchpoint example

Notice how I said ‘thing’. The above example only shows basic integers, but you can watch whole objects, such as structs and other ‘things' that you need to keep track of.

 

Homework

This is really just showing you how to use it.

  • Make a new project using the STM32 CubeIDE. Initializes some peripherals.

  • Initialize the debugger, and try to use the step in, out, and return functions to understand their use

  • Additionally, try setting breakpoints and seeing how the debugger travels to them.