Отладчик и соответствующие инструменты

Предыдущая  Содержание  Следующая V*D*V

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

 

Использование интерактивного отладчика в ядре является непростой задачей. Ядро работает в своём собственном адресном пространстве от имени всех процессов в системе. В результате, ряд общих возможностей, предоставляемых отладчиками пользовательского пространства, таких как точки останова и пошаговый режим, в ядре получить труднее. В этом разделе мы рассмотрим несколько способов отладки ядра; каждый из них имеет свои преимущества и недостатки.

Использование gdb

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

 

Отладчик должен быть вызван, как если бы ядро было приложением. В дополнение к указанию имени файла для ELF образа ядра, вам необходимо ввести в командной строке имя файла ядра. Для работающего ядра, где файл ядра является образом основного ядра, это /proc/kcore. Типичный вызов gdb выглядит следующим образом:

 

gdb /usr/src/linux/vmlinux /proc/kcore

 

Первый аргумент является именем ELF (Executable and Linkable Format, формат исполняемых и компонуемых файлов) несжатого исполняемого  ядра, а не zImage или bzImage, или чего-то собранного специально для среды запуска.

 

Вторым аргументом в командной строке gdb является имя файла ядра. Как и любой файл в /proc, /proc/kcore генерируется, когда его читают. Когда системный вызов call выполняется в файловой системой /proc, он связывается с функцией генерации данных, а не извлечения данных; мы уже эксплуатировали эту возможность в разделе "Использование файловой системы /proc" ранее в этой главе. kcore используется для представления ядра "исполняемым" в формате файла ядра; это огромный файл, потому что он представляет собой целое адресное пространство ядра, которое соответствует всей физической памяти. Из gdb вы можете посмотреть переменные ядра стандартными командами gdb. Например, р jiffies печатает количество тиков времени от загрузки системы до настоящего времени.

 

При печати с данных из gdb ядро является всё же работающим и различные элементы данных имеют разные значения в разное время; gdb, однако, оптимизирует доступ к файлу ядра кэшируя данные, которые уже прочитаны. Если вы попытаетесь взглянуть на переменную jiffies ещё раз, то получите такой же ответ, как и прежде. Кэширование значений, чтобы избежать дополнительного доступа к диску, является правильным поведением для обычных файлов ядра, но является неудобным, когда используется "динамический" образ ядра. Решением является давать команду core-file /proc/kcore всякий раз, когда вы хотите очистить кэш gdb; отладчик получает готовый к использованию новый файл ядра и отбрасывает всю старую информацию. Вам, однако, не требуется всегда давать core-file при чтении новых данных; gdb читает ядро кусками по несколько килобайт и кэширует только уже прочитанные куски.

 

Многочисленные возможности, обычно предоставляемые gdb, не доступны, когда вы работаете с ядром. Например, gdb не сможет изменить данные ядра; он рассчитывает, что программа будет запущена для отладки под его контролем перед игрой с образом памяти. Также невозможно установить точки останова и точки наблюдения, или пошаговое выполнение функций ядра.

 

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

 

Имея отладочную информацию вы можете многое узнать о том, что происходит внутри ядра. gdb весело распечатывает структуры, значения указателей и так далее. Однако, изучение модулей является трудной задачей. Поскольку модули не являются частью переданного gdb образа vmlinux, отладчик ничего о них не знает. К счастью, на ядре с версии 2.6.7 можно научить gdb тому, что требуется знать для проверки загружаемых модулей.

 

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

 

.text

Этот раздел содержит исполняемый код модуля. Отладчик должен знать, где этот раздел, чтобы быть в состоянии делать трассировку или установить точки останова. (Ни одна из этих операций не относится к запуску отладчика на /proc/kcore, но они полезны при работе с kgdb, описанной ниже).

 

.bss

.data

Эти два раздела содержат переменные модуля. Любая переменная, которая не инициализируется во время компиляции находятся в .bss, а те, которые инициализированы, - в .data.

 

