Общее представление отладки

Программаторы

На сегодня самые популярные решения:

  • JLink - Мощный коммерческий продукт с кучей софта который позволяет исследовать встроенное ПО

  • STLink - популярный и узконаправленный программатор

  • CMSIS-DAP - открытая спецификация отладочного интерфейса от ARM; прошивки, реализующие её (например DAPLink, PicoProbe), можно загрузить в камень чтобы он стал программатором

  • blackmagic - открытый проект который можно загрузить в камень чтобы он стал программатором и одновременно gdb сервером

GDB клиент

Каждый ARM-разработчик скачивает arm-none-eabi-gdb чтобы начать отладку из своей среды или из терминала. Для других архитектур нужен соответствующий GDB — например riscv-none-eabi-gdb для RISC-V. Но gdb не обращается к программатору напрямую. Более того gdb вообще ничего не знает о вашем программаторе.

GDB сервер

Чтобы подключиться к программатору через gdb нужна программная прослойка которая знает какие команды нужно отправить программатору по usb чтобы тот начал отладку по нужному интерфейсу. Таким образом нам требуется openOCD в общем случае. Частности разберем позже.

Почему openOCD так популярен?
Но сначала чем же он по сути является?
Как я сказал это некая прослойка между gdb и программатором, а именно openOCD - это gdb сервер, к которому подключается ваш arm-none-eabi-gdb клиент. Т.е. я бы называл openOCD просто gdb сервером для простоты понимания. Он понимает как работать с большинством программаторов и управляется командами от gdb клиента всегда одинаково. Просто интерпретирует их для каждого типа программатора как он того требует.
В этом абзаце в целом уже должно быть понятно почему openOCD так популярен. Он просто поддерживает все программаторы и gdb клиент обращается к ним одинаково.

Краткое резюме по подключению:

Вы вставляете в usb свой программатор.
Запускаете openOCD который общается с физическим программатором и дает "ручки" для arm-none-eabi-gdb клиента
Запускаете arm-none-eabi-gdb клиента который обращается к openOCD и все работает.
Большинство популярных IDE запускают сервер и клиент сами.

Перейдем к частностям

Программаторы

STLink
Разработан STMicroelectronics и поставляется встроенным в большинство их отладочных плат (Nucleo, Discovery). Более узконаправленный, чем JLink — ориентирован на микроконтроллеры STM32 и STM8, хотя существуют неофициальные прошивки, расширяющие его совместимость. Для работы с ним через GDB используется OpenOCD или утилита STM32CubeProgrammer для прошивки. STMicroelectronics также предлагает собственную среду STM32CubeIDE, которая запускает OpenOCD под капотом. Драйверы для macOS/Linux ставить не нужно — он работает через libusb; на Windows ситуация хуже.

JLink
Можно отлаживать через их JLinkGDBServer. Это замена OpenOCD которая работает только с JLink. В той же CubeIDE можно выбрать его. Эта утилита, как и многие другие, идёт вместе с драйверами JLink. Таким образом для отладки через GDB используется JLinkGDBServer или OpenOCD, к которому можно подключиться стандартным arm-none-eabi-gdb.
А еще у них есть свой JLinkRemoteServer. Он позиционируется как сетевой сервер, предоставляющий доступ к физическому JLink по сети, — отсюда и название. Если вы хотите обойтись без стандартного GDB, SEGGER предлагает собственный отладчик OZONE — он работает напрямую с JLink через их фирменный протокол и обладает богатым UI.
Есть много всего что компания SEGGER разработала, но сейчас не об этом. Это очень хорошее и стабильное решение. При этом дорогое и требует дополнительного софта.

CMSIS-DAP
Это открытая спецификация отладочного интерфейса, разработанная компанией ARM. Любой программатор может реализовать этот протокол — например DAPLink (от самой ARM) или PicoProbe (для RP2040). Keil MDK с собственным компилятором (ARMCC/AC6) поддерживает его нативно через встроенный отладчик µVision — без OpenOCD и arm-none-eabi-gdb. Если же в Keil использовать GNU-тулчейн и GDB-сервер сторонних производителей, arm-none-eabi-gdb всё равно потребуется. В других средах CMSIS-DAP программаторы прекрасно работают через OpenOCD или pyOCD (это схожий подход с openOCD, но он изначально ориентирован на CMSIS-DAP/DAPLink устройства и не поддерживает весь спектр программаторов, которые умеет OpenOCD), так что выбор инструментария остаётся за разработчиком.

BlackMagic
Он сразу в себе несет gdb сервер. Таким образом не нужен openOCD. Вы напрямую подключаетесь из arm-none-eabi-gdb и работаете.
Мое субъективное мнение - так и должен выглядеть программатор.

