[PATCH 3/3] depmod: add ability to check symbol versions

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux