2016-09-13

LLCON_DBG - отладка Android ядра на самых ранних этапах

Довольно часто разработчики ядер под Android устройства сталкиваются с тем, что собранное из новых исходников ядро не запускается. Обычно в таких случаях ошибки находят при помощи отладки через JTAG, UART, EmbeddedICE DCC. Но не всем разработчикам Android ядер доступны эти средства, да и не каждый готов разбирать личное устройство ради эксперементов.

В таких случаях можно выводить отладочную информацию на дисплей Android устройства. В этом блоге я уже писал о модуле LLCON, который позволяет выводить системные логи ядра на дисплей, а так же дублировать их в специальную облать оперативной памяти. Причем LLCON начинает свою работу на раннем этапе инициализации ядра. Но бывают ситуации, когда нужно выводить на экран монитора отладочную информацию ещё раньше, чем это позволяет делать модуль LLCON.

Для начала распишу подробно процесс загрузки Android ядра:
  • бутлоадер загружает в память boot.img
  • бутлоадер изменяет командную строку ядра (при необходимости)
  • бутлоадер ищет в DTB нужную QCOM платформу
  • бутлоадер передает управление на распаковщик ядра
  • start (compressed/head.S) - функция, обеспечивающая распаковку образа ядра
  • Kernel startup entry point (head.S) - функция инициализации самой низкоуровневой части ARM платформы
  • start_kernel (main.c) - функция инициализации общих базовых структур Linux ядра
  • setup_arch (main.c) - функция инициализации базовых частей QCOM платформы
  • paging_init (mmu.c) - функция инициализации таблицы страниц виртуальной памяти
  • do_basic_setup (main.c) - функция, с которой начинается полноценная работа Linux ядра
Так вот модуль LLCON создан для работы начиная с вызова функции do_basic_setup. Т.е. получается, что довольно большая часть инициализации Android ядра не поддается отладке через визуальное отображение хода исполнения.

Поэтому в случае проблем с запуском ядра нужно первым делом попробовать использовать модуль LLCON. Если же и в этом случае на дисплее пусто, то скорее всего ядро повисло на одном из выше названных этапов загрузки. И вот тут нужно уже использовать расширение LLCON_DBG, которое представляет из себя средства для отображения графической информации на экране даже на самых ранних этапах загрузки Android ядра.

Но сразу замечу, что текущая реализация LLCON_DBG на ядрах версии 3.10 и старше не работает после вызова paging_init. Как обойти это ограничение я пока еще не разобрался. На ядрах версии 3.4 расширения LLCON_DBG работают и после вызова paging_init (проверено).

Перед описанием реализации расширения LLCON_DBG я приведу все необходимые для него параметры:
  • CONFIG_LLCON_DBG_FB_ADDR - физический адрес FrameBuffer'а , который использует бутлоадер для отрисовки первичной заставки
  • CONFIG_LLCON_DBG_FB_VMEM - виртуальный адрес FrameBuffer'а , с которым будет работать LLCON_DBG
  • CONFIG_LLCON_DBG_FB_HEIGHT - высота дисплея в пикселах
  • CONFIG_LLCON_DBG_FB_WIDTH - ширина дисплея в пикселах
  • CONFIG_LLCON_DBG_FB_STRIDE - реальная ширина одной строки в байтах
  • CONFIG_LLCON_DBG_FONT_SIZE - размер используемого шрифта
Т.е. для включения LLCON_DBG на своём устройстве я в defconfig добавляю следующие параметры:
CONFIG_LLCON_DBG=y
CONFIG_LLCON_DBG_FB_ADDR=0x03200000
CONFIG_LLCON_DBG_FB_VMEM=0x03200000
CONFIG_LLCON_DBG_FB_HEIGHT=1024
CONFIG_LLCON_DBG_FB_WIDTH=720
CONFIG_LLCON_DBG_FB_STRIDE=2160
CONFIG_LLCON_DBG_FONT_SIZE=8 
Теперь можно приступить к процессу ловли багов на раннем этапе загрузки ядра. Представим, что стандартный функционал LLCON не работает (т.е. на экране кроме заставки бутлоадера ничего нету).

Перво наперво стоит отсечь вероятность того, что загрузку ядра "забраковал" бутлоадер. Для этого в код распаковщика нужно добавить следующий код:
#ifdef CONFIG_AUTO_ZRELADDR
  @ determine final kernel image address
  mov r4, pc
  and r4, r4, #0xf8000000
  add r4, r4, #TEXT_OFFSET
#else
  ldr r4, =zreladdr
#endif

#ifdef CONFIG_LLCON_DBG
  mov   r5, #0
  subs  r5, r5, #1
  mov   r3, #CONFIG_LLCON_DBG_FB_ADDR
  str   r5, [r3]
  adds  r3, r3, #CONFIG_LLCON_DBG_FB_STRIDE
  str   r5, [r3]
  adds  r3, r3, #CONFIG_LLCON_DBG_FB_STRIDE
  str   r5, [r3]
  adds  r3, r3, #CONFIG_LLCON_DBG_FB_STRIDE
  str   r5, [r3]
