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. This tool is needed mainly for Rust kernel module compatibility. Signed-off-by: Sami Tolvanen <samitolvanen@xxxxxxxxxx> --- tools/Makefile | 11 +-- tools/gendwarfksyms/Build | 2 + tools/gendwarfksyms/Makefile | 49 +++++++++++ tools/gendwarfksyms/gendwarfksyms.c | 128 ++++++++++++++++++++++++++++ tools/gendwarfksyms/gendwarfksyms.h | 71 +++++++++++++++ tools/gendwarfksyms/types.c | 87 +++++++++++++++++++ 6 files changed, 343 insertions(+), 5 deletions(-) create mode 100644 tools/gendwarfksyms/Build create mode 100644 tools/gendwarfksyms/Makefile create mode 100644 tools/gendwarfksyms/gendwarfksyms.c create mode 100644 tools/gendwarfksyms/gendwarfksyms.h create mode 100644 tools/gendwarfksyms/types.c diff --git a/tools/Makefile b/tools/Makefile index 276f5d0d53a4..60806ff753b7 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -17,6 +17,7 @@ help: @echo ' firewire - the userspace part of nosy, an IEEE-1394 traffic sniffer' @echo ' firmware - Firmware tools' @echo ' freefall - laptop accelerometer program for disk protection' + @echo ' gendwarfksyms - generates symbol versions from DWARF' @echo ' gpio - GPIO tools' @echo ' hv - tools used when in Hyper-V clients' @echo ' iio - IIO tools' @@ -68,7 +69,7 @@ acpi: FORCE cpupower: FORCE $(call descend,power/$@) -counter firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtool leds wmi pci firmware debugging tracing: FORCE +counter firewire hv guest bootconfig spi usb virtio mm bpf iio gendwarfksyms gpio objtool leds wmi pci firmware debugging tracing: FORCE $(call descend,$@) bpf/%: FORCE @@ -154,8 +155,8 @@ freefall_install: kvm_stat_install: $(call descend,kvm/$(@:_install=),install) -install: acpi_install counter_install cpupower_install gpio_install \ - hv_install firewire_install iio_install \ +install: acpi_install counter_install cpupower_install gendwarfksyms_install \ + gpio_install hv_install firewire_install iio_install \ perf_install selftests_install turbostat_install usb_install \ virtio_install mm_install bpf_install x86_energy_perf_policy_install \ tmon_install freefall_install objtool_install kvm_stat_install \ @@ -168,7 +169,7 @@ acpi_clean: cpupower_clean: $(call descend,power/cpupower,clean) -counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean debugging_clean tracing_clean: +counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gendwarfksyms_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean debugging_clean tracing_clean: $(call descend,$(@:_clean=),clean) libapi_clean: @@ -211,7 +212,7 @@ build_clean: clean: acpi_clean counter_clean cpupower_clean hv_clean firewire_clean \ perf_clean selftests_clean turbostat_clean bootconfig_clean spi_clean usb_clean virtio_clean \ mm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ - freefall_clean build_clean libbpf_clean libsubcmd_clean \ + freefall_clean build_clean libbpf_clean libsubcmd_clean gendwarfksyms_clean \ gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \ intel-speed-select_clean tracing_clean thermal_clean thermometer_clean thermal-engine_clean diff --git a/tools/gendwarfksyms/Build b/tools/gendwarfksyms/Build new file mode 100644 index 000000000000..805591b6df80 --- /dev/null +++ b/tools/gendwarfksyms/Build @@ -0,0 +1,2 @@ +gendwarfksyms-y += gendwarfksyms.o +gendwarfksyms-y += types.o diff --git a/tools/gendwarfksyms/Makefile b/tools/gendwarfksyms/Makefile new file mode 100644 index 000000000000..1138ebd8bd0f --- /dev/null +++ b/tools/gendwarfksyms/Makefile @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../scripts/Makefile.include +include ../scripts/Makefile.arch + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +endif + +GENDWARFKSYMS := $(OUTPUT)gendwarfksyms +GENDWARFKSYMS_IN := $(GENDWARFKSYMS)-in.o + +LIBDW_FLAGS := $(shell $(HOSTPKG_CONFIG) libdw --cflags 2>/dev/null) +LIBDW_LIBS := $(shell $(HOSTPKG_CONFIG) libdw --libs 2>/dev/null || echo -ldw -lelf) + +all: $(GENDWARFKSYMS) + +INCLUDES := -I$(srctree)/tools/include + +WARNINGS := -Wall -Wno-unused-value +GENDWARFKSYMS_CFLAGS := -Werror $(WARNINGS) $(KBUILD_HOSTCFLAGS) -g $(INCLUDES) $(LIBDW_FLAGS) +GENDWARFKSYMS_LDFLAGS := $(LIBDW_LIBS) $(KBUILD_HOSTLDFLAGS) + +# Always want host compilation. +HOST_OVERRIDES := CC="$(HOSTCC)" LD="$(HOSTLD)" AR="$(HOSTAR)" + +ifeq ($(V),1) + Q = +else + Q = @ +endif + +export srctree OUTPUT +include $(srctree)/tools/build/Makefile.include + +$(GENDWARFKSYMS_IN): FORCE + $(Q)$(MAKE) $(build)=gendwarfksyms $(HOST_OVERRIDES) CFLAGS="$(GENDWARFKSYMS_CFLAGS)" \ + LDFLAGS="$(GENDWARFKSYMS_LDFLAGS)" + + +$(GENDWARFKSYMS): $(GENDWARFKSYMS_IN) + $(QUIET_LINK)$(HOSTCC) $(GENDWARFKSYMS_IN) $(GENDWARFKSYMS_LDFLAGS) -o $@ + +clean: + $(call QUIET_CLEAN, gendwarfksyms) $(RM) $(GENDWARFKSYMS) + +FORCE: + +.PHONY: clean FORCE diff --git a/tools/gendwarfksyms/gendwarfksyms.c b/tools/gendwarfksyms/gendwarfksyms.c new file mode 100644 index 000000000000..4a2dea307849 --- /dev/null +++ b/tools/gendwarfksyms/gendwarfksyms.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Google LLC + */ + +#include <fcntl.h> +#include <errno.h> +#include <stdarg.h> +#include <string.h> +#include "gendwarfksyms.h" + +/* + * Options + */ + +/* Print type descriptions and debugging output to stderr */ +bool debug; + +static const struct { + const char *arg; + bool *flag; +} options[] = { + { "--debug", &debug }, +}; + +static int usage(void) +{ + error("usage: gendwarfksyms [options] elf-object-file < symbol-list"); + return -1; +} + +static int parse_options(int argc, const char **argv, const char **filename) +{ + *filename = NULL; + + for (int i = 1; i < argc; i++) { + bool found = false; + + for (int j = 0; j < ARRAY_SIZE(options); j++) { + if (!strcmp(argv[i], options[j].arg)) { + *options[j].flag = true; + found = true; + break; + } + } + + if (!found) { + if (!*filename) + *filename = argv[i]; + else + return -1; + } + } + + return *filename ? 0 : -1; +} + +static int process_modules(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?"); + return -1; + } else if (res == 1) { + return DWARF_CB_OK; /* No more units */ + } + + check(process_module(mod, dbg, &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, const char **argv) +{ + const char *filename = NULL; + Dwfl *dwfl; + int fd; + + if (parse_options(argc, argv, &filename) < 0) + return usage(); + + fd = open(filename, O_RDONLY); + if (fd == -1) { + error("open failed for '%s': %s", filename, strerror(errno)); + return -1; + } + + dwfl = dwfl_begin(&callbacks); + if (!dwfl) { + error("dwfl_begin failed for '%s': %s", filename, + dwarf_errmsg(-1)); + return -1; + } + + if (!dwfl_report_offline(dwfl, filename, filename, fd)) { + error("dwfl_report_offline failed for '%s': %s", filename, + dwarf_errmsg(-1)); + return -1; + } + + dwfl_report_end(dwfl, NULL, NULL); + + if (dwfl_getmodules(dwfl, &process_modules, NULL, 0)) { + error("dwfl_getmodules failed for '%s'", filename); + return -1; + } + + dwfl_end(dwfl); + + return 0; +} diff --git a/tools/gendwarfksyms/gendwarfksyms.h b/tools/gendwarfksyms/gendwarfksyms.h new file mode 100644 index 000000000000..44e94f1d9671 --- /dev/null +++ b/tools/gendwarfksyms/gendwarfksyms.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024 Google LLC + */ + +#include <dwarf.h> +#include <elfutils/libdw.h> +#include <elfutils/libdwfl.h> +#include <linux/hashtable.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> + +#ifndef __GENDWARFKSYMS_H +#define __GENDWARFKSYMS_H + +/* + * Options -- in gendwarfksyms.c + */ +extern bool debug; + +/* + * 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, ...) __println("error: ", format, ##__VA_ARGS__) + +/* + * Error handling helpers + */ +#define check(expr) \ + ({ \ + int __res = expr; \ + if (__res < 0) { \ + error("`%s` failed: %d", #expr, __res); \ + return __res; \ + } \ + __res; \ + }) + +/* + * types.c + */ + +struct state { + Dwfl_Module *mod; + Dwarf *dbg; +}; + +typedef int (*die_callback_t)(struct state *state, Dwarf_Die *die); +typedef bool (*die_match_callback_t)(Dwarf_Die *die); +extern bool match_all(Dwarf_Die *die); + +extern int process_die_container(struct state *state, Dwarf_Die *die, + die_callback_t func, + die_match_callback_t match); + +extern int process_module(Dwfl_Module *mod, Dwarf *dbg, Dwarf_Die *cudie); + +#endif /* __GENDWARFKSYMS_H */ diff --git a/tools/gendwarfksyms/types.c b/tools/gendwarfksyms/types.c new file mode 100644 index 000000000000..2a8e45ae911c --- /dev/null +++ b/tools/gendwarfksyms/types.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Google LLC + */ + +#include "gendwarfksyms.h" + +/* + * Type string processing + */ +static int process(struct state *state, const char *s) +{ + s = s ?: "<null>"; + + if (debug) + fputs(s, stderr); + + return 0; +} + +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 = check(dwarf_child(die, ¤t)); + while (!res) { + if (match(¤t)) + check(func(state, ¤t)); + res = check(dwarf_siblingof(¤t, ¤t)); + } + + return 0; +} + +/* + * Symbol processing + */ +static int process_subprogram(struct state *state, Dwarf_Die *die) +{ + return check(process(state, "subprogram;\n")); +} + +static int process_variable(struct state *state, Dwarf_Die *die) +{ + return check(process(state, "variable;\n")); +} + +static int process_exported_symbols(struct state *state, 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( + state, die, process_exported_symbols, match_all)); + + /* Possible exported symbols */ + case DW_TAG_subprogram: + case DW_TAG_variable: + if (tag == DW_TAG_subprogram) + check(process_subprogram(state, die)); + else + check(process_variable(state, die)); + + return 0; + default: + return 0; + } +} + +int process_module(Dwfl_Module *mod, Dwarf *dbg, Dwarf_Die *cudie) +{ + struct state state = { .mod = mod, .dbg = dbg }; + + return check(process_die_container( + &state, cudie, process_exported_symbols, match_all)); +} -- 2.45.2.627.g7a2c4fd464-goog