При работе gdb с загружаемыми модулями требуется информирование отладчика о том, куда были загружены эти разделы модуля. Эта информация доступна в sysfs, в /sys/module. Например, после загрузки модуля scull, каталог /sys/module/scull/sections содержит файлы с именами, такими как .text; содержание каждого файла является базовым адресом для раздела.

 

Теперь мы в состоянии выдать команду gdb, рассказывающую ему о нашем модуле. Нужной нам командой является add-symbol-file; эта команда принимает в качестве параметров имя объектного файла модуля, базовый адрес в .text, а также ряд дополнительных параметров, описывающих, где находятся другие интересующие разделы. Порывшись в разделе данных модуля в sysfs, мы можем построить такую команду:

 

(gdb) add-symbol-file .../scull.ko 0xd0832000 \

         -s .bss 0xd0837100 \

         -s .data 0xd0836be0

 

Мы включили небольшой скрипт в исходник примера (gdbline), который может создать эту команду для данного модуля.

 

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

 

(gdb) add-symbol-file scull.ko 0xd0832000 \

         -s .bss 0xd0837100 \

         -s .data 0xd0836be0

add symbol table from file "scull.ko" at

         .text_addr = 0xd0832000

         .bss_addr = 0xd0837100

         .data_addr = 0xd0836be0

(y or n) y

Reading symbols from scull.ko...done.

(gdb) p scull_devices[0]

$1 = {data = 0xcfd66c50,

         quantum = 4000,

         qset = 1000,

         size = 20881,

         access_key = 0,

         ...}

 

Здесь мы видим, что первое устройство scull в настоящее время занимает 20.881 байт. Если бы мы захотели, мы могли бы проследить данные цепочки, или посмотреть что-нибудь ещё интересное в модуле. Стоит знать ещё один полезный трюк:

 

(gdb) print *(address)

 

Введите адрес в шестнадцатиричном виде вместо address; вывод является файлом и номером строки кода, соответствующей этому адресу. Эта техника может быть полезна, например, чтобы выяснить, куда на самом деле указывают указатели функции.

 

Мы по-прежнему не можем выполнять типичные задачи отладки такие, как установку точек останова или изменение данных; для выполнения этих операций мы должны использовать такой инструмент, как kdb (описанный далее) или kgdb (с которым познакомимся в ближайшее время).

Отладчик ядра kdb

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

 

Однако, другие разработчики ядра периодически используют интерактивные средства отладки. Одним из таких инструментов является встроенный отладчик ядра kdb, доступный как неофициальный патч от oss.sgi.com. Для использования kdb вы должны получить патч (не забудьте получить версию, которая соответствует вашей версии ядра), применить его и пересобрать и переустановить ядро. Отметим, что на момент написания статьи kdb работает только на системах IA-32 (x86) (хотя некоторое время существовала версия для IA-64 в основной линии исходников ядра, пока не была удалена).

 

Когда вы работаете с ядром с включённым kdb, есть несколько способов войти в отладчик. Отладчик запускает нажатие в консоли кнопки Pause (или Break). kdb также запускается, когда в ядре происходит Oops, или когда попадает на точку останова. В любом случае, вы увидите сообщение, которое выглядит примерно так:

 

Entering kdb (0xc0347b80) on processor 0 due to Keyboard Entry

[0]kdb>

 

Обратите внимание, что когда работает kdb, всё в ядре останавливается. Ничто другое не должно быть запущено в системе, где вы вызываете kdb; в частности, вы не должны иметь включенной сеть, если только, конечно, не отлаживаете сетевой драйвер. Обычно, если вы будете использовать kdb, хорошей идеей является загрузка системы в однопользовательском режиме.

 

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

 

[0]kdb> bp scull_read

Instruction(i) BP #0 at 0xcd087c5dc (scull_read)

is enabled globally adjust 1

[0]kdb> go

 

Команда bp сообщает kdb, что в следующий раз требуется остановиться, когда ядро входит в scull_read. Затем введите go для продолжения выполнения. Затем направьте что-то в одно из устройств scull, мы можем попытаться прочитать это, запустив cat под оболочкой на другом терминале, что приведёт к следующему:

 

Instruction(i) breakpoint #0 at 0xd087c5dc (adjusted)

0xd087c5dc scull_read: int3

Entering kdb (current=0xcf09f890, pid 1575) on processor 0 due to

Breakpoint @ 0xd087c5dc

[0]kdb>

 

Мы теперь позиционируемся в начале scull_read. Чтобы увидеть, как мы туда попали, мы можем сделать трассировку стека:

 

[0]kdb> bt

    ESP    EIP        Function (args)

0xcdbddf74 0xd087c5dc [scull]scull_read

0xcdbddf78 0xc0150718 vfs_read+0xb8

0xcdbddfa4 0xc01509c2 sys_read+0x42

0xcdbddfc4 0xc0103fcf syscall_call+0x7

[0]kdb>

 

kdb пытается распечатать аргументы для каждой функции при трассировке вызова. Однако, оказался запутанным приёмами оптимизации, используемыми компилятором. Таким образом, он не смог распечатать аргументы scull_read.

 

Время взглянуть на некоторые данные. Команда mds манипулирует данными; мы можем запросить значение указателя scull_devices такой командой:

 

[0]kdb> mds scull_devices 1

0xd0880de8 cf36ac00 ....

 

Здесь мы запросили одно (4 байта) слово данных, начиная с местонахождения scull_devices; ответ говорит нам, что наш массив устройств находится по адресу 0xd0880de8; сама первая структура устройство находится по адресу 0xcf36ac00. Чтобы посмотреть на структуру этого устройства, мы должны использовать этот адрес:

 

[0]kdb> mds cf36ac00

0xcf36ac00 ce137dbc ....

0xcf36ac04 00000fa0 ....

0xcf36ac08 000003e8 ....

0xcf36ac0c 0000009b ....

0xcf36ac10 00000000 ....

0xcf36ac14 00000001 ....

0xcf36ac18 00000000 ....

0xcf36ac1c 00000001 ....

 

Эти восемь строк здесь соответствуют начальной части структуры scull_dev. Таким образом, мы видим, что память первого устройства выделена по адресу 0xce137dbc, квант размером 4000 (hex fa0), квантовый набор имеет размер 1000 (hex 3e8) и в настоящее время насчитывается 155 (hex 9b) байт, хранящихся в устройстве .

 

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

 

[0]kdb> mm cf36ac0c 0x50

0xcf36ac0c = 0x50

 

Последующий cat на устройстве теперь возвращает меньше данных, чем раньше. kdb имеет ряд других возможностей, включая пошаговое  выполнение (по инструкциям, а не строкам исходного кода Си), установку точек останова на доступ к данным, дизассемблирование кода, продвижение через связные списки, доступ к регистровым данным и многое другое. После того, как вы применили патч kdb, полный набор страниц документации можно найти в каталоге Documentation/kdb в дереве исходных текстов ядра.

Патчи kgdb

Два интерактивных подхода к отладке, которые  мы рассматривали до сих пор (с помощью gdb на /proc/kcore и kdb), не отвечают условиям среды, к которым уже привыкли разработчики приложений пользовательского пространства. Разве не было бы хорошо, если бы там был настоящий отладчик для ядра, который поддерживает такие функции, как изменение переменных, точки останова и так далее?

 

Как оказалось, такое решение действительно существует. На момент написания статьи находятся в обращении два отдельных патча, которые позволяют gdb иметь полные возможности, которые будут работать с ядром. Как ни странно, оба эти патча называются kgdb. Они работают путём разделения системы с запущенным тестовым ядром от системы с работающим отладчиком; как правило, они связываются с помощью последовательного кабеля. Таким образом, разработчик может запустить gdb на его или её стабильной настольной системе, работая с ядром, запущенным на жертвенном тестовом ящике. Настройка gdb в этом режиме занимает в самом начале немного времени, но инвестиции могут быстро окупиться, когда появится трудная ошибка.

 