#endif

  bl cache_on

restart: adr r0, LC0
  ldmia r0, {r1, r2, r3, r6, r10, r11, r12}
  ldr sp, [r0, #28]
Данный код должен нарисовать в верхнем левом углу экрана короткую вертикальную линию высотой в 4 пикселя (см. фотоснимок ниже). Если при тестировании собранного ядра эта засечка появляется на экране, то бутлоадер отрабатывает корректно и проблему стоит искать далее по коду.

Далее по списку следует этап "Kernel startup entry point" (head.S). До этого момента CPU обеспечивал реальную адресацию памяти (доступ по физическим адресам). И поэтому можно было свободно рисовать на экране как в коде бутлоадера (читайте в этом блоге о IBL), так и в коде загрузчика ядра. После передачи управления на "Kernel startup entry point" (head.S) CPU переводится в режим виртуальной адресации. Поэтому нам следует в таблицу виртуальных страниц добавить физический адрес FrameBuffer'а. Эту процедуру осуществляет следующий код:
strne r6, [r3]

#if defined(CONFIG_ARM_LPAE) && defined(CONFIG_CPU_ENDIAN_BE8)
 sub r4, r4, #4   @ Fixup page table pointer
      @ for 64-bit descriptors
#endif

#ifdef CONFIG_LLCON_DBG
 ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags  
 add r0, r4, #CONFIG_LLCON_DBG_FB_ADDR >> (SECTION_SHIFT - PMD_ORDER)
 orr r3, r7, #CONFIG_LLCON_DBG_FB_VMEM
 orr r3, r3, #PMD_SECT_XN
 str r3, [r0]
#endif

#ifdef CONFIG_DEBUG_LL
#if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING)
 /*
  * Map in IO space for serial debugging.
Хочу заметить, что в данном коде виртуальный адрес соответствует физическому, т.к в моем случае обе константы равны 0x03200000. Указание бита NX предотвращает исполнение кода в данном сегменте памяти. Так же замечу, что на 32-битных ARMv7 сегмент памяти имеет размер в 1 MiB. Поэтому для рисования на дисплее нам будет доступен только первый мегабайт видеопамяти, что вполне достаточно для отладки.

Теперь для отладки ядра стоит в самое начало функции start_kernel вставить такой вот вызов:
asmlinkage void __init start_kernel(void)
{
 char * command_line;
 extern const struct kernel_param __start___param[], __stop___param[];

 llcon_dbg_vline(25, 12);
 llcon_dbg_u16(1, 1, 3);
 llcon_dbg_u16(1, 7, 65535);
 llcon_dbg(2, 1, "%s:%d", __func__, __LINE__);
 llcon_dbg(3, 1, "Linux v%d.%d.%d", (LINUX_VERSION_CODE >> 16) & 0xFF,
  (LINUX_VERSION_CODE >> 8) & 0xFF, LINUX_VERSION_CODE & 0xFF);

 /*
  * Need to run as early as possible, to initialize the
  * lockdep hash:
  */
 lockdep_init();
После запуска тестируемого ядра с выше указанными изменениями на экране Android устройства должно отобразиться следующее:
На фотоснимке можно заметить, что самую короткую вертикальную линию нарисовал распаковщик ядра. Всё остальное нарисовала функция start_kernel.

Если же никакого текста не появилось на экране, значит проблема где то в недрах head.S. Про отладку кода head.S писать тут не буду, т.к. выше я уже приводил код на асме, который выводит короткую линию (см. патч распаковщика).

Сразу замечу, что для вывода отладочной информации доступны следующие функции:
  • llcon_dbg_vline - рисует вертикальную линию от верхней границы экрана
  • llcon_dbg_text - выводит фиксированный текст в указанной позиции
  • llcon_dbg_u16 - выводит пятизначное число в указанной позиции
  • llcon_dbg - выводит форматированный текст в указанной позиции
Теперь мы убедились, что до вызова функции start_kernel дело доходит. А вот до вызова do_basic_setup дело уже не доходит, т.к выше я условился, что вывод ядерных логов через LLCON не работает.

Что бы локализировать проблеммное место нужно методично расставлять вызовы любой функции llcon_dbg*.

Напомню, что на ядрах версии 3.10 и выше вывод графики на экран следует прекратить после вызова функции paging_init. А вот на ядре версии 3.4 можно отлаживать инициализацию ядра влоть до вызова llcon_init, которую я рекомендую размещать в самом начале функции do_basic_setup.

Данную технику отладки пришлось разработать только из-за того, что ядро 3.10 от команды sonyxperiadev нивкакую не хотело выводить ядерные логи через LLCON на моём Android устройстве.

Комментариев нет:

Отправить комментарий