Debugging the SAMD21 with GDB

The SAMD21 is a microcontroller developed by Atmel which runs at 48mhz with a Cortex M0+ core. It’s used in the Arduino Zero, Arduino M0 Pro and Feather M0s. Many of our Arduino and MicroPython libraries use C to interface directly with the hardware and when things go wrong it can be hard to figure out why.

Unlike more common web or server software development the code isn’t being run on the same machine as its written on. When code is compiled for a different architecture, such as ARM for the SAMD21, than the architecture the compiler is running on, usually x86, its called cross compiling. There are many different ways to organize how your code compiles we can cover in another guide. What you need to know is that there are a few different toolchains that convert human readable code to machine code. Today we’ll focus on the GNU toolchain that is made up of the GNU Compiler Collection (better known as gcc), the GNU Debugger (gdb) and number of other programs. Since the GNU toolchain is open source, ARM, the designer of the Cortex M0+ core, is able to ensure it produces well working code for their cores. So, we’ll be using the ARM version of the GNU toolchain to compile and debug the C code.

While I won’t cover compiling the code, I’ll be using the Adafruit version of MicroPython in the examples. Its source is here and it uses make to run the compiler. I also use an Arduino Zero for all of my debugging because it has a builtin debug chip which converts USB commands to commands the Cortex M0+ core understands.

There are a couple aspects of debugging we’ll talk about. First we’ll cover a few ways to stop the program at an interesting spot. Second, we’ll cover how to inspect the current program state using backtrace. Lastly, using the Micro Trace Buffer, we’ll inspect the history of the program execution. Lets get setup.

Software Installation

There are two pieces of software we need to install in order to get debugging. OpenOCD is a tool to communicate with debug hardware tools such as the EDBG chip on the Arduino Zero or a JLink. GDB is the GNU Debugger which talks with OpenOCD to control and inspect the raw state of the microcontroller and, using the binary symbols, translate that info back into the source code realm.

OpenOCD

You can get OpenOCD from here. Below are quick start instructions.

Windows

Download and install the latest binary from here.

Mac OSX

Installation on Mac OSX is easiest using Homebrew. (If you don’t have brew installed see here.)

brew install open-ocd

Linux

Its best to use your package manager to install openocd. Exactly how you do that will vary with your distribution. Here are some examples.

# Ubuntu, Debian, Raspbian, Mint
sudo apt-get install openocd # http://packages.ubuntu.com/search?keywords=openocd&searchon=names
# Fedora
su -c 'yum install openocd' # https://apps.fedoraproject.org/packages/openocd
# Arch
pacman -S openocd # https://www.archlinux.org/packages/community/x86_64/openocd/

GDB

GDB is part of the larger ARM toolchain. So don’t be surprised to see a number of arm-none-eabi-* binaries installed.

Windows

Download install the win32 executable from here.

Also make sure that you have 32-bit (x86 not x86-64) Python 2.7x installed from here. We’ll be using Python to interpret the Micro Trace Buffer later.

Mac OSX

Installation on Mac OSX is easiest using Homebrew. (If you don’t have brew installed see here.)

brew install arm-none-eabi-gcc

Linux

Its best to use your package manager to install the arm gcc toolchain. Exactly how you do that will vary with your distribution. Here are some examples.

# Ubuntu, Debian, Raspbian, Mint
sudo apt-get install gdb-arm-none-eabi
# Fedora
su -c 'yum install arm-none-eabi-gdb' # https://apps.fedoraproject.org/packages/arm-none-eabi-gdb
# Arch
pacman -S arm-none-eabi-gdb # https://www.archlinux.org/packages/community/i686/arm-none-eabi-gdb/

Setup

This setup is for the Arduino Zero because it has an easy, on-board debug chip. Debugging other boards including Feathers through J-Link is a slightly different process not covered here.

Before we can get into the nitty gritty of debugging we need to first get everything running.

OpenOCD

First, we need to get OpenOCD going to bridge from our computer to the hardware debugger. Its easy with the Arduino Zero.

Arduino Zero

Connect a USB cable from your computer to the DEBUG USB connector on the Arduino Zero. Now, make sure you have the Arduino Zero config file for OpenOCD available here.

Now run OpenOCD in a terminal. It will stay running while we debug.

openocd -f arduino_zero.cfg

You should see that it found the Arduino Zero with output similar to this:

Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : CMSIS-DAP: FW Version = 02.01.0157
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 1 TDO = 1 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : clock speed 500 kHz
Info : SWD IDCODE 0x0bc11477
Info : at91samd21g18.cpu: hardware has 4 breakpoints, 2 watchpoints

GDB

GDB is similarly straightforward. The most important thing is that your current directory is near your binary. With Adafruit’s MicroPython I like to be in the atmel-samd directory where our binary is build-arduino_zero/firmware.elf. (If you are following along with MicroPython you can compile it with make BOARD=arduino_zero DEBUG=1.)

arm-none-eabi-gdb-py build-arduino_zero/firmware.elf

Now you should see some version information and a prompt that start with (gdb). All examples that start with (gdb) should be run in gdb and you do not need to type (gdb) in.

Now we need to tell GDB to debug through OpenOCD rather than on this computer.

(gdb) target extended-remote :3333

Loading, Resetting and Running

Loading, resetting and running the currently running program on the microcontroller is critical to the debugging process. To load a new version of the program after you’ve compiled outside of gdb do:

(gdb) load
Loading section .text, size 0x2bb84 lma 0x0
Loading section .data, size 0x5a4 lma 0x2bb84
Start address 0x0, load size 180520
Transfer rate: 5 KB/sec, 13886 bytes/write.

To reset the microcontroller to the start of the new program you need to ask OpenOCD via monitor to reset to the initialization state.

(gdb) monitor reset init
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x00018dd0 msp: 0x20008000

Finally, to make the program run type continue or c and hit enter. The prompt won’t return until your program finishes, hits a breakpoint or you type ctrl-c.

Breakpoints

Its great if your program runs as expected but what if it doesn’t? It may crash or return an error when you least expect. Breakpoints are a great way to stop the execution of your code so you can inspect how you got a certain place in your code and with what state. There are two primary ways I use breakpoints:

  1. to stop when a function is called.
  2. to stop at a particular line of code.

Once stopped, we can use backtrace and the Micro Trace Buffer to see our current state and how we got there.