OpenOCD, GDB и (сильно)удалённая отладка

Дано: есть устройство, с ARM926E-JS (Cypress FX3) на борту. Устройство находится на другом континенте. Устройство подключено (JTAG+USB+COM) к Linux компу. На комп есть SSH доступ (и больше ничего, только SSH порт).

Проблема: Устройство нужно отлаживать и писать под него код. И делать это, желательно, удобно.

Решение с использованием OpenOCD, GDB и Qt Creator, а так же описание пути к нему, под катом.
Решений проблемы может быть много. Само быстрое и простое: запуск связки GDB+OpenOCD на удалённом компе через ssh сессию. Удобства не много, т.к. код удобнее править локально, а для отладки нужно постоянно заливать код на сервер при помощи scp или rsync.

После недолгих размышлений, приходит идея:, а ведь мы можем запускать команды удалённо на сервере используя SSH:

ssh user@host some-command some-arguments


Хм… А к тому же GDB может сам запустить OpenOCD в режиме конвейера (pipe) и общаться с ним. Так можно же сделать так, что бы запускался не просто OpenOCD, а удалённый, по ssh, и полученная связка уже использовалась для отладки.

К сожалению такой вариант оказался нежизнеспособным: соединение постоянно отваливалось по таймауту.

Следующая идея была: как-то поднять VPN и использовать его для подключения к любым портам на сервере, после чего запустить удалённо OpenOCD.

Но как поднять VPN, если нет никаких портов, кроме SSH? Ладно, знаем, что SSH может пробрасывать порты. Запускаем удалённо OpenOCD, пробрасываем порт… Да, чуть лучше, чем запуск в режиме конвейера. Но именно, что чуток. Для работы никак не годится.

Почти было решил бросить это дело и пользоваться самым первым, простым, надёжным, но неудобным решением, но тут набрал в гугле связку: SSH VPN. Сказать, что я был удивлён — не сказать ничего. Что бы не искать, этих двух ссылок достаточно:


После настроек, на удалённой машине появился интерфейс tap8 с адресом 192.168.100.1 и локально: интерфейс tap7 с адресом 192.168.100.2 (адреса пригодятся в дальнейшем).

Пробую запускать… О чудо! Решение оказалось рабочим! Код загружается, всё работает, точки останова ставятся. Проблема одна: медленно. И если с ожиданием обновления состояния (стектрейсы, локальные переменные и т.п.) я могу смириться, то загрузка 300 кБ elf’а занимает больше 6 минут. Локально быстрее. Значительно.

В любом случае, вот пара скриптов, которые реализуют данную схему (настройки SSH не привожу):

  1. openocd-remote — просто оболочка для запуска удалённого OpenOCD через ssh. Отмечу, что расположение
    файлов и директорий на локальной машине и удалённой я сделал одинаковым. В противном бы случае
    в этот же скрипт добавил бы препроцессинг параметров при помощи sed, что бы сделать замены. Плюс,
    OpenOCD у меня собран из Git и скопирован в ~/bin/openocd-git/{bin,share} (соответствующие директории).
    Конфиги для FX3 (о них дальше) лежат в ~/bin/openocd-git/ . В ~/bin/ сделан симлинк на исполняемый
    файл openocd.
    #!/bin/sh
    ssh user@host -T killall -9 openocd
    exec ssh -TC user@host bin/openocd $@
    
    
  2. gdb-remote — подключается к удалённому OpenOCD, загружает код:
    #!/bin/sh
    
    gdbcfg=fx3_gdb.ini
    elf=some-code.elf
    
    cat > $gdbcfg << EOF
    set prompt (arm-gdb)
    set remotetimeout 30
    target remote 192.168.100.1:3333
    monitor halt
    monitor soft_reset_halt
    monitor adapter_khz 1000
    set endian little
    load
    EOF
    
    arm-none-eabi-gdb -x $gdbcfg $elf
    
    


Раздумья об ускорении запуска шли примерно в таком русле: простое копирование elf файла на удалённый сервер занимает секунд 10, плюс-минус. А вот бы было круто, загрузить образ на сервер и у в устройство загружать уже с него…

Штудирование документации по OpenOCD и вот оно: сам OpenOCD может загрузить код в устройство, а GDB просто подключится и даст команду на старт прошивки. Волшебная команда: load_image.

Первые эксперименты были неутешительными: загрузка проходит ОЧЕНЬ нестабильно. Код грузится, грузится быстро: 1 минута против 6 с хвостом). Но прошивка то стартует, то нет. При этом, если же в той же сессии GDB сделать load, то всё отлично запускается.

Начал искать различия. Заинтересовала последняя строчка загрузки через load:

Start address 0x40035948, load size 298456


