Back to Home

Build a Linux kernel module without exact header files

android · module · linux · hack

Build a Linux kernel module without exact header files


Imagine that you have a Linux kernel image for an Android phone, but you don’t have the appropriate source code or the kernel header files. Imagine that the kernel has support for loading modules (fortunately), and you want to build a module for this kernel. There are several good reasons why you can’t just compile a new kernel from the source and just end up with that (for example, the assembled kernel does not support any important device, such as an LCD or touchscreen). With the ever-changing ABI of the Linux kernel and the lack of source and header files, you might think that you are finally at a standstill.

As a statement of fact, if you compile the kernel module using other header files (rather than those that were used to build the kernel image that you have - approx. Per.), The module will not be able to boot with errors depending on how much header files were different from those required. He may complain about bad signatures, bad versions, and other things.

But more about this further.

Kernel Configuration


The first step is to find the kernel sources closest to the kernel image as possible. Obviously, getting the correct configuration is the most difficult part of the entire module assembly process. Start with the kernel version number that can be read from /proc/version. If, like me, you are building a module for an Android device, try the Android kernels from Code Aurora, Cyanogen or Android, the ones closest to your device. In my case, it was the msm-3.0 kernel. Note that you do not need to look for exactly the same source version as the version of your kernel image. Small differences in the version are most likely not to be a hindrance. I used the kernel source version 3.0.21, while the version of the existing kernel image was 3.0.8. However, do not try to use the kernel 3.1 sources if you have a 3.0.x kernel image.

If the kernel image that you have is kind enough to provide a file /proc/config.gz, you can start with this, otherwise, you can try to start with the default configuration, but in this case you need to be extremely careful (although I will not go deep in the details of using the default configuration, since I was fortunate enough not to resort to this, hereinafter there will be some details as to why the correct configuration is so important).

Assuming that arm-eabi-gccyou have one of the paths available in the PATH environment variable and that the terminal is open in the folder with the kernel source files, you can start configuring the kernel and installing header files and scripts:

$ 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`

The assembly silentoldconfigis most likely to ask if you want to enable certain options. You can choose defaults, but this may not work.

You can use something else in KERNELRELEASE, but this should exactly match the version of the kernel from which you plan to load the module.

Writing a simple module


To create an empty module, you need to create two files: the source and Makefile. Place the following code in a file hello.cin a separate directory:

#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);

Place the following text in a file Makefilein the same directory:

obj-m = hello.o

The assembly of the module is quite simple, however, at this stage, the resulting module will not be able to boot.

Module assembly


In a typical kernel build, the kernel build system creates a file hello.mod.cwhose contents can create various problems:

MODULE_INFO(vermagic, VERMAGIC_STRING);

The value is VERMAGIC_STRINGdetermined by the macro UTS_RELEASE, which is located in the file include/generated/utsrelease.hgenerated by the kernel assembly system. By default, this value is determined by the version of the kernel and the status of the git repository. This is what installs KERNELRELEASEwhen configuring the kernel. If it VERMAGIC_STRINGdoes not match the kernel version, loading the module will result in a message of this kind in dmesg:

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

Further, we also have here the definition of the structure of the module:

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,
};

By itself, this definition looks harmless, but the structure struct moduledefined in include/linux/module.hcarries an unpleasant surprise:

struct module
{
        (...)
#ifdef CONFIG_UNUSED_SYMBOLS
        (...)
#endif
        (...)
        /* Startup function. */
        int (*init)(void);
        (...)
#ifdef CONFIG_GENERIC_BUG
        (...)
#endif
#ifdef CONFIG_KALLSYMS
        (...)
#endif
        (...)
(... plenty more ifdefs ...)
#ifdef CONFIG_MODULE_UNLOAD
        (...)
        /* Destruction function. */
        void (*exit)(void);
        (...)
#endif
        (...)
}

This means that in order for the pointer to initbe in the right place, it CONFIG_UNUSED_SYMBOLSmust be defined in accordance with what our kernel image uses. What about pointer exit, - is CONFIG_GENERIC_BUG, CONFIG_KALLSYMS, CONFIG_SMP, CONFIG_TRACEPOINTS, CONFIG_JUMP_LABEL, CONFIG_TRACING, CONFIG_EVENT_TRACING, CONFIG_FTRACE_MCOUNT_RECORD and CONFIG_MODULE_UNLOAD.

You begin to understand why it is usually supposed to use exactly the same header files with which our kernel was built?

Next, character version definitions:

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

These definitions are taken from a file Module.symversthat is generated according to the header files.

Each such entry represents the character required by the module, and what signature the character should have. The first character module_layout,, depends on how it looks struct module, that is, it depends on which configuration options mentioned earlier are enabled. The second __aeabi_unwind_cpp_pr0,, is a function specific to ABI ARM, and the last is for our function calls printk.

The signature of each character may differ depending on the kernel code for this function and the compiler used to build the kernel. This means that if you compile the kernel from the source, as well as modules for the given kernel, and then reassemble the kernel after modification, for example, of a functionprintk, even in a compatible way, modules built initially will not boot with the new kernel.

So, if we compile a kernel with sources and configuration that are close enough to those with which we have built our kernel image, there is a chance that we will not get the same signatures as in our kernel image, and it cursed when loading the module:

hello: disagrees about version of symbol symbol_name

Which means that we need the correct file Module.symversthat we don’t have at our disposal , corresponding to the image of the kernel .

Learning the core


Since the kernel does these checks when loading modules, it also contains a list of the characters that it exports and the corresponding signatures. When the kernel loads a module, it goes through all the characters that the module requires, in order to find them in its symbol table (or other symbol tables of the modules that the module uses) and check the corresponding signatures.

The kernel uses the following function to search in its symbol table (in 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;
        (...)

The structure used in this function is defined in 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;
};

Note: this kernel code has not changed significantly over the past four years (apparently, since the release of the kernel 3.0 under consideration, - approx. Per.).

What we have above in the function each_symbol_section is three (or five when the config is CONFIG_UNUSED_SYMBOLS turned on) fields, each of which contains the beginning of the symbol table, its end and two flags.

These data are static and constant, which means that they will appear in the kernel binary as is. Scanning the kernel for three successive sequences consisting of three pointers in the address space of the kernel and followed by the type values ​​coming integer from the definitions in each_symbol_section, we can determine the location of the symbol tables and signatures, and recreate the Module.symvers file from the kernel binary.

Unfortunately, most cores are compressed today (zImage), so a simple search on a compressed image is not possible. A compressed core is actually a small binary, followed by a compressed stream. You can scan the file zImage in order to find the compressed stream and get the unpacked image from it.

I wrote a script to decompress and extract information about kernel symbols in automatic mode . This should work with any fresh version of the kernel, provided that the kernel is not relocatable and you know the base address in memory where it is loaded. The script accepts options for the number and order of bits (endianness) of the architecture, and by default uses values ​​suitable for ARM. The base address, however, must be provided. It can be found, on ARM cores, in dmesg:

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

(approx. lane - however, not all kernels output this data to the log, I happened to meet one such almost unique case when, apparently, due to truncated configuration options, this information was not displayed, in this case you can refer to the PAGE_OFFSET config in the arch file / arm / Kconfig and just hope that the vendor used one of the default values).

The base address in the example above is 0xc0008000.

If, like me, you are interested in loading a module on an Android device, then the kernel binary that you have is a complete boot image . The boot image contains other things besides the kernel, so you cannot directly use it with the script above. The only exception is if the kernel in the boot imagecompressed, while the part of the script that expects a compressed image at the input will still find the kernel.

If the kernel is not compressed, you can use the unbootimg program as described in this post in order to get the kernel image from your boot image . Once you have a kernel image, the script can be run as follows:

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

Kernel assembly


Now that we have the correct file Module.symversfor the kernel from which we want to load the module, we can finally build the module (again, assuming it is arm-eabi-gcc accessible from PATH, and that the terminal is open in the source directory):

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

That's all. You can copy the hello.ko file to the device and download the module:

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

This article is a translation of a Mike Hommey blog post .

Read Next