Нестандартные сценарии использования модулей ядра

Часть 40. Оптимальный подход к реализации поиска символов в ядре

Comments

Серия контента:

Этот контент является частью # из серии # статей: Нестандартные сценарии использования модулей ядра

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Нестандартные сценарии использования модулей ядра

Следите за выходом новых статей этой серии.

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

Эффективные решения

Ещё один недостаток решения, обсуждавшегося в предыдущей статье, состоит в том, что оно получилось громоздким и ненатуральным. Псевдофайл /proc/kallsyms (таблица) - это ни что иное, как отображение во внешнее окружение некоторой внутренней структуры ядра. Ядро предоставляет пользователям (т.е. модулям) вызов kallsyms_lookup_name(), позволяющий выполнять поиск в этой внутренней структуре. Использование этого метода значительно упрощает решение, как можно увидеть в примере из файла mod_kct.c (этот файл можно найти в архиве call_table.tgz в разделе "Материалы для скачивания"). В листинге 1 представлен новый вариант решения задачи.

Листинг 1. Перебор имён посредством kallsyms_lookup_name()
#include <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/init.h> 
#include <linux/kallsyms.h> 

static int __init ksys_call_tbl_init( void ) { 
   void** sct = (void**)kallsyms_lookup_name( "sys_call_table" ); 
   printk( "+ sys_call_table address = %p\n", sct ); 
   if( sct ) { 
      int i; 
      char table[ 120 ] = "sys_call_table : "; 
      for( i = 0; i < 10; i++ ) 
         sprintf( table + strlen( table ), "%p ", sct[ i ] ); 
      printk( "+ %s ...\n", table ); 
   } 
   return -EPERM; 
} 

module_init( ksys_call_tbl_init ); 
MODULE_LICENSE( "GPL" );

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

$ time sudo insmod mod_kct.ko 
insmod: error inserting 'mod_kct.ko': -1 Operation not permitted 
real	0m0.022s 
user	0m0.005s 
sys	0m0.013s 
$ dmesg | tail -n2 
[59595.918185] + sys_call_table address = c07c2438 
[59595.918193] + sys_call_table : c044e80c c0443af8 c0408a04 c04e39e3 
c04e3a45 c04e2e59 c04e1e59 c0443dce c04e2ead c04ed654  ...

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

$ uname -r 
2.6.32.9-70.fc12.i686.PAE 
$ make 
...
WARNING: "kallsyms_lookup_name"
[/home/olej/2011_WORK/LINUX-books/examples.DRAFT/sys_call_table/call_table/mod_kct.ko] 
undefined!

Как видно, имя kallsyms_lookup_name присутствует в версии ядра 2.6.32.

$ cat /proc/kallsyms | grep kallsyms_ | grep T 
c046e815 T module_kallsyms_lookup_name 
c0471764 T kallsyms_on_each_symbol 
c04717f2 T kallsyms_lookup_name 
...

Но в данной версии ядра оно не экспортируется:

$ cat /lib/modules/`uname -r`/build/Module.symvers | grep kallsyms_ 
0x00000000	kallsyms_on_each_symbol	vmlinux	EXPORT_SYMBOL_GPL

Этот вызов ядра стал экспортируемым в промежутке между версиями ядра 2.6.32 и 2.6.35 (или примерно между пакетными дистрибутивами Linux, выпущенными летом 2010г. и весной 2011г.). Во всех более ранних дистрибутивах воспользоваться этим вызовом не удастся.

Однако эту проблему можно обойти, если воспользоваться другим экспортируемым именем kallsyms_on_each_symbol, которое также присутствует в таблице символов. Этот вызов обеспечивает выполнение указанной пользовательской функции последовательно для всех имён ядра. Правда, алгоритм его использования более сложный и поэтому требует дополнительных пояснений. Вызов kallsyms_on_each_symbol имеет такой прототип, объявленный в файле linux/kallsyms.h:

int kallsyms_on_each_symbol( int 
(*fn)(void *, const char *, struct module *, unsigned long), 
                             void *data );

