forked from mirrors/linux
		
	tools: Add gendwarfksyms
Add a basic DWARF parser, which uses libdw to traverse the debugging information in an object file and looks for functions and variables. In follow-up patches, this will be expanded to produce symbol versions for CONFIG_MODVERSIONS from DWARF. Signed-off-by: Sami Tolvanen <samitolvanen@google.com> Reviewed-by: Petr Pavlu <petr.pavlu@suse.com> Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
This commit is contained in:
		
							parent
							
								
									a56fece7f3
								
							
						
					
					
						commit
						f28568841a
					
				
					 9 changed files with 513 additions and 0 deletions
				
			
		|  | @ -9550,6 +9550,13 @@ W:	https://linuxtv.org | |||
| T:	git git://linuxtv.org/media.git | ||||
| F:	drivers/media/radio/radio-gemtek* | ||||
| 
 | ||||
| GENDWARFKSYMS | ||||
| M:	Sami Tolvanen <samitolvanen@google.com> | ||||
| L:	linux-modules@vger.kernel.org | ||||
| L:	linux-kbuild@vger.kernel.org | ||||
| S:	Maintained | ||||
| F:	scripts/gendwarfksyms/ | ||||
| 
 | ||||
| GENERIC ARCHITECTURE TOPOLOGY | ||||
| M:	Sudeep Holla <sudeep.holla@arm.com> | ||||
| L:	linux-kernel@vger.kernel.org | ||||
|  |  | |||
|  | @ -169,6 +169,14 @@ config MODVERSIONS | |||
| 	  make them incompatible with the kernel you are running.  If | ||||
| 	  unsure, say N. | ||||
| 
 | ||||
| config GENDWARFKSYMS | ||||
| 	bool "gendwarfksyms (from debugging information)" | ||||
| 	depends on DEBUG_INFO | ||||
| 	# Requires full debugging information, split DWARF not supported. | ||||
| 	depends on !DEBUG_INFO_REDUCED && !DEBUG_INFO_SPLIT | ||||
| 	# Requires ELF object files. | ||||
| 	depends on !LTO | ||||
| 
 | ||||
| config ASM_MODVERSIONS | ||||
| 	bool | ||||
| 	default HAVE_ASM_MODVERSIONS && MODVERSIONS | ||||
|  |  | |||
|  | @ -54,6 +54,7 @@ targets += module.lds | |||
| 
 | ||||
| subdir-$(CONFIG_GCC_PLUGINS) += gcc-plugins | ||||
| subdir-$(CONFIG_MODVERSIONS) += genksyms | ||||
| subdir-$(CONFIG_GENDWARFKSYMS) += gendwarfksyms | ||||
| subdir-$(CONFIG_SECURITY_SELINUX) += selinux | ||||
| subdir-$(CONFIG_SECURITY_IPE) += ipe | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										2
									
								
								scripts/gendwarfksyms/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								scripts/gendwarfksyms/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| # SPDX-License-Identifier: GPL-2.0 | ||||
| /gendwarfksyms | ||||
							
								
								
									
										8
									
								
								scripts/gendwarfksyms/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								scripts/gendwarfksyms/Makefile
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| # SPDX-License-Identifier: GPL-2.0
 | ||||
| hostprogs-always-y += gendwarfksyms | ||||
| 
 | ||||
| gendwarfksyms-objs += gendwarfksyms.o | ||||
| gendwarfksyms-objs += dwarf.o | ||||
| gendwarfksyms-objs += symbols.o | ||||
| 
 | ||||
| HOSTLDLIBS_gendwarfksyms := -ldw -lelf | ||||
							
								
								
									
										166
									
								
								scripts/gendwarfksyms/dwarf.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								scripts/gendwarfksyms/dwarf.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,166 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| /*
 | ||||
|  * Copyright (C) 2024 Google LLC | ||||
|  */ | ||||
| 
 | ||||
| #include "gendwarfksyms.h" | ||||
| 
 | ||||
| static bool get_ref_die_attr(Dwarf_Die *die, unsigned int id, Dwarf_Die *value) | ||||
| { | ||||
| 	Dwarf_Attribute da; | ||||
| 
 | ||||
| 	/* dwarf_formref_die returns a pointer instead of an error value. */ | ||||
| 	return dwarf_attr(die, id, &da) && dwarf_formref_die(&da, value); | ||||
| } | ||||
| 
 | ||||
