C++ firmware on small embedded systems - Part 2: GDB basics


Introduction

Let’s set up the J-Link GDB Server and GNU ARM Debugger in order to take a complete control of the our program execution flow. In the previous post, we described the installation process of the necessary things to make this possible.

Setting-up

Our hardware is SiLabs EFM32PG22 microcontroller and Linux PC with Kubuntu 22.04.3 LTS.

First step is to start J-Link GDB Server in order to be able to communicate with the on-chip debugger on the SiLabs board. It will be necessary to specify proper flags to the GDB Server.

JLinkGDBServer -if SWD -device EFM32PG22CxxxF512 -endian little -speed 1000 -port 2331 -swoport 2332 -telnetport 2333 -ir -localhostonly 1 -singlerun -strict -timeout 0 -nogui

If successful, the command should print something like Waiting for GDB connection…

Next, in a separate terminal, run the following command to start the GDB debugger and load the program:

arm-none-eabi-gdb switch_led_interrupt3_1.out

Commands

After starting the J-Link GDB Server with the previous command, it starts to listen for incoming connections from a GDB client on port 2331 (reason why we specified -port 2331 flag). To establish a connection from GDB to the J-Link GDB Server, this command is used:

(gdb) target remote localhost:2331

Next, we will flash the program.

(gdb) load switch_led_interrupt3_1.out

Now, we will reset our device to ensure it starts from a known, clean state and after that we will halts the execution of the target device in order to start from the first instruction of our program.

(gdb) monitor reset
(gdb) monitor halt

Now we will divide our terminal in 3 panes: source code, disassembly and GDB command input/output.

(gdb) layout split

Since these commands are useful and frequently used, it would be a good idea to execute them all together. We can create a .txt file containing all the commands and then execute them with a single command:

(gdb) source commands.txt

You can set a breakpoint using either a function name or a specific address.

(gdb) break GPIO_ODD_IRQHandler
(gdb) break *0xf14

Alternatively, you can use the letter b instead of the full word break. Breakpoints can be listed with this command:

(gdb) info breakpoints

It will display all breakpoints along with their numbers, allowing you to remove a breakpoint by specifying its number:

(gdb) delete 1

Lets talk about steping our program. There are few options. The si command stands for step instruction. It allows to execute a single machine level instruction in the program.

(gdb) si

The s command stands for step, and it is used to step through the program at the source code level, not machine instructions.

(gdb) s

The c command stands for continue. It resumes the program’s execution until the next breakpoint or the program terminates.

(gdb) c

The values on certain addresses (for example 0x40008064) can be printed with the command:

(gdb) p *((volatile uint32_t*) 0x40008064)

Also, regitsers values can be showed, for example r1 here:

(gdb) info register r1

You can navigate through the command history using Ctrl+p or Ctrl+n. At the end, to exit debugging mode, use the following intuitive command:

(gdb) exit

Sources

  • Silicon Labs, EFM32PG22 Gecko MCU Family Data Sheet (link)