| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2024 Google LLC | |
| */ | |
| #define _GNU_SOURCE | |
| #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); | |
| } | |
| static void update_fqn(struct die *cache, Dwarf_Die *die) | |
| { | |
| struct die *fqn; | |
| if (!cache->fqn) { | |
| if (!__die_map_get((uintptr_t)die->addr, DIE_FQN, &fqn) && | |
| *fqn->fqn) | |
| cache->fqn = xstrdup(fqn->fqn); | |
| else | |
| cache->fqn = ""; | |
| } | |
| } | |
| 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(encoding) | |
| DEFINE_PROCESS_UDATA_ATTRIBUTE(data_bit_offset) | |
| DEFINE_PROCESS_UDATA_ATTRIBUTE(data_member_location) | |
| DEFINE_PROCESS_UDATA_ATTRIBUTE(discr_value) | |
| static void process_byte_size_attr(struct die *cache, Dwarf_Die *die) | |
| { | |
| Dwarf_Word value; | |
| unsigned long override; | |
| if (get_udata_attr(die, DW_AT_byte_size, &value)) { | |
| if (stable && kabi_get_byte_size(cache->fqn, &override)) | |
| value = override; | |
| process_fmt(cache, " byte_size(%" PRIu64 ")", 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(state.kabi)); | |
| 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; | |
| unsigned long override; | |
| 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, &override); | |
| value = override; | |
| } | |
| 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); | |
| } | |
| static int resolve_fqns(struct state *parent, struct die *unused, | |
| Dwarf_Die *die) | |
| { | |
| struct state state; | |
| struct die *cache; | |
| const char *name; | |
| bool use_prefix; | |
| char *prefix = NULL; | |
| char *fqn = ""; | |
| int tag; | |
| if (!__die_map_get((uintptr_t)die->addr, DIE_FQN, &cache)) | |
| return 0; | |
| tag = dwarf_tag(die); | |
| /* | |
| * Only namespaces and structures need to pass a prefix to the next | |
| * scope. | |
| */ | |
| use_prefix = tag == DW_TAG_namespace || tag == DW_TAG_class_type || | |
| tag == DW_TAG_structure_type; | |
| state.expand.current_fqn = NULL; | |
| name = get_name_attr(die); | |
| if (parent && parent->expand.current_fqn && (use_prefix || name)) { | |
| /* | |
| * The fqn for the current DIE, and if needed, a prefix for the | |
| * next scope. | |
| */ | |
| if (asprintf(&prefix, "%s::%s", parent->expand.current_fqn, | |
| name ? name : "<anonymous>") < 0) | |
| error("asprintf failed"); | |
| if (use_prefix) | |
| state.expand.current_fqn = prefix; | |
| /* | |
| * Use fqn only if the DIE has a name. Otherwise fqn will | |
| * remain empty. | |
| */ | |
| if (name) { | |
| fqn = prefix; | |
| /* prefix will be freed by die_map. */ | |
| prefix = NULL; | |
| } | |
| } else if (name) { | |
| /* No prefix from the previous scope. Use only the name. */ | |
| fqn = xstrdup(name); | |
| if (use_prefix) | |
| state.expand.current_fqn = fqn; | |
| } | |
| /* If the DIE has a non-empty name, cache it. */ | |
| if (*fqn) { | |
| cache = die_map_get(die, DIE_FQN); | |
| /* Move ownership of fqn to die_map. */ | |
| cache->fqn = fqn; | |
| cache->state = DIE_FQN; | |
| } | |
| check(process_die_container(&state, NULL, die, resolve_fqns, | |
| match_all)); | |
| free(prefix); | |
| return 0; | |
| } | |
| void process_cu(Dwarf_Die *cudie) | |
| { | |
| check(process_die_container(NULL, NULL, cudie, resolve_fqns, | |
| match_all)); | |
| 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); | |
| } |