Содержание


Отладка и тестирование модулей ядра: Часть 75. Отладка в ядре. Общие положения

Comments

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

  • код ядра представляет собой набор функциональных возможностей, не связанных ни с каким конкретным процессом, многие из этих возможностей выполняются параллельно в других потоках, не зависящих от самого модуля;
  • код модуля не может полноценно запускаться под отладчиком и трассироваться, так как многие ядерные механизмы принципиально существуют только во временных зависимостях и не могут быть приостановлены;
  • хотя при использовании интерактивных отладчиков (о чём будет рассказано далее) становится возможен динамический контроль значений и состояний (диагностика) модуля, но практически никогда невозможно изменение этих значений, как это практикуется в пользовательском пространстве с использованием gdb (эта особенность обусловлена не технологическими сложностями отладчиков, а уровнем последствий для операционной системы в результате подобного вмешательства);
  • ошибки, возникающие в коде ядра может оказаться чрезвычайно трудно воспроизвести, т.е. повторить ситуацию для последующего детального анализа;
  • поиском ошибок ядра можно легко сломать всю систему, и тем самым уничтожить и большую часть данных, которые и использовались для их поиска (ядро "умрёт" раньше, чем успеет журналировать причину происходящего).

Ещё одна "принципиальная" сложность отладки в пространстве ядра состоит в том, что команда разработчиков ядра Linux (главным образом Л. Торвальдс) крайне негативно относится к самому факту существования интерактивных отладчиков для ядра. Это мотивируется тем, что, при наличии подобных отладчиков, процесс принятия решений, относящихся к архитектуре ядра, может стать легкомысленным, поверхностным, что, в свою очередь, приведёт к накоплению неоптимальных решений в ядре. И хотя существует целый ряд интерактивных отладчиков для ядра, но ни один из них не признан как "официальный", и многие из них появляются и через некоторое время "затухают".

В конечном итоге отладка кода ядра — это, скорее, набор эмпирических приёмов и рекомендаций, а не формальная технология. В этой и последующих статьях мы рассмотрим некоторый минимальный набор таких приёмов и рекомендаций.

Отладочная печать

Основным способом отладки модулей ядра было и остаётся использование вызова отладочного вывода printk(). Использование printk()— это самый универсальный способ выполнения отладки. Детали использования printk() и настройки демонов системного журнала уже рассматривались ранее. Тексты сообщений не должны использовать символы вне основной таблицы ASCII, в частности, не лучшим решением будет использование русских букв в любой кодировке (такой приём может сработать в графическом терминале, но в текстовой консоли он приведёт к выводу бессмысленных сообщений).

Если не проявить известную осторожность, то можно получить тысячи сообщений, созданные выполнением printk(), которые переполнят текстовую консоль или файл системного журнала. В этом нет ничего страшного (для системы), но такой обширный вывод не подлежит никакому анализу и является совершенно бессмысленной тратой времени. Кроме того, такой избыточный вывод радикально изменяет картину временных соотношений происходящих явлений, что может вообще изменить протекание отлаживаемых процессов.

Интерактивные отладчики

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

$ sudo cat /boot/config-`uname -r` | grep CONFIG_DEBUG_INFO 
CONFIG_DEBUG_INFO=y 
# CONFIG_DEBUG_INFO_REDUCED is not set Б.Б.

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

$ yum list available kernel-debug* 

... 
Доступные пакеты 
kernel-debug.i686                  3.9.10-100.fc17                       updates
kernel-debug-devel.i686            3.9.10-100.fc17                       updates
kernel-debug-modules-extra.i686    3.9.10-100.fc17                       updates

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

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

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

Здесь первый параметр указывает на пересобранный образ ядра (несжатый и загружаемый образ вашей системы, находящийся по имени, так /boot/vmlinuz— это сжатый образ), а второй параметр — это имя файла образа ядра, формируемого динамически.

Но для работы с собственным модулем этого мало, так как отладчик ничего не знает о существовании исследуемого модуля, например:

$ sudo insmod ./hello_printk.ko

Мы должны иметь статическую информацию о программных секциях текущей загрузки модуля, и предоставить её для уже запущенного gdb его командой:

(gdb) add-symbol-file ./hello_printk.ko 0xd0832000 -s .bss 0xd0837100 -s .data 0xd0836be0
add symbol table from file "hello_printk.ko" at
    .text_addr = 0xd0832000
    .bss_addr = 0xd0837100
    .data_addr = 0xd0836be0
(y or n) y
Reading symbols from scull.kohello_printk.ko ...done.
...

