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

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

Начальные условия


  • 4 ядра процессора Intel Core i7
  • 4 Гб оперативной памяти + 4 Гб swap
  • Ubuntu 16.10 x64 на виртуальной машине VirtualBox 5.1.10
  • Ядро Linux 4.9.0
  • gcc 6.2.0

Для редактирования конфигурации ядра в псевдографическом режиме нужен ncurses:

sudo apt-get update
sudo apt-get install libncurses5-dev

Сборка чистого ядра


Я рекомендую собрать чистое ядро прежде, чем начать разработку модулей. На это есть 2 причины:

  1. Первая сборка ядра — довольно продолжительный процесс. Чаще всего он длится от 20 минут до 3 часов. Если же провести сборку заранее, вы получите большую часть бинарников ядра, которые не будут нуждаться в перекомпиляции. Это позволит полностью сосредоточиться на разработке модуля, не мучаясь в ожидании ответа на вопрос “Запустится ли мой первый Hello World?”

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

Итак, сборка ядра:

  1. Скачиваем архив с исходниками:

    wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-x.x.x.tar.xz

    где x.x.x — версия ядра.

    Либо можно скачать архив руками с kernel.org

  2. Извлекаем данные из архива:

    tar -xpJf linux-x.x.x.tar.xz

  3. Переходим в только что распакованную папку:

    cd linux-x.x.x

  4. Генерируем конфигурацию ядра по умолчанию:

    make defconfig

    Для продвинутых:
    make menuconfig

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

  5. Запускаем непосредственно сборку ядра и модулей:

    make && make modules

    Сборка будет длиться от 20 минут до 3х часов.

    Лайфхак:
    make -j x && make modules -j x

    Где x — количество ядер процессора + 1. То есть в моём случае x = 5.
    Такое значение рекомендуют установить во всех руководствах, но на самом деле значение можно установить любое. Я решил “увеличить количество ядер вдвое”, то есть запустить сборку c параметром -j 9. Это не ускоряет сборку в 2 раза, но увеличивает конкурентоспособность процессов сборки по отношения ко всем другим процессам в системе.

    Кроме того, в системном мониторе(gnome-system-monitor) всем make-процессам я установил максимальный приоритет. Система после этого буквально зависла, но сборка прошла за 6 минут. Используйте этот метод на свой страх и риск.

    После успешной сборки нужно установить всё то, что мы собрали. Это требует root-прав.

  6. Установка заголовков:

    sudo make headers_install

  7. Установка модулей:

    sudo make modules_install

  8. Установка непосредственно ядра:

    sudo make install

  9. Команды установки должны сгенерировать начальный RAM-диск и обновить grub. Если вдруг начальный RAM-диск не сгенерировался — система с новым ядром не запустится.

    Проверить это можно по наличию файла "/boot/initrd.img-x.x.x" (x.x.x — версия ядра)
    Если файла не обнаружилось — генерируем его руками:

    sudo update-initramfs –c –k x.x.x

  10. Обновляем загрузчик grub:

    sudo update-grub

Готово! После перезапуска система запустится с новым ядром. Проверить текущую версию ядра:

uname -r

Если вдруг что-то пошло не так, и система не загружается с новым ядром, перезагрузите компьютер, в меню grub перейдите в advanced options и выберите другую версию ядра(ту, под которой вы загружались раньше, обычно у версий по умолчанию добавляют суффикс -general)

Создание модуля


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

  • Ядро не имеет доступа к стандартным библиотекам языка C. Причина этого – скорость выполнения и объем кода. Часть функций, однако, можно найти в исходниках ядра. Например, обычные функции работы со строками описаны в файле lib/string.c

  • Отсутствие защиты памяти. Если обычная программа предпринимает попытку некорректного обращения к памяти, ядро может аварийно завершить процесс. Если ядро предпримет попытку некорректного обращения к памяти, результаты будут менее контролируемыми. К тому же ядро не использует замещение страниц: каждый байт, используемый в ядре, – это один байт физической памяти.

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

  • Фиксированный стек(причём довольно небольшой). Именно поэтому не рекомендуется использовать рекурсию в ядре.

Hello world!


Давайте на конкретном примере рассмотрим “Hello world” в виде модуля ядра. Создадим файл hello.c в любой удобной для вас папке:

