Типичная ситуация. Компонент логирования и UART(Universal Asynchronous Receiver/Transmitter) не проинициализировался корректно или устройство где-то зависло в инициализации после reset. Или устройство бесконечно перезагружается после подачи питания. Как же понять на какой строчке возникла run-time ошибка? Классическое решение это пошаговая отладка через JTAG(Joint Test Action Group) или SWD(Serial Wire Debug).
Немного про железо. Работать буду с платой nRF5340-DK. Её блок-схема.
![Отладочная плата nRF5340-DK Отладочная плата nRF5340-DK](https://habrastorage.org/getpro/habr/upload_files/036/993/ec1/036993ec1c38dded9e26eff4890a80e2.png)
Тут на board(e) как программатор U2 так и сам микроконтроллер U1 (Target). Оба 94pin(новые) nrf5340 в корпусе aQFN94. Соединены они интерфейсом SWD. Внутри каждого по 2 ядра Arm Cortex-M33 (Armv8-M). На улицу также выходит 2 UART(а) для интерфейса командной строки. Они же пробрасываются по USB (J2) на DeskTop.
Что понадобится из софта?
Утилита | Назначение | Path |
arm-none-eabi-gdb.exe | GDB Client для 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 | Прошивальщик микроконтроллера | C:\Program Files (x86)\Nordic Semiconductor\nrf-command-line-tools\bin |
ttermpro.exe | Терминал COM портов | C:\Program Files (x86)\teraterm |
Этого должно быть достаточно.
Фаза 1 Залить прошивку в Target
Это можно проделать вот этим скриптом
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%
Примерно вот такой должен быть лог успешной загрузки прошивки в терминале cmd.exe
set dflt SDK ver
Artefact hex: [C:/ncs/v2.1.0/XXX/zephyr/zephyr.hex]
Project Dir:C:/XXX/
Parsing image file.
Verifying programming.
Verified OK.
Applying system reset.
Run.
tools_dir=C:/XXX/tools
>
>
Отработав, этот скрипт загрузит прошивку в On-Chip NorFlash микроконтроллера nrf5340 U1.
Фаза 2. Запуск GDB сервера (Back-End)
GDB(GNU DeBugger) сервер это утилита, которая опрашивает программатор-отладчик. Именно она общается с устройством. Надо запустить GDB сервер. Вот скрипт запуска этой утилиты.
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%
Надо установить интерфейс связи программатора и микроконтроллера, который мы хотим отлаживать. В данном случае это интерфейс SWD.
![](https://habrastorage.org/getpro/habr/upload_files/df4/7e9/762/df47e976222b1baccfbb5b515e08477f.png)
J-link попросит выбрать микроконтроллер
![](https://habrastorage.org/getpro/habr/upload_files/f41/950/f6f/f41950f6f7f7628ae37506733639b4bd.png)
список поддерживаесых чипов очень большой
![](https://habrastorage.org/getpro/habr/upload_files/f63/b7e/e96/f63b7ee96287e23ff65ca74a2e520bd2.png)
В моем случае чип nrf5340
![](https://habrastorage.org/getpro/habr/upload_files/318/56a/2cd/31856a2cd58318555f74e7331df88d17.png)
GDB сервер J-Link запущен локально. Программатор (S/N: 1050009032) подключен по USB. Target подключен к программатору по SWD.
![](https://habrastorage.org/getpro/habr/upload_files/1e8/627/ddc/1e8627ddc199d5c81667b252fd4368f9.png)
вот полный лог ожидающего GDB сервера
Скрытый текст
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: 5000 ms
------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.30 V
Listening on TCP/IP port 2331
Connecting to target...
Connected to target
Waiting for GDB connection...
Сейчас GDB сервер просто ожидает подключения по TCP порту: 2331
На отладочной плате nRF5340-DK, есть встроенный программатор J-Link и там сейчас непрерывно светится зеленый LED, от отладчика.
Фаза 3 Запуск GDB клиента (Front-End )
Если GDB сервер можно метафорично считать Back-End(ом), то Front-End частью для пошаговой отладки выступает GDB клиент. Вот скрипт его запуска.
"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
GDB клиент смог подхватить *.elf файл с отладочными символами
![](https://habrastorage.org/getpro/habr/upload_files/0a1/efa/4c5/0a1efa4c55472e56c4c38d6d8fc456ba.png)
Настало время подключиться к GDB серверу. Это делается командой.
target remote localhost:2331
Сработало
![](https://habrastorage.org/getpro/habr/upload_files/b32/74c/e0d/b3274ce0d8beeedbdf793c049fb504bb.png)
Подключился. Сейчас исполнение прошивки на паузе. HeartBeat LED не мигает, UART-Shell не отвечает.
Самая полезная команда консольного GDB это команда bt (backtrace). Именно эту команду надо выполнить первой. Так как скорее всего вы запустили GDB чтобы выявить причину зависания. И команда bt сразу вам покажет на какой строчке прошивка свалилась в исключение.
![](https://habrastorage.org/getpro/habr/upload_files/1fb/310/61e/1fb31061e7fae5303b324c506bfd3bea.png)
Для запуска прошивки надо набрать команду continue
![](https://habrastorage.org/getpro/habr/upload_files/5b8/a0a/737/5b8a0a7375f90678751e69de91588eb0.png)
Теперь HeartBeat LED мигает CLI отвечает. LED на программаторе тоже мигает только как-то апериодично.
Для того чтобы снова остановить исполнение программы надо в терминале GDB клиента нажать Ctrl+C.
Теперь можно установить точку останова. Меня интересует функция bool hw_init(void)
(gdb) break hw_init
Посмотреть список установленных точек останова можно командой
(gdb) info b
Вот это сейчас отображается
![](https://habrastorage.org/getpro/habr/upload_files/de3/139/680/de3139680510f952439bef3013d5faf4.png)
Снова запускает программу на исполнение. Команда c
(gdb) c
Но вот незадача. Точка останова установлена внутри функции инициализации, которая уже давным-давно исполнилась. Что же делать? Надо как-то перезагрузить Target, чтобы инициализация снова отработала.
Вот тут-то нам как раз поможет еще один терминал. Это терминал командной строки поверх UART реализованный прямо внутри прошивки на Target(е). Я просто открою TeraTerm.exe и попрошу Zephyr-based прошивку перезагрузиться встроенной командой из UART CLI.
-> kernel reboot cold
и устройство в самом деле перезагружается!
![](https://habrastorage.org/getpro/habr/upload_files/e8f/343/578/e8f3435786ebfcd161b90b94c9adcf9d.png)
При этом я даже от клавиатуры рук не отводил. И вот как раз отладчик и зацепил функцию bool hw_init(void). Это называется синергия GDB и UART-CLI. Успех.
![](https://habrastorage.org/getpro/habr/upload_files/945/486/148/945486148082188ba66a0df1ce190fbc.png)
Выполнить одну строчку кода можно командой n (next). Посмотреть содержимое локальный переменный можно командой info locals
![](https://habrastorage.org/getpro/habr/upload_files/3c2/6ab/d9d/3c26abd9d1c087b50e8d6fa220e253dc.png)
Зайти внутрь функции можно командой s (step).
Само собой, в консоли GDB клиента есть история команд, и вы можете стрелками вверх/ вниз на клавиатуре набирать уже используемые в прошлом команды.
Чтобы выйти из GDB клиента достаточно набрать в его консоли команду q (quit). Target все еще suspended. Поэтому также надо закрыть GDB сервер. Только после этого Target станет resumed и продолжит исполнять свой код.
В последующем можно запустить GDB сервер и GDB клиент одним единственным скриптом.
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%
Суммируя вышесказанное, весь процесс можно объяснить вот такой простецкой схемой на одном листе.
![](https://habrastorage.org/getpro/habr/upload_files/0b8/618/32c/0b861832c42b9d40ebe3a4bb298baa50.png)
Также небольшая шпаргалка по наиболее употребительным командам консольного GDB клиента
short | full | description |
- | info args | Show current function arguments |
i b | info b | 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 |
- | delete n | Delete breakpoint number n |
- | info locals | show local variables |
tar rem:2331 | target remote localhost:2331 | Connect to J-Link |
q | quit | quit gdb |
b function | Set a breakpoint at the beginning of function |
Буду также обновлять реестр команд консольного GDB клиента google spreadsheets.
https://docs.google.com/spreadsheets/d/1AWD8GsDfaA9dtdsfqgbB1klagou1yrREc1AAK9CRUik/edit#gid=0
Там проще навигация благодаря наличию сортировки по столбцам и раскраске ячеек
Вывод
Вот теперь вы умеете делать пошаговую DBG отладку из консоли и можете учить этому других. Как видите в GDB отладке через консоль нет вообще ничего сложного. Как по мне, дак, всякие там циклопические и дорогущие IDE(Integrated development environment) для пошаговой отладки не особо-то и нужны как бы.
Консоль даже лучше в том смысле, что внимание концентрируется на сути (коде), а не на стразиках из оформления GUI(ни) от IDE. Можно вообще отлаживать без кода, просто получив по почте один лишь *.elf файл, а все сорцы собирать на удаленном защищенном сервере.
Тем более в консольном отладчике, как правило, не надо проводить много времени. Подключился, выполнил bt понял где зависла программа, отключился. Вот и всё. Easy.
Потом, написав один набор скриптов запуска GDB, в консоли можно пошагово отлаживать микроконтроллеры абсолютно любого вендора: STM32(ST), nRF5x(NS), CC26x2(TI), MDR32(M), LPC21xx (NXP). Меняться будут только GDB серверы. Всё остальное одинаково у всех.
При этом накладные расходы на установку Toolchain(а) для консольной пошаговой отладки минимальные и всё абсолютно бесплатно. Вам уже нравится GDB?
Links
Дайте мне 15 минут, и я изменю ваш взгляд на GDB
https://condor.depaul.edu/glancast/373class/docs/gdb.html#Setting_Breakpoints
https://habr.com/ru/post/259205/
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
Вопрос *
Как работает механизм установки точек останова JTAG под капотом?
Акроним | Расшифровка |
GDB | The GNU Project Debugger |
GUI | graphical user interface |
IDE | integrated development environment |
JTAG | Joint Test Action Group |
UART | universal asynchronous receiver / transmitter |
CLI | command-line interface |
SWD | Serial Wire Debug |
ARM | Advanced RISC Machine |
RISC | reduced instruction set computer |