Эти патчи находятся в состоянии сильного потока и даже могут быть объединены какой-то момент, поэтому мы избегаем многое о них говорить за пределами того, где они находятся и их основных возможностей. Заинтересованным читателям рекомендуется посмотреть и увидеть текущее положение дел. Первый патч kgdb в настоящее время находится в -mm дерева ядра - плацдарма для патчей на их пути в главную линию версии 2.6. Эта версия патча поддерживает архитектуры x86, SuperH, ia64, x86_64, SPARC и 32-х разрядную PPC. В дополнение к обычному режиму работы через последовательный порт, эта версия kgdb также может связываться через локальную сеть. Это просто вопрос включения режима Ethernet и загрузки с установленным параметром kgdboe для указания IP адреса, с которого могут приходить команды отладки. Как его установить, описывается в документации Documentation/i386/kgdb . (* Однако, она забывает указать, что вы должны иметь драйвер сетевого адаптера, встроенный в ядро, или отладчик не сможет найти его во время загрузки и выключит себя.)

 

В качестве альтернативы вы можете использовать патч kgdb, находящийся на http://kgdb.sf.net/. Эта версия отладчика не поддерживает режим связи по сети (хотя, как говорят, находится в стадии разработки), но он имеет встроенную поддержку работы с загружаемыми модулями. Он поддерживает архитектуры x86, x86_64, PowerPC и S/390.

Вариант Linux для пользовательского режима

User-Mode Linux (UML, Linux пользовательского режима) является интересной концепцией. Он структурирован в виде отдельного варианта ядра Linux с собственным подкаталогом arch/um. Однако, он не будет работать на новом типе оборудования; вместо этого он запускается на виртуальной машине, основанной на интерфейсе системных вызовов Linux. Таким образом, UML позволяет ядру Linux работать в качестве отдельного процесса в режиме пользователя на системе Linux.

 

Обладание копией ядра, работающей как процесс в режиме пользователя, приносит целый ряд преимуществ. Так как она запущена на ограниченном, виртуальном процессоре, ядро с ошибками не сможет повредить "реальной" системы. Различные аппаратные и программные конфигурации могут быть легко опробированы на том же компьютере. И, вероятно, что наиболее важно для разработчиков ядра, с ядром в пользовательском режиме можно легко манипулировать с помощью gdb или другого отладчика.

 

В конце концов, это просто другой процесс. UML, несомненно, располагает потенциалом для ускорения разработки ядра.

 

Однако, UML имеет большой недостаток с точки зрения авторов драйверов: в пользовательском режиме ядро не имеет доступа к установленному в системе оборудованию. Таким образом, хотя он может быть полезен для отладки большинства примеров драйверов в этой книге, UML ещё не полезен для отладки драйверов, которые имеют дело с реальным оборудованием. Для более подробной информации о UML смотрите http://user-mode-linux.sf.net/.

Linux Trace Toolkit

Linux Trace Toolkit (LTT) (пакет для трассировки Linux) является патчем для ядра и устанавливает соответствующие утилиты, которые позволяют трассировать события в ядре. Трассировка включает в себя информацию о времени и может создать довольно полную картину того, что произошло в течение определённого периода времени. Таким образом, он может быть использован не только для отладки, но и для отслеживания проблем с производительностью.

 

LTT, наряду с обширной документацией, можно найти на http://www.opersys.com/LTT.

Dynamic Probes

Dynamic Probes (или DProbes) (динамические датчики) является инструментом отладки, выпущенным (под GPL) IBM для Linux на архитектуре IA-32. Они позволяют размещение "зонда" практически в любом месте в системе, и в пространстве пользователя, и в пространстве ядра. Зонд состоит из некоторого кода (написанного на специальном, стек-ориентированном языке), который выполняется, когда управление попадает в заданную точку. Этот код может сообщить информацию обратно в пространство пользователя, изменить регистры, или же сделать ряд других вещей. Полезной особенностью DProbes является то, что после встраивания в ядро зонды могут быть вставлены в любое место в работающей системе без сборок ядра или перезагрузок. DProbes также могут работать с LTT, чтобы вставить новые события трассировки в произвольных местах.

 

Инструмент DProbes можно загрузить с сайта открытых исходников IBM: http://oss.software.ibm.com.

 

Предыдущая  Содержание  Следующая