Step by step GDB debugging ARM processor from console in Win10

Typical situation. The logging and UART component was not initialized correctly, or the device hung somewhere in the initialization after the reset. Or the device endlessly reboots after power up. How to understand on which line the run-time error occurred? The classic solution is step-by-step debugging via JTAG or SWD.

A little about iron. I will work with this board

nRF5340-DK development board
nRF5340-DK development board

Here on the board (e) both the U2 programmer and the U1 (Target) microcontroller itself. Both 94pin (new) nrf5340 in aQFN94 package. They are connected by an SWD interface. Each has 2 Arm Cortex-M33 (Armv8-M) cores. Outside also goes 2 UART(s) for the command line interface. They are forwarded via USB (J2) to DeskTop.

What do you need from the software?

Utility

Purpose

Path

arm-none-eabi-gdb.exe

GDB Client for ARM

C:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2021.10\bin

JLinkGDBServer.exe

GDB Server

C:\Program Files (x86)\SEGGER\JLink\

nrfjprog.exe

microcontroller flash driver

C:\Program Files (x86)\Nordic Semiconductor\nrf-command-line-tools\bin

ttermpro.exe

COM port terminal

C:\Program Files (x86)\teraterm

This should be enough.

Phase 1 Upload firmware to Target

This can be done with this script

echo off
cls

set com_port_num=10
set baudrate=115200
set project_name=headset_app
set project_dir=%~dp0
set NRF_SDK_DIR=C:/ncs/v2.1.0

set SDK_PROJECT_DIR=%NRF_SDK_DIR%/nrf/applications/nrf5340_dk_audio_w
IF "%1"=="" (
    echo set dflt SDK ver 
    set ARTEFACT_HEX=%SDK_PROJECT_DIR%\build\headset_app\zephyr\zephyr.hex
) ELSE (
    echo set dflt SDK from argument 
    set ARTEFACT_HEX=%1
)
echo Artefact hex: [%ARTEFACT_HEX%]


set tools_dir=%cd%\..\..\..\tool
echo Project Dir:%project_dir%

set FlashTool="C:\Program Files (x86)\Nordic Semiconductor\nrf-command-line-tools\bin\nrfjprog.exe"

set options=--iface USB --family NRF53 --coprocessor CP_APPLICATION --program %ARTEFACT_HEX%  --log --chiperase --verify --reset 
call %FlashTool% %options%
echo tools_dir=%tools_dir%

call %tools_dir%\launch_terminal.bat 9 %baudrate% %project_name%
call %tools_dir%\launch_terminal.bat 10 %baudrate% %project_name%

After running this script, it will download the firmware to the nrf5340 microcontroller.

Phase 2 Starting the GDB Server (Back-End)

GDB server is a utility that interrogates the debugger programmer. It is she who communicates with the device. You need to start the GDB server. Here is the launch script for this utility.

 
echo off
cls
set GDBServerOpt = -select USB -device nRF5340_xxAA_APP -endian little -if SWD -speed 400 -ir -LocalhostOnly -logtofile -log "C:\projects\code_base_workspace\code_base_firmware\tool\GdbServerLog.txt"

set GDBServerDir="C:\Program Files (x86)\SEGGER\JLink\"
set GDBServerPath=%GDBServerDir%JLinkGDBServer.exe"
cd %GDBServerDir%
call %GDBServerPath% %GDBServerOpt%

We need to install the communication interface of the programmer and the microcontroller that we want to debug. In this case, it is the SWD interface.

J-link will ask you to select a microcontroller

the list of supported chips is very large

In my case, the nrf5340 chip

GDB server J-Link launched locally. Programmer (S/N: 1050009032) connected via USB. Target is connected to the programmer by SWD.

here is the full log of the waiting gdb server

hidden text

SEGGER J-Link GDB Server V7.66a GUI Version

JLinkARM.dll V7.66a (DLL compiled May 19 2022 15:13:27)

—–GDB Server start settings—–
GDBInit file: none
GDB Server Listening port: 2331
SWO raw output listening port: 2332
Terminal I/O port: 2333
Accept remote connection: localhost only
Generate logfile: off
Verify download: off
Init regs on start: off
Silent mode: off
Single run mode: off
Target connection timeout: 5000ms
——J-Link related settings——
J-Link Host interface: USB
J-Link script: none
J-Link settings file: none
——Target related settings——
Target device: Unspecified
Target interface: SWD
Target interface speed: 4000kHz
Target endian: little

Connecting to J-Link…
J-Link is connected.
Firmware: J-Link OB-nRF5340-NordicSemi compiled Dec 3 2021 15:46:49
Hardware: V1.00
S/N: 1050009032
Checking target voltage…
Target voltage: 3.30V
Listening on TCP/IP port 2331
Connecting to target…
connected to target
Waiting for GDB connection…

