Придя в embedded linux из мира микроконтроллеров, такого привычного инструмента отладки кода, как пошаговая отладка кода на целевой железке с помощью аппаратного программатора, - очень не хватало. В предыдущих статьях описано, как мы учились дебажить загрузчик u-boot: 1, 2. С ядром все оказалось сложнее. Например, выяснилось, что ядро Linux в принципе невозможно скомпилировать с отключенной оптимизацией (-O0). В статье описывается как нам все таки удалось запустить ядро на микропроцессоре ARM в режиме пошаговой отладки.
Подготовка исходников
$ git clone https://github.com/wireless-road/imx6ull-openwrt.git
$ cd imx6ull-openwrt
$ ./compile.sh flexcan_ethernet
Исходники ядра после завершения сборки можно найти тут:
./build_dir/target-arm_cortex-a7+neon-vfpv4_musl_eabi/linux-imx6ull_cortexa7/linux-4.14.199/
Сборка ядра с флагом -Og
Первым делом мы попытались скомпилировать ядро с отключенной оптимизацией и наткнулись на неприятный сюрприз:
$ cd ./build_dir/target-arm_cortex-a7+neon-vfpv4_musl_eabi/linux-imx6ull_cortexa7/linux-4.14.199/
$ make menuconfig
Такой возможности в принципе не предусмотрено! Предлагается только два варианта оптимизации: -O2
и -Os
. Отладка в GDB
в обоих случаях не даст ничего хорошего, - вместо пошагового прохождения кода Program Counter
будет хаотично перепрыгивать целые куски кода и вызовы функций. При попытке вычитать значения переменных вы будете то и дело натыкаться на сообщение: Optimized Out
. Если ручками залезть в .config
и Makefile
и попытаться собрать ядро с флагом -O0
, то сборка упадет с большим количеством сообщений об ошибке. Как выяснилось, это в принципе невозможно, поскольку оптимизация при сборке ядра используется для совершенно других целей, для которых флаги оптимизации не должны использоваться, а именно, для отключения не использующегося кода. Грязный хак, от которого, видимо, уже не избавиться. На наше счастье, кое кто до нас все таки озадачивался проблемой отладки ядра и даже написал патч, который позволяет собрать ядро с флагом -Og
, но, почему-то, отклоненный сообществом. В итоге, пришлось его адаптировать вручную под наши исходники.
Суть патча заключается в том, чтобы добавить третий вариант сборки ядра, оптимизированного под отладку:
+ifdef CONFIG_CC_OPTIMIZE_FOR_DEBUGGING
+KBUILD_CFLAGS += -Og
+KBUILD_CFLAGS += $(call cc-disable-warning,maybe-uninitialized,)
+else
ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
KBUILD_CFLAGS += -Os $(EXTRA_OPTIMIZATION)
else
KBUILD_CFLAGS += -O2 -fno-reorder-blocks -fno-tree-ch $(EXTRA_OPTIMIZATION)
endif
+endif
и отключить дефайнами возникающие на этапе компиляции ошибки:
+#if !defined(__CHECKER__) && !defined(CONFIG_CC_OPTIMIZE_FOR_DEBUGGING)
# define __compiletime_warning(message) __attribute__((warning(message)))
# define __compiletime_error(message) __attribute__((error(message)))
#endif /* __CHECKER__ */
Полный код патча и связанных с ним изменений можно глянуть тут: 1, 2.
Device Tree и JTAG
Далее нужно убедиться, что пины микропроцессора, на которые выведен JTAG интерфейс не переиспользуется для каких-либо других целей. Для этого открываем, используемый нами dts-файл и ищем упоминания jtag-пинов. Если вы находите что-либо похожее:
pinctrl_sai2: sai2grp {
fsl,pins = <
MX6UL_PAD_JTAG_TDI__SAI2_TX_BCLK 0x17088
MX6UL_PAD_JTAG_TDO__SAI2_TX_SYNC 0x17088
MX6UL_PAD_JTAG_TRST_B__SAI2_TX_DATA 0x11088
MX6UL_PAD_JTAG_TCK__SAI2_RX_DATA 0x11088
MX6UL_PAD_JTAG_TMS__SAI2_MCLK 0x17088
>;
};
то вам не повезло. Возможность отладки ядра вы сможете получить только отключив интерфейс, которые переиспользует JTAG-пины. Их придется отключить. Т.е. в худшем случае, если вам нужно отладить функционал, задействующий JTAG-пины микропроцессора, у вас не получится этого сделать. Т.е. необходимость использования JTAG-интерфейса нужно заложить еще на этапе разработки печатной платы устройства. Либо уточнить в документации на приобретаемый модуль.
Последнее, что можно сделать, создать конфигурацию сборки, в котором по умолчанию выбрана оптимизация для целей отладки:
CONFIG_KERNEL_CC_OPTIMIZE_FOR_DEBUGGING=y
После этого можно выполнить повторную сборку образа:
./compile.sh flexcan_ethernet
либо только ядра отдельно:
make target/linux/compile
Графическая IDE Eclipse
Ее настройки под исходники ядра практически ничем не отличается от настройки для отладки загрузчика U-boot: раздел "Установка IDE и создание проекта" предыдущей статьи с тем лишь отличием, что при создании проекта нужно выбрать каталог с исходниками ядра вместо исходников загрузчика.
На данный момент нам пока не удалось корректно дебажить ядро из Eclipse, а потому оно пока используется лишь для навигации по коду. В будущем, планируем прикрутить полноценную графическую отладку кода из IDE.
Отладка ядра в консольном режиме
Итак, приступаем непосредственно к отладке.
Запускаем железку и прерываем загрузку ядра, чтобы остаться в консоли U-boot:
Запускаем GDB сервер:
$ JLinkGDBServer -device MCIMX6Y2 -if JTAG -speed 1000
Запускаем GDB сессию:
$ cd ./build_dir/target-arm_cortex-a7+neon-vfpv4_musl_eabi/linux-imx6ull_cortexa7/
$ gdb-multiarch vmlinux.debug --nx
в консоли gdb сесии:
(gdb) target remote localhost:2331
(gdb) restore flexcan_ethernet-uImage binary 0x82000000
(gdb) restore image-flexcan_ethernet.dtb binary 0x83000000
(gdb) b __hyp_stub_install
Breakpoint 1 at 0x80110b20: file arch/arm/kernel/hyp-stub.S, line 89.
(gdb) c
Continuing.
после этого должна ожить консоль загрузчика. Выполните в ней загрузку ядра:
=> bootm 82000000 - 83000000
После этого переключитесь обратно в консоль gdb сессии, вы должны увидеть что-то подобное:
Breakpoint 1, __hyp_stub_install () at arch/arm/kernel/hyp-stub.S:89
89 store_primary_cpu_mode r4, r5, r6
(gdb)
Вы находитесь в той точке, с которой начинается работа ядра! Попробуйте погулять по коду командами s и n, и вывести значения переменных/регистров командой p:
Если продолжите шагать по коду командами s и n, то можете утомиться. Чтобы быстрее попасть в интересующую вас функцию, задайте точку остановки, например, функцию start_kernel
:
(gdb) b start_kernel
(gdb) c
попробуйте пошагать и в ней. Не должно быть никаких хаотичных перемещений по коду, т.е. если вы задаете команду n (перепрыгнуть функцию), то вы недолжны неожиданно оказаться в теле какой-либо другой функции. Значения большинства встречающихся в коде переменных должно также быть доступно для считывания без каких-либо сообщений "optimized out". Для сверки можете использовать Eclipse, чтобы убедиться, что все действительно идет по плану:
На этом все.
В дальнейших статьях мы:
запустим в отладчике работу u-boot и ядра последовательно;
разберемся, как загрузчик передает управление ядру;
построим карту загрузки ядра по аналогии с картой U-boot;
упакуем инструменты разработки в docker-контейнер, что сведет к минимуму ошибки при развертывании среды разработки новыми разработчиками (в том числе новичками), включая и запуск графической IDE, и подключение к USB программатору из докер-контейнера;
научимся дебажить модули ядра и многое другое.
Комментарии (12)
Kitsok
01.12.2021 07:23+2Класс! Я всегда обходился отладочными принтами, даже в загрузчике, но про JTAG как предпоследний способ помню. Спасибо за статьи!
lorc
01.12.2021 10:51+1Я бы рекомендовал еще посмотреть на OpenOCD. Он неожиданно восстал из мертвых и начал активно развиваться.
А по поводу дебага принтами - это хорошо когда они есть. Мы когда-то чинили раннюю загрузку то ли U-Boot, то ли Xen, уже не помню точно. Но факт в том, что на тех этапах загрузки консоль еще не работала. Кто-то придумал гениальную идею - дергать SMC из разных мест, и смотреть как Secure Monitor (это еще на armv7 было) ругается на неизвестный ID запроса.
Kitsok
01.12.2021 17:08Хм, с этой точки зрения я на OpenOCD не смотрел очень давно, а вот программироваться всякие CPLD - только в путь. Спасибо за наводку!
lorc
01.12.2021 22:53+1Ну там например активно развивается поддержка ARMv8, что меня безмерно радует. Плюс он умеет одну очень крутую штуку, которую не может дорогущий Lauterbach - одновременный дебаг нескольких ядер с разной архитектурой. На том SoC, с которым мы в основном работаем есть как и здоровые Cortex A5x на ARMv8, так и пару Cortex R на ARMv7. С OpenOCD я могу отлаживать их одновременно. В Trace32 - нет.
realimba
01.12.2021 15:23-1наступал 2022 - мы дебажили принтами и роскошным gdb, куда уж тут "убогому" windbg..
Kitsok
01.12.2021 17:06+1Даже в 2122 будут этапы загрузки компьютера, на которых без подобной околожелезной отладки никуда не деться.
lorc
01.12.2021 22:57gdb сервера есть практически везде: от софтовых в Android и заканчивая типа "аппаратными" в каком-нибудь Black Magic Probe. Ну и само собой они есть в qemu и openocd.
А вот удаленный протокол WinDbg поддерживает... кто? Только Windows?
ignat99
01.12.2021 17:24Для отладки в реальном режиме времени надо делать инструментализацию ядра и подключать профилировщик.
Отладка ядра дебагом практически ничего не даёт. Так как многие функции запускают процессы (физические) на железе параллельно. Поэтому либо надо знать порядок запуска, либо отлавливать события в реальном режиме времени. IMHOlorc
01.12.2021 22:49Зависит от того что нужно отлаживать. Если какой-нибудь высокопараллельный процесс вроде скедулинга или там отрисовки буферов в DRM - то тут конечно трейсинг (особенно аппартный) рулит. А вот классические профилировщики типа oprofile помогают в очень редких случаях.
Но бывает класс задач где как раз нужен пошаговый отладчик, чтобы например посмотреть состояние памяти, регистров периферии, да и банально пошагать по ассемблерному коду, который писал вручную.
dmiprok
Время за полночь, наткнулся на Вашу статью, ничего не понимаю, но прочитал с интересом.