⌨ Labor omnia vincit ☮

Welcome to the kernel development!

Posted in Linux Kernel by anaumov on 29.05.2010

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

Итак, напишем модуль ядра, который просто выведет на экран что-то типа “Welcome to the kernel development!”. Есть много разных способов сделать это. Я покажу 3 из них: с помощью функции printk(), файловой системы /proc и просто с помощью чтения содержимого файла из /dev (да, будет созданно новое “устройство”). Ну первый способ разобран и описан по шагам на каждом углу, поэтому не так интересен. Я включил его в эту статейку просто чтобы освежить в памяти читателя основы, и чтобы ему было проще понять остальные два способа… хотя и в них ничего сложного нет😉

Using printk()

Итак, как я уже сказал, пойдем от самого простого. Обычно в книгах по программированию в начале дается самая простая программа, которую только можно написать. Как правило это программа выводит строку “Hello world”. Наш первый модуль не будет выделяться оригинальностью – он выведет строку “Hello GNU/Linux”😉

Итак, функция ядра, аналогичная printf() из стандартной бибилиотеки С, называется pritnk(). Делают эти две функции одно и тоже, за тем лишь исключением, что printf() пишет в стандартный вывод, а вызов pritnk() перехватывает syslogd (/var/log/messages), а так же сообщение идет в буфер ядра.

#include <linux/init.h>
#include <linux/module.h>

static int hello_start(void)
{
	printk("Hello GNU/Linux!\n");
	return 0;
}

static void hello_exit(void)
{
	printk("Bye bye");
}

module_init(hello_start);
module_exit(hello_exit);

MODULE_LICENSE("GPL")

Это пожалуй самый простой модуль, который вообще можно написать (без “warning”). Две функции – start и exit. Точка входа в модуль и… выхода. Название может быть любое, но его надо зарегистрировать и помощью функций module_init() и module_exit().

Для сборки модуля нужен Makefile. Его мы будем использовать и для других примеров (лишь меняя название файла в первой строчке). Итак:

obj-m := hello_gnu_linux.o
KDIR  := /lib/modules/$(shell uname -r)/build
PWD   := $(shell pwd)
default:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

После этого собираем и тестируем наш модуль:

$ make
# insmod ./hello_gnu_linux.ko
# dmesg | tail

Если все ок (тут просто неоткуда взяться проблемам), то мы увидим “Hello GNU/Linux!”. Видим его в списке подключенных модулей:

# lsmod | grep hello

Выгружаем модуль:

# rmmod hello_printk
# dmesg | tail

Наше “Bye bye” из функции exit()😉

Using /proc

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

Задача второго примера – записать фразу типа “Welcome to the kernel development” в файл /proc/welcome. Считаем ее потом оттуда с помощью cat.

#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>

static int
welcome_proc(char *buffer, char **start,
		off_t offset, int size, int *eof, void *data)
{
	char *str = "Welcome to the kernel development!\n";
	int len = strlen(str);
	if (size < len)
 		return -EINVAL;
	if (offset != 0)
		 return 0;
	strcpy(buffer, str);
	*eof = 1;

	return len;
}

static int hello_start(void)
{
if (create_proc_read_entry ("welcome",
	0, NULL, welcome_proc, NULL) == 0){
	printk(KERN_ERR
		"Unable to register \"welcome\" proc file\n");
	return -ENOMEM;
	}
	return 0;
}

static void hello_exit(void)
{
	remove_proc_entry("welcome", NULL);
}

module_init(hello_start);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

На этот раз мы добавили в заголовок PROC_FS, который включает поддержку для регистрации /proc системы. Функция start (отсюда начинается работа модуля) вызывает функцию create_proc_read_entry, которая создает proc-файл. Подробное описание можно найти тут. Функция exit удаляет файл /proc/welcome, так что после удаления модуля этого файла в системе не будет.

