| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2024 Google LLC |
| */ |
| |
| #include <assert.h> |
| #include <inttypes.h> |
| #include <stdarg.h> |
| #include "gendwarfksyms.h" |
| |
| /* See get_union_kabi_status */ |
| #define KABI_PREFIX "__kabi_" |
| #define KABI_PREFIX_LEN (sizeof(KABI_PREFIX) - 1) |
| #define KABI_RESERVED_PREFIX "reserved" |
| #define KABI_RESERVED_PREFIX_LEN (sizeof(KABI_RESERVED_PREFIX) - 1) |
| #define KABI_RENAMED_PREFIX "renamed" |
| #define KABI_RENAMED_PREFIX_LEN (sizeof(KABI_RENAMED_PREFIX) - 1) |
| #define KABI_IGNORED_PREFIX "ignored" |
| #define KABI_IGNORED_PREFIX_LEN (sizeof(KABI_IGNORED_PREFIX) - 1) |
| |
| static inline bool is_kabi_prefix(const char *name) |
| { |
| return name && !strncmp(name, KABI_PREFIX, KABI_PREFIX_LEN); |
| } |
| |
| enum kabi_status { |
| /* >0 to stop DIE processing */ |
| KABI_NORMAL = 1, |
| KABI_RESERVED, |
| KABI_IGNORED, |
| }; |
| |
| 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(struct die *cache, Dwarf_Die *die) |
| { |
| bool value; |
| |
| if (get_flag_attr(die, DW_AT_declaration, &value) && value) |
| return false; |
| |
| if (kabi_is_declonly(cache->fqn)) |
| 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, ¤t)); |
| while (!res) { |
| if (match(¤t)) { |
| /* <0 = error, 0 = continue, >0 = stop */ |
| res = checkp(func(state, cache, ¤t)); |
| if (res) |
| goto out; |
| } |
| |
| res = checkp(dwarf_siblingof(¤t, ¤t)); |
| } |
| |
| 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); |
| |
| if (stable) { |
| if (is_kabi_prefix(name)) |
| name = NULL; |
| state->kabi.orig_name = NULL; |
| } |
| |
| process_list_comma(state, cache); |
| process(cache, type); |
| process_type_attr(state, cache, die); |
| |
| if (stable && state->kabi.orig_name) |
| name = state->kabi.orig_name; |
| 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 get_kabi_status(Dwarf_Die *die, const char **suffix) |
| { |
| const char *name = get_name_attr(die); |
| |
| if (suffix) |
| *suffix = NULL; |
| |
| if (is_kabi_prefix(name)) { |
| name += KABI_PREFIX_LEN; |
| |
| if (!strncmp(name, KABI_RESERVED_PREFIX, |
| KABI_RESERVED_PREFIX_LEN)) |
| return KABI_RESERVED; |
| if (!strncmp(name, KABI_IGNORED_PREFIX, |
| KABI_IGNORED_PREFIX_LEN)) |
| return KABI_IGNORED; |
| |
| if (!strncmp(name, KABI_RENAMED_PREFIX, |
| KABI_RENAMED_PREFIX_LEN)) { |
| if (suffix) { |
| name += KABI_RENAMED_PREFIX_LEN; |
| *suffix = name; |
| } |
| return KABI_RESERVED; |
| } |
| } |
| |
| return KABI_NORMAL; |
| } |
| |
| static int check_struct_member_kabi_status(struct state *state, |
| struct die *__unused, Dwarf_Die *die) |
| { |
| int res; |
| |
| assert(dwarf_tag(die) == DW_TAG_member_type); |
| |
| /* |
| * If the union member is a struct, expect the __kabi field to |
| * be the first member of the structure, i.e..: |
| * |
| * union { |
| * type new_member; |
| * struct { |
| * type __kabi_field; |
| * } |
| * }; |
| */ |
| res = get_kabi_status(die, &state->kabi.orig_name); |
| |
| if (res == KABI_RESERVED && |
| !get_ref_die_attr(die, DW_AT_type, &state->kabi.placeholder)) |
| error("structure member missing a type?"); |
| |
| return res; |
| } |
| |
| static int check_union_member_kabi_status(struct state *state, |
| struct die *__unused, Dwarf_Die *die) |
| { |
| Dwarf_Die type; |
| int res; |
| |
| assert(dwarf_tag(die) == DW_TAG_member_type); |
| |
| if (!get_ref_die_attr(die, DW_AT_type, &type)) |
| error("union member missing a type?"); |
| |
| /* |
| * We expect a union with two members. Check if either of them |
| * has a __kabi name prefix, i.e.: |
| * |
| * union { |
| * ... |
| * type memberN; // <- type, N = {0,1} |
| * ... |
| * }; |
| * |
| * The member can also be a structure type, in which case we'll |
| * check the first structure member. |
| * |
| * In any case, stop processing after we've seen two members. |
| */ |
| res = get_kabi_status(die, &state->kabi.orig_name); |
| |
| if (res == KABI_RESERVED) |
| state->kabi.placeholder = type; |
| if (res != KABI_NORMAL) |
| return res; |
| |
| if (dwarf_tag(&type) == DW_TAG_structure_type) |
| res = checkp(process_die_container( |
| state, NULL, &type, check_struct_member_kabi_status, |
| match_member_type)); |
| |
| if (res <= KABI_NORMAL && ++state->kabi.members < 2) |
| return 0; /* Continue */ |
| |
| return res; |
| } |
| |
| static int get_union_kabi_status(Dwarf_Die *die, Dwarf_Die *placeholder, |
| const char **orig_name) |
| { |
| struct state state; |
| int res; |
| |
| if (!stable) |
| return KABI_NORMAL; |
| |
| /* |
| * To maintain a stable kABI, distributions may choose to reserve |
| * space in structs for later use by adding placeholder members, |
| * for example: |
| * |
| * struct s { |
| * u32 a; |
| * // an 8-byte placeholder for future use |
| * u64 __kabi_reserved_0; |
| * }; |
| * |
| * When the reserved member is taken into use, the type change |
| * would normally cause the symbol version to change as well, but |
| * if the replacement uses the following convention, gendwarfksyms |
| * continues to use the placeholder type for versioning instead, |
| * thus maintaining the same symbol version: |
| * |
| * struct s { |
| * u32 a; |
| * union { |
| * // placeholder replaced with a new member `b` |
| * struct t b; |
| * struct { |
| * // the placeholder type that is still |
| * // used for versioning |
| * u64 __kabi_reserved_0; |
| * }; |
| * }; |
| * }; |
| * |
| * I.e., as long as the replaced member is in a union, and the |
| * placeholder has a __kabi_reserved name prefix, we'll continue |
| * to use the placeholder type (here u64) for version calculation |
| * instead of the union type. |
| * |
| * It's also possible to ignore new members from versioning if |
| * they've been added to alignment holes, for example, by |
| * including them in a union with another member that uses the |
| * __kabi_ignored name prefix: |
| * |
| * struct s { |
| * u32 a; |
| * // an alignment hole is used to add `n` |
| * union { |
| * u32 n; |
| * // hide the entire union member from versioning |
| * u8 __kabi_ignored_0; |
| * }; |
| * u64 b; |
| * }; |
| * |
| * Note that the user of this feature is responsible for ensuring |
| * that the structure actually remains ABI compatible. |
| */ |
| memset(&state.kabi, 0, sizeof(struct kabi_state)); |
| |
| res = checkp(process_die_container(&state, NULL, die, |
| check_union_member_kabi_status, |
| match_member_type)); |
| |
| if (res == KABI_RESERVED) { |
| if (placeholder) |
| *placeholder = state.kabi.placeholder; |
| if (orig_name) |
| *orig_name = state.kabi.orig_name; |
| } |
| |
| return res; |
| } |
| |
| static bool is_kabi_ignored(Dwarf_Die *die) |
| { |
| Dwarf_Die type; |
| |
| if (!stable) |
| return false; |
| |
| if (!get_ref_die_attr(die, DW_AT_type, &type)) |
| error("member missing a type?"); |
| |
| return dwarf_tag(&type) == DW_TAG_union_type && |
| checkp(get_union_kabi_status(&type, NULL, NULL)) == KABI_IGNORED; |
| } |
| |
| static int ___process_structure_type(struct state *state, struct die *cache, |
| Dwarf_Die *die) |
| { |
| switch (dwarf_tag(die)) { |
| case DW_TAG_member: |
| if (is_kabi_ignored(die)) |
| return 0; |
| return check(process_type(state, cache, die)); |
| 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(cache, die); |
| |
| if (expand) { |
| state->expand.current_fqn = cache->fqn; |
| 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) |
| |
| static void process_union_type(struct state *state, struct die *cache, |
| Dwarf_Die *die) |
| { |
| Dwarf_Die placeholder; |
| |
| int res = checkp(get_union_kabi_status(die, &placeholder, |
| &state->kabi.orig_name)); |
| |
| if (res == KABI_RESERVED) |
| check(process_type(state, cache, &placeholder)); |
| if (res > KABI_NORMAL) |
| return; |
| |
| __process_structure_type(state, cache, die, "union_type", |
| ___process_structure_type, match_all); |
| } |
| |
| static void process_enumerator_type(struct state *state, struct die *cache, |
| Dwarf_Die *die) |
| { |
| bool overridden = false; |
| Dwarf_Word value; |
| |
| if (stable) { |
| /* Get the fqn before we process anything */ |
| update_fqn(cache, die); |
| |
| if (kabi_is_enumerator_ignored(state->expand.current_fqn, |
| cache->fqn)) |
| return; |
| |
| overridden = kabi_get_enumerator_value( |
| state->expand.current_fqn, cache->fqn, &value); |
| } |
| |
| process_list_comma(state, cache); |
| process(cache, "enumerator"); |
| process_fqn(cache, die); |
| |
| if (overridden || 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; |
| state->expand.current_fqn = NULL; |
| cache_init(&state->expansion_cache); |
| } |
| |
| static void expansion_state_restore(struct expansion_state *state, |
| struct expansion_state *saved) |
| { |
| state->expand = saved->expand; |
| state->current_fqn = saved->current_fqn; |
| } |
| |
| 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 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, cache, die)); |
| cache->state = DIE_SYMBOL; |
| 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 void save_symbol_ptr(struct state *state) |
| { |
| Dwarf_Die ptr_type; |
| Dwarf_Die type; |
| |
| if (!get_ref_die_attr(&state->die, DW_AT_type, &ptr_type) || |
| dwarf_tag(&ptr_type) != DW_TAG_pointer_type) |
| error("%s must be a pointer type!", |
| get_symbol_name(&state->die)); |
| |
| if (!get_ref_die_attr(&ptr_type, DW_AT_type, &type)) |
| error("%s pointer missing a type attribute?", |
| get_symbol_name(&state->die)); |
| |
| /* |
| * Save the symbol pointer DIE in case the actual symbol is |
| * missing from the DWARF. Clang, for example, intentionally |
| * omits external symbols from the debugging information. |
| */ |
| if (dwarf_tag(&type) == DW_TAG_subroutine_type) |
| symbol_set_ptr(state->sym, &type); |
| else |
| symbol_set_ptr(state->sym, &ptr_type); |
| } |
| |
| 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 (is_symbol_ptr(get_symbol_name(&state.die))) |
| save_symbol_ptr(&state); |
| else 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; |
| } |
| } |
| |
| static void process_symbol_ptr(struct symbol *sym, void *arg) |
| { |
| struct state state; |
| Dwarf *dwarf = arg; |
| |
| if (sym->state != SYMBOL_UNPROCESSED || !sym->ptr_die_addr) |
| return; |
| |
| debug("%s", sym->name); |
| state_init(&state); |
| state.sym = sym; |
| |
| if (!dwarf_die_addr_die(dwarf, (void *)sym->ptr_die_addr, &state.die)) |
| error("dwarf_die_addr_die failed for symbol ptr: '%s'", |
| sym->name); |
| |
| if (dwarf_tag(&state.die) == DW_TAG_subroutine_type) |
| process_subprogram(&state, &state.die); |
| else |
| process_variable(&state, &state.die); |
| |
| cache_free(&state.expansion_cache); |
| } |
| |
| void process_cu(Dwarf_Die *cudie) |
| { |
| check(process_die_container(NULL, NULL, cudie, process_exported_symbols, |
| match_all)); |
| |
| symbol_for_each(process_symbol_ptr, dwarf_cu_getdwarf(cudie->cu)); |
| |
| cache_free(&srcfile_cache); |
| } |