35б. Прошивка и отладка из командной строки: OpenOCD, GDB, CubeProgrammer CLI
Среда прошивает и отлаживает одной кнопкой — пока вы за столом с одной платой. Как только нужно прошить десять плат подряд, встроить прошивку в конвейер сборки (раздел 110), отладить на машине без графики или разобрать чужой проект без его среды — оказывается, что за кнопкой стоят обычные программы командной строки, и звать их можно самому. Три из них закрывают почти всё: OpenOCD держит сервер и прошивает, GDB отлаживает через этот сервер, STM32_Programmer_CLI от ST меняет байты конфигурации и говорит с заводским загрузчиком. Раздел — про то, как связать их в рабочую цепочку.
Всё про GDB показано прогоном в QEMU (машина netduinoplus2, Cortex-M4):
QEMU поднят как сервер GDB (-S -gdb tcp::1234), а gdb-multiarch из образа
stm-arm:1 подключается к нему и выполняет session.gdb. Команды OpenOCD и
STM32_Programmer_CLI требуют физического ST-LINK и платы — они приведены по
их собственному описанию (OpenOCD 0.12.0, STM32CubeProgrammer 2.23.0) без
выдуманных выводов. Сверяет _verify/cli35b_check.py.
Как это называется в документации
Оригинал Перевод adapter / probe программатор-отладчик (ST-LINK, J-Link, CMSIS-DAP) interface / transport интерфейс и транспорт (SWD, JTAG) target config описание камня для отладчика GDB server / remote сервер GDB, к которому подключается отладчик monitorcommandкоманда не для GDB, а для сервера breakpoint / watchpoint точка останова / точка наблюдения SVD (System View Description) XML-описание регистров периферии option bytes / RDP байты конфигурации / защита чтения IDCODE / TAP ID опознавательный код отладочного порта
35б.1 Три программы и что умеет каждая
Их путается ровно столько, что стоит развести сразу. Все три говорят с кристаллом через один и тот же разъём SWD, но делают разное.
| Программа | Откуда | Что умеет | Чего не умеет |
|---|---|---|---|
| OpenOCD | открытая, любой адаптер | прошить, стереть, поднять сервер GDB на порту 3333 | сама не отлаживает — только держит сервер |
| st-flash (stlink-tools) | открытая, только ST-LINK | прошить .bin, стереть, прочитать |
ни GDB-сервера, ни .elf, ни отладки |
| STM32_Programmer_CLI | ST, закрытая | прошить .elf/.bin/.hex, байты конфигурации, RDP, заводской загрузчик по UART и USB DFU |
сервер GDB не поднимает |
GDB (gdb-multiarch) |
открытая | отладка: точки останова, стек, память, регистры | сам к кристаллу не подключается — только к серверу |
Отсюда две типовые связки. Прошить и уйти — одна строка OpenOCD, st-flash
или CubeProgrammer, без всякого GDB (35б.3). Отлаживать — сервер OpenOCD
плюс GDB поверх него (35б.4). Байты конфигурации, RDP и загрузчик по UART —
дело STM32_Programmer_CLI (35б.7); у OpenOCD команды stm32f4x options_* тоже
есть, но у фирменной программы это ровнее.
Версии, на которых всё проверено: OpenOCD 0.12.0, st-flash v1.7.0,
STM32CubeProgrammer 2.23.0, GDB 13.1. Отдельного arm-none-eabi-gdb
в наборе arm-none-eabi-gcc может не быть — в образе его нет, и вместо него
берут gdb-multiarch (одна программа под все архитектуры).
35б.2 OpenOCD: interface и target, частота SWD, подключение под сбросом
OpenOCD собирается из двух описаний: чем подключаемся (адаптер) и к чему (камень). Каждое — свой файл конфигурации из поставки:
# ST-LINK + любой STM32F4
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
# то же одним файлом платы Nucleo-F4
openocd -f board/st_nucleo_f4.cfg
Файл адаптера interface/stlink.cfg выбирает драйвер hla (adapter driver hla,
hla_layout stlink) — «high level adapter», когда ST-LINK сам исполняет
последовательности SWD. Файл камня target/stm32f4x.cfg заводит ядро
cortex_m и знает его флеш. Для F1 — target/stm32f1x.cfg, плата BluePill
покрывается interface/stlink.cfg плюс этим target. Готовые файлы плат
(board/st_nucleo_f103rb.cfg, board/stm32f4discovery.cfg) просто подключают
нужные interface и target и добавляют transport select hla_swd.
Частота SWD. По умолчанию target/stm32f4x.cfg стартует на adapter speed 2000
— 2 МГц, безопасно на холодном камне, пока тактирование не настроено. Скрипт
инициализации после запуска PLL поднимает её до adapter speed 8000. Задать
вручную — до init:
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c "adapter speed 1800"
Слишком высокая частота на длинных проводах даёт Error: init mode failed —
первое, что снижают при капризном подключении.
Сброс. Как OpenOCD дёргает NRST, задаёт reset_config. У stm32f4x.cfg
это srst_nogate (сброс не глушит такт отладки), а cortex_m reset_config
sysresetreq велит сбрасывать через регистр ядра. Если камень сразу после
старта уходит в сон или переназначает выводы SWD (частая беда с загрузчиком
в приложении или с настроенным энергосбережением), к нему не подключиться на
ходу — нужно подключение под сбросом: адаптер держит NRST прижатым, пока
устанавливает связь.
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
-c "reset_config connect_assert_srst" -c "init" -c "reset halt"
⚠️
connect_assert_srstтребует разведённогоNRST. На плате, где вывод сброса ST-LINK не подключён (толькоSWDIO,SWCLK, земля), приём не сработает: держать нечего. На такой плате остаётся заводской загрузчик черезBOOT0(раздел 4.9) или полное стирание.
35б.3 Прошить одной строкой: program … verify reset exit
Самый частый сценарий без всякой отладки — залить и перезапустить. У OpenOCD
для этого одна команда program, а -c после неё закрывает сервер:
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
-c "program fw.elf verify reset exit"
Четыре слова после имени файла — это шаги: program пишет во флеш (адрес берётся
из .elf), verify читает обратно и сверяет, reset перезапускает камень,
exit завершает OpenOCD, чтобы команда вернула управление оболочке и код
выхода. Без exit сервер останется висеть и ждать подключения.
🔧 st-flash — когда нужен только байт-в-байт образ. Она берёт .bin
(не .elf) и явный адрес:
st-info --probe # проверить, что ST-LINK видит камень
st-flash erase # стереть всю флеш
st-flash write fw.bin 0x08000000 # залить с начала флеш
🔧 STM32_Programmer_CLI — фирменная, берёт .elf. Подключение задаётся
-c port=…, запись — -w (он же --download), сброс после — -rst:
STM32_Programmer_CLI -c port=SWD -w fw.elf -rst
По собственному описанию программы, -w принимает «bin, hex, srec, s19, elf,
stm32 or tsv file», а начальный адрес нужен только для .bin — из .elf
и .hex он читается сам.
⚠️ Забытый
verify— прошивка «легла», но не проверена. На плохом контакте или севшей батарее программатора запись проходит частично, а устройство молча не стартует.verifyв OpenOCD и-vв CubeProgrammer стоят доли секунды и снимают полдня поисков.
35б.4 GDB без среды: load, monitor reset halt, точки останова, x, info registers
Отладка — это сервер плюс GDB. С настоящим ST-LINK сервер поднимает OpenOCD
(без exit, он ждёт на порту 3333); в опыте его роль играет QEMU на порту 1234.
GDB подключается к серверу и дальше работает одинаково.
Ключевое различие двух видов команд. Обычные команды GDB (break, continue,
print, x) отладчик исполняет сам, читая память и регистры через сервер.
Команды, начинающиеся с monitor, GDB не понимает — он пересылает их
серверу дословно: monitor reset halt — сброс с остановкой на первой команде,
monitor reset init — плюс настройка тактов из скрипта, monitor flash … —
работа с флеш. Это и есть способ дотянуться до того, что умеет OpenOCD, не
выходя из GDB.
Полный сеанс в опыте (QEMU как сервер, порт 1234):
target extended-remote localhost:1234
load
compare-sections
break main
continue
info registers pc sp
break mix
continue
backtrace
finish
watch ticks
continue
print/x result
x/8wx &samples
Что напечатал GDB, дословно из прогона:
Loading section .isr_vector, size 0x40 lma 0x8000000
Loading section .text, size 0xd0 lma 0x8000040
Start address 0x08000042, load size 272
Section .isr_vector, range 0x8000000 -- 0x8000040: matched.
Section .text, range 0x8000040 -- 0x8000110: matched.
Breakpoint 1, main () at fw35b.c:50
pc 0x80000da sp 0x2001fff8
Breakpoint 2, mix (x=1) at fw35b.c:13
#0 mix (x=1) at fw35b.c:13
#1 0x080000c0 in fill () at fw35b.c:23
#2 0x080000ee in main () at fw35b.c:53
Value returned is $1 = 270369
Hardware watchpoint 3: ticks
Old value = 0
New value = 1
$1 = 0xaf422ba3
0x20000000 <samples>: 0x00042021 0x04080601 0x9dcca8c5 0x1255994f
0x20000010 <samples+16>: 0x8ef917d1 0x2c6f5bd0 0x25b2331a 0x19f91cb2
Разберём по строкам. load кладёт секции .elf во флеш через сервер:
272 байта, .isr_vector с адреса 0x08000000 и .text за ним; на настоящем
камне это и есть прошивка. compare-sections тут же читает обратно и
подтверждает matched — тот же смысл, что verify. break main + continue
останавливают на входе в main: pc = 0x80000da, вершина стека
sp = 0x2001fff8 (у F4 стек с конца 128 КБ ОЗУ). break mix + continue
ловят функцию, а backtrace показывает, откуда пришли: mix вызвана из
fill, та — из main. finish доводит до возврата и печатает результат
mix(1) — 270369. watch ticks ставит точку наблюдения на переменную:
следующий continue встаёт ровно на записи, Old value = 0 / New value = 1.
print/x result даёт итог программы 0xaf422ba3, а x/8wx &samples —
восемь слов массива в памяти: это последовательность xorshift32 от единицы,
и её сумма как раз 0xaf422ba3.
Число аппаратных точек останова у ядра ограничено (у Cortex-M3 и M4 их шесть,
раздел 35.1) — GDB не поставит седьмую. Точка наблюдения watch тоже
аппаратная и тоже считанная, ставится на адрес переменной.
⚠️
loadбез живого сервера вешает GDB. Если OpenOCD не запущен или ST-LINK не видит камень,target extended-remoteне подключится, аloadбудет ждать. Сначала — рабочий сервер, потом GDB.
С настоящим ST-LINK всё то же, только первой строкой target extended-remote
localhost:3333, а сброс с остановкой даёт monitor reset halt перед load.
У QEMU таких monitor-команд нет — там сброс всей машины делает
monitor system_reset.
35б.5 Сценарии: .gdbinit, свои команды, gdb в конвейере
Набирать десять строк каждый раз незачем — GDB читает их из файла.
.gdbinit в каталоге проекта подхватывается автоматически, но с оговоркой
про безопасность. В опыте GDB отказался выполнить локальный .gdbinit:
warning: File ".gdbinit" auto-loading has been declined by your
`auto-load safe-path' set to "$debugdir:$datadir/auto-load".
Это защита: чужой репозиторий не должен исполнять произвольные команды при
открытии. Разрешают либо разово ключом, либо строкой в домашнем ~/.gdbinit:
gdb-multiarch -iex "set auto-load safe-path /" fw35b.elf # доверять всему
# или в ~/.gdbinit: add-auto-load-safe-path /путь/к/проекту
С разрешением тот же файл выполнился — в опыте он напечатал local .gdbinit loaded.
Свои команды заводят через define — удобно для повторяющихся действий:
define reflash
monitor reset halt
load
compare-sections
end
Теперь reflash в сеансе перешивает и сверяет одной командой.
GDB в конвейере — прошивку гоняют как обычную программу и смотрят на код
выхода (раздел 110). Тонкость: в неинтерактивном скрипте continue после
далёкой точки останова возвращается раньше, чем ядро встанет, и следующая
команда падает с «Cannot execute this command while the target is running».
Надёжный приём — не гнать программу из GDB, а подключиться к уже досчитавшей:
QEMU (или плату) запускают без остановки, GDB подключается, сервер тут же
останавливает ядро в цикле после расчёта, а скрипт лишь читает результат и
возвращает код:
target extended-remote localhost:1234
if result == 0xaf422ba3
quit 0
else
quit 1
end
В опыте годный образ дал CI: result ok и код выхода 0, а тот же скрипт с
подменённым эталоном — CI: result BAD и код выхода 1. Дальше система
сборки смотрит на код, как у любой команды.
35б.6 SVD из пака: регистры периферии по имени
Голый GDB знает переменные программы, но не периферию: USART1->SR для него
просто адрес 0x40011000. Имена регистров и полей лежат в SVD — XML-описании
камня, которое ST кладёт в пакет поддержки (.../Keil/STM32F4xx_DFP/…/CMSIS/SVD/
STM32F405.svd). Небольшой скрипт на Python внутри GDB читает этот файл и
добавляет команду per, печатающую регистр с разобранными битами:
source svd35b.py
per USART1 SR CR1
В опыте (после настройки USART в программе) команда напечатала:
USART1.SR @ 0x40011000 = 0x000000C0 TXE=1 TC=1
USART1.CR1 @ 0x4001100C = 0x00002008 UE=1 TE=1
Адреса и биты не выдуманы: базовый адрес USART1 0x40011000, смещение SR — 0,
CR1 — 0x0C, бит TXE — 7-й, TC — 6-й, UE — 13-й, TE — 3-й — всё из
того же SVD. Скрипт учитывает, что в SVD от ST регистры USART1 заданы через
derivedFrom от USART6, и берёт их оттуда. У сред (CubeIDE, Keil) окно
периферии устроено так же — читает SVD; из командной строки то же делается
двадцатью строками Python.
35б.7 STM32_Programmer_CLI: байты конфигурации, RDP, заводской загрузчик по UART
То, чего нет у чистого GDB, — работа с байтами конфигурации (раздел 11.6) и заводским загрузчиком. Это дело фирменной программы.
Показать и задать байты конфигурации. По её собственному описанию,
-ob (--optionbytes) «displaying or modifying» байтов, -ob displ печатает
весь набор, а -ob <имя>=<значение> программирует один:
STM32_Programmer_CLI -c port=SWD -ob displ # показать все
STM32_Programmer_CLI -c port=SWD -ob RDP=0xAA # снять защиту чтения (F4)
STM32_Programmer_CLI -c port=SWD -ob nRST_STOP=1 # не сбрасываться в STOP
RDP — защита чтения. Уровень задаётся байтом RDP; значения из заголовков
HAL (OB_RDP_LEVEL_*):
| Уровень | Значение (F4) | Что делает | Снятие |
|---|---|---|---|
| 0 | 0xAA |
защиты нет, отладчик читает флеш | — |
| 1 | любое, кроме 0xAA и 0xCC (напр. 0x55) |
отладчик флеш не читает | полным стиранием флеш |
| 2 | 0xCC |
отладка и загрузчик отключены навсегда | нельзя |
У F1 уровня 2 нет, а «без защиты» — 0xA5 (раздел 11.6). Перевод с уровня 1 на
0 фирменная программа выполняет сама, попутно стирая всю флеш, — иначе защита
не имела бы смысла.
⚠️ Уровень 2 необратим.
-ob RDP=0xCCнавсегда отключает SWD и заводской загрузчик: перепрошить камень будет нечем. На отладке ставят только уровень 1 и только осознанно.
Заводской загрузчик по UART. Когда SWD недоступен (защита, нет разъёма,
серийная заливка), прошивают через встроенный загрузчик: BOOT0 = 1, сброс
(раздел 4.9) — и камень слушает USART1. CubeProgrammer подключается к нему
через переходник USB-UART, задав COM-порт и скорость вместо port=SWD:
STM32_Programmer_CLI -c port=COM5 br=115200 -w app.bin 0x08000000 -v -rst
Тот же -w, но транспорт — UART; загрузчик принимает образ и проверяет
контрольную сумму каждого блока сам. У F4 загрузчик умеет ещё и USB DFU —
тогда -c port=usb1 без всякого переходника.
35б.8 Грабли
Клон камня — OpenOCD ругается на IDCODE. Дешёвые «STM32» (GD32, CKS и
прочие) отвечают отладочным кодом, не совпадающим с ожидаемым, и OpenOCD встаёт
на expected-id. Лечится подсказкой настоящего кода до target — у F4 по SWD
это 0x2ba01477, у F1 — 0x1ba01477 (по JTAG — 0x4ba00477 и 0x3ba00477):
openocd -f interface/stlink.cfg -c "set CPUTAPID 0x2ba01477" -f target/stm32f4x.cfg
Совсем отключить проверку — set CPUTAPID 0.
monitor-команды уходят серверу, а не GDB. monitor reset halt — это команда
OpenOCD; в QEMU её нет, там сброс машины — monitor system_reset. Перепутав
сервер, получаешь «Undefined command».
st-flash не берёт .elf. Ей нужен .bin и явный адрес; .elf и .hex
понимают только OpenOCD (program) и CubeProgrammer (-w).
load в GDB висит. Сервер не запущен или ST-LINK не видит камень: load
ждёт подключения. Сначала поднять OpenOCD, потом GDB.
continue в неинтерактивном скрипте гонит вперёд. В батч-режиме он
возвращается раньше остановки — следующая команда падает с «target is running».
Подключаться к уже досчитавшей программе или гнать сеанс через Python-API GDB
(35б.5).
RDP снят, а флеш пустая. Снятие защиты чтения с уровня 1 обязательно стирает всю флеш — это не сбой, а замысел (раздел 11.6). Прошивку заливать заново.
Забыт verify/-v. Запись прошла частично, устройство молча не стартует.
Проверка чтением стоит доли секунды (35б.3).
35б.9 Итоги
- Три программы: OpenOCD (сервер GDB + прошивка), st-flash (только
.bin, без отладки), STM32_Programmer_CLI (.elf, байты конфигурации, RDP, загрузчик UART/DFU); GDB отлаживает только через сервер. - OpenOCD собирают из
interface/*.cfgиtarget/*.cfg(илиboard/*.cfg); частота SWD стартует с 2 МГц; капризное подключение —connect_assert_srstс разведённымNRST. - Прошить и уйти:
program fw.elf verify reset exitу OpenOCD,st-flash write … 0x08000000,-w fw.elf -rstу CubeProgrammer; безverifyзапись не проверена. - Отладка:
target extended-remote :3333(QEMU —:1234),loadпишет флеш,compare-sectionsсверяет;break/continue,backtrace,finish,watch,x,info registers— в опыте дошли до значения0xaf422ba3и восьми словsamples. monitor …— команда серверу, не GDB:monitor reset haltу OpenOCD противsystem_resetу QEMU.- Сценарии автоматизируют через
.gdbinit(с оглядкой наauto-load safe-path),defineи код выхода; в конвейере надёжнее читать результат у уже досчитавшей программы (коды выхода 0 и 1 в опыте). - SVD из пака даёт регистры периферии по имени:
USART1.SR = 0x000000C0(TXE=1 TC=1) в опыте — адреса и биты из того же файла. - Клон с чужим IDCODE лечится
set CPUTAPID(F4 SWD0x2ba01477, F10x1ba01477); RDP уровня 2 (0xCC) необратим.
Дальше: раздел 35в «Отказ в поле: слепок состояния, переживающий сброс, и разбор после перезагрузки» — как сохранить отказ прибора без отладчика и разобрать его по ELF.