Где указаны, соответственно, адреса загрузки секций кода (0xd0832000), инициализированных (0xd0837100) и не инициализированных (0xd0836be0) данных (зачастую достаточно указать секцию кода). Вопрос: а откуда взять адреса загрузки секций? Для этого предлагается целый ряд способов, но все они относятся к эмпирическим приёмам, а не формальным техникам. В предыдущих версиях ядра в каталоге /sys/module/hello_printk/sections находятся файлы .text, .bss, .data, содержащие адреса начала загрузки соответствующих секций. В последних версиях ядра этих файлов нет, но можно, к примеру, определив адрес текущей загрузки модуля, добавить к нему смещения секций (способ, заимствованный из FreeBSD):

$ objdump --section-headers hello_printk.ko 
hello_printk.ko:     file format elf32-i386 
Sections: 
Idx Name          Size      VMA       LMA       File off  Algn 
...
  1 .text         00000000  00000000  00000000  00000058  2**2 
                  CONTENTS, ALLOC, LOAD, READONLY, CODE 
...
  6 .data         00000000  00000000  00000000  0000010c  2**2 
                  CONTENTS, ALLOC, LOAD, DATA 
...
  8 .bss          00000000  00000000  00000000  0000029c  2**2 
                  ALLOC 
...

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

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

  • Встроенный отладчик ядра kdb, являющийся неофициальным патчем к ядру (доступен по адресу http://oss.sgi.com— Silicon Graphics International Corp.). Для использования kdb следует взять патч, в версии, в точности соответствующей версии отлаживаемого ядра, применить его, пересобрать и переустановить ядро. В настоящее время существует только для архитектуры IA-32 (x86).
  • Патч kgdb, находящийся даже в дереве исходных кодов ядра — эта технология поддерживает удалённую отладку с другого хоста, соединённого с отлаживаемым последовательным соединением или через Ethernet (его описание можно найти в кодах ядра по адресу Documentation/i386/kgdb). Для использования kgdb ядро должно быть собрано с соответствующей поддержкой, что имеет место в некоторых дистрибутивах, например, в Fedora:
    $ grep -i kgdb /boot/config-`uname -r` 
    CONFIG_HAVE_ARCH_KGDB=y 
    CONFIG_KGDB=y 
    CONFIG_KGDB_SERIAL_CONSOLE=y 
    CONFIG_KGDB_TESTS=y 
    # CONFIG_KGDB_TESTS_ON_BOOT is not set 
    CONFIG_KGDB_LOW_LEVEL_TRAP=y 
    CONFIG_KGDB_KDB=y
  • независимый проект под тем же именем kgdb (доступен по адресу http://kgdb.linsyssoft.com), не поддерживающий удалённую отладку по сети.
  • независимый проект SystemTap, функциональность которого шире, чем только потребности отладки (доступен по адресу http://sourceware.org/systemtap, представлен широкий спектр примеров использования).

Здесь перечислены далеко не все проекты этого целевого предназначения: отладка в ядре Linux отнюдь не тривиальная задача, и для её реализации порождаются всё новые и новые проекты.

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

Отладка в виртуальной машине

Весьма продуктивной оказывается отладка модулей в среде виртуальной машины (VM). В этом направлении у автора имеется положительный опыт, полученный с использованием динамично развивающихся проектов виртуальных машин QEMU/KVM (свободный проект http://wiki.qemu.org) и VirtualBox (также основанный на коде QEMU проект, начинавшийся от компании Sun Microsystems, а ныне продолжающийся компанией Oracle). Отладка в среде виртуальной машины (естественно, с учётом всех минусов, привносимых любым моделированием) создаёт целый ряд дополнительных преимуществ:

  • выполнение модуля ядра производится в изолированном окружении, где нет риска разрушения базовой операционной системы и необходимости постоянных перезагрузок;
  • простота связи (загрузка модуля, наблюдение результатов) со средой разработки по внутренней TCP/IP виртуальной сети на основе туннельного интерфейса Linux (можно иметь и 2, и 3, и произвольное число виртуальных сетевых интерфейсов для служебных целей, с IP из различных подсетей);
  • возможность использования отладчика gdb в базовой системе, для наблюдения "извне" за процессами, происходящими в виртуальной машине;
  • возможность ведения разработки для иных процессорных архитектур (ARM, PPC, MIPS) на развитой рабочей станции x86 с наличием обширного инструментария (эта возможность доступна только для QEMU, так как VirtualBox поддерживает только x86 архитектуру).

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

Хотелось бы специально остановиться ещё на одной, не совсем очевидной стороне использования виртуальных машин Linux при разработке модулей ядра. Напомню, что модуль скомпилированный в одном ядре, неработоспособен в отличающемся ядре (даже просто по написанию сигнатуры, имени ядра в команде: uname -r). Но, из-за изменчивости API ядра, о которой уже неоднократно говорилось, модуль может вообще даже не скомпилироваться в следующей (предыдущей) версии ядра из-за изменения состава функций API, изменения их наименований, прототипов и т.д. Поэтому заранее подготовленный тестовый набор виртуальных машин для нескольких версий ядер позволяет оттестировать разрабатываемый модуль и подготовить его промышленную поставку (за счёт, например, использования в коде модуля препроцессорных директив #defined для различных версий ядра в коде модуля, это неоднократно показывалась в предыдущих статьях цикла).

Искусственные приёмы отладки для модуля ядра

Далее мы перечислим некоторые приёмы, применяемые в процессе отладки, которые уже показали свою продуктивность в процессе работы над реальными проектами в области модулей ядра.

Модуль исполняемый как разовая задача

Одним из приёмов, который уже неоднократно встречался по ходу рассмотрения, является сознательное написание модуля, возвращающего ненулевое значение из инициализирующей функции, т.е. который вовсе и "не собирался" загружаться. Такой модуль выполняется однократно, подобно пользовательскому процессу, но отличаясь тем, что делает он это в супервизорном режиме (с полными привилегиями) и в адресном пространстве ядра. Пример такого простейшего модуля приводится в архиве simple-debug.tgz в разделе "Материалы для скачивания":

Листинг 1. Пример "однократного" модуля (файл md.c)
#include <linux/module.h>

static int __init hello_init( void ) {
   extern int sys_close( int fd );
   void* Addr;
   Addr = (void*)sys_close;
   printk( KERN_INFO "sys_close address: %p\n", Addr );
   return -1;
}

module_init( hello_init );

Такой модуль в принципе не может загрузиться, так как он возвращает -1 (или точнее не 0). В этой связи у модуля даже нет процедуры завершения, так как она ему не нужна:

$ sudo /sbin/insmod ./md.ko
insmod: error inserting './md.ko': -1 Operation not permitted

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

$ dmesg | tail -n2
md: module license 'unspecified' taints kernel.
sys_close address: c047047a

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

Тестирующий модуль

При организации модульного тестирования (unit testing) разработчик может столкнуться с проблемой, как оформлять тесты, ведь создаваемый код модуля нельзя скомпилировать для работы в пользовательском режиме. Но в этом случае в коде проектируемого модуля могут быть созданы экспортируемые точки входа вида test_01(), test_02(), ... test_MN(), а для их последовательного вызова создан отдельный тестирующий модуль, выполняемый однократно, весь код которого умещается в единственную функцию инициализации. Пример подобной упрощённой реализации можно найти в архиве simple-debug.tgz:

Листинг 2. Экспортирование точек входа для тестирования модуля (файл md1.h)
#include <linux/module.h> 
MODULE_LICENSE( "GPL" ); 
MODULE_AUTHOR( "Oleg Tsiliuric <olej@front.ru>" ); 
extern char* test_01( void ); 
extern char* test_02( void ); 
static int __init init( void ); 
module_init( init );
Листинг 3. Модуль, реализующий данные точки входа (файл md1.c)
#include "md1.h"
static char retpref[] = "this string returned from "; 

char* test_01( void ) { 
   static char res[ 80 ]; 
   strcpy( res, retpref ); 
   strcat( res, __FUNCTION__ ); 
   return res; 
}; 
EXPORT_SYMBOL( test_01 ); 

char* test_02( void ) { 
   static char res[ 80 ]; 
   strcpy( res, retpref ); 
   strcat( res, __FUNCTION__ ); 
   return res; 
}; 
EXPORT_SYMBOL( test_02 ); 

static int __init init( void ) { 
   return 0; 
} 
static void __exit exit( void ) {} 
module_exit( exit );

А это — полный код тестирующего модуля для однократного выполнения:

Листинг 4. Модуль для тестирования точек входа (файл mt1.c)
#include "md1.h"
static int __init init( void ) {
   printk( "%s\n", test_01() );
   printk( "%s\n", test_02() );
   return -1;
}

А вот как выглядит выполнение последовательности тестов проектируемого модуля:

$ sudo insmod md1.ko$ sudo insmod mt1.ko
insmod: error inserting 'mt1.ko': -1 Operation not permitted
$ dmesg | tail -n2
this string returned from test_01
this string returned from test_02

Заключение

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


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=950565
ArticleTitle=Отладка и тестирование модулей ядра: Часть 75. Отладка в ядре. Общие положения
publish-date=10292013