# lsmod welcome
# lsmod | grep welcome
welcome                 1512  0
# cat /proc/welcome
Welcome to the kernel development!
# rmmod welcome
# cat /proc/welcome
cat: /proc/welcome: No such file or directory

Можно создать много /proc-файлов, которые будут относится к одному драйверу, и которые будут информировать ПО или пользователя о происходящем. Так же можно добавить функции, которые будут позволять запись в /proc-файлы. Этот пример очень прост, но для чего-либо более сложного так же можно использовать вспомогательные процедуры интерфейса seq_file. Driver porting: The seq_file interface.

Using /dev

Теперь давайте напишем драйвер инициализирующий новое псевдоустройство. “Псевдо-” потому что существовать оно будет лишь в виде некой абстракции ядра. Пускай там тоже будет какая-нить строка типа “Be free like freedom”. Другими словами мы создадим новый файл “устройства” /dev/freedom и будем считывать оттуда инфу.

Раньше файл устройства являлся очень специфичным файлом, который создавал crufty при запуске обычного shell-скрипта MAKEDEV. Этот скрипт в свою очередь запускал mknod, который создавал все возможные файлы устройств, независимо от того есть ли в системе драйвер для этого устройства или нет.

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

То, что мы имеем сегодня, и с чем мы сейчас будем работать, это система udev. Называется она так (первая буква ‘u’), потому что создает связь с программой (да да, опять связь пространство ядра — пользовательское простанство). Когда модули ядра регистрируют устройства, они появляются в ФС sysfs. Чтобы лучше предствить о чем я говорю, загляни сюда:

$ ls -la /sys/bus/pci/devices
$ ls -la /sys/bus/pci/drivers

Важным тут является, как я уже сказал, связь kernelspace — userspace, т.е. программы в пользовательском пространстве видят изменения в /sys и /dev и создают соответсвующие записи в /etc/udev.

$ ls -la /etc/udev/rules.d/

Для создания файла-устройства нам понадобится 3 файла. Makefile, сам код и файл с правилами freedom.rules:

$ l
total 20
drwxr-xr-x 2 alex users 4096 2010-05-30 20:42 ./
drwxr-xr-x 3 alex users 4096 2010-05-30 20:37 ../
-rw-r--r-- 1 alex users 2172 2010-05-30 20:42 freedom.c
-rw-r--r-- 1 alex users   54 2010-05-30 20:42 freedom.rules
-rw-r--r-- 1 alex users  136 2010-05-30 20:42 Makefile

Makefile мы используем из первого примера лишь переписывая названия файла (для этого примера: hello_gnu_linux.o на freedom.o), о freedom.rules я расскажу чуть позже, а вот содержания freedom.c

#include <linux/fs.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/module.h>

#include <asm/uaccess.h>

static ssize_t freedom_read(struct file * file, char * buf,
			  size_t count, loff_t *ppos)
{
	char *str = "Be free like Freedom\n";
	int len = strlen(str);
	if (count < len)
		return -EINVAL;
	if (*ppos != 0)
		return 0;
	if (copy_to_user(buf, str, len))
		return -EINVAL;
	*ppos = len;

	return len;
}

static const struct file_operations freedom_fops = {
	.owner		= THIS_MODULE,
	.read		= freedom_read,
};

static struct miscdevice freedom_dev = {
	MISC_DYNAMIC_MINOR,
	"freedom",
	&freedom_fops
};

static int hello_start(void)
{
	int ret;
	ret = misc_register(&freedom_dev);
	if (ret)
		printk(KERN_ERR
		"Unable to register \"Freedom\"	misc device\n");

	return ret;
}

static void hello_exit(void)
{
	misc_deregister(&freedom_dev);
}

module_init(hello_start);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

Как мы видим, для создания устройства нам необходимо больше заголовочных файлов, чем прежде: в fs.h включены структуры для работы с файлами такого типа; miscdevice.h включает поддержку регистации нового устройства; asm/uaccess.h – механизм разграничения доступа, включающий структуры для проверки на чтение и запись в userspace область памяти.

