Add a new option -E Module.symvers to read symbol versions from a Module.symvers files and modules and warn about mismatches if -e is given. Signed-off-by: Michal Marek <mmarek@xxxxxxx> --- depmod.c | 100 +++++++++++++++++++++++++++++++++++--------- elfops.h | 5 +- elfops_core.c | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 208 insertions(+), 28 deletions(-) diff --git a/depmod.c b/depmod.c index ba405fe..fe843ad 100644 --- a/depmod.c +++ b/depmod.c @@ -68,6 +68,7 @@ struct symbol { struct symbol *next; struct module *owner; + uint64_t ver; char name[0]; }; @@ -86,12 +87,13 @@ static inline unsigned int tdb_hash(const char *name) return (1103515243 * value + 12345); } -void add_symbol(const char *name, struct module *owner) +void add_symbol(const char *name, uint64_t ver, struct module *owner) { unsigned int hash; struct symbol *new = NOFAIL(malloc(sizeof *new + strlen(name) + 1)); new->owner = owner; + new->ver = ver; strcpy(new->name, name); hash = tdb_hash(name) % SYMBOL_HASH_SIZE; @@ -99,9 +101,10 @@ void add_symbol(const char *name, struct module *owner) symbolhash[hash] = new; } -static int print_unknown; +static int print_unknown, check_symvers; -struct module *find_symbol(const char *name, const char *modname, int weak) +struct module *find_symbol(const char *name, uint64_t ver, + const char *modname, int weak) { struct symbol *s; @@ -111,7 +114,13 @@ struct module *find_symbol(const char *name, const char *modname, int weak) for (s = symbolhash[tdb_hash(name) % SYMBOL_HASH_SIZE]; s; s=s->next) { if (streq(s->name, name)) - return s->owner; + break; + } + if (s) { + if (ver && s->ver && s->ver != ver && print_unknown && !weak) + warn("%s disagrees about version of symbol %s\n", + modname, name); + return s->owner; } if (print_unknown && !weak) @@ -132,6 +141,14 @@ void add_dep(struct module *mod, struct module *depends_on) mod->deps[mod->num_deps++] = depends_on; } +static void add_fake_syms(void) +{ + /* __this_module is magic inserted by kernel loader. */ + add_symbol("__this_module", 0, NULL); + /* On S390, this is faked up too */ + add_symbol("_GLOBAL_OFFSET_TABLE_", 0, NULL); +} + static void load_system_map(const char *filename) { FILE *system_map; @@ -158,15 +175,38 @@ static void load_system_map(const char *filename) /* Covers gpl-only and normal symbols. */ if (strstarts(ptr+1, ksymstr)) - add_symbol(ptr+1+ksymstr_len, NULL); + add_symbol(ptr+1+ksymstr_len, 0, NULL); } fclose(system_map); + add_fake_syms(); +} - /* __this_module is magic inserted by kernel loader. */ - add_symbol("__this_module", NULL); - /* On S390, this is faked up too */ - add_symbol("_GLOBAL_OFFSET_TABLE_", NULL); +static void load_module_symvers(const char *filename) +{ + FILE *module_symvers; + char line[10240]; + + module_symvers = fopen(filename, "r"); + if (!module_symvers) + fatal("Could not open '%s': %s\n", filename, strerror(errno)); + + /* eg. "0xb352177e\tfind_first_bit\tvmlinux\tEXPORT_SYMBOL" */ + while (fgets(line, sizeof(line)-1, module_symvers)) { + const char *ver, *sym, *where; + + ver = strtok(line, " \t"); + sym = strtok(NULL, " \t"); + where = strtok(NULL, " \t"); + if (!ver || !sym || !where) + continue; + + if (streq(where, "vmlinux")) + add_symbol(sym, strtoull(ver, NULL, 16), NULL); + } + + fclose(module_symvers); + add_fake_syms(); } static struct option options[] = { { "all", 0, NULL, 'a' }, @@ -174,6 +214,7 @@ static struct option options[] = { { "all", 0, NULL, 'a' }, { "basedir", 1, NULL, 'b' }, { "errsyms", 0, NULL, 'e' }, { "filesyms", 1, NULL, 'F' }, + { "symvers", 1, NULL, 'E' }, { "help", 0, NULL, 'h' }, { "show", 0, NULL, 'n' }, { "dry-run", 0, NULL, 'n' }, @@ -242,7 +283,10 @@ static void print_usage(const char *name) "\t --basedir basedirectory Use an image of a module tree.\n" "\t-F kernelsyms\n" "\t --filesyms kernelsyms Use the file instead of the\n" - "\t current kernel symbols.\n", + "\t current kernel symbols.\n" + "\t-E Module.symvers\n" + "\t --symvers Module.symvers Use Module.symvers file to check\n" + "\t symbol versions.\n", "depmod", "depmod"); } @@ -650,24 +694,28 @@ static void calculate_deps(struct module *module) unsigned int i; struct string_table *symnames; struct string_table *symtypes; + uint64_t *symvers = NULL; struct elf_file *file; module->num_deps = 0; module->deps = NULL; file = module->file; - symnames = file->ops->load_dep_syms(file, &symtypes); + symnames = file->ops->load_dep_syms(file, &symtypes, + check_symvers ? &symvers : NULL); if (!symnames || !symtypes) return; for (i = 0; i < symnames->cnt; i++) { const char *name; + uint64_t ver; struct module *owner; int weak; name = symnames->str[i]; + ver = symvers ? symvers[i] : 0; weak = (*(symtypes->str[i]) == 'W'); - owner = find_symbol(name, module->pathname, weak); + owner = find_symbol(name, ver, module->pathname, weak); if (owner) { info("%s needs \"%s\": %s\n", module->pathname, name, @@ -678,6 +726,7 @@ static void calculate_deps(struct module *module) free(symnames); free(symtypes); + free(symvers); } static struct module *parse_modules(struct module *list) @@ -688,13 +737,17 @@ static struct module *parse_modules(struct module *list) int j; for (i = list; i; i = i->next) { + uint64_t *symvers = NULL; file = i->file; - syms = file->ops->load_symbols(file); + syms = file->ops->load_symbols(file, + check_symvers ? &symvers : NULL); if (syms) { for (j = 0; j < syms->cnt; j++) - add_symbol(syms->str[j], i); + add_symbol(syms->str[j], + symvers ? symvers[j] : 0, i); strtbl_free(syms); } + free(symvers); file->ops->fetch_tables(file, &i->tables); } @@ -1176,7 +1229,7 @@ int main(int argc, char *argv[]) { int opt, all = 0, maybe_all = 0, doing_stdout = 0; char *basedir = "", *dirname, *version, *badopt = NULL, - *system_map = NULL; + *system_map = NULL, *module_symvers = NULL; int i; const char *config = NULL; @@ -1186,7 +1239,7 @@ int main(int argc, char *argv[]) /* Don't print out any errors just yet, we might want to exec backwards compat version. */ opterr = 0; - while ((opt = getopt_long(argc, argv, "ab:ArehnqruvVF:C:wm", options, NULL)) + while ((opt = getopt_long(argc, argv, "ab:ArehnqruvVE:F:C:wm", options, NULL)) != -1) { switch (opt) { case 'a': @@ -1199,6 +1252,10 @@ int main(int argc, char *argv[]) case 'A': maybe_all = 1; break; + case 'E': + module_symvers = optarg; + check_symvers = 1; + break; case 'F': system_map = optarg; break; @@ -1236,11 +1293,14 @@ int main(int argc, char *argv[]) } } - /* We can't print unknowns without a System.map */ - if (!system_map) - print_unknown = 0; - else + if (module_symvers) + load_module_symvers(module_symvers); + else if (system_map) load_system_map(system_map); + else if (print_unknown) { + warn("-e needs -E or -F"); + print_unknown = 0; + } /* They can specify the version naked on the command line */ if (optind < argc && is_version_number(argv[optind])) { diff --git a/elfops.h b/elfops.h index 6cdfc07..0fb5167 100644 --- a/elfops.h +++ b/elfops.h @@ -64,9 +64,10 @@ struct module_ops const char *secname, unsigned long *secsize); struct string_table *(*load_strings)(struct elf_file *module, const char *secname, struct string_table *tbl, errfn_t error); - struct string_table *(*load_symbols)(struct elf_file *module); + struct string_table *(*load_symbols)(struct elf_file *module, + uint64_t **versions); struct string_table *(*load_dep_syms)(struct elf_file *module, - struct string_table **types); + struct string_table **types, uint64_t **versions); void (*fetch_tables)(struct elf_file *module, struct module_tables *tables); char *(*get_aliases)(struct elf_file *module, unsigned long *size); diff --git a/elfops_core.c b/elfops_core.c index 5df9f25..1495f68 100644 --- a/elfops_core.c +++ b/elfops_core.c @@ -100,10 +100,53 @@ static struct string_table *PERBIT(load_strings)(struct elf_file *module, return tbl; } -static struct string_table *PERBIT(load_symbols)(struct elf_file *module) +static struct string_table *PERBIT(load_symbols)(struct elf_file *module, + uint64_t **versions) { struct string_table *symtbl = NULL; + if (versions) { + static const char crc[] = "__crc_"; + static const int crc_len = sizeof(crc) - 1; + unsigned int num_syms, i; + unsigned long size; + ElfPERBIT(Sym) *syms; + char *strings; + int conv; + + *versions = NULL; + strings = PERBIT(load_section)(module, ".strtab", &size); + syms = PERBIT(load_section)(module, ".symtab", &size); + if (!strings || !syms) + goto fallback; + num_syms = size / sizeof(syms[0]); + *versions = NOFAIL(calloc(sizeof(**versions), num_syms)); + + conv = module->conv; + for (i = 1; i < num_syms; i++) { + const char *name; + name = strings + END(syms[i].st_name, conv); + if (strncmp(name, crc, crc_len) != 0) + continue; + name += crc_len; + symtbl = NOFAIL(strtbl_add(name, symtbl)); + (*versions)[symtbl->cnt - 1] = END(syms[i].st_value, + conv); + } + if (!symtbl) { + /* Either this module does not export any symbols, or + * it was compiled without CONFIG_MODVERSIONS. If the + * latter, we will print a warning in load_dep_syms, + * so just silently fallback to __ksymtab_strings in + * both cases. + */ + free(*versions); + *versions = NULL; + goto fallback; + } + return symtbl; + } +fallback: return PERBIT(load_strings)(module, "__ksymtab_strings", symtbl, fatal); } @@ -123,37 +166,78 @@ static char *PERBIT(get_modinfo)(struct elf_file *module, unsigned long *size) #endif static struct string_table *PERBIT(load_dep_syms)(struct elf_file *module, - struct string_table **types) + struct string_table **types, + uint64_t **versions) { - unsigned int i; + unsigned int i, num_syms; + unsigned int j, num_symvers, versions_size; unsigned long size; char *strings; ElfPERBIT(Sym) *syms; ElfPERBIT(Ehdr) *hdr; + struct PERBIT(modver_info) **symvers; int handle_register_symbols; struct string_table *names; int conv; names = NULL; *types = NULL; + symvers = NULL; + num_symvers = versions_size = 0; + + if (versions) { + int ok = 1; + *versions = NULL; + struct PERBIT(modver_info) *symvers_sec; + + symvers_sec = module->ops->load_section(module, "__versions", + &size); + if (!symvers_sec) { + warn("%s is built without modversions", + module->pathname); + ok = 0; + } + if (size % sizeof(symvers[0]) != 0) { + warn("invalid __versions section size in %s", + module->pathname); + ok = 0; + } + if (ok) { + num_symvers = size / sizeof(symvers_sec[0]); + /* symvers is used to keep track of each visited entry. + * The table also contains the fake struct_module / + * module_layout symbol which we don't want to miss. + */ + symvers = NOFAIL(malloc(num_symvers * + sizeof(symvers[0]))); + for (j = 0; j < num_symvers; j++) + symvers[j] = &symvers_sec[j]; + } else { + versions = NULL; + } + } strings = PERBIT(load_section)(module, ".strtab", &size); syms = PERBIT(load_section)(module, ".symtab", &size); - if (!strings || !syms) { warn("Couldn't find symtab and strtab in module %s\n", module->pathname); - return NULL; + goto out; } + num_syms = size / sizeof(syms[0]); hdr = module->data; conv = module->conv; + if (versions) { + versions_size = num_syms; + *versions = NOFAIL(calloc(sizeof(**versions), versions_size)); + } handle_register_symbols = (END(hdr->e_machine, conv) == EM_SPARC || END(hdr->e_machine, conv) == EM_SPARCV9); - for (i = 1; i < size / sizeof(syms[0]); i++) { + for (i = 1; i < num_syms; i++) { if (END(syms[i].st_shndx, conv) == SHN_UNDEF) { /* Look for symbol */ const char *name; @@ -175,8 +259,43 @@ static struct string_table *PERBIT(load_dep_syms)(struct elf_file *module, names = NOFAIL(strtbl_add(name, names)); *types = NOFAIL(strtbl_add(weak ? weak_sym : undef_sym, *types)); + + if (!versions) + continue; + /* Not optimal, but the number of required symbols + * is usually not huge and this is only called by + * depmod. + */ + for (j = 0; j < num_symvers; j++) { + struct PERBIT(modver_info) *info = symvers[j]; + + if (!info) + continue; + if (streq(name, info->name)) { + (*versions)[names->cnt - 1] = + END(info->crc, conv); + symvers[j] = NULL; + break; + } + } + } + } + /* add struct_module / module_layout */ + for (j = 0; j < num_symvers; j++) { + struct PERBIT(modver_info) *info = symvers[j]; + + if (!info) + continue; + if ((names ? names->cnt : 0) >= versions_size) { + versions_size++; + *versions = NOFAIL(realloc(*versions, versions_size)); } + names = NOFAIL(strtbl_add(info->name, names)); + *types = NOFAIL(strtbl_add(undef_sym, *types)); + (*versions)[names->cnt - 1] = END(info->crc, conv); } +out: + free(symvers); return names; } -- 1.6.3 -- To unsubscribe from this list: send the line "unsubscribe linux-modules" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html