Продолжение предыдущей статьи, в которой мы ускорили разработку под embedded linux. Рабочая станция + sftp сервер + nfs сервер ускорили на порядок (10х) доставку изменений кода на целевое железо. Теперь не нужно часами компилировать код. В этой статье продолжаем очеловечивать разработку. На этот раз прикручиваем полноценную графическую IDE и пошаговую отладку кода на целевом железе с помощью программатора J-Link. Но пока только загрузчика U-boot. И автоматизируем развертывание рабочей среды разработчика с помощью Docker.
Микропроцессор, используемый в статье - imx6ull (ArmV7), программатор - j-link. В случае, если ваш процессор есть в списке поддерживаемых архитектур, - то у вас есть все шансы повторить все шаги, описанные в статье и получить возможность полноценной отладки программного кода на удаленном железе. По крайней мере, с ARM-ами затруднений возникнуть не должно. В данной статье рассматривается только отладка загрузчика (u-boot). Надеюсь, что и до ядра доберемся в одной из следующих статей.
Подготовка железа
Подключаем программатор по JTAG к целевой железке.
подключаем программатор по USB к хосту (рабочему ПК).
подключаем железку к хосту с помощью COM-порта.
подаем питание на железку.
Может случиться такое, что, в ядре выводы JTAG-интерфейса переинициализируются на другие функции. В этом случае, сразу после подачи питания нужно не дать загрузчику начать загрузку ядра. Для этого нужно в консоли нажать любую клавишу до окончания обратного отсчета и остаться в консоли загрузчика.
Установка драйверов
Устанавливаем gdb-multiarch:
$ sudo apt-get install gdb-multiarch
Скачиваем драйвера j-link и устанавливаем их:
$ dpkg -i /drivers/JLink_Linux_V754b_x86_64.deb
Сборка toolchain
В вашем случае он может быть уже предустановлен/предскомпилирован. В нашем случае компилируем:
$ mkdir /opt/eclipse/
$ cd /opt/elcipse/
$ git clone https://github.com/wireless-road/imx6ull-openwrt.git
$ cd imx6ull-openwrt
$ ./compile.sh flexcan_wifi
Установка IDE и создание проекта
Скачиваем Eclipse IDE for Embedded C/C++ Developers и распаковываем архив в /opt/eclipse.
Нажимаем File --> New Project и выбираем Makefile Project with Existing Code:
Задаем каталог с исходниками U-boot. В нашем случае, их можно найти в build_dir/target-arm_cortex-a7+neon-vfpv4_musl_eabi/u-boot-wirelessroad_ecspi3/u-boot-2017.07:
Далее, необходимо задать переменные окружения для корректной работы eclipse с вашим toolchain. Для этого кликаете правой кнопкой мыши на проект в окне Eclipse, нажимаете Properties, а далее - C/C++ Build --> Environment и создаете несколько переменных:
ARCH=arm
CROSS_COMPILE=arm-openwrt-linux-muslgnueabi-
STAGING_DIR=/opt/eclipse/imx6ull/imx6ull-openwrt/staging_dir/
Переменная STAGING_DIR специфична для OpenWrt, - вам она, возможно, и не нужна.
Далее редактируем уже имеющуюся переменную PATH, добавив в ее начало следующее:
/opt/eclipse/imx6ull-openwrt/build_dir/target-arm_cortex-a7+neon-vfpv4_musl_eabi/linux-imx6ull_cortexa7/linux-4.14.199/scripts/dtc/:/opt/eclipse/imx6ull-openwrt/staging_dir/target-arm_cortex-a7+neon-vfpv4_musl_eabi/host/bin:/opt/eclipse/imx6ull-openwrt/staging_dir/hostpkg/bin:/opt/eclipse/imx6ull-openwrt/staging_dir/toolchain-arm_cortex-a7+neon-vfpv4_gcc-7.5.0_musl_eabi/bin:/opt/eclipse/imx6ull-openwrt/staging_dir/host/bin:/bin:
Здесь, несколько каталогов тулчейна, разделенных двоеточием. В итоге, должно получиться следующее:
Следующим шагом отключаем дефолтный компилятор:
и задаем пути к заголовочным файлам нашего тулчейна:
/opt/eclipse/imx6ull-openwrt/staging_dir/toolchain-arm_cortex-a7+neon-vfpv4_gcc-7.5.0_musl_eabi/arm-openwrt-linux-muslgnueabi/include/
/opt/eclipse/imx6ull-openwrt/staging_dir/toolchain-arm_cortex-a7+neon-vfpv4_gcc-7.5.0_musl_eabi/include/
/opt/eclipse/imx6ull-openwrt/staging_dir/toolchain-arm_cortex-a7+neon-vfpv4_gcc-7.5.0_musl_eabi/lib/gcc/arm-openwrt-linux-muslgnueabi/7.5.0/include/
Не забываем включить параллельную сборку:
Готово. Теперь можно попробовать собрать u-boot. Нажмите правой кнопкой мыши на проект и затем Clean Project для очистки проекта, Build Project для сборки:
Далее, необходимо, либо включить в концигурации u-boot опцию CONFIG_TOOLS_DEBUG, либо с помощью утилиты make menuconfig, либо в ручную задав в .config файле следующее:
CONFIG_TOOLS_DEBUG=y
Возможно, в ide вы не найдете этого файла, поскольку он скрыт. Включить отображение скрытых файлов можно следующим образом:
После этого пересоберите проект, чтобы отладочные символы попали в итоговый бинарник.
Осталось совсем чуть-чуть, а именно настроить конфигурацию программатора. Нажмите Run --> Debug Configurations и задайте настройки:
Автоматизируй это.
Довольно длительная последовательность действий и практически каждый клиент, использующий наше BSP, каждый новый разработчик на каком-нибудь этапе да и споткнется, проверено на практике. Поэтому, логично будет автоматизировать развертнывание всей среды разработки, включая скачивание исходных кодов, сборку тулчейна и установку IDE. При этом, упаковка в docker контейнер дает надежку, что вся процедура развертывания будет более-менее стабильно разработать на максимально большом количестве компьютеров. Таким образом, вся выше-описанная инструкция заменяется четырьмя консольными командами:
$ git clone https://github.com/wireless-road/openwrt-ide.git
$ docker-compose run --name openwrt openwrt
И уже из консоли контейнера:
$ ./setup_ide.sh
$ /opt/eclipse/eclipse/eclipse
Исходники среды и более подробную инструкцию с некоторыми пояснениями можно найти тут. Eclipse, к сожалению, имеет ограниченные возможности для работы из консоли, но, к счастью, он portable. Таким образом, один раз настроив проект в eclipse, и упаковав его обратно в архив, можно переносить пред-настроенную IDE с машины на машину. Использование docker, в добавок к изоляции среды позволяет также решить возможные ошибки при распаковке архива по другому пути. В теории должно работать, практика покажет как оно на самом деле.
И последнее, - грязный хак, без которого, в нашем случае, u-boot не хотел работать под отладчиком. Вначале, в консоли u-boot вводим:
=> printenv fdtcontroladdr
fdtcontroladdr=8ef216d8
и данное значение хард-кодим в u-boot/lib/fdtdec.c в функции fdtdec_setup():
Как этот костыль заменить корректными методами мы пока, к сожалению, не разобрались. Надеюсь, осилим в ближайшее время, но пока так. В вашем случае, если eclipse не сможет стартануть отладку кода, может оказаться полезным запустить отладку в консольном режиме. Для этого запускаете GDBServer:
$ JLinkGDBServer -device MCIMX6Y2 -if JTAG -speed 1000
а следом запускаете консольный дебаггер, где u-boot - скомпилированный ранее образ загрузчика:
$ gdb-multiarch u-boot --nx
(gdb) target remote localhost:2331
(gdb) monitor reset
(gdb) monitor halt
(gdb) monitor sleep 200
(gdb) load
а далее шагаете до участка кода, на котором все рушится:
(gdb) s
reset () at arch/arm/cpu/armv7/start.S:40
40 b save_boot_params
(gdb) s
save_boot_params () at arch/arm/cpu/armv7/start.S:116
116 b save_boot_params_ret @ back to my caller
(gdb) s
save_boot_params_ret () at arch/arm/cpu/armv7/start.S:56
56 mrs r0, cpsr
(gdb) s
57 and r1, r0, #0x1f @ mask mode bits
(gdb) s
58 teq r1, #0x1a @ test for HYP mode
Тут можно ознакомиться с тем, как мы искали проблемный код.
Собственно, отладка
Задаем точку остановки, например функцию initcall_run_list, которая в цикле запускает функции инициализации перферии:
И нажимаем на кнопку Debug, а следом на кнопку Resume:
В итоге, должны попасть в выбранную нами функцию. Попробуйте нажать на кнопку Step Over несколько раз - курсор текущего положения должен начать перемещаться по коду:
Давайте попробуем начать знакомство с u-boot. Дойдя до строчки подсвеченной зеленым цветом на предыдущем скриншоте поставьте на ней точку остановки (двойной щелчок левой кнопкой мыши по синему полю слева напротив нужной строки), а далее нажимайте на кнопку Resume. После каждого нажатия на Resume, наводите курсор init_fnc_ptr, - в появляющемся всплывающем окне будет появляться название очередной функции, которая будет вызвана. В итоге, можно получить предстваление о том, какими функциями собтсвенно, и происходит инициализация периферии:
setup_mon_len
fdtdec_setup
initf_malloc
initf_bootstage
init_console_record
arch_cpu_init
march_cpu_init
initf_dm
arch_cpu_init_dm
board_early_init_f
get_clocks
timer_init
board_post_clk_init
env_init
init_baud_rate
serial_init
console_init_f
display_options U-Boot 2017.07 (Sep 19 2021 - 11:24:07 +0300)
display_text_info
print_cpuinfo CPU: Freescale i.MX6ULL rev1.1 528 MHz (running at 396 MHz)
CPU: Industrial temperature grade (-40C to 105C) at 70C
Reset cause: unknown reset
show_board_info Model: WirelessRoad ecspi3
Board: MX6ULL WirelessRoad ECSPI3 module
init_func_i2c I2C: ready
announce_dram_init DRAM:
dram_init
setup_dest_addr
reserve_round_4k
reserve_mmu
reserve_video
reserve_trace
reserve_uboot
reserve_malloc
reserve_board
setup_machine
reserve_global_data
reserve_fdt
reserve_bootstage
reserve_arch
reserve_stacks
dram_init_banksize
show_dram_config 256MiB
display_new_sp
reloc_fdt
reloc_bootstage
setup_reloc Setup fec 0
NAND: 0 MiB
MMC: FSL_SDHC: 0
board_spi_cs_gpio bus 2 cs 0
SF: Detected w25q256 with page size 256 Bytes, erase size 4 KiB, total 32 MiB
In: serial
Out: serial
Err: serial
Net: FEC [PRIME]
Hit any key to stop autoboot: 0
Здесь, левая колонка - вызывающиеся функции, правая - логи в консоли, выдаваемые функцией.
Продолжение
Тут и тут можно ознакомиться с удаленной отладкой приложений пользовательского уровня. В следующей же статье надеюсь добраться до пошаговой отладки ядра.
victor_1212
>Как этот костыль заменить корректными методами
припоминаю, когда-то было примерно так, можно попробовать
+ /* Allow the early environment to override the fdt address */
+ gd->fdt_blob = (void *)getenv_int("fdtcontroladdr", 16,
+ (uintptr_t)gd->fdt_blob);
victor_1212
упомянутая "getenv_int" - функция из той же древней patch:
https://lists.denx.de/pipermail/u-boot/2011-October/104855.html
+/**
+ * Decode the value of an environment variable and return it.
+ *
+ * @param name Name of environment variable
+ * @param base Number base to use (normally 10, or 16 for hex)
+ * @param default_val Default value to return if the variable is not
+ * found
+ * @return the decoded value, or default_val if not found
+ */
+static int getenv_int(const char *name, int base, int default_val)
+{
+ char tmp[64]; /* long enough for environment variables */
+ int i = getenv_f(name, tmp, sizeof(tmp));
+ return (i > 0)
+ ? (int) simple_strtoul(tmp, NULL, base)
+ : default_val;
+}
almaz1c Автор
Аналогичный код вызывается и у меня. Но что-то идет не так.
victor_1212
может breakpoint поставить и посмотреть значение "gd->fdt_blob" после вызова, конечно без правильного адреса "device tree" вряд ли будет работать