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

При сборке исходных текстов определён символ препроцессора __KERNEL__.
Как пишут Alessandro Rubini и Jonathan Corbet в книге «Драйверы устройств в Linux»:
Например, в makefile может присутствовать строчка «CFLAGS = -D__KERNEL__».
Или "-D__KERNEL__" можно обнаружить в логах сборки .
Если модуль не линкуется к ядру статически, то в составе переменной CFLAGS обязательно будет присутствовать строка "-DMODULE". Этот символ препроцессора должен быть определён до подключения файла linux/module.h.
Таким образом разработчик избегает «загрязнения» пространства имён ядра — иначе при отладке ему пришлось бы отлавливать имена своего модуля среди всех имён ядра. Использование префикса освобождает от обязанности придумывать уникальные имена, которые не будут совпадать с именами, уже присутствующими в пространстве имён ядра.
В исходных текстах вместо функции printf() используется функция printk(). «Драйверы устройств в Linux» говорит:
Upd: Более надёжным признаком является наличие в тексте функции cleanup_module, т.к. функции с таким именем встречаются в примерно в 20 раз реже, чем с именем “init_module». Судя по всему, название "init_module" пользуется популярностью не только среди писателей модулей ядра.
А какие вы ещё знаете отличия модуля ядра от исполняемого файла на уровне исходников?
«Пишем свой драйвер под 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
Допустим, что информации о назначении исходников нет или её пытаются преднамеренно скрыть.
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