Now the GDB server just waits for a connection on a TCP port: 2331

On the nRF5340-DK debug board, there is built-in the J-Link programmer and the green LED is constantly on there, from the debugger.

Phase 3 Launching the GDB Client (Front-End )

If the GDB server can be metaphorically considered the Back-End (th), then the Front-End part for step-by-step debugging is the GDB client. Here is the script to run it.


"C:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2021.10\bin\arm-none-eabi-gdb.exe" --help
"C:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2021.10\bin\arm-none-eabi-gdb.exe"  C:/ncs/v2.1.0/nrf/applications/nrf5340_dk_audio_w/build/headset_app/zephyr/zephyr.elf

GBD client was able to pick up *.elf file with debug symbols

It’s time to connect to the GBD server. This is done by the team.

target remote localhost:2331

It worked

Connected. Now the execution of the firmware is paused. HeartBeat LED is not blinking, UART-Shell is not responding.

To start the firmware, you need to type the command continue

Now the HeartBeat LED is blinking. The CLI is responding. The LED on the programmer also blinks only somehow aperiodically.

In order to stop the program execution again, press Ctrl+C in the client’s GDB terminal.

Now you can set a breakpoint. I’m interested in the function bool hw_init(void)

(gdb) break hw_init

You can view the list of set breakpoints with the command

(gdb) info b

This is what is displayed now

Restarts the program for execution. Team c

(gdb) c

But here’s the problem. The breakpoint is set inside the initialization function, which has already worked out a long time ago. You need to somehow reload trarget so that the initialization works again.

This is where another terminal will help us. This is a command line terminal over UART implemented right inside the firmware on the Target (e). I will just ask the device to reboot with a command in the UART CLI

-> kernel reboot cold

and the device actually reboots

At the same time, I didn’t even take my hands off the keyboard. And here the debugger hooked the bool function hw_init(void). This is called the synergy of GDB and UART-CLI. Success.

You can execute one line of code with the command n. You can view the contents of a local variable with the command info locals

You can get inside the function with the command s.

Of course, there is a history of commands in the GBD client console and you can use the up and down arrows on the keyboard to type commands that have already been used in the past.

To exit the GDB client, type the q command in its console. Target is still suspended. Therefore, it is also necessary to close the GDB server. Only after that Target will become resumed and continue to execute its code.

Subsequently, you can start the GDB server and the GDB client with a single script.

echo off
cls

set FIRMWARE_FILE=C:/ncs/v2.1.0/nrf/applications/nrf5340_dk_audio_w/build/headset_app/zephyr/zephyr.elf
set GDB_CLIENT="C:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2021.10\bin\arm-none-eabi-gdb.exe"

set tools_dir=%cd%\..\..\..\tool
start %tools_dir%\0_LaunchGdbServerNrfAppCore.bat


%GDB_CLIENT% --help
%GDB_CLIENT% %FIRMWARE_FILE%

Summarizing the above, the whole process can be explained with this simple diagram

Also a small cheat sheet on the most common commands of the console GDB client

short

full

description

info args

Show current function arguments

ib

infob

List all breakpoints

bt

backtrace

show function call stack

n

next

Step over functions

f

finish

Execute the rest of the current function. Step out of the current function.

s

step

Step into functions

p count

Print the value of a variable count

c

continue

Continue execution up to the next breakpoint or until termination if no breakpoints are encountered

deleten

Delete breakpoint number n

info locals

show local variables

target remote localhost:2331

Connect to J-Link

q

quit

quit gdb

b function

Set a breakpoint at the beginning of function

I will also update the GDB client command registry here
https://docs.google.com/spreadsheets/d/1AWD8GsDfaA9dtdsfqgbB1klagou1yrREc1AAK9CRUik/edit#gid=0

Conclusion

As you can see, debugging via the console in GDB is not difficult at all. Therefore, all sorts of cyclopean and expensive IDEs for step-by-step debugging are not really needed, as it were. The console is even better in the sense that attention is focused on the essence (code), and not on the straziks from the design of the IDE. You can generally debug without code, simply by receiving only *.elf file by mail.

At the same time, the overhead costs for installing the Toolchain (a) for step-by-step debugging are minimal and everything is absolutely free.

Links

https://condor.depaul.edu/glancast/373class/docs/gdb.html#Setting_Breakpoints

https://habr.com/ru/post/181738/
https://habr.com/ru/post/535960/
https://habr.com/ru/post/546216/
https://mcuoneclipse.com/2015/03/25/command-line-programming-and-debugging-with-gdb/
https://www.electricmonk.nl/docs/gdb_debugging/gdb_debugging.html

Question *

How does the JTAG breakpoint setting mechanism work under the hood?