В первом параметре fn передаётся указатель на вашу пользовательскую функцию, которая будет последовательно вызываться для всех символов в таблице ядра, а во втором параметре data — указатель на произвольный блок данных (параметров), который будет передаваться в каждый вызов функции fn. Такая передача данных (параметров вызова) — это стандартная практика, подобный подход, например, также применяется при создании нового потока (как потока ядра, так и потока пользовательского пространства в POSIX API). Прототип пользовательской функции fn, которая циклически вызывается для каждого имени:

int func( void *data, const char *symb, struct module *mod, unsigned long addr );

где:

  • data — блок параметров, заполненный в вызывающей единице и переданный из вызова функции kallsyms_on_each_symbol() (2-й параметр вызова), как это было описано выше, здесь обычно передаётся имя разыскиваемого символа;
  • symb — символьное изображение (строка) имени из таблицы имён ядра, которое обрабатывается на текущем вызове func (естественно, это значение изменяется при каждом вызове func);
  • mod — модуль ядра, к которому относится обрабатываемый символ;
  • addr — адрес символа в адресном пространстве ядра (то, что нам требуется найти).

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

Несмотря на то, что принцип использования вызова kallsyms_on_each_symbol() сложнее, нежели у появившегося позже вызова kallsyms_lookup_name(), у "старого" варианта гораздо больше возможностей. С их помощью мы создадим третий вариант модуля для осуществления поиска в таблице имен ядра. В листинге 2 представлена основная часть функциональности, в которой задействуется вызов kallsyms_on_each_symbol(), а полный код можно найти в файле mod_koes.c в архиве call_table.tgz.

Листинг 2. Перебор имён посредством kallsyms_on_each_symbol().
#include <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/init.h> 
#include <linux/kallsyms.h> 

static int nsym = 0; 
static unsigned long taddr = 0; 

int symb_fn( void* data, const char* sym, struct module* mod, unsigned long addr ) { 
   nsym++; 
   if( 0 == strcmp( (char*)data, sym ) ) { 
      printk( "+ sys_call_table address = %lx\n", addr ); 
      taddr = addr; 
      return 1; 
   } 
   return 0; 
}; 

static int __init ksys_call_tbl_init( void ) { 
   int n = kallsyms_on_each_symbol( symb_fn, (void*)"sys_call_table" ); 
   if( n != 0 ) { 
      int i; 
      char table[ 120 ] = "sys_call_table : "; 
      printk( "+ find in position %d\n", nsym ); 
      for( i = 0; i < 10; i++ ) { 
         unsigned long sa = *( (unsigned long*)taddr + i ); 
         sprintf( table + strlen( table ), "%p ", (void*)sa ); 
      } 
      printk( "+ %s ...\n", table ); 
   } 
   else printk( "+ symbol not found\n" ); 
   return -EPERM; 
} 

module_init( ksys_call_tbl_init ); 
MODULE_LICENSE( "GPL" ); 
MODULE_AUTHOR( "Oleg Tsiliuric <olej@front.ru>" );

Для убедительности вернёмся и выполним этот код в ядре 2.6.32, в котором предыдущий пример не работал:

$ uname -r 
2.6.32.9-70.fc12.i686.PAE 
$ time sudo insmod mod_koes.ko 
insmod: error inserting 'mod_koes.ko': -1 Operation not permitted 
real    0m0.042s 
user    0m0.005s 
sys     0m0.027s 
$ dmesg | tail -n30 | grep + 
+ sys_call_table address = c07ab3d8 
+ find in position 25239 
+ sys_call_table : c044ec61 c0444f63 c040929c c04e149d c04e12fc c04dec35 
c04dea99 c0444767 c04dec60 
$ cat /proc/kallsyms | wc -l 
69423 
$ cat /proc/kallsyms | grep c04dec35 
c04dec35 T sys_open

Как можно заметить:

  • это те же результаты, что и в самом первом примере (непосредственно читающем /proc/kallsyms);
  • но время выполнения сократилось в ~20 раз;
  • для нахождения требуемого символа читалась не вся таблица имён ядра (69423 символов), а только около (25239) одной её трети;

Заключение

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


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


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=856076
ArticleTitle=Нестандартные сценарии использования модулей ядра: Часть 40. Оптимальный подход к реализации поиска символов в ядре
publish-date=01242013