Перейти к тексту

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

Профиль создается, когда вы в первый раз заходите в developerWorks. Выберите данные в своем профиле (имя, страна/регион, компания) которые будут общедоступными и будут отображаться, когда вы публикуете какую-либо информацию. Вы можете изменить данные вашего ИБМ аккаунта в любое время.

Вся введенная информация защищена.

  • Закрыть [x]

При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

Вся введенная информация защищена.

  • Закрыть [x]

Нестандартные сценарии использования модулей ядра: Часть 43. Добавление системного вызова

Олег Цилюрик, преподаватель тренингового отделения, Global Logic
Фото автора
Олег Иванович Цилюрик, много лет был разработчиком программного обеспечения в крупных центрах разработки: ВНИИ РТ, НПО "Дельта", КБ ПМ. Последние годы работал над проектами в области промышленной автоматики, IP телефонии и коммуникаций. Автор нескольких книг. Преподаватель тренингового отделения международной софтверной компании Global Logic.

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

Дата:  07.02.2013
Уровень сложности:  средний
Активность:  1451 просмотров
Комментарии:  


Введение

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


Добавление новых системных вызовов

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

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

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

Размер таблицы sys_call_table достаточно велик и может меняться в разных версиях ядра:

$ cat /proc/kallsyms | grep ' sys_' | grep T | wc -l 
345 

Выше приведена примерная оценка только порядка величины, так как некоторые обработчики в современных версиях ядра подменены на другие их формы, например, обработчик ptregs_fork вызова fork() в одной из начальных (__NR_fork == 2) позиций sys_call_table:

$ cat /proc/kallsyms | grep ptregs_fork 
c040929c t ptregs_fork
$ cat /proc/kallsyms | grep sys_fork 
c040ee13 T sys_fork 

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

$ cat /proc/kallsyms | grep sys_ni_syscall 
c045b9a8 T sys_ni_syscall 

Следуя таким путём, можно добавить новый обработчик системного вызова в любую неиспользуемую позицию таблицы sys_call_table. Статически структуру таблицы можно детально рассмотреть в дереве исходных кодов ядра для целевой платформы. Для примера возьмём дерево ядра 3.0.9 (в листинге показаны только некоторые неиспользуемые позиции, а комментарии вида __NR_# в конце строк, подсказывающие номер системного вызова, были добавлены специально).

$ cat /usr/src/linux-3.0.9/arch/x86/kernel /syscall_table_32.S 
ENTRY(sys_call_table) 
        .long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
        .long sys_exit 
        .long ptregs_fork 
...
        .long sys_ni_syscall    /* old break syscall holder */       //17
        .long sys_ni_syscall    /* old stty syscall holder */        //31
        .long sys_ni_syscall    /* old gtty syscall holder */        //32
        .long sys_ni_syscall    /* 35 - old ftime syscall holder */  //35
...
        .long sys_ni_syscall    /* reserved for TUX */               //222
        .long sys_ni_syscall                                         //223
        .long sys_ni_syscall                                         //251
        .long sys_ni_syscall    /* sys_vserver */                    //273
        .long sys_ni_syscall    /* 285 */ /* available */            //285
... 
        .long sys_setns                                              //346

Видно, что в этой версии ядра таблица содержит 347 позиций, из которых 21 не задействована (в листинге приведены не все свободные позиции). Эти позиции лучше не использовать повторно, так как назначить новый системный вызов устаревшему старому слишком рискованно с точки зрения последующего использования системы: никто не гарантирует, что на системе не будут выполняться старые программы, а последствия их обращений к «старым-новым» вызовам не окажутся фатальными для ядра.

Динамический анализ неиспользуемых позиций в таблице системных вызовов выполняется в представленном модуле ni-test, исходный код которого можно найти в файле ni-test.c в архиве add_sys.tgz в разделе "Материалы для скачивания".


Листинг 1. Поиск неиспользуемых системных вызовов.

#include <linux/module.h> 
#include <linux/kallsyms.h> 
#include <linux/uaccess.h> 

static unsigned long asct = 0, anif = 0; 

int symb_fn( void* data, const char* sym, struct module* mod, unsigned long addr ) { 
   if( 0 == strcmp( "sys_call_table", sym ) ) 
      asct = addr; 
   else if( 0 == strcmp( "sys_ni_syscall", sym ) ) 
      anif = addr; 
   return 0; 
} 

#define SYS_NR_MAX_OLD 340  
// - этот размер таблицы взят довольно произвольно из ядра 2.6.37 
static void show_entries( void ) { 
   int i, ni = 0; 
   char buf[ 200 ] = ""; 
   for( i = 0; i <= SYS_NR_MAX_OLD; i++ ) { 
      unsigned long *taddr = ((unsigned long*)asct) + i; 
      if( *taddr == anif ) { 
         ni++; 
         sprintf( buf + strlen( buf ), "%03d, ", i ); 
      } 
   } 
   printk( "! найдено %d входов: %s\n", ni, buf ); 
} 

static int __init init_driver( void ) { 
   kallsyms_on_each_symbol( symb_fn, NULL ); 
   printk( "! адрес таблицы системных вызовов = %lx\n", asct ); 
   printk( "! адрес не реализованных вызовов  = %lx\n", anif ); 
   if( 0 == asct || 0 == anif ) { 
      printk( "! не найдены символы ядра\n" ); 
      return -EFAULT; 
   } 
   show_entries(); 
   return -EPERM; 
} 
module_init( init_driver ); 

MODULE_DESCRIPTION( "test unused entries" ); 
MODULE_AUTHOR( "Oleg Tsiliuric <olej@front.ru>" ); 
MODULE_LICENSE( "GPL" ); 

Так как код достаточно простой и понятный, и не требует подробного обсуждения, то сразу перейдем к его исполнению на версии ядра 2.6.32:

$ sudo insmod ni-test.ko 
insmod: error inserting 'ni-test.ko': -1 Operation not permitted 
$ dmesg | tail -n 18 | grep ! 
! найдено 26 входов: 017, 031, 032, 035, 044, 053, 056, 058, 098, 112, 127, 130, 137, 
167, 188, 189, 222, 223, 251, 273, 274, 275, 276, 285, 294, 317, 

Изучив полученную информацию, можно сделать вывод:

  • почти 10% таблицы системных вызовов не используются;
  • стабильность этого списка очень высока (так как для статического анализа была выбрана версия 3.0.9, а для динамического разбора версия 2.6.32, отстоящие друг от друга более, чем на 2 года выпуска).

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


Листинг 2. Файл syscall.h с общими определениями.

// номер нового системного вызова 
#define __NR_own 223 
// может быть взят любой, полученный при загрузке модуля ni-test.ko 
// для ядра 2.6.32 был получен список свободных номеров: 
// 017, 031, 032, 035, 044, 053, 056, 058, 098, 112, 127, 130, 137, 
// 167, 188, 189, 222, 223, 251, 273, 274, 275, 276, 285, 294, 317, 

В листинге 3 приведена тестовая программа, использующая новый системный вызов.


Листинг 3. Файл syscall.с с тестовой программой.

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include "syscall.h" 

static int sys_own_call( char *str, int len ) { 
   long __res; 
   __asm__ volatile ( 
      "int $0x80": 
     "=a" (__res):"a"(__NR_own),"b"((long)(str)),"c"((long)(len)) ); 
   return -((int)__res); 
} 

static void do_own_call( char *str ) { 
   int n = sys_own_call( str, strlen( str ) ); 
   printf( "syscall return %d : %s\n", n, strerror( n ) ); 
} 

int main( int argc, char *argv[] ) { 
   char str[] = "DEFAULT STRING"; 
   if( 1 == argc ) do_own_call( str ); 
   else 
      while( --argc > 0 ) do_own_call( argv[ argc ] ); 
   return EXIT_SUCCESS; 
}; 

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

В листинге 4 приведен файл add-sys.c, содержащий модуль, отвечающий за реализацию этого системного вызова в пространстве ядра.


Листинг 4. Модуль, добавляющий новый системный вызов.

#include <linux/module.h> 
#include <linux/kallsyms.h> 
#include <linux/uaccess.h> 
#include <linux/unistd.h> 

#include "../find.c" 
#include "../CR0.c" 
#include "syscall.h" 

// системный вызов с двумя параметрами: 
asmlinkage long (*old_sys_addr) ( const char __user *buf, size_t count ); 

asmlinkage long new_sys_call ( const char __user *buf, size_t count ) { 
   static char buf_msg[ 80 ]; 
   int res = copy_from_user( buf_msg, (void*)buf, count ); 
   buf_msg[ count ] = '\0'; 
   printk( "! передано %d байт: %s\n", count, buf_msg ); 
   return res; 
}; 
EXPORT_SYMBOL( new_sys_call ); 

static void **taddr; // адрес таблицы sys_call_table 

static int __init new_sys_init( void ) { 
   void *waddr; 
   if( ( taddr = find_sym( "sys_call_table" ) ) != NULL ) 
      printk( "! адрес sys_call_table = %p\n", taddr ); 
   else { 
      printk( "! sys_call_table не найден\n" ); 
      return -EINVAL; 
   } 
   old_sys_addr = (void*)taddr[ __NR_own ]; 
   printk( "! адрес в позиции %d[__NR_own] = %p\n", __NR_own, old_sys_addr ); 
   if( ( waddr = find_sym( "sys_ni_syscall" ) ) != NULL ) 
      printk( "! адрес sys_ni_syscall = %p\n", waddr ); 
   else { 
      printk( "! sys_ni_syscall не найден\n" ); 
      return -EINVAL; 
   } 
   if( old_sys_addr != waddr ) { 
      printk( "! непонятно! : адреса не совпадают\n" ); 
      return -EINVAL; 
   } 
   printk( "! адрес нового sys_call = %p\n", &new_sys_call ); 
   rw_enable(); 
   taddr[ __NR_own ] = new_sys_call; 
   rw_disable(); 
   return 0; 
} 

static void __exit new_sys_exit( void ) { 
   printk( "! адрес sys_call при выгрузке = %p\n", (void*)taddr[ __NR_own ] ); 
   rw_enable(); 
   taddr[ __NR_own ] = old_sys_addr; 
   rw_disable(); 
   return; 
} 

module_init( new_sys_init ); 
module_exit( new_sys_exit ); 

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

В листинге 4 также выполняется двойная проверка соответствия адреса в заданной (__NR_own) позиции таблицы sys_call_table адресу sys_ni_syscall.

Теперь можно проверить совместную работу модуля и тестовой программы.

$ ./syscall 
syscall return 38 : Function not implemented 

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

$ sudo insmod add-sys.ko 
$ lsmod | grep 'sys' 
add_sys                 1432  0 
$ dmesg | tail -n 30 | grep ! 
! адрес sys_call_table = c07ab3d8 
! адрес в позиции 223[__NR_own] = c045b9a8 
! адрес sys_ni_syscall = c045b9a8 
! адрес нового sys_call = fdd7c024 
$ ./syscall новые аргументы 
syscall return 0 : Success 
syscall return 0 : Success 

Как видно, программа сделала 2 системных вызова.

$ dmesg | tail -n 30 | grep ! 
! адрес sys_call_table = c07ab3d8 
! адрес в позиции 223[__NR_own] = c045b9a8 
! адрес sys_ni_syscall = c045b9a8 
! адрес нового sys_call = fdd7c024 
! передано 18 байт: аргументы 
! передано 10 байт: новые 

Выгрузим реализующий модуль и снова запустим программу:

$ sudo rmmod add-sys 
$ dmesg | tail -n 50 | grep ! 
! адрес sys_call_table = c07ab3d8 
! адрес в позиции 223[__NR_own] = c045b9a8 
! адрес sys_ni_syscall = c045b9a8 
! адрес нового sys_call = fdd7c024 
! передано 18 байт: аргументы 
! передано 10 байт: новые 
! адрес sys_call при выгрузке = fdd7c024 
$ ./syscall 1 2 3 
syscall return 38 : Function not implemented 
syscall return 38 : Function not implemented 
syscall return 38 : Function not implemented 

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


Заключение

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



Загрузка

ИмяРазмерМетод загрузки
add_sys.tgz4KBHTTP

Информация о методах загрузки


Ресурсы

Об авторе

Фото автора

Олег Иванович Цилюрик, много лет был разработчиком программного обеспечения в крупных центрах разработки: ВНИИ РТ, НПО "Дельта", КБ ПМ. Последние годы работал над проектами в области промышленной автоматики, IP телефонии и коммуникаций. Автор нескольких книг. Преподаватель тренингового отделения международной софтверной компании Global Logic.

Помощь по сообщениям о нарушениях

Сообщение о нарушениях

Спасибо. Эта запись была помечена для модератора.


Помощь по сообщениям о нарушениях

Сообщение о нарушениях

Сообщение о нарушении не было отправлено. Попробуйте, пожалуйста, позже.


developerWorks: вход


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


При первом входе в developerWorks для Вас будет создан профиль. Выберите информацию отображаемую в Вашем профиле — скрыть или отобразить поля можно в любой момент.

Выберите ваше отображаемое имя

При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

(Должно содержать от 3 до 31 символа.)


Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Оценить эту статью

Комментарии

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