Компилируем и подгружаем наш модуль:

$ make
# insmod ./freedom.ko
# lsmod | grep freedom
freedom                 1720  0

Теперь у нас есть устройство /dev/freedom, из которого без проблем рутом читается “Be free like Freedom”:

# cat /dev/freedom
Be free like Freedom

Но у обычного пользователеля нет доступа прочитать содержимое freedom😦

$ cat /dev/freedom
cat: /dev/freedom: Permission denied
$ ls -l /dev/freedom
crw-rw---- 1 root root 10, 59 2010-05-30 21:14 /dev/freedom

Так работает udev по умолчанию. Он не открывает всем доступ для только что созданного устройства. Права, с которыми создается /dev/freedom (или любое другое) 0660.  Права, с которыми создается /dev/freedom (или любое другое) 0660. Давайте изменим правила так, чтобы у любого пользователся был досуп к freedom😉

Две вещи должен сделать udev: создать символическую ссылку и изменять права доступа для устройства. Выглядит это как-то так:

KERNEL=="freedom", SYMLINK+="freedom_dev", MODE="0444"

KERNEL == “freedom” — поиск устройства в /sys. Наше устройство создала misc_register () в /sys со структурой, содержащей имя устройства “freedom”:

> ls -d /sys/class/misc/freedom/
/sys/class/misc/freedom/

SYMLINK + = “freedom_dev” — добавить freedom_dev к списку символических связей, которые должны быть созданы, когда устройство появится. В нашем случае мы знаем, что это единственная символическая связь в списке, но у других устройств может быть более одного правила udev, которые создают многократные различные символические связи, таким образом это очень удобно именно добавлять к списку связей, расширяя тем самым функциональность.

Ну про MODE = “0444” я думаю все понято. Это стандартная система разграничения доступа. У всех есть права доступа на чтение.

Не перепутайте: Оператор “==” сравнение, оператор “+=” добавляет свойства, и наконец оператор “=” присваивает какие-либо свойства.

Теперь, когда мы хорошо себе предствляем как менять правила, давайте дадим свободу простым пользователям в системе. /etc/udev написан в стиле System V (скрипты /etc/init.d), т.е. udev выполняет правила в алфавитном порядке, а сами правила являеются ничем иным как символическими линками, созданными специально, чтобы правила были выполненны в определенном порядке.

Скопируйте файл freedom.rules в дирикторию /etc/udev/rules.d/ и создайте линк на него:

# cp freedom.rules /etc/udev/
# ln -s freedom.rules /etc/udev/rules.d/010_freedom.rules

Теперь, перезагрузите драйвер и обратите внимание на новые /dev файлы:

# rmmod freedom
# insmod ./freedom.ko
# ls -l /dev/freedom*
cr--r--r-- 1 root root 10, 59 2010-05-30 22:44 /dev/freedom
lrwxrwxrwx 1 root root      5 2010-05-30 22:44 /dev/freedom_dev -> freedom

Есть доступ.

> cat /dev/freedom_dev
Be free like Freedom
> cat /dev/freedom
Be free like Freedom

Для тех, кого заинтересовал последний пример, советую почитать Writing udev rules.

А что дальше?

Эти 3 примера – это основа для написания модулей ядра Linux. Тут я не касался хакинга самого ядра, мы имели дело лишь с его отдельно компилируемыми и подгружаемыми в последствии частями. Мы не вникали в принцип работы subsystem, через которые, к примеру, передается инфа внутри самого ядра от одной ее подсистемы к другой (или к модулю). Как и при изучении программирования, теперь можно взять то, что у нас уже есть и усовершенствовать. Например написать модуль, который будет создавать /dev устройство, информацию от которого мы будем получать через /proc, а если возникнет какая-нить ошибка, то он сообщит об этом с помощью printk() в syslog. Это лишь пример. Можно усовершенствовать уже существующий драйвер, а можно написать модуль собирающий информацию и через /proc передающий ее в GUI…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: