Перейти к содержанию

Отладка на уровне ядра

Исходники по этой теме

Модули ядра (формат ELF, расширение ko, лежат в /lib/modules/) — фрагменты кода, которые могут загружаться и выгружаться из ядра по требованию. Большинство драйверов ПК реализованы в виде модулей. Для встраевоемых устройств большинство драйверов обычно статически компилируются в ядро Linux. По умолчанию модули ядра находятся в каталоге. Модуль ядра использует память ядра без механизмов защиты.

Модуль должен содержать init_module() (вызывается insmod) и cleanup_module() (вызывается rmmod).

  • lsmod – список загруженных модулей.
  • modinfo – получить информацию о модуле.
  • modprobe – 'smart' загрузка/выгрузка модулей с зависимостями.
obj-m += hellomod.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
    make ic /lib/modules/$(shell uname -r)/build M=$(PWD) clean
cd kernel_debugging
make all
sudo insmod hellomod.ko
lsmod | grep hellomod

Отладка

Для отладки полезен printk. Например printk(KERN_INFO "Message: %s\n", arg); содержит KERN_INFO— это log level (он связан со строкой формата, log level не является отдельным аргументом). Все сообщения, напечатанные с помощью printk(), пересылаются в специальный кольцевой буфер ядра. Для доступа к сообщениям можно использовать команду dmesg

Для проверки текущего значения console_log_level:

$ cat /proc/sys/kernel/printk
4        4        1        7

Результат текущий (4), по умолчанию(4), минимальный(1) и boot-time-default(8).

Для изменения текущего console_loglevel можно изменить /proc/sys/kernel/printk. Для печати всех сообщений на консоль:

echo 8 > /proc/sys/kernel/printk

Чтобы изменить console_loglevel для печати с KERN_WARNING (4) и более для консоли:

dmesg –n 5

Дополнительно можно исползовать чуть более гибкое DebugFS с макросом pr_devel(). Важно пересобрать ядро linux с DEBUG и CONFIG_DYNAMIC_DEBUG (дополнительно).

Если ARM процессор CoreSight, то через JTag/SWD (тот же STLink) можно управления потоком выполнения.

KGDB

В linux нельзя отлаживать ядро на нем же, но можно на другом хосте.

Настроить .config:

  • Set CONFIG_DEBUG_INFO to y
  • Set CONFIG_KGDB to y
  • Comment out CONFIG_RANDOMIZE_BASE
  • Set CONFIG_GDB_SCRIPTS to y
  • Set CONFIG_KGDB_SERIAL_CONSOLE to y

и собрать ядро.

Выполнение задания

Убедиться, что включены в конфигурации следующие поля:

  • CONFIG_DEBUG_INFO=y
  • CONFIG_KGDB=y
  • CONFIG_RANDOMIZE_BASE=y
  • CONFIG_GDB_SCRIPTS=y
  • CONFIG_KGDB_SERIAL_CONSOLE=y

Собираем ОС и запускаем:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image -j$(nproc)
cd ..
qemu-system-aarch64 \
  -M virt -cpu cortex-a57 -m 512M \
  -kernel linux-v6.18.8/arch/arm64/boot/Image \
  -initrd initramfs.cpio.gz \
  -append "console=ttyAMA0 rw nokaslr" -nographic \
  -s -S

qemu запущен с флагом S s = приостанавливает загрузку и запускает сервер gdb (порт по умолчанию — 1234).

В новой консоли:

cd build/linux-v6.18.8
gdb-multiarch vmlinux
set arch aarch64
target remote :1234

Дальше начинается пошаговая стратегия, где можно отследить каждый шаг ОС:

b start_kernel
c
list
n
(gdb) set arch aarch64
The target architecture is set to "aarch64".
(gdb) target remote :1234
Remote debugging using :1234
0x0000000040000000 in ?? ()
(gdb) b start_kernel
Breakpoint 1 at 0xffff800081e60930: file init/main.c, line 915.
(gdb) c
Continuing.

Breakpoint 1, start_kernel () at init/main.c:915
915             set_task_stack_end_magic(&init_task);
(gdb) list
910     void start_kernel(void)
911     {
912             char *command_line;
913             char *after_dashes;
914
915             set_task_stack_end_magic(&init_task);
916             smp_setup_processor_id();
917             debug_objects_early_init();
918             init_vmlinux_build_id();