kbuild: improved modversioning support for external modules

With following patch a second option is enabled to obtain
symbol information from a second external module when a
external module is build.
The recommended approach is to use a common kbuild file but
that may be impractical in certain cases.
With this patch one can copy over a Module.symvers from one
external module to make symbols (and symbol versions) available
for another external module.

Updated documentation in Documentation/kbuild/modules.txt

Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
This commit is contained in:
Sam Ravnborg 2006-01-28 22:15:55 +01:00
parent 5c3ead8c72
commit 040fcc819a
3 changed files with 158 additions and 59 deletions

View file

@ -23,7 +23,10 @@ In this document you will find information about:
=== 6. Module installation === 6. Module installation
--- 6.1 INSTALL_MOD_PATH --- 6.1 INSTALL_MOD_PATH
--- 6.2 INSTALL_MOD_DIR --- 6.2 INSTALL_MOD_DIR
=== 7. Module versioning === 7. Module versioning & Module.symvers
--- 7.1 Symbols fron the kernel (vmlinux + modules)
--- 7.2 Symbols and external modules
--- 7.3 Symbols from another external module
=== 8. Tips & Tricks === 8. Tips & Tricks
--- 8.1 Testing for CONFIG_FOO_BAR --- 8.1 Testing for CONFIG_FOO_BAR
@ -89,7 +92,8 @@ when building an external module.
make -C $KDIR M=$PWD modules_install make -C $KDIR M=$PWD modules_install
Install the external module(s). Install the external module(s).
Installation default is in /lib/modules/<kernel-version>/extra, Installation default is in /lib/modules/<kernel-version>/extra,
but may be prefixed with INSTALL_MOD_PATH - see separate chapter. but may be prefixed with INSTALL_MOD_PATH - see separate
chapter.
make -C $KDIR M=$PWD clean make -C $KDIR M=$PWD clean
Remove all generated files for the module - the kernel Remove all generated files for the module - the kernel
@ -433,7 +437,7 @@ External modules are installed in the directory:
=> Install dir: /lib/modules/$(KERNELRELEASE)/gandalf => Install dir: /lib/modules/$(KERNELRELEASE)/gandalf
=== 7. Module versioning === 7. Module versioning & Module.symvers
Module versioning is enabled by the CONFIG_MODVERSIONS tag. Module versioning is enabled by the CONFIG_MODVERSIONS tag.
@ -443,10 +447,79 @@ when a module is loaded/used then the CRC values contained in the kernel are
compared with similar values in the module. If they are not equal then the compared with similar values in the module. If they are not equal then the
kernel refuses to load the module. kernel refuses to load the module.
During a kernel build a file named Module.symvers will be generated. This Module.symvers contains a list of all exported symbols from a kernel build.
file includes the symbol version of all symbols within the kernel. If the
Module.symvers file is saved from the last full kernel compile one does not --- 7.1 Symbols fron the kernel (vmlinux + modules)
have to do a full kernel compile to build a module version's compatible module.
During a kernel build a file named Module.symvers will be generated.
Module.symvers contains all exported symbols from the kernel and
compiled modules. For each symbols the corresponding CRC value
is stored too.
The syntax of the Module.symvers file is:
<CRC> <Symbol> <module>
Sample:
0x2d036834 scsi_remove_host drivers/scsi/scsi_mod
For a kernel build without CONFIG_MODVERSIONING enabled the crc
would read: 0x00000000
Module.symvers serve two purposes.
1) It list all exported symbols both from vmlinux and all modules
2) It list CRC if CONFIG_MODVERSION is enabled
--- 7.2 Symbols and external modules
When building an external module the build system needs access to
the symbols from the kernel to check if all external symbols are
defined. This is done in the MODPOST step and to obtain all
symbols modpost reads Module.symvers from the kernel.
If a Module.symvers file is present in the directory where
the external module is being build this file will be read too.
During the MODPOST step a new Module.symvers file will be written
containing all exported symbols that was not defined in the kernel.
--- 7.3 Symbols from another external module
Sometimes one external module uses exported symbols from another
external module. Kbuild needs to have full knowledge on all symbols
to avoid spitting out warnings about undefined symbols.
Two solutions exist to let kbuild know all symbols of more than
one external module.
The method with a top-level kbuild file is recommended but may be
impractical in certain situations.
Use a top-level Kbuild file
If you have two modules: 'foo', 'bar' and 'foo' needs symbols
from 'bar' then one can use a common top-level kbuild file so
both modules are compiled in same build.
Consider following directory layout:
./foo/ <= contains the foo module
./bar/ <= contains the bar module
The top-level Kbuild file would then look like:
#./Kbuild: (this file may also be named Makefile)
obj-y := foo/ bar/
Executing:
make -C $KDIR M=`pwd`
will then do the expected and compile both modules with full
knowledge on symbols from both modules.
Use an extra Module.symvers file
When an external module is build a Module.symvers file is
generated containing all exported symbols which are not
defined in the kernel.
To get access to symbols from module 'bar' one can copy the
Module.symvers file from the compilation of the 'bar' module
to the directory where the 'foo' module is build.
During the module build kbuild will read the Module.symvers
file in the directory of the external module and when the
build is finished a new Module.symvers file is created
containing the sum of all symbols defined and not part of the
kernel.
=== 8. Tips & Tricks === 8. Tips & Tricks

