Скандальная правда об отладке ядерного кода в Linux

7 сентября 2016

Как это всегда бывает в мире Linux, нормальной документации нет. Когда мне захотелось разобраться, как в это время суток разработчики отлаживают модули ядра Linux, а также само ядро, мне пришлось прочитать, наверное, около двадцати статей, разбросанных по всему интернету. Из этих источников половина содержала устаревшую информацию в стиле «Линус запретил использовать отладчики, смиритесь». Еще половина освещала вопрос где-то на 1/10. В итоге, скорее вопреки, чем благодаря, мне все-таки удалось более-менее разобраться в вопросе и собрать всю информацию в одном месте. Не благодарите.

Важно! Далее предполагается, что вы уже умеете пользоваться отладчиком GDB, а также собирать ядро Linux из исходных кодов. Кстати, последняя статья содержит ссылки на множество интересных материалов по разработке ядра Linux.

SysRq-сочетания клавиш

Начнем немного издалека. В мире Linux есть такое понятие, как SysRq-сочетания клавиш. На PC им соответствуют сочетания Alt + PrintScreen + что-то. Предназначены они главным образом для отладочных целей, а значит нам не повредит про них знать.

Чтобы ядро поддерживало SysRq-сочетания, оно должно быть собрано с соответствующей опцией (в Ubuntu включена по умолчанию). Кроме того, их поддержку нужно явно активировать одной из следующих команд:

sudo sh -c 'echo 1 > /proc/sys/kernel/sysrq'
# или:
sudo sysctl kernel.sysrq=1

Чтобы не вводить их заново после перезагрузки, можно отредактировать файл /etc/sysctl.conf.

Примеры некоторых интересных сочетаний:

  • SysRq+H — подсказка по всем сочетаниям;
  • SysRq+L — показать стектрейсы всех CPU;
  • SysRq+P — показать значения всех регистров;
  • SysRq+B — перезагрузить систему;
  • SysRq+O — выключить питание;
  • SysRq+C — уронить ядро;

Вывод некоторых сочетаний довольно длинный и целиком на экране он не умещается. К счастью, вывод дублируется в файл /var/log/kern.log.

Само собой разумеется, по SSH эти сочетания клавиш не работают. Если физического доступа к машине нет, можно делать все то же самое как-то так:

sudo sh -c 'echo p > /proc/sysrq-trigger'

Приведенная выше команда эквивалентна нажатию SysRq+P. Все это, на самом деле, очень ценная информация, которая скоро нам пригодится!

Как уронить ядро и получить корку

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

Говорим:

sudo apt-get install linux-crashdump

После установки этого пакета требуется перезагрузка. После перезагрузки проверяем, что в /proc/cmdline был добавлен параметр crashkernel.

Если все настроено, пробуем уронить ядро. К счастью, мы уже знаем, как это делается:

sudo sh -c 'echo c > /proc/sysrq-trigger'

Система перезагрузится и в каталоге /var/crash будет записана корка!

Анализ корок и /proc/kcore в отладчике GDB

В Linux есть такой волшебный файл /proc/kcore, позволяющий обращаться на чтение к памяти работающего ядра. С точки зрения отладчика нет разницы, анализировать этот файл, или корки, полученные нами выше.

Как вы можете помнить, для анализа coredump’а отладчику помимо корки нужен и сам исполняемый файл. В случае с ядром Linux этот файл называется vmlinux, но его не так-то просто получить. Дело в том, что Linux настолько жирный, что он сжимает этот самый исполняемый файл и выкидывает из него все «лишнее». Полученный в итоге файл сохраняется в /boot/vmlinuz-что-то-там (с буквой z на конце, а не x).

Если вы собирали ядро самостоятельно из исходных кодов, то vmlinux содержится в -dbg версии пакета linux-image. У меня в системе vmlinux был обнаружен под таким именем:

/usr/lib/debug/lib/modules/4.7.0-custom/vmlinux

Это обычный ELF-файл (только очень большой, у меня он весит около 400 Мб) на который можно натравить readelf, objdump и прочие утилиты.

Также vmlinux можно получить из файла /boot/vmlinuz*. В исходниках ядра для этого есть специальный скрипт:

./scripts/extract-vmlinux /boot/vmlinuz-4.7.0-custom > ~/vmlinux

Однако в извлеченном таким образом файле vmlinux полностью отсутствует информация о символах. Если вы когда-нибудь «сжимали» исполняемые файлы при помощи утилиты strip, то вот тут причина та же. К счастью, символы можно восстановить при помощи скрипта kdress:

git clone https://github.com/elfmaster/kdress.git
cd kdress
gcc kunpress.c -o kunpress
gcc build_ksyms.c -o build_ksyms
./kdress /boot/vmlinuz-4.7.0-custom ~/vmlinux \
  /boot/System.map-4.7.0-custom

Можно убедиться, что у полученного файла vmlinux есть символы:

readelf -s vmlinux | grep sys_call_table

В восстановленном таким образом vmlinux меньше отладочной информации, чем в нормальном vmlinux из -dbg пакета. Однако следует принять во внимание, что ядро Linux (шок!) вообще не компилируется с флагом -O0. То есть, даже при наличии всей отладочной информации вместо значения многих переменных вы все равно увидите «optimized out», плюс отладчик будет прыгать по исходному коду то вперед, то назад, как сумасшедший. Так что, на практике польза от обоих версий vmlinux, похоже, примерно одинаковая. Надеюсь, вы не боитесь отлаживать ассемблерный код :)

Итак, теперь, имея файл vmlinux, можно проанализировать корку или /proc/kcore:

sudo gdb path/to/vmlinux /proc/kcore

При использовании нормального vmlinux работает вообще все:

(gdb) print &sys_call_table
(gdb) l sys_open

При использовании восстановленного vmlinux можно делать так:

(gdb) x/4gx &sys_call_table
(gdb) x/5i 0xffffffff812206e0
(gdb) x/5i sys_read

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

Удаленная отладка ядра по последовательному порту

Для следования инструкциям из этого раздела убедитесь, что ваше ядро было собрано с поддержкой KGDB. В Ubuntu по умолчанию оно собрано именно таким образом, но перепроверьте файл .config не повредит.

Далее предполагается, что у вас есть две машины, соединенные между собой через последовательный порт. В реальной жизни машины с последовательными портами сейчас встречаются редко. Хотя вот тут, например, кое-кому удалось прикрутить последовательный порт к Raspberry Pi, так что всякое может быть. Я лично предпочитаю использовать виртуальные машины. Как соединить по последовательному порту виртуалки, работающие под VirtualBox или KVM, ранее рассказывалось в посте Памятка по отладке ядра и драйверов во FreeBSD.

На машину, с которой будем отлаживать ядро, предварительно следует залить файл vmlinux, а также исходники ядра. От последних, впрочем, как уже отмечалось, будет мало пользы.

На машине, которую будем отлаживать, говорим:

sudo sh -c 'echo ttyS0 > /sys/module/kgdboc/parameters/kgdboc'

После выполнения этой команды в SysRq+H вы увидите новое сочетание SysRq+G, позволяющее войти в отладчик. Почему бы им сразу и не воспользоваться:

sudo sh -c 'echo g > /proc/sysrq-trigger'

На второй машине говорим:

sudo gdb ./vmlinux

В отладчике выполняем команду:

(gdb) target remote /dev/ttyS0

Если все было сделано правильно, вы обнаружите себя за отладкой работающей системы с возможностью ставить брейкпоинты, менять значения переменных (хотя, скорее, памяти и регистров) и так далее. Пожалуй, единственное отличие от отладки обычных приложений заключается в том, что вы не можете прервать выполнение ядра нажатием в отладчике Ctr+C. Вместо этого нужно снова послать SysRq+G из отлаживаемой системы.

Отладка на ранних этапах загрузки системы

Иногда нужно отлаживать систему на самых ранних этапах ее работы. Вот как это делается в Linux.

При загрузке системы в меню GRUB’а выбираем наше ядро и нажимаем E. Попадем в простой текстовый редактор (как это примерно выглядит), где нужно найти место с указанием параметров ядра. Допишите следующие параметры:

console=ttyS0,115200 kgdboc=ttyS0,115200 kgdbwait

Затем нажмите F10, чтобы загрузиться с указанными параметрами.

Первые два параметра говорят использовать для отладки последовательный порт. Их при желании можно прописать перманентно, чтобы не напоминать о них системе после каждой перезагрузке через /sys/module/kgdboc, как мы это делали выше. Ключевым же параметром здесь является kgdbwait, который говорит не загружать систему, пока к ней не подключится отладчик. В остальном отладка в таком режиме ничем не отличаются от описанной выше.

Итак, чтобы в следующий раз нам пришлось меньше вводить, в /etc/default/grub найдите параметр GRUB_CMDLINE_LINUX_DEFAULT и допишите в него:

console=ttyS0,115200 kgdboc=ttyS0,115200

Затем скажите:

sudo update-grub

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

Заключение

Поздравляю, теперь вы знаете все об отладке ядерного кода в Linux. А как вы отлаживаете ядро Linux и его модули?

Дополнение: Основы написания модулей ядра в Linux

Метки: , , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.