linux-yocto/scripts/gendwarfksyms/dwarf.c
Sami Tolvanen ab4439981f gendwarfksyms: Add symtypes output
Add support for producing genksyms-style symtypes files. Process
die_map to find the longest expansions for each type, and use symtypes
references in type definitions. The basic file format is similar to
genksyms, with two notable exceptions:

  1. Type names with spaces (common with Rust) in references are
     wrapped in single quotes. E.g.:

     s#'core::result::Result<u8, core::num::error::ParseIntError>'

  2. The actual type definition is the simple parsed DWARF format we
     output with --dump-dies, not the preprocessed C-style format
     genksyms produces.

Signed-off-by: Sami Tolvanen <samitolvanen@google.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
2025-01-11 01:25:25 +09:00

821 lines
20 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Google LLC
*/
#include <inttypes.h>
#include <stdarg.h>
#include "gendwarfksyms.h"
static bool do_linebreak;
static int indentation_level;
/* Line breaks and indentation for pretty-printing */
static void process_linebreak(struct die *cache, int n)
{
indentation_level += n;
do_linebreak = true;
die_map_add_linebreak(cache, n);
}
#define DEFINE_GET_ATTR(attr, type) \
static bool get_##attr##_attr(Dwarf_Die *die, unsigned int id, \
type *value) \
{ \
Dwarf_Attribute da; \
return dwarf_attr(die, id, &da) && \
!dwarf_form##attr(&da, value); \
}
DEFINE_GET_ATTR(flag, bool)
DEFINE_GET_ATTR(udata, Dwarf_Word)
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;
}
/* DW_AT_decl_file -> struct srcfile */
static struct cache srcfile_cache;
static bool is_definition_private(Dwarf_Die *die)
{
Dwarf_Word filenum;
Dwarf_Files *files;
Dwarf_Die cudie;
const char *s;
int res;
/*
* Definitions in .c files cannot change the public ABI,
* so consider them private.
*/
if (!get_udata_attr(die, DW_AT_decl_file, &filenum))
return false;
res = cache_get(&srcfile_cache, filenum);
if (res >= 0)
return !!res;
if (!dwarf_cu_die(die->cu, &cudie, NULL, NULL, NULL, NULL, NULL, NULL))
error("dwarf_cu_die failed: '%s'", dwarf_errmsg(-1));
if (dwarf_getsrcfiles(&cudie, &files, NULL))
error("dwarf_getsrcfiles failed: '%s'", dwarf_errmsg(-1));
s = dwarf_filesrc(files, filenum, NULL, NULL);
if (!s)
error("dwarf_filesrc failed: '%s'", dwarf_errmsg(-1));
s = strrchr(s, '.');
res = s && !strcmp(s, ".c");
cache_set(&srcfile_cache, filenum, res);
return !!res;
}
static bool is_kabi_definition(Dwarf_Die *die)
{
bool value;
if (get_flag_attr(die, DW_AT_declaration, &value) && value)
return false;
return !is_definition_private(die);
}
/*
* Type string processing
*/
static void process(struct die *cache, const char *s)
{
s = s ?: "<null>";
if (dump_dies && do_linebreak) {
fputs("\n", stderr);
for (int i = 0; i < indentation_level; i++)
fputs(" ", stderr);
do_linebreak = false;
}
if (dump_dies)
fputs(s, stderr);
if (cache)
die_debug_r("cache %p string '%s'", cache, s);
die_map_add_string(cache, s);
}
#define MAX_FMT_BUFFER_SIZE 128
static void process_fmt(struct die *cache, const char *fmt, ...)
{
char buf[MAX_FMT_BUFFER_SIZE];
va_list args;
va_start(args, fmt);
if (checkp(vsnprintf(buf, sizeof(buf), fmt, args)) >= sizeof(buf))
error("vsnprintf overflow: increase MAX_FMT_BUFFER_SIZE");
process(cache, buf);
va_end(args);
}
#define MAX_FQN_SIZE 64
/* Get a fully qualified name from DWARF scopes */
static char *get_fqn(Dwarf_Die *die)
{
const char *list[MAX_FQN_SIZE];
Dwarf_Die *scopes = NULL;
bool has_name = false;
char *fqn = NULL;
char *p;
int count = 0;
int len = 0;
int res;
int i;
res = checkp(dwarf_getscopes_die(die, &scopes));
if (!res) {
list[count] = get_name_attr(die);
if (!list[count])
return NULL;
len += strlen(list[count]);
count++;
goto done;
}
for (i = res - 1; i >= 0 && count < MAX_FQN_SIZE; i--) {
if (dwarf_tag(&scopes[i]) == DW_TAG_compile_unit)
continue;
list[count] = get_name_attr(&scopes[i]);
if (list[count]) {
has_name = true;
} else {
list[count] = "<anonymous>";
has_name = false;
}
len += strlen(list[count]);
count++;
if (i > 0) {
list[count++] = "::";
len += 2;
}
}
free(scopes);
if (count == MAX_FQN_SIZE)
warn("increase MAX_FQN_SIZE: reached the maximum");
/* Consider the DIE unnamed if the last scope doesn't have a name */
if (!has_name)
return NULL;
done:
fqn = xmalloc(len + 1);
*fqn = '\0';
p = fqn;
for (i = 0; i < count; i++)
p = stpcpy(p, list[i]);
return fqn;
}
static void update_fqn(struct die *cache, Dwarf_Die *die)
{
if (!cache->fqn)
cache->fqn = get_fqn(die) ?: "";
}
static void process_fqn(struct die *cache, Dwarf_Die *die)
{
update_fqn(cache, die);
if (*cache->fqn)
process(cache, " ");
process(cache, cache->fqn);
}
#define DEFINE_PROCESS_UDATA_ATTRIBUTE(attribute) \
static void process_##attribute##_attr(struct die *cache, \
Dwarf_Die *die) \
{ \
Dwarf_Word value; \
if (get_udata_attr(die, DW_AT_##attribute, &value)) \
process_fmt(cache, " " #attribute "(%" PRIu64 ")", \
value); \
}
DEFINE_PROCESS_UDATA_ATTRIBUTE(accessibility)
DEFINE_PROCESS_UDATA_ATTRIBUTE(alignment)
DEFINE_PROCESS_UDATA_ATTRIBUTE(bit_size)
DEFINE_PROCESS_UDATA_ATTRIBUTE(byte_size)
DEFINE_PROCESS_UDATA_ATTRIBUTE(encoding)
DEFINE_PROCESS_UDATA_ATTRIBUTE(data_bit_offset)
DEFINE_PROCESS_UDATA_ATTRIBUTE(data_member_location)
DEFINE_PROCESS_UDATA_ATTRIBUTE(discr_value)
/* Match functions -- die_match_callback_t */
#define DEFINE_MATCH(type) \
static bool match_##type##_type(Dwarf_Die *die) \
{ \
return dwarf_tag(die) == DW_TAG_##type##_type; \
}
DEFINE_MATCH(enumerator)
DEFINE_MATCH(formal_parameter)
DEFINE_MATCH(member)
DEFINE_MATCH(subrange)
bool match_all(Dwarf_Die *die)
{
return true;
}
int process_die_container(struct state *state, struct die *cache,
Dwarf_Die *die, die_callback_t func,
die_match_callback_t match)
{
Dwarf_Die current;
int res;
/* Track the first item in lists. */
if (state)
state->first_list_item = true;
res = checkp(dwarf_child(die, &current));
while (!res) {
if (match(&current)) {
/* <0 = error, 0 = continue, >0 = stop */
res = checkp(func(state, cache, &current));
if (res)
goto out;
}
res = checkp(dwarf_siblingof(&current, &current));
}
res = 0;
out:
if (state)
state->first_list_item = false;
return res;
}
static int process_type(struct state *state, struct die *parent,
Dwarf_Die *die);
static void process_type_attr(struct state *state, struct die *cache,
Dwarf_Die *die)
{
Dwarf_Die type;
if (get_ref_die_attr(die, DW_AT_type, &type)) {
check(process_type(state, cache, &type));
return;
}
/* Compilers can omit DW_AT_type -- print out 'void' to clarify */
process(cache, "base_type void");
}
static void process_list_comma(struct state *state, struct die *cache)
{
if (state->first_list_item) {
state->first_list_item = false;
} else {
process(cache, " ,");
process_linebreak(cache, 0);
}
}
/* Comma-separated with DW_AT_type */
static void __process_list_type(struct state *state, struct die *cache,
Dwarf_Die *die, const char *type)
{
const char *name = get_name_attr(die);
process_list_comma(state, cache);
process(cache, type);
process_type_attr(state, cache, die);
if (name) {
process(cache, " ");
process(cache, name);
}
process_accessibility_attr(cache, die);
process_bit_size_attr(cache, die);
process_data_bit_offset_attr(cache, die);
process_data_member_location_attr(cache, die);
}
#define DEFINE_PROCESS_LIST_TYPE(type) \
static void process_##type##_type(struct state *state, \
struct die *cache, Dwarf_Die *die) \
{ \
__process_list_type(state, cache, die, #type " "); \
}
DEFINE_PROCESS_LIST_TYPE(formal_parameter)
DEFINE_PROCESS_LIST_TYPE(member)
/* Container types with DW_AT_type */
static void __process_type(struct state *state, struct die *cache,
Dwarf_Die *die, const char *type)
{
process(cache, type);
process_fqn(cache, die);
process(cache, " {");
process_linebreak(cache, 1);
process_type_attr(state, cache, die);
process_linebreak(cache, -1);
process(cache, "}");
process_byte_size_attr(cache, die);
process_alignment_attr(cache, die);
}
#define DEFINE_PROCESS_TYPE(type) \
static void process_##type##_type(struct state *state, \
struct die *cache, Dwarf_Die *die) \
{ \
__process_type(state, cache, die, #type "_type"); \
}
DEFINE_PROCESS_TYPE(atomic)
DEFINE_PROCESS_TYPE(const)
DEFINE_PROCESS_TYPE(immutable)
DEFINE_PROCESS_TYPE(packed)
DEFINE_PROCESS_TYPE(pointer)
DEFINE_PROCESS_TYPE(reference)
DEFINE_PROCESS_TYPE(restrict)
DEFINE_PROCESS_TYPE(rvalue_reference)
DEFINE_PROCESS_TYPE(shared)
DEFINE_PROCESS_TYPE(template_type_parameter)
DEFINE_PROCESS_TYPE(volatile)
DEFINE_PROCESS_TYPE(typedef)
static void process_subrange_type(struct state *state, struct die *cache,
Dwarf_Die *die)
{
Dwarf_Word count = 0;
if (get_udata_attr(die, DW_AT_count, &count))
process_fmt(cache, "[%" PRIu64 "]", count);
else if (get_udata_attr(die, DW_AT_upper_bound, &count))
process_fmt(cache, "[%" PRIu64 "]", count + 1);
else
process(cache, "[]");
}
static void process_array_type(struct state *state, struct die *cache,
Dwarf_Die *die)
{
process(cache, "array_type");
/* Array size */
check(process_die_container(state, cache, die, process_type,
match_subrange_type));
process(cache, " {");
process_linebreak(cache, 1);
process_type_attr(state, cache, die);
process_linebreak(cache, -1);
process(cache, "}");
}
static void __process_subroutine_type(struct state *state, struct die *cache,
Dwarf_Die *die, const char *type)
{
process(cache, type);
process(cache, " (");
process_linebreak(cache, 1);
/* Parameters */
check(process_die_container(state, cache, die, process_type,
match_formal_parameter_type));
process_linebreak(cache, -1);
process(cache, ")");
process_linebreak(cache, 0);
/* Return type */
process(cache, "-> ");
process_type_attr(state, cache, die);
}
static void process_subroutine_type(struct state *state, struct die *cache,
Dwarf_Die *die)
{
__process_subroutine_type(state, cache, die, "subroutine_type");
}
static void process_variant_type(struct state *state, struct die *cache,
Dwarf_Die *die)
{
process_list_comma(state, cache);
process(cache, "variant {");
process_linebreak(cache, 1);
check(process_die_container(state, cache, die, process_type,
match_member_type));
process_linebreak(cache, -1);
process(cache, "}");
process_discr_value_attr(cache, die);
}
static void process_variant_part_type(struct state *state, struct die *cache,
Dwarf_Die *die)
{
process_list_comma(state, cache);
process(cache, "variant_part {");
process_linebreak(cache, 1);
check(process_die_container(state, cache, die, process_type,
match_all));
process_linebreak(cache, -1);
process(cache, "}");
}
static int ___process_structure_type(struct state *state, struct die *cache,
Dwarf_Die *die)
{
switch (dwarf_tag(die)) {
case DW_TAG_member:
case DW_TAG_variant_part:
return check(process_type(state, cache, die));
case DW_TAG_class_type:
case DW_TAG_enumeration_type:
case DW_TAG_structure_type:
case DW_TAG_template_type_parameter:
case DW_TAG_union_type:
case DW_TAG_subprogram:
/* Skip non-member types, including member functions */
return 0;
default:
error("unexpected structure_type child: %x", dwarf_tag(die));
}
}
static void __process_structure_type(struct state *state, struct die *cache,
Dwarf_Die *die, const char *type,
die_callback_t process_func,
die_match_callback_t match_func)
{
bool expand;
process(cache, type);
process_fqn(cache, die);
process(cache, " {");
process_linebreak(cache, 1);
expand = state->expand.expand && is_kabi_definition(die);
if (expand) {
check(process_die_container(state, cache, die, process_func,
match_func));
}
process_linebreak(cache, -1);
process(cache, "}");
if (expand) {
process_byte_size_attr(cache, die);
process_alignment_attr(cache, die);
}
}
#define DEFINE_PROCESS_STRUCTURE_TYPE(structure) \
static void process_##structure##_type( \
struct state *state, struct die *cache, Dwarf_Die *die) \
{ \
__process_structure_type(state, cache, die, \
#structure "_type", \
___process_structure_type, \
match_all); \
}
DEFINE_PROCESS_STRUCTURE_TYPE(class)
DEFINE_PROCESS_STRUCTURE_TYPE(structure)
DEFINE_PROCESS_STRUCTURE_TYPE(union)
static void process_enumerator_type(struct state *state, struct die *cache,
Dwarf_Die *die)
{
Dwarf_Word value;
process_list_comma(state, cache);
process(cache, "enumerator");
process_fqn(cache, die);
if (get_udata_attr(die, DW_AT_const_value, &value)) {
process(cache, " = ");
process_fmt(cache, "%" PRIu64, value);
}
}
static void process_enumeration_type(struct state *state, struct die *cache,
Dwarf_Die *die)
{
__process_structure_type(state, cache, die, "enumeration_type",
process_type, match_enumerator_type);
}
static void process_base_type(struct state *state, struct die *cache,
Dwarf_Die *die)
{
process(cache, "base_type");
process_fqn(cache, die);
process_byte_size_attr(cache, die);
process_encoding_attr(cache, die);
process_alignment_attr(cache, die);
}
static void process_unspecified_type(struct state *state, struct die *cache,
Dwarf_Die *die)
{
/*
* These can be emitted for stand-alone assembly code, which means we
* might run into them in vmlinux.o.
*/
process(cache, "unspecified_type");
}
static void process_cached(struct state *state, struct die *cache,
Dwarf_Die *die)
{
struct die_fragment *df;
Dwarf_Die child;
list_for_each_entry(df, &cache->fragments, list) {
switch (df->type) {
case FRAGMENT_STRING:
die_debug_b("cache %p STRING '%s'", cache,
df->data.str);
process(NULL, df->data.str);
break;
case FRAGMENT_LINEBREAK:
process_linebreak(NULL, df->data.linebreak);
break;
case FRAGMENT_DIE:
if (!dwarf_die_addr_die(dwarf_cu_getdwarf(die->cu),
(void *)df->data.addr, &child))
error("dwarf_die_addr_die failed");
die_debug_b("cache %p DIE addr %" PRIxPTR " tag %x",
cache, df->data.addr, dwarf_tag(&child));
check(process_type(state, NULL, &child));
break;
default:
error("empty die_fragment");
}
}
}
static void state_init(struct state *state)
{
state->expand.expand = true;
cache_init(&state->expansion_cache);
}
static void expansion_state_restore(struct expansion_state *state,
struct expansion_state *saved)
{
state->expand = saved->expand;
}
static void expansion_state_save(struct expansion_state *state,
struct expansion_state *saved)
{
expansion_state_restore(saved, state);
}
static bool is_expanded_type(int tag)
{
return tag == DW_TAG_class_type || tag == DW_TAG_structure_type ||
tag == DW_TAG_union_type || tag == DW_TAG_enumeration_type;
}
#define PROCESS_TYPE(type) \
case DW_TAG_##type##_type: \
process_##type##_type(state, cache, die); \
break;
static int process_type(struct state *state, struct die *parent, Dwarf_Die *die)
{
enum die_state want_state = DIE_COMPLETE;
struct die *cache;
struct expansion_state saved;
int tag = dwarf_tag(die);
expansion_state_save(&state->expand, &saved);
/*
* Structures and enumeration types are expanded only once per
* exported symbol. This is sufficient for detecting ABI changes
* within the structure.
*/
if (is_expanded_type(tag)) {
if (cache_was_expanded(&state->expansion_cache, die->addr))
state->expand.expand = false;
if (state->expand.expand)
cache_mark_expanded(&state->expansion_cache, die->addr);
else
want_state = DIE_UNEXPANDED;
}
/*
* If we have want_state already cached, use it instead of walking
* through DWARF.
*/
cache = die_map_get(die, want_state);
if (cache->state == want_state) {
die_debug_g("cached addr %p tag %x -- %s", die->addr, tag,
die_state_name(cache->state));
process_cached(state, cache, die);
die_map_add_die(parent, cache);
expansion_state_restore(&state->expand, &saved);
return 0;
}
die_debug_g("addr %p tag %x -- %s -> %s", die->addr, tag,
die_state_name(cache->state), die_state_name(want_state));
switch (tag) {
/* Type modifiers */
PROCESS_TYPE(atomic)
PROCESS_TYPE(const)
PROCESS_TYPE(immutable)
PROCESS_TYPE(packed)
PROCESS_TYPE(pointer)
PROCESS_TYPE(reference)
PROCESS_TYPE(restrict)
PROCESS_TYPE(rvalue_reference)
PROCESS_TYPE(shared)
PROCESS_TYPE(volatile)
/* Container types */
PROCESS_TYPE(class)
PROCESS_TYPE(structure)
PROCESS_TYPE(union)
PROCESS_TYPE(enumeration)
/* Subtypes */
PROCESS_TYPE(enumerator)
PROCESS_TYPE(formal_parameter)
PROCESS_TYPE(member)
PROCESS_TYPE(subrange)
PROCESS_TYPE(template_type_parameter)
PROCESS_TYPE(variant)
PROCESS_TYPE(variant_part)
/* Other types */
PROCESS_TYPE(array)
PROCESS_TYPE(base)
PROCESS_TYPE(subroutine)
PROCESS_TYPE(typedef)
PROCESS_TYPE(unspecified)
default:
error("unexpected type: %x", tag);
}
die_debug_r("parent %p cache %p die addr %p tag %x", parent, cache,
die->addr, tag);
/* Update cache state and append to the parent (if any) */
cache->tag = tag;
cache->state = want_state;
die_map_add_die(parent, cache);
expansion_state_restore(&state->expand, &saved);
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, NULL, die));
state->sym->state = SYMBOL_MAPPED;
if (dump_dies)
fputs("\n", stderr);
}
static int __process_subprogram(struct state *state, struct die *cache,
Dwarf_Die *die)
{
__process_subroutine_type(state, cache, die, "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, struct die *cache,
Dwarf_Die *die)
{
process(cache, "variable ");
process_type_attr(state, cache, die);
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, struct die *cache,
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, cache, 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;
state_init(&state);
if (tag == DW_TAG_subprogram)
process_subprogram(&state, &state.die);
else
process_variable(&state, &state.die);
cache_free(&state.expansion_cache);
return 0;
}
default:
return 0;
}
}
void process_cu(Dwarf_Die *cudie)
{
check(process_die_container(NULL, NULL, cudie, process_exported_symbols,
match_all));
cache_free(&srcfile_cache);
}