// hello.c
#include <linux/module.h>
#include <linux/kernel.h>

static int  __init myinit(void)
{
   printk("%s\n","<my_tag> hello world");

   return 0;
}

static void __exit myexit(void) {}

module_init(myinit);
module_exit(myexit);

MODULE_LICENSE("GPL");

Обычная пользовательская программа начинается с вызова функции main() и работает, пока не возвратит системе какое-то значение.

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

Функции, которые обрабатывают эти события — соответственно

static int __init myinit(void)
static void __exit myexit(void)


Они отмечены макросами __init, __exit и зарегистрированы с помощью module_init и module_exit как обработчики событий. Название этих функций может быть любым, но не должно конфликтовать с другими функциями в ядре.

Поскольку ядро не использует стандартную библиотеку C, мы не можем использовать stdio.h. Вместо этого мы подключаем файл kernel.h, в котором реализована функция printk. Эта функция аналогична printf с тем лишь отличием, что выводит сообщения не в окно терминала, а в системный лог (/var/log/syslog).

В этот лог пишется очень много сообщений со всей системы, поэтому наши нужно пометить каким-то оригинальным тегом, чтобы потом с помощью утилиты grep можно было выделить только сообщения нашего модуля.

Ещё одна непонятная строчка — MODULE_LICENSE(«GPL»);

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

Сборка


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

# указываем путь к недавно собранному ядру
KERNEL_PATH = /path-to-your-kernel/linux-x.x.x

# перечисляем файлы, которые будут собираться в файлы модулей
obj-m += hello.o

all:
# запуск make с параметром -C указывает, что мы запускаем сборку в окружении
# KERNEL_PATH. Это нужно для того, чтобы компилятор подтянул зависимости
# из исходников ядра
# SUBDIRS - это место, в котором будут лежать результаты сборки, 
# в данном случае - текущая папка
	make -C $(KERNEL_PATH) SUBDIRS=$(PWD) modules
# Удаление ненужных файлов, которые создались в процессе сборки
	make clean
# описание файлов, которые можно удалить после сборки
clean:
	rm -f *.o *.mod* Module.symvers modules.order

После создания Makefile переходим непосредственно к сборке:

make

Через пару секунд в нашей папке появится файл hello.ko — готовый скомпилированный модуль.

Загрузка и выгрузка


Существует 2 способа загрузки модуля в ядро:

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

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

Загрузить модуль:

sudo insmod hello.ko

Команда insmod загружает модуль в пространство ядра, тем самым вызывая функцию инициализации.

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

image

В функции инициализации мы добавили вызов printk, который выводит в системный лог наше сообщение.

Для просмотра системного лога существует утилита dmesg:

dmesg | grep '<my_tag>'

Вышеуказанная команда выведет

<my_tag> hello world

После того, как мы загрузили модуль, он так и останется висеть в ядре до тех пор, пока его не выгрузят. Чтобы сделать это:

sudo rmmod hello.ko

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

Лайфхак
Для того, чтобы каждый раз не вводить 2 команды для загрузки и выгрузки модуля во время отладки, в функции инициализации возвращают значение -1. Такой модуль при попытке загрузки выводит в терминал ошибку, после чего прекращает работу, но при этом функция инициализации отрабатывает полностью и корректно, превращаясь, по сути, в аналог функции main() пользовательских программ.

static int  __init myinit(void)
{
   printk("%s\n","<my_tag> hello world");

   return -1;
}


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

Перехват системных вызовов


Небезопасный способ


Когда-то давно, ещё до ядра версии 2.6, для того, чтобы перехватить системный вызов, писали функцию-хук, которая её заменяла: выполняла другой код + вызывала непосредственно сам syscall(чтобы не нарушить работоспособность системы).

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

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

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

LSM


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

Самое ценное для нас в данном фреймворке то, что он реализован через набор заранее предустановленных в ядро хуков(по сути, тот способ, который я описывал выше, но безопасный, потому что ядро заранее рассчитано на наличие таких хуков).

LSM позволяет вставлять в код своих хуков вызов пользовательских, что позволяет безопасно работать с системными вызовами без изменения таблицы символов.

Всё предельно просто. Рассмотрим пример создания модуля безопасности foobar, который перехватывает системный вызов mk_dir.