Это навеяло залогировать после загрузки кода через load и через load_image (через OpenOCD) и перед стартом (continue) содержимое регистра $pc. И… отличие найдено: после load $pc установлен именно в этот «Start address», тогда как после load_image в $pc остаётся что-то, в момент чего была начала загрузка. После установки pc в правильное значение загрузка стала стабильной. Остался вопрос: магические числа не есть гуд. Но тут помогло то, что в GDB можно указать символ и будет взят его адрес. В случае FX3 этот символ: CyU3PFirmwareEntry (к слову, на локальных приложениях это будет, скорее всего, _start) и команда установки $pc превратилась в такую:

set $pc = CyU3PFirmwareEntry

Кроме того, у GDB есть возможность звать команды оболочки, поэтому мы можем легко и непринуждённо при старте залить elf файл на удалённый сервер и дать команду запущенному OpenOCD загрузить его (любую команду для OpenOCD можно дать из GDB предварив её словом monitor).

Итоговый скрипт для запуска GDB:

#!/bin/sh

gdbcfg=fx3_gdb.ini
elf=some-code.elf

# Генерим конфиг для GDB
cat > $gdbcfg << EOF
set prompt (arm-gdb)
set remotetimeout 30
target remote 192.168.100.1:3333
shell scp $elf user@192.168.100.1:
monitor halt
monitor soft_reset_halt
monitor sleep 1000
monitor load_image %elf 0x00 elf
set $pc = CyU3PFirmwareEntry
EOF

arm-none-eabi-gdb -x $gdbcfg $elf


Скрипт для запуска OpenOCD остаётся таким же.

Что нам теперь нужно, что бы начать удалённую отладку:

  1. Запусить скрипт openocd-remote. Перезапускать его можно по потребностям.
  2. Остроить код и запустить gdb скриптом выше.
  3. PROFIT

А PROFIT ли? По мне, так нет. Код я пишу в Qt Creator и хочется в один клик всё это делать из него. И это делается в один клик. Достаточно:

  1. Открыть диалог настроек
  2. Выбрать Bare Metal и добавить новый GDB Server Provider с типом OpenOCD со следующими параметрами:
  3. Затем идём в Devices, делаем Add → Bare Metal Device → Даём имя (пусть будет FX3 Device Remote) и назначаем наш GDB Server provider (FX3 Remote)
  4. Затем идём в Build & Runs и в используемом наборе (Kits) для Cypress (или ARM или чего у вас там) выбираем:
    • Device type: Bare Metal Device
    • Device: FX3 Device Remote

Тут мне пришлось создавать для набора — для локальной и для удалённой разработки. Но что ж… Пережить можно.

Всё, после чего в настройках проекта добавляем новый набор, конфигурируем его, на вкладке Run добавляем конфирурации в названии которых есть »(via GDB Server or hardware debugger)» и начинаем отладку простым нажатием F5.

Полезные материалы

Скрипты fx3-common.cfg, fx3-threadx.cfg, fx3-boot.cfg (для отладки бутлодера или когда нет ThreadX) соответственно:

fx3-common.cfg
######################################
# Target: CYPRESS FX3 ARM926-ejs
# Common part
######################################
if { [info exists CHIPNAME] } {
set _CHIPNAME $CHIPNAME
} else {
set _CHIPNAME fx3
}

if { [info exists ENDIAN] } {
set _ENDIAN $ENDIAN
} else {
set _ENDIAN little
}

if { [info exists CPUTAPID] } {
set _CPUTAPID $CPUTAPID
} else {
set _CPUTAPID 0x07926069
}

#delays on reset lines
adapter_nsrst_delay 200
jtag_ntrst_delay 200

adapter_khz 1000

#reset_config trst_only
#reset_config trst_only combined
#reset_config trst_and_srst combined
#reset_config trst_and_srst srst_pulls_trst
# From the Cypress SDK
reset_config trst_and_srst srst_pulls_trst
# My own well worked
#reset_config trst_only

jtag newtap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID
jtag_rclk 3



fx3-threadx.cfg
######################################
# Target: CYPRESS FX3 ARM926-ejs
######################################

#source [find fx3-common.inc]

######################
# Target configuration
######################
set _TARGETNAME $_CHIPNAME.cpu 
target create $_TARGETNAME arm926ejs -endian $_ENDIAN -chain-position $_TARGETNAME -rtos ThreadX
adapter_khz 1000



fx3-boot.cfg
######################################
# Target: CYPRESS FX3 ARM926-ejs
######################################

#source [find fx3-common.inc]

######################
# Target configuration
######################
set _TARGETNAME $_CHIPNAME.cpu 
target create $_TARGETNAME arm926ejs -endian $_ENDIAN -chain-position $_TARGETNAME 
adapter_khz 1000


Тип проекта в Qt Creator для ARM, FX3 и иже с ними может быть Generic, но я написал CMake правила для FX3: github.com/h4tr3d/fx3-cmake и использую CMake Project manager, что позволяет легко иметь несколько конфигураций в разных директориях, теневую сборку и повод не путаться в параметрах сборки на сложных проектах.

Команды OpenOCD: openocd.org/doc/html/General-Commands.html

Для вычисления Entry Point автоматически, можно собрать GDB с поддержкой питон и воспользоваться рекомендациями:

© Habrahabr.ru