Памятка по отладке ядра и драйверов во FreeBSD

7 апреля 2016

Недавно мы научились собирать FreeBSD из исходных кодов. Этих знаний достаточно, если вы хотите просто попробовать самые свежие фичи системы или оптимизировать FreeBSD под конкретное железо. Но если вы собираетесь как следует порыться в кишках системы, дабы лучше разобраться в ее работае, или даже что-то изменить в ней, необходимо уметь цепляться к системе отладчиком. Да и если вы админите сервера под управлением FreeBSD, знания о том, как анализировать корку упавшего ядра, пожалуй, будут не лишними.

Несколько замечаний

Хочу отметить, что мир отлаживать мы уже умеем, так как он представляет собой обычные пользовательские приложения, которые прекрасно отлаживаются при помощи GDB, LLDB и так далее. Поэтому далее мы сосредоточимся на отладке ядра и драйверов.

Упомянутая чуть выше заметка «Собираем ядро и мир FreeBSD из исходников» является обязательной к предварительному прочтению. В частности, в ней рассказывается, как загрузиться с альтернативным ядром, если с текущим что-то очень сильно не так.

Отмечу также, что чтобы повторить действия, описанные далее, тратить время на компиляцию ядра и мира не обязательно. Вместо этого можно просто скачать ISO-образ с CURRENT-веткой FreeBSD. Заметьте, что RELEASE и STABLE не подходят, так как в них по умолчанию отключены отладочные опции, и ядро таки придется пересобрать.

Анализ корок при помощи отладчика KGDB

Самый простой способ отладки кода уровня ядра во FreeBSD — это уронить ядро, перезагрузиться, и изучить записанную корку. Зачастую это является единственным доступным способом отладки, так как ядро упало где-то там у пользователя, не умеющего и не желающего отлаживать систему на лету, а  локально проблема не воспроизводится.

Запись kernel dump’ов включается путем записи в /etc/rc.conf:

dumpdev="AUTO" # возможно, и так уже есть
ddb_enable="YES"

Также в /etc/sysctl.conf дописываем:

debug.debugger_on_panic=0

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

sudo sysctl debug.kdb.panic=1

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

ls -la /var/crash

Должны увидеть файлы с именами core.txt.0 и vmcore.0. Первый представляет собой обычный текстовый файл со стэктрейсом того места, в котором все упало, выводом dmesg, списком параметров, с которыми было собрано ядро, и прочей отладочной информацией.

Иногда core.txt.* могут по каким-то причинам не создаваться. Тогда используйте команду вроде следующей:

sudo crashinfo -k /boot/kernel/kernel /var/crash/vmcore.0

Файлы vmcore.* — это корки, которые можно проанализировать отладчиком:

sudo kgdb /boot/kernel/kernel /var/crash/vmcore.0

Важно! Если вдруг вы собрали ядро следующей мажорной версии FreeBSD, и пытаетесь дебажить его отладчиком из мира предыдущей мажороной версии, это может не работать. Я в этом случае получил пустые стэктрейсы, все нити выполнялись по адресу 0x00000000 и так далее. Для решения этой проблемы требуется также обновить и мир.

Все команды в KGDB такие же, как и в GDB. Смотрим нити, стэктрейсы, значения переменных и аргументов процедур — все то же самое.

Дополнение: Во FreeBSD для записи корок обязательно требуется swap-раздел. Подробности см в посте Пример переразбиения диска во FreeBSD с помощью gpart.

Отладка на лету при помощи DDB

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

Важно! Для поддержки DDB система должна быть собрана с опциями KDB и DDB. Заметьте, что GENERIC-ядро RC- и RELEASE-сборок FreeBSD по умолчанию этих опций не имеет. В CURRENT эти опции есть из коробки.

Провалиться в DDB можно несколькими способами.

Во-первых, выбрав на этапе загрузки Escape to a loader prompt (нажатием 3) и загрузив ядро с флагом -d:

boot kernel -d

Во-вторых, прямо в процессе работы системы сказав:

sudo sysctl debug.kdb.enter=1

Наконец, можно выйти из иксов (например, Ctr+Alt+F1) и нажать Ctr+Alt+Escape.

Основные команды DDB перечислены ниже.

Подсказка:

help

Step и next — как в GDB:

s
n

Бэктрейс:

bt

Поставить бряк:

b fork_trampoline

Показать список бряков:

show break

Удалить бряк:

d fork_trampoline

Продолжить работу системы:

c

Показать значения регистров:

show registers

Информация о текущей нитке:

show thread

Показать все нитки:

show threads

Список процессов:

ps

Информация обо все локах:

show alllocks

Информация обо всех файлах в системе:

show files

Записать дамп для анализа потом:

dump

Перезагрузить систему:

reboot

Прочие команды DDB можно подсмотреть в man 4 ddb. Помимо перечисленных выше основных команд DDB предоставляет действительно впечатляющий список других возможностей. К сожалению, насколько я смог разобраться, сам по себе DDB не позволяет просматривать в удобочитаемом виде значения локальных переменных и аргументов процедур, переключаться между фреймами стека или нитками, ровно как и смотреть исходный или ассемблерный код того места, на котором мы сейчас остановились.

Поэтому рассматрим еще один способ.

Отладка через последовательный порт при помощи KGDB

Этот способ позволяет отлаживать систему в реальном времени с удобным просмотром значений переменных, переключением между фреймами стэка, и так далее. Однако он требует наличия двух машин, соединенных через последовательный порт. Сказать по правде, компьютеров, оснащенных последовательным портом, я очень давно не видел. Вроде как делают переходники в/из USB, но сам я их не пробовал, к тому же, они требуют установки драйверов. Поэтому далее мы будем использовать не настоящие машины, а виртуальные. Желательно клонировать одну и ту же виртуальную машину, чтобы ядро и мир на них не отличались.

Ядро должно быть собрано со следующими опциями (уже есть в CURRENT):

options DDB
options KDB
options GDB

makeoptions DEBUG=-g

Какая из опций что означает можно прочитать здесь. Еще не лишено смысла собирать ядро с флагом -O0, иначе вместо значений переменных вы будете часто видеть «value optimized out».

Если вы используете VirtualBox, то соединить машины по последовательному порту достаточно просто. На первой машине, которую будете отлаживать, идете в Settings → Serial Ports, ставите галочку Enable Serial Port, в Path/Address пишите что-то вроде /tmp/com1. На второй машине, с которой будем отлаживать первую, повторяете все то же самое, плюс ставите галочку Connect to existing pipe/socket. Если вы клонировали виртуалки, проверьте также, что у них прописаны разные MAC-адреса.

Соединение виртуалок через последовательный порт в VirtualBox

Если же вы предпочитаете KVM, то создаем пайпы следующим образом:

mkdir /var/lib/libvirt/pipes
cd /var/lib/libvirt/pipes
mkfifo null-cable-1.{in,out}
ln null-cable-1.in null-cable-2.out
ln null-cable-1.out null-cable-2.in

Затем в virt-manager в свойствах виртуальных машин заводим последовательные порты, указывая путь до null-cable-1 в случае одной виртуальной машины и null-cable-2 в случае второй:

Соединение виртуалок через последовательный порт в KVM / virt-manager

В результате машины будут соединены друг с другам через виртуальный serial port, который они будут видеть как /dev/cuau0.

На машине, с которой будем отлаживаеть (на ней для удобства можно сидать по SSH), переходим в /usr/src. Затем говорим:

sudo kgdb /boot/kernel/kernel

На целевой машине открываем /boot/device.hints и правим следующее значение:

hint.uart.0.flags="0x90"

Флаг 0x80 говорит ядру использовать последовательный порт для удаленной отладки, см man uart. При этом значение 0x10 стоит там по дэфолту (device is potential system console), отсюда получаем значение 0x90.

Затем все также на целевой машине перезагружаемся и попадем в отладчик DDB одним из описанных ранее способов, в нем говорим:

gdb

На второй машине внутри отладчика говорим:

target remote /dev/cuau0

Должны в результате увидеть что-то вроде:

(kgdb) target remote /dev/cuau0
Remote debugging using /dev/cuau0
0xffffffff80a3fdae in kdb_enter ()

На случай, если вам нравится вводить поменьше команд в отладчике, сообщаю, что вместо запуска KGDB и затем выполнения в нем команд можно просто сказать:

sudo kgdb -r /dev/cuau0 /boot/kernel/kernel

После выполнения команды c вернуться обратно в отладчик нажатием Ctr+C, как это обычно делается в GDB, нельзя. Вместо этого нужно либо нажать в окне целевой системы сочетание Ctr+Alt+Escape, либо выполнить на ней команду:

sudo sysctl debug.kdb.enter=1

В итоге получаем отладку живой операционной системы, которая мало чем отличается от отладки обычных приложений в GDB.

Заключение

Согласитесь, в итоге все оказалось не так уж и сложно!

Некоторые ссылки по теме:

А ковыряетесь ли вы в кишках операционных систем и если да, то каких и что за инструменты при этом используете?

Дополнение: Вас также могут заинтересовать статьи Скандальная правда об отладке ядерного кода в Linux и Использование DTrace на примере FreeBSD и Linux.

Метки: , , .


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