Linux kernel debug

Материал из OpenWiki
Перейти к: навигация, поиск

Использование только отладчика GDB

Запуск отладчика для работы с ядром почти ни чем не отличается от отладки выполняющегося процесса.

$ sudo gdb vmlinux /proc/kcore

Можно пользоваться практически всеми командами программы GDB для чтения информации. Если ядро было собрано с отладочной информацией, то GDB сможет выдавать больше информации (разыменовывать указатели и выводить дампы структур данных). Нельзя изменять данные ядра. Нет возможности пошагово выполнять код ядра, или устанавливать точки остановки.

Отладка ядра Linux с использованием отладчика GDB и эмулятора аппаратного обеспечения QEMU

Подготовка ядра

Ядро необходимо собрать с отладочной информацией:

$ cd /usr/src/linux
$ make menuconfig
Kernel hacking  --->
   [*] Compile the kernel with debug info
   [*] Compile the kernel with frame pointers

Или в файле .config изменить строки "# CONFIG_DEBUG_KERNEL is not set" и "# CONFIG_FRAME_POINTER is not set" на "CONFIG_DEBUG_KERNEL=y" и "CONFIG_FRAME_POINTER=y" соответственно.

Запуск QEMU

После того как ядро будет собрано можно ( в том случае если не требуется отлаживать модули ) сразу же приступать к запуску QEMU без подготовки образа диска.

#Запускаем QEMU с ядром, которое собираемся отлаживать.
#Эта команда предназначена для того чтобы отлаживать ядро до того, как оно начнёт обращаться к диску.
#Для неё не требуется образа диска с установленной системой.
$ qemu -s -S -hda /dev/zero -kernel /usr/src/linux/arch/x86/boot/bzImage &
#Для данной команды требуется образ диска с установленной системой.
#В параметре root необходимо указать устройство с корневым разделом.
#Команда позволяет отлаживать любую функциональность ядра, за исключением модулей.
$ qemu -s -S -hda hda.img -kernel /usr/src/linux/arch/x86/boot/bzImage -append root=/dev/sda &

Аргумент -s необходим для того, чтобы эмулятор ожидал подключения отладчика к порту 1234. Сменить порт можно аргументом [-p порт].
Аргумент -S необходим для того, чтобы QEMU не запускал процессор при запуске эмулятора ( это необходимо для того, что бы ядро можно было отлаживать начиная со start_kernel ).

В том случае, если требуется отладка модулей то необходимо сначала скопировать модули и ядро на образ диска QEMU. Далее предполагается что образ диска в формате raw.

Примечание:
Преобразовать из формата, например qcow, в raw можно следующей командой:

#qemu-img convert filename [-O output_format ] output_filename
$ qemu-img convert qemu-disk-image.qcow -O raw qemu-disk-image.raw

Из других форматов преобразуется аналогичным образом.

Монтирование образа диска:

$ sudo fdisk -ul hda.img
Диск hda.img: 0 МБ, 0 байт 
255 heads, 63 sectors/track, 0 cylinders, всего 0 секторов 
Units = секторы of 1 * 512 = 512 bytes 
Disk identifier: 0x00000000 
Устр-во Загр     Начало       Конец       Блоки   Id  Система 
hda.img1   *          63       64259       32098+  83  Linux 
hda.img2           64260      562274      249007+  82  Linux своп / Solaris 
hda.img3          562275     7164989     3301357+  83  Linux

$ sudo mkdir /mnt/guest

#Аргумент offset рассчитывается следующим образом:  начало * Units
#63 * 512 = 32256
#562275 * 512 = 287884800
$ sudo mount -o loop,offset=287884800 hda.img /mnt/guest/
$ sudo mount -o loop,offset=32256 hda.img /mnt/guest/boot/

После этого нужно установить ядро и модули на образ диска и прописать загрузчику параметры, необходимые для загрузки отлаживаемого ядра. Для того, чтобы установить ядро нужно собрать пакет одной из команд:

$ make binrpm-pkg
$ make deb-pkg
$ make tarbz2-pkg
$ make targz-pkg

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

Пример для rpm и дистрибутива Fedora 9:

$ cp /usr/src/redhat/RPMS/i386/kernel-2.6.29-2.i386.rpm /mnt/guest/root/ 
$ sudo mount -t proc none /mnt/guest/proc
$ sudo mount -o bind /dev /mnt/guest/dev
$ sudo chroot /mnt/guest /bin/bash
$ rpm -i --force  root/kernel-2.6.29-2.i386.rpm
$ sudo umount /mnt/guest/proc /mnt/guest/dev
$ exit

Примечание:
Иногда установить пакет не получается, тогда можно сделать это вручную.

#Установка откомпилированных модулей в созданную директорию
$ sudo make modules_install INSTALL_MOD_PATH=/mnt/guest/

#Копирование файла Sysmap и ядра
$ sudo cp System.map /mnt/guest/root
$ sudo cp arch/i386/boot/bzImage /mnt/guest/root
$ sudo mount -t proc none /mnt/guest/proc
$ sudo mount -o bind /dev /mnt/guest/dev
$ sudo chroot /mnt/guest /bin/bash

#Установка ядра и настройка загрузчика
#installkernel <version> <image> <System.map>
$ /sbin/installkernel 2.6.29 /root/bzImage /root/System.map
$ sudo umount /mnt/guest/proc /mnt/guest/dev
$ exit

Если скрипта installkernel нет, то необходимо скопировать ядро и настроить загрузчик самостоятельно информацию о том, как это сделать можно найти, например, здесь:
Grub: http://en.gentoo-wiki.com/wiki/Quick_GRUB
Lilo: http://www.acm.uiuc.edu/workshops/linux_install/lilo.html

#Отмонтируем образ и запускаем QEMU
$ sudo umount /mnt/guest/boot /mnt/guest
$ qemu -s -S -hda hda.img &

Запуск GDB

# Запускаем gdb на основной машине и подключаемся к порту 1234
$ gdb
(gdb) target remote localhost:1234
# Подключаем образ ядра (должен совпадать с отлаживаемым ядром)
(gdb) file vmlinux
(gdb) break start_kernel

Файл vmlinux — это декомпрессированный исполняемый образ ядра, который хранится в корне каталога исходных кодов, где выполнялась сборка работающего ядра. Сжатые файлы zImage, или bzImage использовать нельзя.

Чтобы отлаживать модуль необходимо импортировать таблицу имён модуля в GDB и указать адрес в памяти, куда он был загружен.

Загрузка модуля:

# На виртуальной машине загружаем модуль
$ sudo modprobe lg

# Узнать адреса, куда он был загружен
$ cat /sys/module/lg/sections/.text
0xe0baa000

$ cat /sys/module/lg/sections/.data
0xe0baec50

$ cat /sys/module/lg/sections/.bss
0xe0baf20c

# На основной машине. Загрузка модуля для отладки с расширением *.o, а не *.ko.
(gdb) add-symbol-file /usr/src/linux/drivers/lguest/lg.o 0xe0baa000  -s .data 0xe0baec50 \ -s .bss 0xe0baf20c
add symbol table from file "/usr/src/linux/drivers/lguest/lg.o" at 
	.text_addr = 0xe0baa000 
	.data_addr = 0xe0baec50 
	.bss_addr = 0xe0baf20c 
(y or n) y 
Reading symbols from /usr/src/linux/drivers/lguest/lg.o...done. 

(gdb) print lguest_lock
$1 = {count = {counter = 1}, wait_lock = {raw_lock = {slock = 0}}, wait_list = {next = 0xe0baec58, prev = 0xe0baec58}}