Pull to refresh

Идентификация загружаемых модулей ядра Linux [ч.1]: исходные тексты

Reading time 4 min
Views 13K
В этом посте я расскажу о своих поисках признаков того, как можно определить, что из некоторых файлов исходных текстов собирается загружаемый модуль ядра Linux (LKM), а не обычный исполняемый файл.
Допустим, что информации о назначении исходников нет или её пытаются преднамеренно скрыть.
Upd: Объём кода > 4 Гб и надо оперативно выделить только те исходники, которые реализуют модули ядра.





#01 __KERNEL__ 


При сборке исходных текстов определён символ препроцессора __KERNEL__.



Как пишут Alessandro Rubini и Jonathan Corbet в книге «Драйверы устройств в Linux»:

«Поскольку модуль не связывается ни с одной из стандартных библиотек, исходные тексты модуля не должны подключать обычные заголовочные файлы. В модулях ядра могут использоваться только те функции, которые экспортируются ядром. Все заголовочные файлы, которые относятся к ядру, расположены в каталогах include/linux и include/asm, внутри дерева каталогов с исходными текстами ядра (как правило это каталог/usr/src/linux).
Ранние версии Linux (основанные на libc версии 5 и более ранних) устанавливали символические ссылки из /usr/include/linux и /usr/include/asm на фактические каталоги из исходных текстов ядра, таким образом дерево заголовочных файлов libc могло ссылаться на заголовочные файлы ядра. Это позволяло подключать заголовочные файлы ядра в пользовательские приложения тогда, когда в этом возникала необходимость.
Но даже теперь, когда заголовочные файлы ядра отделены от заголовочных файлов, используемых прикладными программами, все равно иногда возникает необходимость включения их в программы, работающие в пространстве пользователя, чтобы воспользоваться определениями, отсутствующими в обычных заголовочных файлах. Однако большая часть определений из заголовочных файлов ядра относится исключительно к ядру и „невидима“ для обычных приложений, поскольку доступ к этим определениям заключен в блоки #ifdef __KERNEL__. Это кстати одна из причин, почему необходимо определять символ __KERNEL__ при сборке модуля.»

Например, в makefile может присутствовать строчка «CFLAGS = -D__KERNEL__».
Или "-D__KERNEL__" можно обнаружить в логах сборки .

#02 MODULE


Если модуль не линкуется к ядру статически, то в составе переменной CFLAGS обязательно будет присутствовать строка "-DMODULE". Этот символ препроцессора должен быть определён до подключения файла linux/module.h.

#03 Все имена объявлены как static и имеют уникальный префикс


Таким образом разработчик избегает «загрязнения» пространства имён ядра — иначе при отладке ему пришлось бы отлавливать имена своего модуля среди всех имён ядра. Использование префикса освобождает от обязанности придумывать уникальные имена, которые не будут совпадать с именами, уже присутствующими в пространстве имён ядра.


#04 printk()


В исходных текстах вместо функции printf() используется функция printk(). «Драйверы устройств в Linux» говорит:
«Функция printk определена в ядре и по своему поведению напоминает функцию printf из стандартной библиотеки языка C. Зачем же тогда ядру своя собственная функция? Все просто — ядро, это самостоятельный код, который собирается без вспомогательных библиотек языка C.»


#05 init_module и cleanup_module


«Драйверы устройств в Linux» говорит:

«Приложение выполняется как цельная задача, от начала и до конца. Модуль же просто регистрирует себя самого в ядре, подготавливая его для обслуживания возможных запросов и его функция „main“ завершает свою работу сразу же после вызова. Другими словами, задача функции init_module (точка входа) состоит в подготовке функций модуля для последующих вызовов. Она как бы говорит ядру: „Эй! Я здесь! Вот то, что я могу делать!“. Вторая точка входа в модуль — cleanup_module — вызывается непосредственно перед выгрузкой модуля. Она сообщает ядру: „Я ухожу! Больше не проси меня ни о чем!“. „

Upd: Более надёжным признаком является наличие в тексте функции cleanup_module, т.к. функции с таким именем встречаются в примерно в 20 раз реже, чем с именем “init_module». Судя по всему, название "init_module" пользуется популярностью не только среди писателей модулей ядра.

#06 Использование current->


«Драйверы устройств в Linux» говорит:

"<...> Код ядра может определить текущий процесс, обратившийся к модулю, через глобальный элемент current — указатель на struct task_struct, который в ядре 2.4 объявляется в файле <asm/current.h>. Указатель current ссылается на текущий пользовательский процесс. В процессе выполнения системных вызовов, таких как read или write, текущим считается процесс, сделавший этот вызов. Ядро может воспользоваться информацией о текущем процессе, используя указатель current, если возникнет такая необходимость. <...>
Фактически current более не является глобальной переменной ядра, как это было ранее. Разработчики оптимизировали доступ к структуре, описывающей текущий процесс, перенеся ее на стек. Реализацию current вы найдете в файле <asm/current.h>. Но прежде, чем вы отправитесь исследовать этот файл, вы должны запомнить, что Linux — это SMP-совместимая система (от англ. SMP — Symmetric Multi-Processing) и потому простая глобальная переменная здесь просто неприменима. Детали реализации находятся в других подсистемах ядра и все же, драйвер устройства может подключить заголовочный файл <linux/sched.h> и обращаться к указателю current.
С точки зрения модуля, current — обычная внешняя ссылка, как например printk. Модуль может обращаться к current всякий раз, когда сочтет это необходимым. Например, следующий код выведет идентификатор (ID) процесса и имя команды, запустившей процесс:
printk(«The process is \»%s\" (pid %i)\n", current->comm, current->pid);


Имя команды хранится в поле current->comm и представляет собой имя файла программы.


А какие вы ещё знаете отличия модуля ядра от исполняемого файла на уровне исходников?




Хабрасылки по теме:

«Пишем свой драйвер под Linux» от iznakurnozh
«Написание драйвера для LCD дисплея под embedded linux» от alexzoidberg
«Обзор шины SPI и разработка драйвера ведомого SPI устройства для embedded Linux (Часть первая, обзорная)» от Lampus
«Обзор шины SPI и разработка драйвера ведомого SPI устройства для embedded Linux (Часть вторая, практическая)» от Lampus
Работаем с модулями ядра в Linux от Vorb
Учимся писать модуль ядра (Netfilter) или Прозрачный прокси для HTTPS от sindo
«Пишем файловую систему в ядре Linux» от kmu1990
«Простая маскировка модуля ядра Linux с применением DKOM» от milabs
Tags:
Hubs:
+25
Comments 5
Comments Comments 5

Articles