- Что-то беспокоит меня Гондурас...
                                                                                    - Беспокоит? А ты его не чеши.


В предыдущих частях обсуждения (1-я, 2-я и 3-я) мы рассматривали как, используя возможность поменять содержимое sys_call_table, изменить поведение того или иного системного вызова Linux. Сейчас мы продолжим эксперименты в сторону того, можно ли (и как) динамически добавить новый системный вызов в целях вашего программного проекта.

Мы не станем акцентироваться на вопросе «зачем?» — в программировании последнее дело спрашивать «зачем?», нужно спрашивать «как?»: если какая-то техника вам не близка — вы её просто не используйте (см. эпиграф). Но тем не мене, мы вернёмся коротко к этому к концу, в обсуждении.

Как это выглядит?


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

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

Размер таблицы sys_call_table достаточно велик, и он меняется от версии к версии ядра (версия 3.13), вот его очень грубая оценка:

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

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

Смягчает выше перечисленные ограничивающие обстоятельства то, что таблица системных вызовов не плотная, достаточно сильно разреженная, в ней есть не использующиеся позиции (оставшиеся от устаревших системных вызовов и не поддерживаемых в настоящее время). Все такие позиции заполнены одним адресом — указателем на функцию обработчика нереализованных вызовов sys_ni_syscall():
$ cat /proc/kallsyms | grep sys_ni_syscall 
c045b9a8 T sys_ni_syscall 

А сам системный вызов sys_ni_syscall() определён как-то так:
asmlinkage long sys_ni_syscall( void ) {
   return -ENOSYS;
}

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

Статически, текстуально в исходном коде, можно в деталях рассмотреть структуру таблицы sys_call_table (для выбранной платформы и версии). Для таких изучений мало пригоден сам исходный код как он представлен разработчиками, но, к счастью для наших целей, на сегодня существует весьма много ресурсов, визуализирующих код ядра средствами проекта LXR (Linux Kernel Cross Reference), например здесь или здесь (это позволяет сравнивать версии и с лёгкостью находить нужные идентификаторы). Я для примера покажу только те позиции sys_call_table ядра 3.0.26 архитектуры x86 которые содержат (файл <arch/x86/kernel/syscall_table_32.S>) ссылку на sys_ni_syscall (но к ядру 3.2 и далее этот файл исчезнет даже из дерева кодов… но принципы формирования таблицы останутся те же и вид её не изменится):

ENTRY(sys_call_table)
        .long sys_restart_syscall    /* 0 - old "setup()" system call, used for restarting */
        .long sys_exit
...
        .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         /* old prof syscall holder */        //44 
        .long sys_ni_syscall         /* old lock syscall holder */        //53 
        .long sys_ni_syscall         /* old mpx syscall holder */         //56 
        .long sys_ni_syscall         /* old ulimit syscall holder */      //58 
        .long sys_ni_syscall         /* old profil syscall holder */      //98 
        .long sys_ni_syscall         /* old "idle" system call */         //112 
        .long sys_ni_syscall         /* old "create_module" */            //127 
        .long sys_ni_syscall         /* 130: old "get_kernel_syms" */     //130 
        .long sys_ni_syscall         /* reserved for afs_syscall */       //137 
        .long sys_ni_syscall         /* Old sys_query_module */           //167 
        .long sys_ni_syscall         /* reserved for streams1 */          //188 
        .long sys_ni_syscall         /* reserved for streams2 */          //189 
        .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 не задействованы. Анализу неиспользуемых позиций в динамике, не полагаясь на изменчивые коды ядра, и будет посвящён первый рассматриваемый модуль ядра:

static void **taddr,                     // адрес sys_call_table 
            *niaddr;                     // адрес sys_ni_syscall() 
static int nsys = 0;                    // число системных вызовов в версии

#define SYS_NR_MAX 450 
// SYS_NR_MAX - произвольно большое, больше длины sys_call_table 
static int sys_length( void* data, const char* sym, struct module* mod, unsigned long addr ) { 
   int i; 
   if( ( strstr( sym, "sys_" ) != sym ) || 
       ( 0 == strcmp( "sys_call_table", sym ) ) ) return 0; 
   for( i = 0; i < SYS_NR_MAX; i++ ) { 
      if( taddr[ i ] == (void*)addr ) {  // найден sys_* в sys_call_table 
         if( i > nsys ) nsys = i; 
         break; 
      } 
   } 
   return 0; 
} 

static void put_entries( void ) { 
   int i, ni = 0; 
   char buf[ 200 ] = ""; 
   for( i = 0; i <= nsys; i++ ) 
      if( taddr[ i ] == niaddr ) { 
         ni++; 
         sprintf( buf + strlen( buf ), "%03d, ", i ); 
      } 
   LOG( "found %d unused entries: %s\n", ni, buf ); 
} 

static int __init init_driver( void ) { 
   if( NULL == ( taddr = (void**)kallsyms_lookup_name( "sys_call_table" ) ) ) { 
      ERR( "sys_call_table not found!\n" ); 
      return -EFAULT; 
   } 
   LOG( "sys_call_table address = %p\n", taddr ); 
   if( NULL == ( niaddr = (void*)kallsyms_lookup_name( "sys_ni_syscall" ) ) ) { 
      ERR( "sys_ni_syscall found!\n" ); 
      return -EFAULT; 
   } 
   LOG( "sys_ni_syscall address = %p\n", niaddr ); 
   kallsyms_on_each_symbol( sys_length, NULL ); 
   LOG( "sys_call_table length = %d\n", nsys + 1 ); 
   put_entries(); 
   return -EPERM; 
} 

module_init( init_driver ); 

Как и раньше, необязательные детали (такие как макрос LOG() и др.) не показаны, все они есть в полных прилагаемых файлах.
Можно было бы пойти проще (что тоже корректно) — для выяснения протяжённости sys_call_table просто пересчитать число символов ядра по маске sys_* и вычесть 1 (сам символ sys_call_table). Но мы идём избыточным путём:
  • в цикле находится очередной символ по маске sys_*;
  • его позиция разыскивается в sys_call_table (это дополнительная перестраховка, что это системный вызов);
  • если эта позиция больше, чем найденные ранее для предыдущих символов, то на считается текущим номером последнего вызова (текущим размером sys_call_table);

Такая избыточная (но вовсе не необходимая) схема позволяет попутно уточнить точный размер таблицы системных вызовов для ваши архитектуры и версии ядра Linux:

$ uname -p 
i686 
$ uname -r 
3.13.0-37-generic 
$ sudo insmod nsys.ko 
insmod: ERROR: could not insert module nsys.ko: Operation not permitted 
$ dmesg | tail -n 4 
[10751.601851] ! sys_call_table address = c1666140 
[10751.602194] ! sys_ni_syscall address = c1075930 
[10751.659769] ! sys_call_table length = 351 
[10751.659779] ! found 27 unused entries: 017, 031, 032, 035, 044, 053, 056, 058, 098, 112, 127, 130, 137, 167, 169, 188, 189, 222, 223, 251, 273, 274, 275, 276, 285, 294, 317, 

Итого, в этой версии 351 системных вызовов, из которых 27 не используются (почти 10% размера таблицы). Стабильность этого списка очень высока (сознательно для анализа кода была выбрана версия 3.0.26, а для исполнения в динамике версии 2.6.32 и 3.13, отстоящие друг от друга более чем на 4 года выпуска).

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

Реализация нового системного вызова


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

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

Проще и понятнее начать именно с пользовательского приложения, которое будет выполнять новый системный вызов. Здесь всё просто — проще не бывает:

static void do_own_call( char *str ) { 
   int n = syscall( __NR_own, str, strlen( str ) ); 
   if( n == 0 ) LOG( "syscall return %d\n", n ); 
   else { 
      ERR( "syscall error %d : %s\n", n, strerror( -n ) ); 
      exit( n ); 
   } 
} 

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

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

А вот и модуль, который «подхватывает» такой вызов со стороны ядра:

asmlinkage long (*old_sys_addr) ( void ); 

// системный вызов с двумя параметрами: 
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'; 
   LOG( "accepted %d bytes: %s\n", count, buf_msg ); 
   return res; 
}; 

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

static int __init new_sys_init( void ) { 
   void *waddr; 
   if( NULL == ( taddr = (void**)kallsyms_lookup_name( "sys_call_table" ) ) ) { 
      ERR( "sys_call_table not found!\n" ); 
      return -EFAULT; 
   } 
   old_sys_addr = (void*)taddr[ __NR_own ]; 
   if( ( waddr = (void*)kallsyms_lookup_name( "sys_ni_syscall" ) ) != NULL ) 
      LOG( "sys_ni_syscall address = %p\n", waddr ); 
   else { 
      ERR( "sys_ni_syscall not found!\n" ); 
      return -EFAULT; 
   } 
   if( old_sys_addr != waddr ) { 
      ERR( "not free slot!\n" ); 
      return -EINVAL; 
   } 
   LOG( "old sys_call_table[%d] = %p\n", __NR_own, taddr[ __NR_own ] ); 
   rw_enable(); 
   taddr[ __NR_own ] = new_sys_call; 
   rw_disable(); 
   LOG( "new sys_call_table[%d] = %p\n", __NR_own, taddr[ __NR_own ] ); 
   return 0; 
} 

