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:
Sami Tolvanen 2025-01-03 20:45:23 +00:00 committed by Masahiro Yamada
parent a56fece7f3
commit f28568841a
9 changed files with 513 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0
/gendwarfksyms

View 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

View 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, &current));
while (!res) {
if (match(&current)) {
/* <0 = error, 0 = continue, >0 = stop */
res = checkp(func(state, &current));
if (res)
return res;
}
res = checkp(dwarf_siblingof(&current, &current));
}
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));
}

View 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;
}

View 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 */

View 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);
}