Продолжение предыдущей статьи, в которой мы ускорили разработку под 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 

Здесь, левая колонка - вызывающиеся функции, правая - логи в консоли, выдаваемые функцией.

Продолжение

Тут и тут можно ознакомиться с удаленной отладкой приложений пользовательского уровня. В следующей же статье надеюсь добраться до пошаговой отладки ядра.

Комментарии (4)


  1. victor_1212
    21.09.2021 14:12

    >Как этот костыль заменить корректными методами

    припоминаю, когда-то было примерно так, можно попробовать

    + /* Allow the early environment to override the fdt address */

    + gd->fdt_blob = (void *)getenv_int("fdtcontroladdr", 16,

    + (uintptr_t)gd->fdt_blob);


    1. victor_1212
      21.09.2021 20:28
      +1

      упомянутая "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;

      +}


      1. almaz1c Автор
        21.09.2021 20:33

        	/* Allow the early environment to override the fdt address */
        	gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16,
        						(uintptr_t)gd->fdt_blob);
        
        /**
         * Decode the integer value of an environment variable and return it.
         *
         * @param name		Name of environemnt 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
         */
        ulong getenv_ulong(const char *name, int base, ulong default_val)
        {
        	/*
        	 * We can use getenv() here, even before relocation, since the
        	 * environment variable value is an integer and thus short.
        	 */
        	const char *str = getenv(name);
        
        	return str ? simple_strtoul(str, NULL, base) : default_val;
        }
        

        Аналогичный код вызывается и у меня. Но что-то идет не так.


        1. victor_1212
          21.09.2021 21:00

          может breakpoint поставить и посмотреть значение "gd->fdt_blob" после вызова, конечно без правильного адреса "device tree" вряд ли будет работать