Стандартный BlackMagic Probe подключается по USB и представляется системе как два CDC устройства (на Linux — ttyACM0/ttyACM1, на macOS — /dev/cu.usbmodemXXXX//dev/cu.usbmodemXXXX1): первый порт — GDB-сервер, второй — UART passthrough. Драйверы не нужны. Отладка начинается одной командой:

# Linux
target extended-remote /dev/ttyACM0
# macOS
target extended-remote /dev/cu.usbmodem<serial>1

GDB клиенты

Почему нужен всегда arm-none-eabi-gdb?
Вообще-то для архитектуры. Если вы хотите отлаживать risc-v или старые esp (ядро Xtensa), то вам потребуется gdb, поддерживающий эту архитектуру.

В чем разница между ними?
Вот запустился gdb клиент. И вы хотите начать отладку по вашему .elf файлу. Когда вы останавливаетесь в breakpoint gdb клиент получает значения всех регистров вашей цели. И в зависимости от архитектуры эти регистры разные — их количество, назначение и разрядность отличаются от платформы к платформе. Плюс ко всему у каждой архитектуры свой набор ассемблерных инструкций.

Что такое ассемблерная инструкция?
В бинарном файле хранится машинный код — последовательность байт/слов в hex формате. Ассемблерная инструкция — это человекочитаемое представление одной такой машинной команды: например, 0x2001 в Thumb2 это MOVS R0, #1. Вот вы написали где-то if, и компилятор превратил его в несколько машинных инструкций: сравнение и условный переход. Чтобы понять что это за инструкции их нужно из машинного кода перевести обратно в ассемблерные мнемоники. Это и называется дизассемблирование.
Архитектуры кодируют инструкции по-разному — условный переход в ARM Cortex-M это один набор байт, в RISC-V другой. Этим и занимается gdb клиент. И поэтому они разные.
Если вы пишете под Linux x86 у вас один hex будет прочитан во время breakpoint на if(), в ARM Cortex-M другой, в risc-v третий. Хотя на C вы писали одно и то же. Но компилировали под разные архитектуры, а значит будут разные hex инструкции.
Надеюсь разжевал.
Так и получается что для интерпретации кода, например, в breakpoint нужны разные gdb клиенты которые понимают как интерпретировать инструкции которые присылает им наша цель.

Почему бы тогда не сделать один, единый gdb?
Набор инструкций огромен. И все их поместить в один gdb довольно затратно. Пожалуй это достаточный аргумент. Но для меня, если честно, он не убедителен. К слову, gdb-multiarch существует и поддерживает сразу несколько архитектур — но он менее распространён в тулчейнах для конкретных платформ.

Немного про ELF файл

ELF (Executable and Linkable Format) — это стандартный формат исполняемых файлов в Unix-подобных системах и встраиваемых платформах. Отладочные ELF-файлы собираются с секциями DWARF, в которых хранится человекочитаемая информация: по какому адресу в памяти располагаются те или иные функции, типы переменных, ссылки на исходные файлы и номера строк. Благодаря этому IDE может показывать текущую строку при breakpoint и раскрывать значения переменных.
Если посмотреть elf файл который мы используем для отладки, там есть информация об архитектуре.

Субъективное резюме

Все это на мой взгляд очень плохо. Такое ощущение что сообщество не смогло договориться о некоей единой системе и каждый тянет одеяло на себя. Спустя годы разработки я пришел к выводу что нет хорошего решения. Но остановился на blackmagic.
У меня есть довольно конкретные запросы к отладчику:

  • Я хочу отлаживать устройства удаленно

  • Меня раздражает запуск gdb серверов

  • Меня раздражает их настройка и все эти конфигурационные файлы

  • Ставить драйвера я тоже не хочу

  • Я хочу использовать кроссплатформенную легковесную IDE

  • Мой программатор должен уметь отлаживать широкий спектр целей: ARM Cortex-M/A/R, RISC-V

Все это мне дает blackmagic probe. У него есть свои недостатки, но они не входят в приведённый выше перечень. Например, поддержка отображения потоков FreeRTOS в IDE реализована в BlackMagic частично и работает не во всех конфигурациях так же удобно, как в JLink с Ozone или в OpenOCD с плагинами. Но признаться это не такой серьёзный недостаток, потому что остаются механизмы которые позволят мне вычитать стек каждой задачи в любой момент времени через GDB напрямую. Это просто не выведется во вкладках IDE автоматически.

blackmagic-esp32-c5

ESP32-C5 — это RISC-V микроконтроллер с встроенным Wi-Fi и Bluetooth. Идеальный кандидат для беспроводного сетевого BMP: один чип, Wi-Fi из коробки, USB-C для питания и прошивки — никаких драйверов.

Проект blackmagic-esp32-c5 реализует эту идею полностью:

  • Сетевой GDB-сервер на порту 2345 — подключение командой: target extended-remote <ip>:2345 если пользуетесь GDB напрямую, или настройка в IDE для удалённого отладчика

  • RTT через telnet на порту 2346 — лог целевого устройства по сети без USB

  • Веб-интерфейс — drag & drop загрузка .bin из браузера

  • Хранение настроек в NVS — конфигурация сохраняется между перезагрузками

Цена решения — стоимость чипа ESP32-C5. Проект открыт. Никакого дополнительного железа.