@@ -3,9 +3,32 @@
* Copyright (C) 2024 Google LLC
*/
+#include <assert.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;
@@ -352,13 +375,23 @@ static void __process_list_type(struct state *state, struct die *cache,
{
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);
@@ -485,11 +518,208 @@ static void process_variant_part_type(struct state *state, struct die *cache,
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:
@@ -546,7 +776,23 @@ static void __process_structure_type(struct state *state, struct die *cache,
DEFINE_PROCESS_STRUCTURE_TYPE(class)
DEFINE_PROCESS_STRUCTURE_TYPE(structure)
-DEFINE_PROCESS_STRUCTURE_TYPE(union)
+
+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)
@@ -43,6 +43,28 @@
__section(".discard.gendwarfksyms.kabi_rules") = \
"1\0" #hint "\0" #target "\0" #value
+#define __KABI_NORMAL_SIZE_ALIGN(_orig, _new) \
+ union { \
+ _Static_assert( \
+ sizeof(struct { _new; }) <= sizeof(struct { _orig; }), \
+ __FILE__ ":" __stringify(__LINE__) ": " __stringify( \
+ _new) " is larger than " __stringify(_orig)); \
+ _Static_assert( \
+ __alignof__(struct { _new; }) <= \
+ __alignof__(struct { _orig; }), \
+ __FILE__ ":" __stringify(__LINE__) ": " __stringify( \
+ _orig) " is not aligned the same as " __stringify(_new)); \
+ }
+
+#define __KABI_REPLACE(_orig, _new) \
+ union { \
+ _new; \
+ struct { \
+ _orig; \
+ }; \
+ __KABI_NORMAL_SIZE_ALIGN(_orig, _new); \
+ }
+
/*
* KABI_DECLONLY(fqn)
* Treat the struct/union/enum fqn as a declaration, i.e. even if
@@ -67,4 +89,69 @@
#define KABI_ENUMERATOR_VALUE(fqn, field, value) \
__KABI_RULE(enumerator_value, fqn field, value)
+/*
+ * KABI_RESERVE
+ * Reserve some "padding" in a structure for use by LTS backports.
+ * This is normally placed at the end of a structure.
+ * number: the "number" of the padding variable in the structure. Start with
+ * 1 and go up.
+ */
+#define KABI_RESERVE(n) unsigned long __kabi_reserved##n
+
+/*
+ * KABI_RESERVE_ARRAY
+ * Same as _BACKPORT_RESERVE but allocates an array with the specified
+ * size in bytes.
+ */
+#define KABI_RESERVE_ARRAY(n, s) \
+ unsigned char __aligned(8) __kabi_reserved##n[s]
+
+/*
+ * KABI_IGNORE
+ * Add a new field that's ignored in versioning.
+ */
+#define KABI_IGNORE(n, _new) \
+ union { \
+ _new; \
+ unsigned char __kabi_ignored##n; \
+ }
+
+/*
+ * KABI_REPLACE
+ * Replace a field with a compatible new field.
+ */
+#define KABI_REPLACE(_oldtype, _oldname, _new) \
+ __KABI_REPLACE(_oldtype __kabi_renamed##_oldname, struct { _new; })
+
+/*
+ * KABI_USE(number, _new)
+ * Use a previous padding entry that was defined with KABI_RESERVE
+ * number: the previous "number" of the padding variable
+ * _new: the variable to use now instead of the padding variable
+ */
+#define KABI_USE(number, _new) __KABI_REPLACE(KABI_RESERVE(number), _new)
+
+/*
+ * KABI_USE2(number, _new1, _new2)
+ * Use a previous padding entry that was defined with KABI_RESERVE for
+ * two new variables that fit into 64 bits. This is good for when you do not
+ * want to "burn" a 64bit padding variable for a smaller variable size if not
+ * needed.
+ */
+#define KABI_USE2(number, _new1, _new2) \
+ __KABI_REPLACE( \
+ KABI_RESERVE(number), struct { \
+ _new1; \
+ _new2; \
+ })
+/*
+ * KABI_USE_ARRAY(number, bytes, _new)
+ * Use a previous padding entry that was defined with KABI_RESERVE_ARRAY
+ * number: the previous "number" of the padding variable
+ * bytes: the size in bytes reserved for the array
+ * _new: the variable to use now instead of the padding variable
+ */
+#define KABI_USE_ARRAY(number, bytes, _new) \
+ __KABI_REPLACE(KABI_RESERVE_ARRAY(number, bytes), _new)
+
#endif /* __KABI_H__ */
@@ -12,3 +12,19 @@
struct s e0;
enum e e1;
+
+struct ex0a ex0a;
+struct ex0b ex0b;
+struct ex0c ex0c;
+
+struct ex1a ex1a;
+struct ex1b ex1b;
+struct ex1c ex1c;
+
+struct ex2a ex2a;
+struct ex2b ex2b;
+struct ex2c ex2c;
+
+struct ex3a ex3a;
+struct ex3b ex3b;
+struct ex3c ex3c;
@@ -59,6 +59,205 @@ KABI_ENUMERATOR_VALUE(e, D, 123456789);
* STABLE-NEXT: enumerator A = 0 ,
* STABLE-NEXT: enumerator D = 123456789
* STABLE-NEXT: } byte_size(4)
+*/
+
+/*
+ * Example: Reserved fields
+ */
+struct ex0a {
+ int a;
+ KABI_RESERVE(0);
+ KABI_RESERVE(1);
+};
+
+/*
+ * STABLE: variable structure_type ex0a {
+ * STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
+ * STABLE-NEXT: member base_type [[ULONG:long unsigned int|unsigned long]] byte_size(8) encoding(7) data_member_location(8) ,
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(16)
+ * STABLE-NEXT: } byte_size(24)
+ */
+
+struct ex0b {
+ int a;
+ KABI_RESERVE(0);
+ KABI_USE2(1, int b, int c);
+};
+
+/*
+ * STABLE: variable structure_type ex0b {
+ * STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(8) ,
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(16)
+ * STABLE-NEXT: } byte_size(24)
+ */
+
+struct ex0c {
+ int a;
+ KABI_USE(0, void *p);
+ KABI_USE2(1, int b, int c);
+};
+
+/*
+ * STABLE: variable structure_type ex0c {
+ * STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(8) ,
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(16)
+ * STABLE-NEXT: } byte_size(24)
+ */
+
+/*
+ * Example: A reserved array
+ */
+
+struct ex1a {
+ unsigned int a;
+ KABI_RESERVE_ARRAY(0, 64);
+};
+
+/*
+ * STABLE: variable structure_type ex1a {
+ * STABLE-NEXT: member base_type unsigned int byte_size(4) encoding(7) a data_member_location(0) ,
+ * STABLE-NEXT: member array_type[64] {
+ * STABLE-NEXT: base_type unsigned char byte_size(1) encoding(8)
+ * STABLE-NEXT: } data_member_location(8)
+ * STABLE-NEXT: } byte_size(72)
+ */
+
+struct ex1b {
+ unsigned int a;
+ KABI_USE_ARRAY(
+ 0, 64, struct {
+ void *p;
+ KABI_RESERVE_ARRAY(1, 56);
+ });
+};
+
+/*
+ * STABLE: variable structure_type ex1b {
+ * STABLE-NEXT: member base_type unsigned int byte_size(4) encoding(7) a data_member_location(0) ,
+ * STABLE-NEXT: member array_type[64] {
+ * STABLE-NEXT: base_type unsigned char byte_size(1) encoding(8)
+ * STABLE-NEXT: } data_member_location(8)
+ * STABLE-NEXT: } byte_size(72)
+ */
+
+struct ex1c {
+ unsigned int a;
+ KABI_USE_ARRAY(0, 64, void *p[8]);
+};
+
+/*
+ * STABLE: variable structure_type ex1c {
+ * STABLE-NEXT: member base_type unsigned int byte_size(4) encoding(7) a data_member_location(0) ,
+ * STABLE-NEXT: member array_type[64] {
+ * STABLE-NEXT: base_type unsigned char byte_size(1) encoding(8)
+ * STABLE-NEXT: } data_member_location(8)
+ * STABLE-NEXT: } byte_size(72)
+ */
+
+/*
+ * Example: An ignored field added to an alignment hole
+ */
+
+struct ex2a {
+ int a;
+ unsigned long b;
+ int c;
+ unsigned long d;
+};
+
+/*
+ * STABLE: variable structure_type ex2a {
+ * STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
+ * STABLE-NEXT: member base_type [[ULONG:long unsigned int|unsigned long]] byte_size(8) encoding(7) b data_member_location(8)
+ * STABLE-NEXT: member base_type int byte_size(4) encoding(5) c data_member_location(16) ,
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) d data_member_location(24)
+ * STABLE-NEXT: } byte_size(32)
+ */
+
+struct ex2b {
+ int a;
+ KABI_IGNORE(0, unsigned int n);
+ unsigned long b;
+ int c;
+ unsigned long d;
+};
+
+_Static_assert(sizeof(struct ex2a) == sizeof(struct ex2b), "ex2a size doesn't match ex2b");
+
+/*
+ * STABLE: variable structure_type ex2b {
+ * STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) b data_member_location(8)
+ * STABLE-NEXT: member base_type int byte_size(4) encoding(5) c data_member_location(16) ,
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) d data_member_location(24)
+ * STABLE-NEXT: } byte_size(32)
+ */
+
+struct ex2c {
+ int a;
+ KABI_IGNORE(0, unsigned int n);
+ unsigned long b;
+ int c;
+ KABI_IGNORE(1, unsigned int m);
+ unsigned long d;
+};
+
+_Static_assert(sizeof(struct ex2a) == sizeof(struct ex2c), "ex2a size doesn't match ex2c");
+
+/*
+ * STABLE: variable structure_type ex2c {
+ * STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) b data_member_location(8)
+ * STABLE-NEXT: member base_type int byte_size(4) encoding(5) c data_member_location(16) ,
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) d data_member_location(24)
+ * STABLE-NEXT: } byte_size(32)
+ */
+
+
+/*
+ * Example: A replaced field
+ */
+
+struct ex3a {
+ unsigned long a;
+ unsigned long unused;
+};
+
+/*
+ * STABLE: variable structure_type ex3a {
+ * STABLE-NEXT: member base_type [[ULONG:long unsigned int|unsigned long]] byte_size(8) encoding(7) a data_member_location(0)
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) unused data_member_location(8)
+ * STABLE-NEXT: } byte_size(16)
+ */
+
+struct ex3b {
+ unsigned long a;
+ KABI_REPLACE(unsigned long, unused, unsigned long renamed);
+};
+
+_Static_assert(sizeof(struct ex3a) == sizeof(struct ex3b), "ex3a size doesn't match ex3b");
+
+/*
+ * STABLE: variable structure_type ex3b {
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) a data_member_location(0)
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) unused data_member_location(8)
+ * STABLE-NEXT: } byte_size(16)
+ */
+
+struct ex3c {
+ unsigned long a;
+ KABI_REPLACE(unsigned long, unused, long replaced);
+};
+
+_Static_assert(sizeof(struct ex3a) == sizeof(struct ex3c), "ex3a size doesn't match ex3c");
+
+/*
+ * STABLE: variable structure_type ex3c {
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) a data_member_location(0)
+ * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) unused data_member_location(8)
+ * STABLE-NEXT: } byte_size(16)
*/
#endif /* __KABI_EX_H__ */
@@ -241,6 +241,12 @@ struct expansion_state {
const char *current_fqn;
};
+struct kabi_state {
+ int members;
+ Dwarf_Die placeholder;
+ const char *orig_name;
+};
+
struct state {
struct symbol *sym;
Dwarf_Die die;
@@ -251,6 +257,9 @@ struct state {
/* Structure expansion */
struct expansion_state expand;
struct cache expansion_cache;
+
+ /* Reserved or ignored members */
+ struct kabi_state kabi;
};
typedef int (*die_callback_t)(struct state *state, struct die *cache,