Написание кода


  1. Находим в исходниках ядра папку security, создаём в ней папку для нашего модуля, а в ней — его исходный код foobar.c:

    // /security/foobar/foobar.c
    //---INCLUDES
    #include <linux/module.h>
    #include <linux/lsm_hooks.h>
    
    //---HOOKS
    //mkdir hook
    static int foobar_inode_mkdir(struct inode *dir, struct dentry *dentry, umode_t mask)
    {
        	printk("%s\n","<my_tag> mkdir hook");
        	return 0;
    }
    
    //---HOOKS REGISTERING
    static struct security_hook_list foobar_hooks[] =
    {
        	LSM_HOOK_INIT(inode_mkdir, foobar_inode_mkdir),
    };
    
    //---INIT
    void __init foobar_add_hooks(void)
    {
        	security_add_hooks(foobar_hooks, ARRAY_SIZE(foobar_hooks));
    }
    

    Файл lsm_hooks.h содержит заголовки тех самых предустановленных хуков, LSM_HOOK_INIT регистрирует соответствие foobar_inode_mkdir() хуку inode_mkdir(), а security_add_hooks() добавляет нашу функцию в общий список пользовательских хуков LSM.

    Таким образом, при каждом вызове mkdir будет вызываться наша функция foobar_inode_mkdir().

  2. Добавляем заголовок нашей функции в файл “/include/linux/lsm_hooks.h”:

    #ifdef CONFIG_SECURITY_FOOBAR
          	extern void __init foobar_add_hooks(void);
    #else
          	static inline void __init foobar_add_hooks(void) { }
    #endif
    

    Все вызовы происходят в исходном файле security.c (далее), этим шагом мы оповещаем его о существовании нашей функции.

  3. В файле “/security/security.c” находим функцию “int __init security_init(void)” и добавляем в её тело следующий вызов:

    foobar_add_hooks();

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

Конфигурация сборки


  1. В папке с нашим модулем(/security/foobar/) создадим файл Kconfig:

    config SECURITY_FOOBAR
    bool "FooBar security module"
          	default y
    help
          	Any help text here
    

    Это создаст пункт меню с нашим модулем.

  2. Откроем файл /security/Kconfig и добавим следующий текст сразу за строчкой “menu «Security options»":

    source security/foobar/Kconfig
    

    Это добавит наш пункт меню в глобальное меню настроек ядра.

  3. Создадим Makefile в папке с нашим модулем:

    obj-$(CONFIG_SECURITY_FOOBAR) += foobar.o
    

  4. Откроем Makefile всего раздела безопасности(/security/Makefile) и добавим в него следующие строчки(по аналогии с такими же строчками для других модулей):

    subdir-$(CONFIG_SECURITY_FOOBAR) += foobar
    obj-$(CONFIG_SECURITY_FOOBAR) += foobar/
    

  5. Запустим конфигурирование в псевдографическом режиме:

    make menuconfig

    Если перейти в подменю “Security options”, первым пунктом мы увидим наш модуль, отмеченный символом “y” (мы установили это значение по умолчанию, когда создавали файл Kconfig), что означает, что мы интегрируем наш модуль непосредственно в код ядра.

Сборка


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

make && make modules

make не требует параметра -j, поскольку пересоберёт ядро с нашим модулем за несколько секунд.

sudo make install

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

Всё!

Осталось перезагрузить систему, после чего в ядре будет висеть наш модуль с перехватом mkdir. Как и говорил ранее, проверяем так:

dmesg | grep '<my_tag>'

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

Надеюсь, кому-то это руководство будет полезным(если бы кто-то написал его вместо меня до того, как я начал копаться в ядре — он сэкономил бы мне 2-3 недели жизни).

Любая критика приветствуется.
Спасибо за внимание.
Поделиться с друзьями
-->

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


  1. afiskon
    21.12.2016 11:56
    +13

    В высшей мере интересно, спасибо! Реквестирую больше статей о ядерной разработке в Linux.


    1. ph4n70m
      21.12.2016 17:43

      Интересно, есть ли у linux сообщества разработчиков общая база знаний и актуальной информации? Или всё надо находить по статьям в интернете? Что-то помимо /usr/src/linux/Documentation/


      1. afiskon
        21.12.2016 18:31

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


  1. aaefimov
    21.12.2016 13:10

    Для загрузки модуля еще можно использовать modprobe


    1. shlemisto
      21.12.2016 13:42

      если только он лежит в /lib/modules/`uname -r`


      1. aaefimov
        21.12.2016 13:50

        Хмм, а ты прав!


  1. nckma
    21.12.2016 13:35

    А как перехватывает системные вызовы strace?
    https://habrahabr.ru/post/215577/


    1. aaefimov
      21.12.2016 14:17
      +2

      Вот мне стало интересно и я скачал исходники strace. Он работает через ptrace (это набор функций под каждую архитектуру в ядре) далее он обращается к ptrace_traceme, в тех же самых security_hook_heads


    1. kmu1990
      21.12.2016 14:36

      Он использует ptrace, т. е. это в userspace — мы можем оставновить процесс перед входом в системный вызов и после завершения, посмотреть на состояние и даже изменить его, но свой код в привелигированном режиме ptrace нам выполнить не даст.


  1. selenite
    21.12.2016 15:03

    самая большая проблема с LSM, а также подсистемами трассировки/профилирования — это то, что количество вещей, которые можно сделать исключительно на них, ограничено фантазией разработчиков ядра :)

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


  1. nitso
    21.12.2016 15:07
    +3

    В крайней мере заинтересовал макрос MODULE_LICENCE. В большинстве источников указывается «напишите GPL или свою лицензию». В гораздо меньшем упоминается «ограничение возможностей взаимодействия с ядром». Еще в одном месте упоминается «загрузка такого модуля опозорит ядро» (loading xxx.ko will taint the kernel) в логах.

    А как на самом деле работает этот механизм и на что влияет? Или все ограничивается ворнингом?


    1. melon
      21.12.2016 16:04
      +1

      если найдёте какую-то информацию на этот счёт, отпишитесь пожалуйста, тоже интересно.


    1. Tujh
      21.12.2016 16:08

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


    1. jok40
      21.12.2016 16:23

      Если мне не изменяет память, то если модуль вызывает методы из других GPL-модулей (например, platform_driver_unregister или irq_of_parse_and_map) и в нём нет макроса MODULE_LICENSE(«GPL»), то Linux отругается и не даст загрузить этот модуль.


    1. dimorphus
      21.12.2016 18:16
      +6

      Модуль ядра представляет собой объектный файл. При загрузке его в ядро командой insmod или modprobe происходит системный вызов. Ядро загружает объектный код модуля и само производит линковку. Линковка производится для вызываемых функций в модуле, которые в ядре помечаются как экспортируемые. Помечаются следующей строчкой: EXPORT_SYMBOL(<function_name>). Так вот, есть ещё директива EXPORT_SYMBOL_GPL. Делает она тоже самое за одним нюансом: эту функцию могут вызывать только модули под лицензией GPL. Если у вас модуль под другой лицензией и при этом в его коде вызывается функция ядра, экспортируемая через EXPORT_SYMBOL_GPL, то ядро просто не загрузит модуль, не произведёт линковку.


      1. dimorphus
        21.12.2016 18:40
        +4

        Уточнение. Сейчас проверил, модуль даже не соберётся имея лицензию отличную от GPL и попытавшись вызвать функцию EXPORT_SYMBOL_GPL. Вот пример:

        #include <linux/module.h>
        #include <linux/kernel.h>
        #include <linux/slab.h>
        #include <linux/reboot.h>
        
        MODULE_LICENSE("I hate GPL!");
        
        int init(void)
        {
           int *i = kmalloc(sizeof(int), GFP_KERNEL); //exported with EXPORT_SYMBOL
        
           kernel_restart(NULL); // exported with EXPORT_SYMBOL_GPL
        
           return *i;
        }
        module_init(init);
        


        И результат попытки сборки:
        make -C /lib/modules/4.8.14-300.fc25.x86_64/build M=/home/work/
        make[1]: Entering directory '/usr/src/kernels/4.8.14-300.fc25.x86_64'
          CC [M]  /home/work/workspace/work/module/gpl_test.o
          Building modules, stage 2.
          MODPOST 1 modules
        FATAL: modpost: GPL-incompatible module gpl_test.ko uses
        GPL-only symbol 'kernel_restart'
        scripts/Makefile.modpost:91: recipe for target '__modpost' failed
        


        Посмотреть на объявление kernel_restart можно здесь.


        1. win32asm
          29.12.2016 09:28

          Небольшое уточнение для педантов 8-)
          лицензия должна включать GPL — например, «Dual BSD/GPL»
          по ссылке есть список лицензий, которые кернел считает совместимыми. Как они сами говорят — "… but when running with Linux it is the GPL that is relevant"


  1. melon
    21.12.2016 16:03
    -1

    а такой способ позволяет перехватывать вызовы fork & mmap? Какие ограничения у данного подхода? Потому что я пробовал метод с заменой таблицы модулей, там в случае перехвата fork & mmap у меня был Kernel Panic, но потом мне объяснили, почему это плохо и я стал смотреть в сторону systemtap, но он большой и разбираться было лень, поэтому пока забил.


  1. BiosUefi
    21.12.2016 17:44
    +1

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


    1. win32asm
      27.12.2016 13:50

      В таком виде — нет.
      Если я правильно понимаю код ядра, то этот хук он на самом деле «паразитирует» на задаче SELinux`а проверить права процесса на действие _перед самим действием_.
      Есть интересное следствие — если из хука вернуть не 0 а ошибку, то действие не будет выполнено.


  1. anybkarnak
    21.12.2016 17:44

    Спасибо за статью. Можно ли так же хукнуть системные вызовы без пересборки самого ядра, а используя insmod?


    1. win32asm
      27.12.2016 13:39

      Ядро >=4.2 (или пропатчено), включены CONFIG_SECURITY_PATH, CONFIG_SECURITY_NETWORK, CONFIG_SECURITY_NETWORK_XFRM, CONFIG_KEYS, CONFIG_AUDIT — можно пользоваться в своём модуле «искаропки».


      1. loskamo
        28.12.2016 00:01

        Как удалить коментарий?


  1. dimorphus
    21.12.2016 17:44
    +4

    Используя LSM вы не перехватываете системные вызовы по нескольким причинам. Во-первых, прежде чем вызвать ваш LSM обработчик сначала производится стандартная проверка прав доступа. Так, если у пользователя не хватает прав на создание директории, то системный вызов сразу вернёт код ошибки пользовательскому процессу. Ваш обработчик при этом не вызовется. Во-вторых, не для всех системных вызовов есть соответствующий LSM hook.
    Предназначение LSM несколько в другом — в возможности реализации дополнительных проверок прав доступа поверх стандартных. Более того, c некоторых пор LSM стал стековым интерфейсом, то есть приобрёл возможность подключать сразу несколько модулей безопасности (например SELinux, Yama). Это означает, что если ваш модуль работает после SELinux, а он решает не давать доступа на mkdir, то ваш обработчик не будет вызываться.
    На мой взгляд, если нужен именно перехват всех системных вызовов, то можно было бы использовать Systemtap. Вот, кстати стандартный примерчик https://sourceware.org/systemtap/examples/process/syscalls_by_proc.stp

    #! /usr/bin/env stap
    
    global syscalls
    
    probe nd_syscall.* {
       syscalls[execname()]++
    }
    
    probe end {
       printf ("%-10s %-s\n", "#SysCalls", "Process Name")
       foreach (proc in syscalls-)
          printf("%-10d %-s\n", syscalls[proc], proc)
    }
    


    1. dimorphus
      21.12.2016 18:53
      +1

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

      syscall_test.stp:

      #!/usr/bin/stp
      
      probe nd_syscall.* {
      #if (pid() == target()) {
      	printf("<syscall %s: %s(%s)\n", execname(), name, argstr)
      #}
      }
      
      probe nd_syscall.*.return {
      #if (pid() == target()) {
      	printf(">syscall ret %s: %s result: %s\n", execname(), name, retstr)
      #}
      }
      


      Запускается так:
      $ sudo stap ./syscall_test.stp
      


      Вот пример вывода (ядро скомпилировано с поддержкой многоядерности, поэтому системные вызовы обрабатываются не последовательно):
      <syscall konsole: write(95, "\0", 1)
      
      <syscall ksmserver: ioctl(28, 21531, 0x7fffab1c9ec4)
      >syscall ret konsole: write result: 1
      >syscall ret ksmserver: ioctl result: 0
      
      <syscall ksmserver: read(28, 0x5626d6785938, 40)
      >syscall ret ksmserver: read result: 40
      
      <syscall konsole: lseek(94, -2147482516, SEEK_SET)
      
      <syscall ksmserver: poll(0x5626d66df110, 64, -1)
      >syscall ret konsole: lseek result: -22 (EINVAL)
      
      <syscall konsole: write(2, "HistoryFile::add.seek: Invalid argument\n", 40)
      >syscall ret konsole: write result: 40
      
      <syscall konsole: lseek(93, 22447548, SEEK_SET)
      >syscall ret konsole: lseek result: 22447548
      
      <syscall konsole: write(93, "l\004\0\200", 4)
      >syscall ret konsole: write result: 4
      


  1. Vcoderlab
    21.12.2016 17:44

    Очень интересно, спасибо!

    Любопытно, а virtualbox при установке своих модулей тоже так же перекомпилирует ядро?


    1. kmu1990
      21.12.2016 17:47

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


    1. shlemisto
      21.12.2016 17:47

      А сорцы он откуда берёт?


  1. denis_obrezkov
    21.12.2016 17:45
    +1

    Вместе с ядром поставляются на выбор несколько систем, основанных на LSM: AppArmor, SELinux, Smack, TOMOYO, Yama. Мне кажется, что документация по LSM и фреймворк Yama представляют сами по себе немалый объем информации — как раз тот случай, когда код говорит сам за себя.
    Было бы интересно прочитать про хуки на исполнение файлов с минимальной реализацией какой-либо системы доступа, мандатной, к примеру.


  1. loskamo
    21.12.2016 18:48

    Ребят, много таких, кто не просто прочитал статью, но и прошёлся по мануалу?

    Интересно, у всех ли всё получилось, и если не получилось, то почему?
    Если что-то не так — будем разбираться, править.


    1. pavel_pimenov
      22.12.2016 10:42

      А почему максимальный приоритет выставлялся для make?
      всегда думал, что максимум CPU в подобных случаях поедает компилятор,
      или измененный приоритет make из гнома наследуется и на него как дочернего?


      1. loskamo
        22.12.2016 14:10

        Да, там порождается куча процессов, и приоритет будет наследоваться от make


    1. A1EF
      23.12.2016 19:32
      +1

      Я вообще ни разу не системный программист, но статья понравилась своей простотой и ясностью. Проделал все тоже самое на Debian 8.6. Взял ядро 4.9 и все прекрасно получилось:)


  1. Godless
    22.12.2016 00:05

    спасибо за статью. прямо как-будто читаю Шрайбера с недокументированными возможностями вин2к и волшебным int 2e…


  1. jcmvbkbc
    22.12.2016 11:37

    После успешной сборки нужно установить всё то, что мы собрали. Это требует root-прав.
    Установка заголовков:
    sudo make headers_install

    Это нужно только для сборки libc, при обычной сборке ядра/модулей этот шаг не нужен.


    1. loskamo
      22.12.2016 14:12

      Согласен, но заголовки также нужны и для модулей virtual-box, я решил что туториал это не усложнит, но пользу принесёт


  1. KosmiK2001
    28.12.2016 14:17

    Ну и пример в дефолте. дичь.
    >Makefile:13: *** missing separator.
    Ну ладно, выровнял через пробел и;

    >arch/x86/Makefile:173: CONFIG_X86_X32 enabled but no binutils support
    >Makefile:670: Cannot use CONFIG_CC_STACKPROTECTOR_REGULAR: -fstack-protector not supported by compiler
    >make[1]: *** Нет правила для сборки цели «стол/test». Останов.

    Класс, чё тут сказать.


    1. loskamo
      28.12.2016 14:18

      это при сборке конфига, который собирается defconfig-ом?


    1. shlemisto
      28.12.2016 15:44

      лечится вот этим https://bugs.launchpad.net/ubuntu/+source/gcc-defaults/+bug/1574982/comments/15


  1. LinuxComp
    28.12.2016 21:23

    Спасибо за статью!
    Подписался. Продолжайте писать в таком жанре.