The compiler may choose not to emit type information in DWARF for external symbols. Clang, for example, does this for symbols not defined in the current TU. To provide a way to work around this issue, add support for __gendwarfksyms_ptr_<symbol> pointers that force the compiler to emit the necessary type information in DWARF also for the missing symbols. Example usage: #define GENDWARFKSYMS_PTR(sym) \ static typeof(sym) *__gendwarfksyms_ptr_##sym __used \ __section(".discard.gendwarfksyms") = &sym; extern int external_symbol(void); GENDWARFKSYMS_PTR(external_symbol); Signed-off-by: Sami Tolvanen <samitolvanen@google.com> Reviewed-by: Petr Pavlu <petr.pavlu@suse.com> Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
1159 lines
29 KiB
C
1159 lines
29 KiB
C
// 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);
|
|
}
|