Придя в 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
![](https://habrastorage.org/getpro/habr/upload_files/a90/efa/0eb/a90efa0eb758eb2fdb89d62fba53c7c6.png)
![](https://habrastorage.org/getpro/habr/upload_files/e0a/86f/126/e0a86f1267e5ad9691494f3bcec8c48b.png)
![](https://habrastorage.org/getpro/habr/upload_files/299/0ba/5ea/2990ba5ea695f2f26f956df52818e1d3.png)
Такой возможности в принципе не предусмотрено! Предлагается только два варианта оптимизации: -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:
![](https://habrastorage.org/getpro/habr/upload_files/229/ca0/b0c/229ca0b0c9840642a213775c497d7ff7.png)
Запускаем 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:
![](https://habrastorage.org/getpro/habr/upload_files/6fc/708/9a1/6fc7089a188087d0c6f493283bfca621.png)
Если продолжите шагать по коду командами s и n, то можете утомиться. Чтобы быстрее попасть в интересующую вас функцию, задайте точку остановки, например, функцию start_kernel
:
(gdb) b start_kernel (gdb) c
![](https://habrastorage.org/getpro/habr/upload_files/b1b/c29/f73/b1bc29f73b3247ab8af447ca0abb94e4.png)
попробуйте пошагать и в ней. Не должно быть никаких хаотичных перемещений по коду, т.е. если вы задаете команду n (перепрыгнуть функцию), то вы недолжны неожиданно оказаться в теле какой-либо другой функции. Значения большинства встречающихся в коде переменных должно также быть доступно для считывания без каких-либо сообщений «optimized out«. Для сверки можете использовать Eclipse, чтобы убедиться, что все действительно идет по плану:
![](https://habrastorage.org/getpro/habr/upload_files/e02/dd6/640/e02dd6640eef106db3a404cd5acca713.png)
На этом все.
В дальнейших статьях мы:
-
запустим в отладчике работу u-boot и ядра последовательно;
-
разберемся, как загрузчик передает управление ядру;
-
построим карту загрузки ядра по аналогии с картой U-boot;
-
упакуем инструменты разработки в docker-контейнер, что сведет к минимуму ошибки при развертывании среды разработки новыми разработчиками (в том числе новичками), включая и запуск графической IDE, и подключение к USB программатору из докер-контейнера;
-
научимся дебажить модули ядра и многое другое.
ссылка на оригинал статьи https://habr.com/ru/articles/592671/
Добавить комментарий