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.
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.
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.
3.145.15.205