Компиляция модуля ядра linux

Сборка модуля ядра Linux без точных заголовочных файлов

Представьте, что у вас имеется образ ядра Linux для телефона на базе Android, но вы не располагаете ни соответствующими исходниками, ни заголовочными файлами ядра. Представьте, что ядро имеет поддержку подгрузки модулей (к счастью), и вы хотите собрать модуль для данного ядра. Существует несколько хороших причин, почему нельзя просто собрать новое ядро из исходников и просто закончить на том (например, в собранном ядре отсутствует поддержка какого-нибудь важного устройства, вроде LCD или тачскрина). С постоянно меняющимся ABI ядра Linux и отсутствием исходников и заголовочных файлов, вы можете подумать, что окончательно зашли в тупик.

Как констатация факта, если вы соберете модуль ядра, используя другие заголовочные файлы (нежели те, что были использованы для сборки того образа ядра, которым вы располагаете, — прим. пер.), модуль не сможет загрузиться с ошибками, зависящими от того, насколько заголовочные файлы отличались от требуемых. Он может жаловаться о плохих сигнатурах, плохих версиях и о прочих вещах.

Конфигурация ядра

Первый шаг — найти исходники ядра наиболее близкие к тому образу ядра, насколько это возможно. Наверное, получение правильной конфигурации — наиболее сложная составляющая всего процесса сборки модуля. Начните с того номера версии ядра, который может быть прочитан из /proc/version . Если, как я, вы собираете модуль для устройства Android, попробуйте ядра Android от Code Aurora, Cyanogen или Android, те, что наиболее ближе к вашему устройству. В моем случае, это было ядро msm-3.0. Заметьте, вам не обязательно необходимо искать в точности ту же версию исходников, что и версия вашего образа ядра. Небольшие отличия версии, наиболее вероятно, не станут помехой. Я использовал исходники ядра 3.0.21, в то время как версия имеющегося образа ядра была 3.0.8. Не пытайтесь, однако, использовать исходники ядра 3.1, если у вас образ ядра 3.0.x.

Если образ ядра, что у вас есть, достаточно любезен, чтобы предоставить файл /proc/config.gz , вы можете начать с этого, в противном случае, вы можете попытаться начать с конфигурацией по умолчанию, но в этом случае нужно быть крайне аккуратным (хотя я и не буду углубляться в детали использования дефолтной конфигурации, поскольку мне посчастливилось не прибегать к этому, далее будут некоторые детали относительно того, почему правильная конфигурация настолько важна).

Предполагая, что arm-eabi-gcc у вас доступен по одному из путей в переменной окружения PATH, и что терминал открыт в папке с исходными файлами ядра, вы можете начать конфигурацию ядра и установку заголовочных файлов и скриптов:

$ mkdir build $ gunzip config.gz > build/.config # или что угодно, для того, чтобы приготовить .config $ make silentoldconfig prepare headers_install scripts ARCH=arm CROSS_COMPILE=arm-eabi- O=build KERNELRELEASE=`adb shell uname -r` 

Сборка silentoldconfig , наиболее вероятно, спросит, хотите ли вы включить те или иные опции. Вы можете выбрать умолчания, но это вполне может и не сработать.

Читайте также:  Файл сервер http linux

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

Написание простого модуля

Чтобы создать пустой модуль, необходимо создать два файла: исходник и Makefile . Расположите следующий код в файле hello.c , в некоторой отдельной директории:

#include /* Needed by all modules */ #include /* Needed for KERN_INFO */ #include /* Needed for the macros */ static int __init hello_start(void) < printk(KERN_INFO "Hello world\n"); return 0; >static void __exit hello_end(void) < printk(KERN_INFO "Goodbye world\n"); >module_init(hello_start); module_exit(hello_end); 

Поместите следующий текст в файл Makefile в той же директории:

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

Сборка модуля

При обычной сборки ядра система сборки ядра создает файл hello.mod.c , содержимое которого может создать различные проблемы:

MODULE_INFO(vermagic, VERMAGIC_STRING); 

