16.4. Отладка ядра в режиме реального времени с помощью DDB

Хотя gdb -k является отладчиком не реального времени с высокоуровневым пользовательским интерфейсом, есть несколько вещей, которые он сделать не сможет. Самыми важными из них являются точки останова и пошаговое выполнение кода ядра.

Если вам нужно выполнять низкоуровневую отладку вашего ядра, то на этот случай имеется отладчик реального времени, который называется DDB. Он позволяет устанавливать точки останова, выполнять функции ядра по шагам, исследовать и изменять переменные ядра и прочее. Однако он не может использовать исходные тексты ядра и имеет доступ только к глобальным и статическим символам, а не ко всей отладочной информации, как gdb.

Чтобы отконфигурировать ваше ядро для включения DDB, добавьте строчку с параметром

    options DDB
в ваш конфигурационный файл, и перестройте ядро. (Обратитесь к Руководству по FreeBSD для выяснения подробностей о конфигурации ядра FreeBSD.

Note: Если у вас устаревшая версия загрузочных блоков, то отладочная информация может оказаться не загруженной. Обновите блоки загрузки; самые новые загружают символы для DDB автомагически.)

После того, как ядро с DDB запущено, есть несколько способов войти в DDB. Первый, и самый простой, способ заключается в наборе флага загрузки -d прямо в приглашении загрузчика. Ядро будет запущено в режиме отладки и войдет в DDB до выполнения процедуры распознавания каких бы то ни было устройств. Поэтому вы можете выполнить отладку даже функций распознавания/присоединения устройств.

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

    # sysctl debug.enter_debugger=ddb

Либо, если вы работаете за системной консолью, можете воспользоваться определенной комбинацией клавиш. По умолчанию для перехода в отладчик используется комбинация Ctrl+Alt+ESC. Для драйвера syscons эта последовательность может быть изменена, и в некоторых распространяемых раскладках это сделано, так что обязательно выясните правильную комбинацию. Для последовательных консолей имеется параметр, позволяющий использовать последовательность BREAK на канале консоли для входа в DDB (options BREAK_TO_DEBUGGER в конфигурационном файле ядра). По умолчанию этого не делается, так как существует множество паршивых последовательных адаптеров, которые генерируют последовательность BREAK, к примеру, при вытягивании кабеля.

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

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

    b function-name
    b address
       

Значения по умолчанию воспринимаются в шестнадцатиричном виде, но чтобы отличать их от имен символов; шестнадцатиричные числа, начинающиеся с букв a-f, должны предваряться символами 0x (это опционально для других чисел). Разрешены простые выражения, например: function-name + 0x103.

Чтобы продолжить работу прерванного ядра, просто наберите:

    c

Чтобы получить трассировку стека, задайте:

    trace

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

Если вы хотите убрать точку останова, введите

    del
    del address-expression
       

В первом варианте команда будет исполнена сразу же по достижении точки останова, а текущая точка останова будет удалена. Во второй форме можно удалить любую точку останова, однако вам нужно будет указать ее точный адрес; его можно получить из:

    show b

Чтобы выполнить один шаг ядра, попробуйте:

    s

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

    n

Note: Это отличается от команды next отладчика gdb; это похоже на команду gdb finish.

Чтобы выводить значения в памяти, используйте, (к примеру):

    x/wx 0xf0133fe0,40
    x/hd db_symtab_space
    x/bc termbuf,10
    x/s stringbuf
         
для доступа к данным типа слово/полуслово/байт и вывода в шестнадцатиричном/десятичном/символьном виде. Число после запятой означает счетчик объектов. Чтобы вывести следующие 0x10 объектов, просто укажите:

    x ,10

Подобным же образом используйте

    x/ia foofunc,10
для дизассемблирования и вывода первых 0x10 инструкций функции foofunc вместе с их адресом относительно начала foofunc.

Чтобы изменить значения в памяти, используйте команду write:

    w/b termbuf 0xa 0xb 0
    w/w 0xf0010030 0 0
       

Модификатор команды (b/h/w) указывает на размер записываемых данных, первое следующее за ним выражение является адресом для записи, а оставшаяся часть интерпретируется как данные для записи в доступные области памяти.

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

    show reg

Альтернативно вы можете вывести содержимое одного регистра по команде, скажем,

    p $eax
и изменить его по:

    set $eax new-value

Если вам нужно вызвать некоторую функцию ядра из DDB, просто укажите:

    call func(arg1, arg2, ...)

Будет выведено возвращаемое значение.

Для вывода суммарной статистики по всем работающим процессам в стиле команды ps(1) воспользуйтесь такой командой:

    ps

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

    panic

Это приведет к созданию дампа ядра и перезагрузке, так что позже вы можете проанализировать дамп на более высоком уровне при помощи gdb. Как правило, эта команда должна следовать за другой командой continue.

    call boot(0)

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

    call cpu_reset()

самый последнее средство при аварии и практически то же самое, что нажатие Большой Красной Кнопки.

Если вам нужен краткий справочник по командам, просто наберите:

    help

Однако настоятельно рекомендуем отпечатать копию страницы справочника по ddb(4) при подготовке к сеансу отладки. Помните, что трудно читать онлайновое руководство при пошаговом выполнении ядра.