View file

@ -39,7 +39,8 @@ include .config
include scripts/Kbuild.include include scripts/Kbuild.include
include scripts/Makefile.lib include scripts/Makefile.lib
symverfile := $(objtree)/Module.symvers kernelsymfile := $(objtree)/Module.symvers
modulesymfile := $(KBUILD_EXTMOD)/Modules.symvers
# Step 1), find all modules listed in $(MODVERDIR)/ # Step 1), find all modules listed in $(MODVERDIR)/
__modules := $(sort $(shell grep -h '\.ko' /dev/null $(wildcard $(MODVERDIR)/*.mod))) __modules := $(sort $(shell grep -h '\.ko' /dev/null $(wildcard $(MODVERDIR)/*.mod)))
@ -54,7 +55,9 @@ quiet_cmd_modpost = MODPOST
cmd_modpost = scripts/mod/modpost \ cmd_modpost = scripts/mod/modpost \
$(if $(CONFIG_MODVERSIONS),-m) \ $(if $(CONFIG_MODVERSIONS),-m) \
$(if $(CONFIG_MODULE_SRCVERSION_ALL),-a,) \ $(if $(CONFIG_MODULE_SRCVERSION_ALL),-a,) \
$(if $(KBUILD_EXTMOD),-i,-o) $(symverfile) \ $(if $(KBUILD_EXTMOD),-i,-o) $(kernelsymfile) \
$(if $(KBUILD_EXTMOD),-I $(modulesymfile)) \
$(if $(KBUILD_EXTMOD),-o $(modulesymfile)) \
$(filter-out FORCE,$^) $(filter-out FORCE,$^)
.PHONY: __modpost .PHONY: __modpost

View file

@ -20,6 +20,8 @@ int modversions = 0;
int have_vmlinux = 0; int have_vmlinux = 0;
/* Is CONFIG_MODULE_SRCVERSION_ALL set? */ /* Is CONFIG_MODULE_SRCVERSION_ALL set? */
static int all_versions = 0; static int all_versions = 0;
/* If we are modposting external module set to 1 */
static int external_module = 0;
void fatal(const char *fmt, ...) void fatal(const char *fmt, ...)
{ {
@ -45,6 +47,18 @@ void warn(const char *fmt, ...)
va_end(arglist); va_end(arglist);
} }
static int is_vmlinux(const char *modname)
{
const char *myname;
if ((myname = strrchr(modname, '/')))
myname++;
else
myname = modname;
return strcmp(myname, "vmlinux") == 0;
}
void *do_nofail(void *ptr, const char *expr) void *do_nofail(void *ptr, const char *expr)
{ {
if (!ptr) { if (!ptr) {
@ -100,6 +114,9 @@ struct symbol {
unsigned int crc; unsigned int crc;
int crc_valid; int crc_valid;
unsigned int weak:1; unsigned int weak:1;
unsigned int vmlinux:1; /* 1 if symbol is defined in vmlinux */
unsigned int kernel:1; /* 1 if symbol is from kernel
* (only for external modules) **/
char name[0]; char name[0];
}; };
@ -135,8 +152,7 @@ static struct symbol *alloc_symbol(const char *name, unsigned int weak,
} }
/* For the hash of exported symbols */ /* For the hash of exported symbols */
static void new_symbol(const char *name, struct module *module, static struct symbol *new_symbol(const char *name, struct module *module)
unsigned int *crc)
{ {
unsigned int hash; unsigned int hash;
struct symbol *new; struct symbol *new;
@ -144,10 +160,7 @@ static void new_symbol(const char *name, struct module *module,
hash = tdb_hash(name) % SYMBOL_HASH_SIZE; hash = tdb_hash(name) % SYMBOL_HASH_SIZE;
new = symbolhash[hash] = alloc_symbol(name, 0, symbolhash[hash]); new = symbolhash[hash] = alloc_symbol(name, 0, symbolhash[hash]);
new->module = module; new->module = module;
if (crc) { return new;
new->crc = *crc;
new->crc_valid = 1;
}
} }
static struct symbol *find_symbol(const char *name) static struct symbol *find_symbol(const char *name)
@ -169,19 +182,27 @@ static struct symbol *find_symbol(const char *name)
* Add an exported symbol - it may have already been added without a * Add an exported symbol - it may have already been added without a
* CRC, in this case just update the CRC * CRC, in this case just update the CRC
**/ **/
static void add_exported_symbol(const char *name, struct module *module, static struct symbol *sym_add_exported(const char *name, struct module *mod)
unsigned int *crc)
{ {
struct symbol *s = find_symbol(name); struct symbol *s = find_symbol(name);
if (!s) { if (!s)
new_symbol(name, module, crc); s = new_symbol(name, mod);
return;
} s->vmlinux = is_vmlinux(mod->name);
if (crc) { s->kernel = 0;
s->crc = *crc; return s;
}
static void sym_update_crc(const char *name, struct module *mod,
unsigned int crc)
{
struct symbol *s = find_symbol(name);
if (!s)
s = new_symbol(name, mod);
s->crc = crc;
s->crc_valid = 1; s->crc_valid = 1;
}
} }
void *grab_file(const char *filename, unsigned long *size) void *grab_file(const char *filename, unsigned long *size)
@ -332,8 +353,7 @@ static void handle_modversions(struct module *mod, struct elf_info *info,
/* CRC'd symbol */ /* CRC'd symbol */
if (memcmp(symname, CRC_PFX, strlen(CRC_PFX)) == 0) { if (memcmp(symname, CRC_PFX, strlen(CRC_PFX)) == 0) {
crc = (unsigned int) sym->st_value; crc = (unsigned int) sym->st_value;
add_exported_symbol(symname + strlen(CRC_PFX), sym_update_crc(symname + strlen(CRC_PFX), mod, crc);
mod, &crc);
} }
break; break;
case SHN_UNDEF: case SHN_UNDEF:
@ -377,8 +397,7 @@ static void handle_modversions(struct module *mod, struct elf_info *info,
default: default:
/* All exported symbols */ /* All exported symbols */
if (memcmp(symname, KSYMTAB_PFX, strlen(KSYMTAB_PFX)) == 0) { if (memcmp(symname, KSYMTAB_PFX, strlen(KSYMTAB_PFX)) == 0) {
add_exported_symbol(symname + strlen(KSYMTAB_PFX), sym_add_exported(symname + strlen(KSYMTAB_PFX), mod);
mod, NULL);
} }
if (strcmp(symname, MODULE_SYMBOL_PREFIX "init_module") == 0) if (strcmp(symname, MODULE_SYMBOL_PREFIX "init_module") == 0)
mod->has_init = 1; mod->has_init = 1;
@ -388,18 +407,6 @@ static void handle_modversions(struct module *mod, struct elf_info *info,
} }
} }
static int is_vmlinux(const char *modname)
{
const char *myname;
if ((myname = strrchr(modname, '/')))
myname++;
else
myname = modname;
return strcmp(myname, "vmlinux") == 0;
}
/** /**
* Parse tag=value strings from .modinfo section * Parse tag=value strings from .modinfo section
**/ **/
@ -450,9 +457,7 @@ static void read_symbols(char *modname)
/* When there's no vmlinux, don't print warnings about /* When there's no vmlinux, don't print warnings about
* unresolved symbols (since there'll be too many ;) */ * unresolved symbols (since there'll be too many ;) */
if (is_vmlinux(modname)) { if (is_vmlinux(modname)) {
unsigned int fake_crc = 0;
have_vmlinux = 1; have_vmlinux = 1;
add_exported_symbol("struct_module", mod, &fake_crc);
mod->skip = 1; mod->skip = 1;
} }
@ -665,7 +670,7 @@ static void write_if_changed(struct buffer *b, const char *fname)
fclose(file); fclose(file);
} }
static void read_dump(const char *fname) static void read_dump(const char *fname, unsigned int kernel)
{ {
unsigned long size, pos = 0; unsigned long size, pos = 0;
void *file = grab_file(fname, &size); void *file = grab_file(fname, &size);
@ -679,6 +684,7 @@ static void read_dump(const char *fname)
char *symname, *modname, *d; char *symname, *modname, *d;
unsigned int crc; unsigned int crc;
struct module *mod; struct module *mod;
struct symbol *s;
if (!(symname = strchr(line, '\t'))) if (!(symname = strchr(line, '\t')))
goto fail; goto fail;
@ -699,13 +705,28 @@ static void read_dump(const char *fname)
mod = new_module(NOFAIL(strdup(modname))); mod = new_module(NOFAIL(strdup(modname)));
mod->skip = 1; mod->skip = 1;
} }
add_exported_symbol(symname, mod, &crc); s = sym_add_exported(symname, mod);
s->kernel = kernel;
sym_update_crc(symname, mod, crc);
} }
return; return;
fail: fail:
fatal("parse error in symbol dump file\n"); fatal("parse error in symbol dump file\n");
} }
/* For normal builds always dump all symbols.
* For external modules only dump symbols
* that are not read from kernel Module.symvers.
**/
static int dump_sym(struct symbol *sym)
{
if (!external_module)
return 1;
if (sym->vmlinux || sym->kernel)
return 0;
return 1;
}
static void write_dump(const char *fname) static void write_dump(const char *fname)
{ {
struct buffer buf = { }; struct buffer buf = { };
@ -715,15 +736,10 @@ static void write_dump(const char *fname)
for (n = 0; n < SYMBOL_HASH_SIZE ; n++) { for (n = 0; n < SYMBOL_HASH_SIZE ; n++) {
symbol = symbolhash[n]; symbol = symbolhash[n];
while (symbol) { while (symbol) {
symbol = symbol->next; if (dump_sym(symbol))
} buf_printf(&buf, "0x%08x\t%s\t%s\n",
} symbol->crc, symbol->name,
symbol->module->name);
for (n = 0; n < SYMBOL_HASH_SIZE ; n++) {
symbol = symbolhash[n];
while (symbol) {
buf_printf(&buf, "0x%08x\t%s\t%s\n", symbol->crc,
symbol->name, symbol->module->name);
symbol = symbol->next; symbol = symbol->next;
} }
} }
@ -735,13 +751,18 @@ int main(int argc, char **argv)
struct module *mod; struct module *mod;
struct buffer buf = { }; struct buffer buf = { };
char fname[SZ]; char fname[SZ];
char *dump_read = NULL, *dump_write = NULL; char *kernel_read = NULL, *module_read = NULL;
char *dump_write = NULL;
int opt; int opt;
while ((opt = getopt(argc, argv, "i:mo:a")) != -1) { while ((opt = getopt(argc, argv, "i:I:mo:a")) != -1) {
switch(opt) { switch(opt) {
case 'i': case 'i':
dump_read = optarg; kernel_read = optarg;
break;
case 'I':
module_read = optarg;
external_module = 1;
break; break;
case 'm': case 'm':
modversions = 1; modversions = 1;
@ -757,8 +778,10 @@ int main(int argc, char **argv)
} }
} }
if (dump_read) if (kernel_read)
read_dump(dump_read); read_dump(kernel_read, 1);
if (module_read)
read_dump(module_read, 0);
while (optind < argc) { while (optind < argc) {
read_symbols(argv[optind++]); read_symbols(argv[optind++]);