static void __exit new_sys_exit( void ) { 
   rw_enable(); 
   taddr[ __NR_own ] = old_sys_addr; 
   rw_disable(); 
   LOG( "restore sys_call_table[%d] = %p\n", __NR_own, taddr[ __NR_own ] ); 
   return; 
} 

module_init( new_sys_init ); 
module_exit( new_sys_exit ); 

Здесь также делается двойная перестраховка — проверка соответствия адреса в заданной (__NR_own) позиции таблицы sys_call_table адресу неиспользуемых системных вызовов sys_ni_syscall.

И теперь оцениваем то, что у нас получилось:

$ ./syscall 
syscall error -1 : Operation not permitted 
$ echo $? 
255 
$ sudo insmod adds.ko 
$ lsmod | head -n3 
Module                  Size  Used by 
adds                   12622  0 
pci_stub               12550  1 
$ dmesg | tail -n3 
[15000.600618] ! sys_ni_syscall address = c1075930 
[15000.600622] ! old sys_call_table[223] = c1075930 
[15000.600623] ! new sys_call_table[223] = f87d9000 
$ ./syscall new string for call 
syscall return 0 
syscall return 0 
syscall return 0 
syscall return 0 
$ dmesg | tail -n4 
[15070.680753] ! accepted 4 bytes: call 
[15070.680799] ! accepted 3 bytes: for 
[15070.680804] ! accepted 6 bytes: string 
[15070.680807] ! accepted 3 bytes: new 
$ ./syscall 'new string for call' 
syscall return 0 
$ dmesg | tail -n1 
[15167.526452] ! accepted 19 bytes: new string for call 
$ sudo rmmod adds 
$ dmesg | tail -n1 
[15199.917817] ! restore sys_call_table[223] = c1075930 
$ ./syscall 
syscall error -1 : Operation not permitted 

После выгрузки модуля ядро более не в состоянии поддержать выполнение требуемого программе системного вызова!

Обсуждение


Обсуждать тут, собственно, нечего — всё прозрачно показано примером. Но я вначале обещал высказать свои соображения зачем такое вообще может иметь применение (но ещё раз повторю своё твёрдое убеждение в том, что вопрос «зачем?» в программировании, в общем случае, бессмысленный). Показанный трюк предоставляет ещё один путь взаимодействия (двухстороннего) приложений с ядром. Да, конечно есть возможность сделать то же через /dev, /proc, или /sys … но каждый из этих способов тяжеловеснее, чем системный вызов, он вовлекает в работу большее число промежуточных механизмов ядра.

Когда представляется возможность использовать подобный механизм? Например, для асинхронных уведомлений приложения о некоторых событиях в ядре, когда отдельный поток приложения заблокирован на системном вызове до наступления ожидаемого события. Таким событием может быть, например, аппаратное прерывание (IRQ) от отлаживаемого нового устройства (в меру не быстрого). При таком подходе любые операции ввода-вывода с устройством можно реализовать из пространства пользователя, используя операции группы inb(), outb()…, или ioperm() и iopl(). Всё это вместе даёт возможность изучить работу и выписать код обмена с устройством в самых тонких деталях не выходя за пределы пространства пользователя, без рисков и сложностей, связанных с привилегированным режимом ядра. А дальше уже по обстоятельствам и по желанию: можно механически переписать код этого оттестированного драйвера в форме модуля, или оставить как он и есть в пользовательском пространстве.

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

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

И опять же, как ранее, архив кода можно взять здесь или здесь

Эпилог


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

Когда приступаешь к написанию модулей ли ядра, или патчей к ядру, первоначально возникает ощущение скованности, ограниченности только теми возможностями, которые предоставляет плохо документированный API ядра, либо описаны в немногочисленных и давно устаревших книгах по типу «написание драйверов Linux». Но опыты, подобные описанным в этом цикле, и ещё множество иных подобных, подсказывают, что в модуле ядра вы имеете в доступе все (без исключения!) возможности пространства пользователя (запуск новых процессов и потоков, посылка сигналов UNIX др.). И плюс к этому недостижимые в пользовательском пространстве возможности, связанные с привилегированным (супервизор, кольцо 0) режимом защиты процессора (привилегированные команды, внутренние регистры процессора, реакция на прерывания).

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