Значение VERMAGIC_STRING определяется макросом UTS_RELEASE , который располагается в файле include/generated/utsrelease.h , генерируемом системой сборки ядра. По умолчанию, это значение определяется версией ядра и статуса git-репозитория. Это то, что устанавливает KERNELRELEASE при конфигурации ядра. Если VERMAGIC_STRING не совпадает с версией ядра, загрузка модуля приведет к сообщению подобного рода в dmesg :

hello: version magic '3.0.21-perf-ge728813-00399-gd5fa0c9' should be '3.0.8-perf' 

Далее, также имеем здесь определение структуры модуля:

struct module __this_module __attribute__((section(".gnu.linkonce.this_module"))) = < .name = KBUILD_MODNAME, .init = init_module, #ifdef CONFIG_MODULE_UNLOAD .exit = cleanup_module, #endif .arch = MODULE_ARCH_INIT, >; 

Само по себе, это определение выглядит безобидно, но структура struct module , определенная в include/linux/module.h , несет в себе неприятный сюрприз:

Это означает, что для того, чтобы указатель init оказался в правильном месте, CONFIG_UNUSED_SYMBOLS должен быть определен в соответствии с тем, что использует наш образ ядра. Что же насчет указателя exit, — это CONFIG_GENERIC_BUG , CONFIG_KALLSYMS , CONFIG_SMP , CONFIG_TRACEPOINTS , CONFIG_JUMP_LABEL , CONFIG_TRACING , CONFIG_EVENT_TRACING , CONFIG_FTRACE_MCOUNT_RECORD и CONFIG_MODULE_UNLOAD .

Начинаете понимать, почему обычно предполагается использовать в точности те же заголовочные файлы, с которыми было собрано наше ядро?

Далее, определения версий символов:

static const struct modversion_info ____versions[] __used __attribute__((section("__versions"))) = < < 0xsomehex, "module_layout" >, < 0xsomehex, "__aeabi_unwind_cpp_pr0" >, < 0xsomehex, "printk" >, >; 

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

Каждая такая запись представляет символ, требуемый модулю, и то, какую сигнатуру должен иметь символ. Первый символ, module_layout , зависит от того, как выглядит struct module , то есть, зависит от того, какие опции конфигурации, упомянутые ранее, включены. Второй, __aeabi_unwind_cpp_pr0 , — функция, специфичная ABI ARM, и последний — для наших вызовов функции printk .

Читайте также:  Не работают звук в линуксе

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

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

hello: disagrees about version of symbol symbol_name 

Что значит, что нам нужен правильный, соответствующий образу ядра, файл Module.symvers , которым мы не располагаем.

Изучаем ядро

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

Ядро использует следующую функцию для поиска в своей таблицы символов (в kernel/module.c):

bool each_symbol_section(bool (*fn)(const struct symsearch *arr, struct module *owner, void *data), void *data) < struct module *mod; static const struct symsearch arr[] = < < __start___ksymtab, __stop___ksymtab, __start___kcrctab, NOT_GPL_ONLY, false >, < __start___ksymtab_gpl, __stop___ksymtab_gpl, __start___kcrctab_gpl, GPL_ONLY, false >, < __start___ksymtab_gpl_future, __stop___ksymtab_gpl_future, __start___kcrctab_gpl_future, WILL_BE_GPL_ONLY, false >, #ifdef CONFIG_UNUSED_SYMBOLS < __start___ksymtab_unused, __stop___ksymtab_unused, __start___kcrctab_unused, NOT_GPL_ONLY, true >, < __start___ksymtab_unused_gpl, __stop___ksymtab_unused_gpl, __start___kcrctab_unused_gpl, GPL_ONLY, true >, #endif >; if (each_symbol_in_section(arr, ARRAY_SIZE(arr), NULL, fn, data)) return true; (. ) 

Структура, используемая в данной функции, определена в include/linux/module.h:

struct symsearch < const struct kernel_symbol *start, *stop; const unsigned long *crcs; enum < NOT_GPL_ONLY, GPL_ONLY, WILL_BE_GPL_ONLY, >licence; bool unused; >; 

Примечание: данный код ядра не изменился значительно за последние четыре года (видимо, с момента рассматриваемого релиза ядра 3.0, — прим. пер.).

То, что мы имеем выше в функции each_symbol_section — три (или пять, когда конфиг CONFIG_UNUSED_SYMBOLS включен) поля, каждое из которых содержит начало таблицы символов, ее конец и два флага.

Данные эти статичны и постоянны, что означает, что они появятся в бинарнике ядра как есть. Сканируя ядро на предмет трех идущих друг за другом последовательностей состоящих из трех указателей в адресном пространстве ядра и следом идущих значений типа integer из определений в each_symbol_section , мы можем определить расположение таблиц символов и сигнатур, и воссоздать файл Module.symvers из бинарника ядра.

Читайте также:  Windows office on linux

К несчастью, большинство ядер сегодня сжатые ( zImage ), так что простой поиск по сжатому образу невозможен. Сжатое ядро на самом деле представляет небольшой бинарник, следом за которым идет сжатый поток. Можно просканировать файл zImage с тем, чтобы найти сжатый поток и получить из него распакованный образ.

Я написал скрипт для декомпрессии и извлечения информации о символах ядра в автоматическом режиме. Это должно работать с любой свежей версией ядра, при условии, что ядро не перемещаемое (relocatable) и вы знаете базовый адрес в памяти, куда оно грузится. Скрипт принимает опции для количества и порядка следования битов (endianness) архитектуры, и по умолчанию использует значения, подходящие для ARM. Базовый адрес, однако, должен быть указан. Он может быть найден, на ядрах ARM, в dmesg :

$ adb shell dmesg | grep "\.init" [01-01 00:00:00.000] [0: swapper] .init : 0xc0008000 - 0xc0037000 ( 188 kB) 

(прим. пер. — однако, не все ядра выводят эти данные в лог, мне довелось встретить один такой практически уникальный случай, когда, видимо, ввиду урезанных опций конфигурации эта информация не выводилась, в таком случае можно обратиться к конфигу PAGE_OFFSET в файле arch/arm/Kconfig и просто надеяться, что вендор использовал одно из дефолтных значений).

Базовый адрес в примере выше — 0xc0008000 .

Если как я, вы интересуетесь загрузкой модуля на девайсе Android, тогда бинарник ядра, что вы имеете — полный образ boot. Образ boot содержит другие вещи помимо ядра, так что вы не можете напрямую использовать его со скриптом выше. Исключение составляет только тот случай, если ядро в образе boot сжато, при этом часть скрипта, которая ожидает на входе сжатый образ, все равно найдет ядро.

Если ядро не сжато, вы можете использовать программу unbootimg как изложено в данном посте, для того, чтобы получить образ ядра из вашего образа boot. Как только у вас есть образ ядра, скрипт может быть запущен следующим образом:

$ python extract-symvers.py -B 0xc0008000 kernel-filename > Module.symvers 

Сборка ядра

Теперь, когда мы имеем правильный файл Module.symvers для ядра, из которого мы хотим загрузить модуль, мы наконец можем собрать модуль (опять, полагая, arm-eabi-gcc доступно из PATH , и что терминал открыт в директории с исходниками):

$ cp /path/to/Module.symvers build/ $ make M=/path/to/module/source ARCH=arm CROSS_COMPILE=arm-eabi- O=build modules 

Вот и собственно все. Вы можете скопировать файл hello.ko на девайс и загрузить модуль:

$ adb shell # insmod hello.ko # dmesg | grep insmod [mm-dd hh:mm:ss.xxx] [id: insmod]Hello world # lsmod hello 586 0 - Live 0xbf008000 (P) # rmmod hello # dmesg | grep rmmod [mm-dd hh:mm:ss.xxx] [id: rmmod]Goodbye world 

Источник

Оцените статью
Adblock
detector