Version Control in Modules

One of the main problems with modules is their version dependency, which was introduced in Section 2.2.1, in Chapter 2. The need to recompile the module against the headers of each version being used can become a real pain when you run several custom modules, and recompiling is not even possible if you run a commercial module distributed in binary form.

Fortunately, the kernel developers found a flexible way to deal with version problems. The idea is that a module is incompatible with a different kernel version only if the software interface offered by the kernel has changed. The software interface, then, can be represented by a function prototype and the exact definition of all the data structures involved in the function call. Finally, a CRC algorithm can be used to map all the information about the software interface to a single 32-bit number.[29]

The issue of version dependence is thus handled by mangling the name of each symbol exported by the kernel to include the checksum of all the information related to that symbol. This information is obtained by parsing the header files and extracting the information from them. This facility is optional and can be enabled at compilation time.

For example, the symbol printk is exported to modules as something like printk_R12345678 when version support is enabled, where 12345678 is the hex representation of the checksum of the software interface used by the function. When a module is loaded into the kernel, insmod (or modprobe) can accomplish its task only if the checksum added to each symbol in the kernel matches the one added to the same symbol in the module.

But let’s see what happens in both the kernel and the module when version support is enabled:

  • In the kernel itself, the symbol is not modified. The linking process happens in the usual way, and the symbol table of the vmlinux file looks the same as before.

  • The public symbol table is built using the versioned names, and this is what appears in /proc/ksyms.

  • The module must be compiled using the mangled names, which appear in the object files as undefined symbols.

  • The loading program matches the undefined symbols in the module with the public symbols in the kernel, thus using version information.

The previous scenario is, however, valid only if both the kernel and the module are built to support versioning; if either one uses the original symbol names, insmod drops the version information and tries to match the kernel version declared by the module and the one exported by the kernel, in the way described in Section 2.2.1 in Chapter 2.

Using Version Support in Modules

While kernel code is already prepared to (optionally) export versioned symbols, the module source needs to be prepared to support the option. Version control can be inserted in one of two places: in the Makefile or in the source itself. Since the documentation of the modules package describes how to do it in the Makefile, I’ll show you how to do it in the C source. The master module used to demonstrate how kerneld works is able to support versioned symbols. The capability is automatically enabled if the kernel used to compile the module exploits version support.

The main facility used to mangle symbol names is the header <linux/modversions.h>, which includes preprocessor definitions for all the public kernel symbols. After the header is included, whenever the module uses a kernel symbol, the compiler sees the mangled version. The definitions in modversions.h are effective only if MODVERSIONS is defined in advance.

In order to enable versioning in the module if it has been enabled in the kernel, we must make sure that CONFIG_MODVERSIONS has been defined in <linux/autoconf.h>. That header controls what features are enabled (compiled) in the current kernel. Each CONFIG_ macro defined states that the corresponding option is active.

The initial part of master.c, therefore, consists of the following lines:

#include <linux/autoconf.h> /* retrieve the CONFIG_* macros */
#if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
#   define MODVERSIONS /* force it on */
#endif

#ifdef MODVERSIONS
#  include <linux/modversions.h>
#endif

When compiling the file against a versioned kernel, the symbol table in the object file refers to versioned symbols, which match the ones exported by the kernel itself. The following screenshot shows the symbol names stored in master.o. In the output of nm, ``T'' means ``text,'' ``D'' means ``data,'' and ``U'' means ``undefined.'' The last tag denotes symbols that the object file references but doesn’t declare.

morgana% nm master.o
000000b0 T cleanup_module
00000000 T init_module
00000000 D kernel_version
         U kerneld_send_R7d428f45
         U printk_Rad1148ba
morgana% egrep 'printk|kerneld_send' /proc/ksyms
00131b40 kerneld_send_R7d428f45
0011234c printk_Rad1148ba

Since the checksum added to the symbol names in master.o includes the whole interface related to printk and kerneld_send, the module is compatible with a wide range of kernel versions. If, however, the data structures related to either function get modified, insmod will refuse to load the module because of its incompatibility with the kernel.

Exporting Versioned Symbols

The situation not covered by the previous discussion is what happens when a module exports symbols to be used by other modules. If we rely on version information to achieve module portability, we’d like to be able to add a CRC code to our own symbols. This subject is slightly trickier than just linking to the kernel, because we need to export the mangled symbol name to other modules; we need a way to build the checksums.

The task of parsing the header files and building the checksums is performed by genksyms, a tool released with the modules package. This program receives the output of the C preprocessor on its own standard input and prints a new header file on standard output. The output file defines the checksummed version of each symbol exported by the original source file. The output of genksyms is usually saved with a .ver suffix; I’ll follow the same practice.

To show you how symbols are exported, I’ve created two dummy modules called export.c and import.c. export exports a simple function called export_function, which is used by the second module, import.c. This function receives two integer arguments and returns their sum--we are not interested in the function, but rather in the linking process.

The Makefile in the misc-modules directory has a rule to build an export.ver file from export.c, so that the checksummed symbol for export_function can be used by the import module:

ifdef MODVERSIONS
export.o import.o: export.ver
endif

export.ver: export.c
	$(CC) -I$(INCLUDEDIR) -E -D__GENKSYMS__ $^ | genksyms > $@

These lines demonstrate how to build export.ver and add it to the dependencies of both object files, but only if MODVERSIONS is defined. A few lines added to Makefile take care of defining MODVERSIONS if version support is enabled in the kernel, but they are not worth showing here.

The source file, then, must declare the right preprocessor symbols for every conceivable preprocessor pass: the input to genksyms and the actual compilation, both with version support enabled and with it disabled. Moreover, export.c should be able to autodetect version support in the kernel, as master.c does. The following lines show you how to do this successfully:

#ifndef EXPORT_SYMTAB
#  define EXPORT_SYMTAB  /* need this one 'cause we export symbols */
#endif

#include <linux/autoconf.h>  /* retrieve the CONFIG_* macros */
#if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
#   define MODVERSIONS
#endif

/*
 * Include the versioned definitions for both kernel symbols and our
 * symbol, *unless* we are generating checksums (__GENKSYMS__
 * defined)
 */
#if defined(MODVERSIONS) && !defined(__GENKSYMS__)
#    include <linux/modversions.h>
#    include "export.ver"    /* redefine "export_function" */
                             /* to include CRC */
#endif

The code, though hairy, has the advantage of leaving the Makefile in a clean state. Passing the correct flags from make, on the other hand, involves writing long command lines for the various cases, which I won’t do here.

The simple import module calls export_function by passing the numbers 2 and 2 as arguments; the expected result is therefore 4. The following example shows that import actually links to the versioned symbol of export and calls the function. The versioned symbol appears in /proc/ksyms.

morgana.root# insmod export
morgana.root# grep export /proc/ksyms
0202d024 export_function_R2eb14c1e      [export]
morgana.root# insmod import
import: my mate tells that 2+2 = 4
morgana.root# cat /proc/modules
import             1            0
export             3    [import]        0


[29] Actually, the incompatibility between SMP and non-SMP modules isn’t detected by the CRC algorithm, because a lot of interface functions are inline and compile differently on SMP and non-SMP machines, even if they feature the same checksum. You must be very careful not to mix SMP modules with conventional modules.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.15.205