Комментарии (15)


  1. Delphinum
    07.10.2015 20:26

    Шутка про уехавшую цитату.

    Я так вижу.


    1. Olej
      07.10.2015 21:25
      +1

      А сейчас?
      … цитата приехала?


      1. Delphinum
        07.10.2015 22:10

        Нет, все так же.


    1. Olej
      07.10.2015 21:34

      Я так вижу.

      Это связано с шириной окна браузера.
      Раздвиньте шире! ;-)


      1. Delphinum
        07.10.2015 22:09

        У меня xmonad, тут шире некуда.


  1. khim
    07.10.2015 23:12
    +9

    Неплохая серия статей, одно только напрягает: постоянный повтор дурацкого тезиса «в программировании последнее дело спрашивать «зачем?», нужно спрашивать «как?»» — мой опыт показывает, что как-раз-таки вопрос «зачем» это первый вопрос, который нужно себе задавать при программировании. Самый главный, в общем-то — без ответа на него не стоит делать вообще ничего. Именно отказ от ответа на этот вопрос приносит с собой 99% разнообразных сложностей и глюков. И, в частности, в данном случае даже после завершения серии так и не стало понятнее — к чему все эти сложности и к чему покрывать себе дорожку плотным слоем граблей, когда есть куча других способов (начиная от упомянутых в статье /proc, /sys и кончая ioctl'ом).

    P.S. Я понимаю что иногда приходится-таки «забивать шурупы молотком» (ну там найти нормальную работу не удалось или срочно нужно сделать что-то к выставке), но обычные люди подобными эпизодами своей жизни редко гордятся и уж тем более не пишут статьи, которые объясняют, что «так и нужно делать»…


  1. Olej
    08.10.2015 00:33
    -2

    ну там найти нормальную работу не удалось или срочно нужно сделать что-то к выставке

    А что?
    И впрямь вот такая беда?
    Может помощь какая нужна?… материальная ;-)


  1. milabs
    08.10.2015 16:18
    +4

    Аффтар, ты слишком пафосен. Изучай матчасть, твой rw_enable и трюки с CR0 опасны на реальных системах, если не понимаешь что делаешь. А ты, как видно, не понимаешь. Почитай про preemption. Ну и меня почтитай: habrahabr.ru/users/milabs/topics. И меньше пафоса, соглашусь с khim.


    1. Olej
      08.10.2015 18:22
      -2

      а тыкать мне не надо, сынок… ;-)

      трюки с CR0 опасны на реальных системах

      ну так давайте обсудим чем же так «опасны»?


  1. Olej
    09.10.2015 10:42
    -1

    и трюки с CR0 опасны на реальных системах,

    Н-да…
    Вы слышите меня, стены?

    Как я понимаю, предметного обсуждения этой «опасности» мы не услышим…


  1. Olej
    09.10.2015 11:05

    По поводу «опасности» трюков с CR0 я слышал десятки раз и в разных компаниях, поэтому это действительно стоит уточнения:
    — речь о том, что в SMP между изменением CR0 и следующей записьж в защищённую область RAM может произойти перепланирование потока выполнения на другой процессор…
    — … может…, но вероятность такого события примерно равна вероятности рождения сверхновой во Вселенной: если непрерывно развлекаться тем, что только загружать-выгружать модуль, то такое можно будет наблюдать… ну раз в 1000 лет… если кто доживёт
    — но факт такой возможности настолько на виду, настолько это элементарно, что его сразу же указывают как только речь об этом заходит… т.е. говорится такое не для обсуждения предмета, а чтобы сказать «какой же я умный»… ну не могут разные люди слово в слово повторять эту элементарщину — это как пыанэр-зубрила, который руку тянет: «и я, и я, и я ...»
    — и в чём выражается эта «опасность» если она и случится: операционную систему подвесите? вообще её инсталляцию снесёте? компьютер у вас задымится и выгорит ярким пламенем?… Нет, всего лишь не загрузится ваш собственный модуль с сообщением Оооps…
    — и лечится это так же элементарно, как и объясняется: запрет прерываний на время записи на локальном процессоре на котором выполняется операция (и в коде приводимом это есть)…
    — и давно это известно и описано… в этой вот, например, публикации: WP: Safe or Not? (на которую и ссылка показывалась где-то в предыдущей части… но это же читать надо!)


    1. milabs
      10.10.2015 22:22

      Опыт практических и внедрённых программных разработок около 40 лет, на 20-ти языках программирования.
      Преподаватель-тренер международной софтверной компании Global Logic. Постоянный автор публикаций IBM Developer Works.
      Около 10 лет научный редактор переводных книг издательства компьютерной литературы «Символ-Плюс», Санкт-Петербург.


      Повеселило. Листал я как-то бумажный экземпляр книги этого издательства. Ужоснах. Научные редакторы и тренеры-международники, убейтесь. Короче, Олежка, тренеруй лохов, читай про COW и пеши исчо. Хабр ждёт тебя.

      stackoverflow.com/questions/15275059/whats-the-purpose-of-x86-cr0-wp-bit


      1. khim
        11.10.2015 01:45
        +3

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

        Если вы по коридору
        Мчитесь на велосипеде,
        А на встречу вам из ванной
        Вышел папа погулять,
        Не сворачивайте в кухню,
        В кухне твердый холодильник.
        Тормозите лучше в папу.
        Папа мягкий. Он простит.
        Но все ли потенциальные читатели этого творения это понимают? Я очень сомневаюсь. Более того, я почти уверен, что некоторая часть читателей вполне может пойти и начать использовать эти подходы в своей деятельности. И? Чего потом? Это уже даже не смешно, это грустно.

        Почему? О чём это я? А очень просто: как я уже сказал вопрос «зачем» — это то, с чего нужно начинать.

        Итак: где и куда мы можем применить эту технику? Зачем она нужна? И кому?

        При разработке какой-нибудь встроенной системы? Нет смысла: если мы контролируем устройство всей системы, а, стало быть, и ядра, то там не нужно производить странные выкрутасы с таблицей системных вызовов, можно просто взять и добавить системый вызов, если уж он нам так нужен. Можно даже попросить номер у разработчиков ядра, если так уж приспичило! К примеру разработчики ядра категорически отказались в своё время поддерживать стандарт STREAMS, но выделили парочку системных вызовоа для Caldera'ы. Понятно, что в этом случае у вас будет нормальная заглушка в ядре, которую вы сможете перекрыть в нужный момент и все описанные ужасы будут не очень-то и нужны.

        При разработке модуля для какой-нибудь железяки? Мало смысла, неудобно: так как мы не контролируем всю систему, то наш «любимый» системный вызов может захватить кто-то ещё — а особенно весело будет если он захватит его после нас и попробует по каким-то причинам всё-таки иногда вызывать старый адрес… (люди, работавшие с TSR-программами в DOS могут с содроганием вспомнить «старые добрые» времена, когда попытка выгрузить программу из памяти вешала всю систему… люди, тогда не жившие, смогут пережить все эти «прелести» заново)…

        Ладно — но ведь это можно разрулить! Вопрос: как? Мы хотим, чтобы нашим творением можно было бы как-то пользоваться, то нужно будет придумать альтернативный способ указания нашей компоненте в userspace номера того syscall'а, куда мы вешаемся… это уже какая-та инсталляцию Sound Blaster'а до появления Plug-and-Play получается! Или SCSI… А там уже и до вопросов типа «у нас есть три устройства, но какой-то идиот-конструктор решил, что все три могут иметь только адреса 5 или 6… какое из них нам вскрыть, чтобы попробовать что-то внутри перепаять?» рукой подать.

        Можно, конечно, номер системного вызова через файл в /sys передать… но тогда непонятно что мешает просто там же и данные для создания драйвера оставить и через IOCTLы всё сделать…

        А автор все эти вопросы, котороые куда важнее технической части серии статей «заметает под ковёр»: ну я типа рассказал, как в доманшних условиях вскипятить нитроглицерин в кастрюльке и смылся в кусты… если что пойдёт не так — я не виноват! Нет уж, извините, виноват: мы в отчете за тех, кого приручили.

        Дико видеть такое отношение «преподавателя-тренера», если честно…


      1. jcmvbkbc
        11.10.2015 02:29
        -1

        Переход на личности — не ок.
        Как приведённая ссылка подтверждает или опровергает заявление о том, что

        трюки с CR0 опасны на реальных системах
        ?


  1. zim32
    14.10.2015 02:06

    Надеюсь автор после всех комментариев еще будет писать статьи. Было познавательно.