gendwarfksyms: Add symbol versioning

Calculate symbol versions from the fully expanded type strings in
type_map, and output the versions in a genksyms-compatible format.

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:34 +00:00 committed by Masahiro Yamada
parent ab4439981f
commit 7137888801
6 changed files with 216 additions and 9 deletions

View File

@ -8,4 +8,4 @@ gendwarfksyms-objs += dwarf.o
gendwarfksyms-objs += symbols.o
gendwarfksyms-objs += types.o
HOSTLDLIBS_gendwarfksyms := -ldw -lelf
HOSTLDLIBS_gendwarfksyms := -ldw -lelf -lz

View File

@ -740,12 +740,33 @@ static int process_type(struct state *state, struct die *parent, Dwarf_Die *die)
/*
* Exported symbol processing
*/
static struct die *get_symbol_cache(struct state *state, Dwarf_Die *die)
{
struct die *cache;
cache = die_map_get(die, DIE_SYMBOL);
if (cache->state != DIE_INCOMPLETE)
return NULL; /* We already processed a symbol for this DIE */
cache->tag = dwarf_tag(die);
return cache;
}
static void process_symbol(struct state *state, Dwarf_Die *die,
die_callback_t process_func)
{
struct die *cache;
symbol_set_die(state->sym, die);
cache = get_symbol_cache(state, die);
if (!cache)
return;
debug("%s", state->sym->name);
check(process_func(state, NULL, die));
state->sym->state = SYMBOL_MAPPED;
check(process_func(state, cache, die));
cache->state = DIE_SYMBOL;
if (dump_dies)
fputs("\n", stderr);
}

View File

@ -23,6 +23,8 @@ int dump_dies;
int dump_die_map;
/* Print out type strings (i.e. type_map) */
int dump_types;
/* Print out expanded type strings used for symbol versions */
int dump_versions;
/* Write a symtypes file */
int symtypes;
static const char *symtypes_file;
@ -35,6 +37,7 @@ static void usage(void)
" --dump-dies Dump DWARF DIE contents\n"
" --dump-die-map Print debugging information about die_map changes\n"
" --dump-types Dump type strings\n"
" --dump-versions Dump expanded type strings used for symbol versions\n"
" -T, --symtypes file Write a symtypes file\n"
" -h, --help Print this message\n"
"\n",
@ -69,9 +72,10 @@ static int process_module(Dwfl_Module *mod, void **userdata, const char *name,
} while (cu);
/*
* Use die_map to expand type strings and write them to `symfile`.
* Use die_map to expand type strings, write them to `symfile`, and
* calculate symbol versions.
*/
generate_symtypes(symfile);
generate_symtypes_and_versions(symfile);
die_map_free();
return DWARF_CB_OK;
@ -93,6 +97,7 @@ int main(int argc, char **argv)
{ "dump-dies", 0, &dump_dies, 1 },
{ "dump-die-map", 0, &dump_die_map, 1 },
{ "dump-types", 0, &dump_types, 1 },
{ "dump-versions", 0, &dump_versions, 1 },
{ "symtypes", 1, NULL, 'T' },
{ "help", 0, NULL, 'h' },
{ 0, 0, NULL, 0 }
@ -166,6 +171,7 @@ int main(int argc, char **argv)
if (symfile)
check(fclose(symfile));
symbol_print_versions();
symbol_free();
return 0;

View File

@ -23,6 +23,7 @@ extern int debug;
extern int dump_dies;
extern int dump_die_map;
extern int dump_types;
extern int dump_versions;
extern int symtypes;
/*
@ -95,6 +96,7 @@ static inline unsigned int addr_hash(uintptr_t addr)
enum symbol_state {
SYMBOL_UNPROCESSED,
SYMBOL_MAPPED,
SYMBOL_PROCESSED
};
struct symbol_addr {
@ -109,6 +111,7 @@ struct symbol {
struct hlist_node name_hash;
enum symbol_state state;
uintptr_t die_addr;
unsigned long crc;
};
typedef void (*symbol_callback_t)(struct symbol *, void *arg);
@ -116,6 +119,10 @@ typedef void (*symbol_callback_t)(struct symbol *, void *arg);
void symbol_read_exports(FILE *file);
void symbol_read_symtab(int fd);
struct symbol *symbol_get(const char *name);
void symbol_set_die(struct symbol *sym, Dwarf_Die *die);
void symbol_set_crc(struct symbol *sym, unsigned long crc);
void symbol_for_each(symbol_callback_t func, void *arg);
void symbol_print_versions(void);
void symbol_free(void);
/*
@ -126,7 +133,8 @@ enum die_state {
DIE_INCOMPLETE,
DIE_UNEXPANDED,
DIE_COMPLETE,
DIE_LAST = DIE_COMPLETE
DIE_SYMBOL,
DIE_LAST = DIE_SYMBOL
};
enum die_fragment_type {
@ -156,6 +164,7 @@ static inline const char *die_state_name(enum die_state state)
CASE_CONST_TO_STR(DIE_INCOMPLETE)
CASE_CONST_TO_STR(DIE_UNEXPANDED)
CASE_CONST_TO_STR(DIE_COMPLETE)
CASE_CONST_TO_STR(DIE_SYMBOL)
}
error("unexpected die_state: %d", state);
@ -252,6 +261,6 @@ void process_cu(Dwarf_Die *cudie);
* types.c
*/
void generate_symtypes(FILE *file);
void generate_symtypes_and_versions(FILE *file);
#endif /* __GENDWARFKSYMS_H */

View File

@ -66,6 +66,36 @@ static unsigned int for_each(const char *name, symbol_callback_t func,
return 0;
}
static void set_crc(struct symbol *sym, void *data)
{
unsigned long *crc = data;
if (sym->state == SYMBOL_PROCESSED && sym->crc != *crc)
warn("overriding version for symbol %s (crc %lx vs. %lx)",
sym->name, sym->crc, *crc);
sym->state = SYMBOL_PROCESSED;
sym->crc = *crc;
}
void symbol_set_crc(struct symbol *sym, unsigned long crc)
{
if (for_each(sym->name, set_crc, &crc) == 0)
error("no matching symbols: '%s'", sym->name);
}
static void set_die(struct symbol *sym, void *data)
{
sym->die_addr = (uintptr_t)((Dwarf_Die *)data)->addr;
sym->state = SYMBOL_MAPPED;
}
void symbol_set_die(struct symbol *sym, Dwarf_Die *die)
{
if (for_each(sym->name, set_die, die) == 0)
error("no matching symbols: '%s'", sym->name);
}
static bool is_exported(const char *name)
{
return for_each(name, NULL, NULL) > 0;
@ -120,6 +150,16 @@ struct symbol *symbol_get(const char *name)
return sym;
}
void symbol_for_each(symbol_callback_t func, void *arg)
{
struct hlist_node *tmp;
struct symbol *sym;
hash_for_each_safe(symbol_names, sym, tmp, name_hash) {
func(sym, arg);
}
}
typedef void (*elf_symbol_callback_t)(const char *name, GElf_Sym *sym,
Elf32_Word xndx, void *arg);
@ -246,6 +286,19 @@ void symbol_read_symtab(int fd)
elf_for_each_global(fd, elf_set_symbol_addr, NULL);
}
void symbol_print_versions(void)
{
struct hlist_node *tmp;
struct symbol *sym;
hash_for_each_safe(symbol_names, sym, tmp, name_hash) {
if (sym->state != SYMBOL_PROCESSED)
warn("no information for symbol %s", sym->name);
printf("#SYMVER %s 0x%08lx\n", sym->name, sym->crc);
}
}
void symbol_free(void)
{
struct hlist_node *tmp;

View File

@ -6,6 +6,7 @@
#define _GNU_SOURCE
#include <inttypes.h>
#include <stdio.h>
#include <zlib.h>
#include "gendwarfksyms.h"
@ -178,6 +179,33 @@ static void type_map_free(void)
hash_init(type_map);
}
/*
* CRC for a type, with an optional fully expanded type string for
* debugging.
*/
struct version {
struct type_expansion type;
unsigned long crc;
};
static void version_init(struct version *version)
{
version->crc = crc32(0, NULL, 0);
type_expansion_init(&version->type);
}
static void version_free(struct version *version)
{
type_expansion_free(&version->type);
}
static void version_add(struct version *version, const char *s)
{
version->crc = crc32(version->crc, (void *)s, strlen(s));
if (dump_versions)
type_expansion_append(&version->type, s, NULL);
}
/*
* Type reference format: <prefix>#<name>, where prefix:
* s -> structure
@ -187,6 +215,12 @@ static void type_map_free(void)
*
* Names with spaces are additionally wrapped in single quotes.
*/
static inline bool is_type_prefix(const char *s)
{
return (s[0] == 's' || s[0] == 'u' || s[0] == 'e' || s[0] == 't') &&
s[1] == '#';
}
static char get_type_prefix(int tag)
{
switch (tag) {
@ -214,6 +248,8 @@ static char *get_type_name(struct die *cache)
warn("found incomplete cache entry: %p", cache);
return NULL;
}
if (cache->state == DIE_SYMBOL)
return NULL;
if (!cache->fqn || !*cache->fqn)
return NULL;
@ -231,6 +267,39 @@ static char *get_type_name(struct die *cache)
return name;
}
static void __calculate_version(struct version *version, struct list_head *list)
{
struct type_list_entry *entry;
struct type_expansion *e;
/* Calculate a CRC over an expanded type string */
list_for_each_entry(entry, list, list) {
if (is_type_prefix(entry->str)) {
check(type_map_get(entry->str, &e));
/*
* It's sufficient to expand each type reference just
* once to detect changes.
*/
if (cache_was_expanded(&expansion_cache, e)) {
version_add(version, entry->str);
} else {
cache_mark_expanded(&expansion_cache, e);
__calculate_version(version, &e->expanded);
}
} else {
version_add(version, entry->str);
}
}
}
static void calculate_version(struct version *version, struct list_head *list)
{
version_init(version);
__calculate_version(version, list);
cache_free(&expansion_cache);
}
static void __type_expand(struct die *cache, struct type_expansion *type,
bool recursive);
@ -337,7 +406,49 @@ static void expand_type(struct die *cache, void *arg)
free(name);
}
void generate_symtypes(FILE *file)
static void expand_symbol(struct symbol *sym, void *arg)
{
struct type_expansion type;
struct version version;
struct die *cache;
/*
* No need to expand again unless we want a symtypes file entry
* for the symbol. Note that this means `sym` has the same address
* as another symbol that was already processed.
*/
if (!symtypes && sym->state == SYMBOL_PROCESSED)
return;
if (__die_map_get(sym->die_addr, DIE_SYMBOL, &cache))
return; /* We'll warn about missing CRCs later. */
type_expand(cache, &type, false);
/* If the symbol already has a version, don't calculate it again. */
if (sym->state != SYMBOL_PROCESSED) {
calculate_version(&version, &type.expanded);
symbol_set_crc(sym, version.crc);
debug("%s = %lx", sym->name, version.crc);
if (dump_versions) {
checkp(fputs(sym->name, stderr));
checkp(fputs(" ", stderr));
type_list_write(&version.type.expanded, stderr);
checkp(fputs("\n", stderr));
}
version_free(&version);
}
/* These aren't needed in type_map unless we want a symtypes file. */
if (symtypes)
type_map_add(sym->name, &type);
type_expansion_free(&type);
}
void generate_symtypes_and_versions(FILE *file)
{
cache_init(&expansion_cache);
@ -355,7 +466,14 @@ void generate_symtypes(FILE *file)
die_map_for_each(expand_type, NULL);
/*
* 2. If a symtypes file is requested, write type_map contents to
* 2. For each exported symbol, expand the die_map type, and use
* type_map expansions to calculate a symbol version from the
* fully expanded type string.
*/
symbol_for_each(expand_symbol, NULL);
/*
* 3. If a symtypes file is requested, write type_map contents to
* the file.
*/
type_map_write(file);