| #define DEFINE_GET_STRING_ATTR(attr)                         \ | ||||
| 	static const char *get_##attr##_attr(Dwarf_Die *die) \ | ||||
| 	{                                                    \ | ||||
| 		Dwarf_Attribute da;                          \ | ||||
| 		if (dwarf_attr(die, DW_AT_##attr, &da))      \ | ||||
| 			return dwarf_formstring(&da);        \ | ||||
| 		return NULL;                                 \ | ||||
| 	} | ||||
| 
 | ||||
| DEFINE_GET_STRING_ATTR(name) | ||||
| DEFINE_GET_STRING_ATTR(linkage_name) | ||||
| 
 | ||||
| static const char *get_symbol_name(Dwarf_Die *die) | ||||
| { | ||||
| 	const char *name; | ||||
| 
 | ||||
| 	/* rustc uses DW_AT_linkage_name for exported symbols */ | ||||
| 	name = get_linkage_name_attr(die); | ||||
| 	if (!name) | ||||
| 		name = get_name_attr(die); | ||||
| 
 | ||||
| 	return name; | ||||
| } | ||||
| 
 | ||||
| static bool match_export_symbol(struct state *state, Dwarf_Die *die) | ||||
| { | ||||
| 	Dwarf_Die *source = die; | ||||
| 	Dwarf_Die origin; | ||||
| 
 | ||||
| 	/* If the DIE has an abstract origin, use it for type information. */ | ||||
| 	if (get_ref_die_attr(die, DW_AT_abstract_origin, &origin)) | ||||
| 		source = &origin; | ||||
| 
 | ||||
| 	state->sym = symbol_get(get_symbol_name(die)); | ||||
| 
 | ||||
| 	/* Look up using the origin name if there are no matches. */ | ||||
| 	if (!state->sym && source != die) | ||||
| 		state->sym = symbol_get(get_symbol_name(source)); | ||||
| 
 | ||||
| 	state->die = *source; | ||||
| 	return !!state->sym; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Type string processing | ||||
|  */ | ||||
| static void process(const char *s) | ||||
| { | ||||
| 	s = s ?: "<null>"; | ||||
| 
 | ||||
| 	if (dump_dies) | ||||
| 		fputs(s, stderr); | ||||
| } | ||||
| 
 | ||||
| bool match_all(Dwarf_Die *die) | ||||
| { | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| int process_die_container(struct state *state, Dwarf_Die *die, | ||||
| 			  die_callback_t func, die_match_callback_t match) | ||||
| { | ||||
| 	Dwarf_Die current; | ||||
| 	int res; | ||||
| 
 | ||||
| 	res = checkp(dwarf_child(die, ¤t)); | ||||
| 	while (!res) { | ||||
| 		if (match(¤t)) { | ||||
| 			/* <0 = error, 0 = continue, >0 = stop */ | ||||
| 			res = checkp(func(state, ¤t)); | ||||
| 			if (res) | ||||
| 				return res; | ||||
| 		} | ||||
| 
 | ||||
| 		res = checkp(dwarf_siblingof(¤t, ¤t)); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Exported symbol processing | ||||
|  */ | ||||
| static void process_symbol(struct state *state, Dwarf_Die *die, | ||||
| 			   die_callback_t process_func) | ||||
| { | ||||
| 	debug("%s", state->sym->name); | ||||
| 	check(process_func(state, die)); | ||||
| 	if (dump_dies) | ||||
| 		fputs("\n", stderr); | ||||
| } | ||||
| 
 | ||||
| static int __process_subprogram(struct state *state, Dwarf_Die *die) | ||||
| { | ||||
| 	process("subprogram"); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void process_subprogram(struct state *state, Dwarf_Die *die) | ||||
| { | ||||
| 	process_symbol(state, die, __process_subprogram); | ||||
| } | ||||
| 
 | ||||
| static int __process_variable(struct state *state, Dwarf_Die *die) | ||||
| { | ||||
| 	process("variable "); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void process_variable(struct state *state, Dwarf_Die *die) | ||||
| { | ||||
| 	process_symbol(state, die, __process_variable); | ||||
| } | ||||
| 
 | ||||
| static int process_exported_symbols(struct state *unused, Dwarf_Die *die) | ||||
| { | ||||
| 	int tag = dwarf_tag(die); | ||||
| 
 | ||||
| 	switch (tag) { | ||||
| 	/* Possible containers of exported symbols */ | ||||
| 	case DW_TAG_namespace: | ||||
| 	case DW_TAG_class_type: | ||||
| 	case DW_TAG_structure_type: | ||||
| 		return check(process_die_container( | ||||
| 			NULL, die, process_exported_symbols, match_all)); | ||||
| 
 | ||||
| 	/* Possible exported symbols */ | ||||
| 	case DW_TAG_subprogram: | ||||
| 	case DW_TAG_variable: { | ||||
| 		struct state state; | ||||
| 
 | ||||
| 		if (!match_export_symbol(&state, die)) | ||||
| 			return 0; | ||||
| 
 | ||||
| 		if (tag == DW_TAG_subprogram) | ||||
| 			process_subprogram(&state, &state.die); | ||||
| 		else | ||||
| 			process_variable(&state, &state.die); | ||||
| 
 | ||||
| 		return 0; | ||||
| 	} | ||||
| 	default: | ||||
| 		return 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void process_cu(Dwarf_Die *cudie) | ||||
| { | ||||
| 	check(process_die_container(NULL, cudie, process_exported_symbols, | ||||
| 				    match_all)); | ||||
| } | ||||
							
								
								
									
										128
									
								
								scripts/gendwarfksyms/gendwarfksyms.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								scripts/gendwarfksyms/gendwarfksyms.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,128 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| /*
 | ||||
|  * Copyright (C) 2024 Google LLC | ||||
|  */ | ||||
| 
 | ||||
| #include <fcntl.h> | ||||
| #include <getopt.h> | ||||
| #include <errno.h> | ||||
| #include <stdarg.h> | ||||
| #include <string.h> | ||||
| #include <unistd.h> | ||||
| #include "gendwarfksyms.h" | ||||
| 
 | ||||
| /*
 | ||||
|  * Options | ||||
|  */ | ||||
| 
 | ||||
| /* Print debugging information to stderr */ | ||||
| int debug; | ||||
| /* Dump DIE contents */ | ||||
| int dump_dies; | ||||
| 
 | ||||
| static void usage(void) | ||||
| { | ||||
| 	fputs("Usage: gendwarfksyms [options] elf-object-file ... < symbol-list\n\n" | ||||
| 	      "Options:\n" | ||||
| 	      "  -d, --debug          Print debugging information\n" | ||||
| 	      "      --dump-dies      Dump DWARF DIE contents\n" | ||||
| 	      "  -h, --help           Print this message\n" | ||||
| 	      "\n", | ||||
| 	      stderr); | ||||
| } | ||||
| 
 | ||||
| static int process_module(Dwfl_Module *mod, void **userdata, const char *name, | ||||
| 			  Dwarf_Addr base, void *arg) | ||||
| { | ||||
| 	Dwarf_Addr dwbias; | ||||
| 	Dwarf_Die cudie; | ||||
| 	Dwarf_CU *cu = NULL; | ||||
| 	Dwarf *dbg; | ||||
| 	int res; | ||||
| 
 | ||||
| 	debug("%s", name); | ||||
| 	dbg = dwfl_module_getdwarf(mod, &dwbias); | ||||
| 
 | ||||
| 	do { | ||||
| 		res = dwarf_get_units(dbg, cu, &cu, NULL, NULL, &cudie, NULL); | ||||
| 		if (res < 0) | ||||
| 			error("dwarf_get_units failed: no debugging information?"); | ||||
| 		if (res == 1) | ||||
| 			break; /* No more units */ | ||||
| 
 | ||||
| 		process_cu(&cudie); | ||||
| 	} while (cu); | ||||
| 
 | ||||
| 	return DWARF_CB_OK; | ||||
| } | ||||
| 
 | ||||
| static const Dwfl_Callbacks callbacks = { | ||||
| 	.section_address = dwfl_offline_section_address, | ||||
| 	.find_debuginfo = dwfl_standard_find_debuginfo, | ||||
| }; | ||||
| 
 | ||||
| int main(int argc, char **argv) | ||||
| { | ||||
| 	unsigned int n; | ||||
| 	int opt; | ||||
| 
 | ||||
| 	static const struct option opts[] = { | ||||
| 		{ "debug", 0, NULL, 'd' }, | ||||
| 		{ "dump-dies", 0, &dump_dies, 1 }, | ||||
| 		{ "help", 0, NULL, 'h' }, | ||||
| 		{ 0, 0, NULL, 0 } | ||||
| 	}; | ||||
| 
 | ||||
| 	while ((opt = getopt_long(argc, argv, "dh", opts, NULL)) != EOF) { | ||||
| 		switch (opt) { | ||||
| 		case 0: | ||||
| 			break; | ||||
| 		case 'd': | ||||
| 			debug = 1; | ||||
| 			break; | ||||
| 		case 'h': | ||||
| 			usage(); | ||||
| 			return 0; | ||||
| 		default: | ||||
| 			usage(); | ||||
| 			return 1; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (optind >= argc) { | ||||
| 		usage(); | ||||
| 		error("no input files?"); | ||||
| 	} | ||||
| 
 | ||||
| 	symbol_read_exports(stdin); | ||||
| 
 | ||||
| 	for (n = optind; n < argc; n++) { | ||||
| 		Dwfl *dwfl; | ||||
| 		int fd; | ||||
| 
 | ||||
| 		fd = open(argv[n], O_RDONLY); | ||||
| 		if (fd == -1) | ||||
| 			error("open failed for '%s': %s", argv[n], | ||||
| 			      strerror(errno)); | ||||
| 
 | ||||
| 		dwfl = dwfl_begin(&callbacks); | ||||
| 		if (!dwfl) | ||||
| 			error("dwfl_begin failed for '%s': %s", argv[n], | ||||
| 			      dwarf_errmsg(-1)); | ||||
| 
 | ||||
| 		if (!dwfl_report_offline(dwfl, argv[n], argv[n], fd)) | ||||
| 			error("dwfl_report_offline failed for '%s': %s", | ||||
| 			      argv[n], dwarf_errmsg(-1)); | ||||
| 
 | ||||
| 		dwfl_report_end(dwfl, NULL, NULL); | ||||
| 
 | ||||
| 		if (dwfl_getmodules(dwfl, &process_module, NULL, 0)) | ||||
| 			error("dwfl_getmodules failed for '%s'", argv[n]); | ||||
| 
 | ||||
| 		dwfl_end(dwfl); | ||||
| 	} | ||||
| 
 | ||||
| 	symbol_free(); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
							
								
								
									
										95
									
								
								scripts/gendwarfksyms/gendwarfksyms.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								scripts/gendwarfksyms/gendwarfksyms.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,95 @@ | |||
| /* SPDX-License-Identifier: GPL-2.0 */ | ||||
| /*
 | ||||
|  * Copyright (C) 2024 Google LLC | ||||
|  */ | ||||
| 
 | ||||
| #include <dwarf.h> | ||||
| #include <elfutils/libdw.h> | ||||
| #include <elfutils/libdwfl.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdio.h> | ||||
| 
 | ||||
| #include <hash.h> | ||||
| #include <hashtable.h> | ||||
| #include <xalloc.h> | ||||
| 
 | ||||
| #ifndef __GENDWARFKSYMS_H | ||||
| #define __GENDWARFKSYMS_H | ||||
| 
 | ||||
| /*
 | ||||
|  * Options -- in gendwarfksyms.c | ||||
|  */ | ||||
| extern int debug; | ||||
| extern int dump_dies; | ||||
| 
 | ||||
| /*
 | ||||
|  * Output helpers | ||||
|  */ | ||||
| #define __PREFIX "gendwarfksyms: " | ||||
| #define __println(prefix, format, ...)                                \ | ||||
| 	fprintf(stderr, prefix __PREFIX "%s: " format "\n", __func__, \ | ||||
| 		##__VA_ARGS__) | ||||
| 
 | ||||
| #define debug(format, ...)                                    \ | ||||
| 	do {                                                  \ | ||||
| 		if (debug)                                    \ | ||||
| 			__println("", format, ##__VA_ARGS__); \ | ||||
| 	} while (0) | ||||
| 
 | ||||
| #define warn(format, ...) __println("warning: ", format, ##__VA_ARGS__) | ||||
| #define error(format, ...)                                   \ | ||||
| 	do {                                                 \ | ||||
| 		__println("error: ", format, ##__VA_ARGS__); \ | ||||
| 		exit(1);                                     \ | ||||
| 	} while (0) | ||||
| 
 | ||||
| /*
 | ||||
|  * Error handling helpers | ||||
|  */ | ||||
| #define __check(expr, test)                                     \ | ||||
| 	({                                                      \ | ||||
| 		int __res = expr;                               \ | ||||
| 		if (test)                                       \ | ||||
| 			error("`%s` failed: %d", #expr, __res); \ | ||||
| 		__res;                                          \ | ||||
| 	}) | ||||
| 
 | ||||
| /* Error == non-zero values */ | ||||
| #define check(expr) __check(expr, __res) | ||||
| /* Error == negative values */ | ||||
| #define checkp(expr) __check(expr, __res < 0) | ||||
| 
 | ||||
| /*
 | ||||
|  * symbols.c | ||||
|  */ | ||||
| 
 | ||||
| struct symbol { | ||||
| 	const char *name; | ||||
| 	struct hlist_node name_hash; | ||||
| }; | ||||
| 
 | ||||
| typedef void (*symbol_callback_t)(struct symbol *, void *arg); | ||||
| 
 | ||||
| void symbol_read_exports(FILE *file); | ||||
| struct symbol *symbol_get(const char *name); | ||||
| void symbol_free(void); | ||||
| 
 | ||||
| /*
 | ||||
|  * dwarf.c | ||||
|  */ | ||||
| 
 | ||||
| struct state { | ||||
| 	struct symbol *sym; | ||||
| 	Dwarf_Die die; | ||||
| }; | ||||
| 
 | ||||
| typedef int (*die_callback_t)(struct state *state, Dwarf_Die *die); | ||||
| typedef bool (*die_match_callback_t)(Dwarf_Die *die); | ||||
| bool match_all(Dwarf_Die *die); | ||||
| 
 | ||||
| int process_die_container(struct state *state, Dwarf_Die *die, | ||||
| 			  die_callback_t func, die_match_callback_t match); | ||||
| 
 | ||||
| void process_cu(Dwarf_Die *cudie); | ||||
| 
 | ||||
| #endif /* __GENDWARFKSYMS_H */ | ||||
							
								
								
									
										98
									
								
								scripts/gendwarfksyms/symbols.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								scripts/gendwarfksyms/symbols.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,98 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| /*
 | ||||
|  * Copyright (C) 2024 Google LLC | ||||
|  */ | ||||
| 
 | ||||
| #include "gendwarfksyms.h" | ||||
| 
 | ||||
| #define SYMBOL_HASH_BITS 12 | ||||
| 
 | ||||
| /* name -> struct symbol */ | ||||
| static HASHTABLE_DEFINE(symbol_names, 1 << SYMBOL_HASH_BITS); | ||||
| 
 | ||||
| static unsigned int for_each(const char *name, symbol_callback_t func, | ||||
| 			     void *data) | ||||
| { | ||||
| 	struct hlist_node *tmp; | ||||
| 	struct symbol *match; | ||||
| 
 | ||||
| 	if (!name || !*name) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	hash_for_each_possible_safe(symbol_names, match, tmp, name_hash, | ||||
| 				    hash_str(name)) { | ||||
| 		if (strcmp(match->name, name)) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (func) | ||||
| 			func(match, data); | ||||
| 
 | ||||
| 		return 1; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static bool is_exported(const char *name) | ||||
| { | ||||
| 	return for_each(name, NULL, NULL) > 0; | ||||
| } | ||||
| 
 | ||||
| void symbol_read_exports(FILE *file) | ||||
| { | ||||
| 	struct symbol *sym; | ||||
| 	char *line = NULL; | ||||
| 	char *name = NULL; | ||||
| 	size_t size = 0; | ||||
| 	int nsym = 0; | ||||
| 
 | ||||
| 	while (getline(&line, &size, file) > 0) { | ||||
| 		if (sscanf(line, "%ms\n", &name) != 1) | ||||
| 			error("malformed input line: %s", line); | ||||
| 
 | ||||
| 		if (is_exported(name)) { | ||||
| 			/* Ignore duplicates */ | ||||
| 			free(name); | ||||
| 			continue; | ||||
| 		} | ||||
| 
 | ||||
| 		sym = xcalloc(1, sizeof(struct symbol)); | ||||
| 		sym->name = name; | ||||
| 
 | ||||
| 		hash_add(symbol_names, &sym->name_hash, hash_str(sym->name)); | ||||
| 		++nsym; | ||||
| 
 | ||||
| 		debug("%s", sym->name); | ||||
| 	} | ||||
| 
 | ||||
| 	free(line); | ||||
| 	debug("%d exported symbols", nsym); | ||||
| } | ||||
| 
 | ||||
| static void get_symbol(struct symbol *sym, void *arg) | ||||
| { | ||||
| 	struct symbol **res = arg; | ||||
| 
 | ||||
| 	*res = sym; | ||||
| } | ||||
| 
 | ||||
| struct symbol *symbol_get(const char *name) | ||||
| { | ||||
| 	struct symbol *sym = NULL; | ||||
| 
 | ||||
| 	for_each(name, get_symbol, &sym); | ||||
| 	return sym; | ||||
| } | ||||
| 
 | ||||
| void symbol_free(void) | ||||
| { | ||||
| 	struct hlist_node *tmp; | ||||
| 	struct symbol *sym; | ||||
| 
 | ||||
| 	hash_for_each_safe(symbol_names, sym, tmp, name_hash) { | ||||
| 		free((void *)sym->name); | ||||
| 		free(sym); | ||||
| 	} | ||||
| 
 | ||||
| 	hash_init(symbol_names); | ||||
| } | ||||
		Loading…
	
		Reference in a new issue
	
	 Sami Tolvanen
						Sami Tolvanen