diff mbox series

[v4,15/19] gendwarfksyms: Add support for reserved and ignored fields

Message ID 20241008183823.36676-36-samitolvanen@google.com (mailing list archive)
State New
Headers show
Series Implement DWARF modversions | expand

Commit Message

Sami Tolvanen Oct. 8, 2024, 6:38 p.m. UTC
Distributions that want to maintain a stable kABI need the ability
to make ABI compatible changes to kernel data structures without
affecting symbol versions, either because of LTS updates or backports.

With genksyms, developers would typically hide these changes from
version calculation with #ifndef __GENKSYMS__, which would result
in the symbol version not changing even though the actual type has
changed.  When we process precompiled object files, this isn't an
option.

Change union processing to recognize field name prefixes that allow
the user to ignore the union completely during symbol versioning with
a __kabi_ignored prefix in a field name, or to replace the type of a
placeholder field using a __kabi_reserved field name prefix.

For example, assume we want to add a new field to an existing
alignment hole in a data structure, and ignore the new field when
calculating symbol versions:

  struct struct1 {
    int a;
    /* a 4-byte alignment hole */
    unsigned long b;
  };

To add `int n` to the alignment hole, we can add a union that includes
a __kabi_ignored field that causes gendwarfksyms to ignore the entire
union:

  struct struct1 {
    int a;
    union {
      char __kabi_ignored_0;
      int n;
    };
    unsigned long b;
  };

With --stable, both structs produce the same symbol version.

Alternatively, when a distribution expects future modification to a
data structure, they can explicitly add reserved fields:

  struct struct2 {
    long a;
    long __kabi_reserved_0; /* reserved for future use */
  };

To take the field into use, we can again replace it with a union, with
one of the fields keeping the __kabi_reserved name prefix to indicate
the original type:

  struct struct2 {
    long a;
    union {
      long __kabi_reserved_0;
      struct {
          int b;
          int v;
      };
    };

Here gendwarfksyms --stable replaces the union with the type of the
placeholder field when calculating versions.

Signed-off-by: Sami Tolvanen <samitolvanen@google.com>
Acked-by: Neal Gompa <neal@gompa.dev>
---
 scripts/gendwarfksyms/dwarf.c             | 202 +++++++++++++++++++++-
 scripts/gendwarfksyms/examples/kabi.h     |  80 +++++++++
 scripts/gendwarfksyms/examples/kabi_ex0.c |  86 +++++++++
 scripts/gendwarfksyms/examples/kabi_ex1.c |  89 ++++++++++
 scripts/gendwarfksyms/examples/kabi_ex2.c |  98 +++++++++++
 scripts/gendwarfksyms/gendwarfksyms.h     |  29 ++++
 6 files changed, 583 insertions(+), 1 deletion(-)
 create mode 100644 scripts/gendwarfksyms/examples/kabi_ex0.c
 create mode 100644 scripts/gendwarfksyms/examples/kabi_ex1.c
 create mode 100644 scripts/gendwarfksyms/examples/kabi_ex2.c

Comments

Petr Pavlu Oct. 23, 2024, 2:53 p.m. UTC | #1
On 10/8/24 20:38, Sami Tolvanen wrote:
> Distributions that want to maintain a stable kABI need the ability
> to make ABI compatible changes to kernel data structures without
> affecting symbol versions, either because of LTS updates or backports.
> 
> With genksyms, developers would typically hide these changes from
> version calculation with #ifndef __GENKSYMS__, which would result
> in the symbol version not changing even though the actual type has
> changed.  When we process precompiled object files, this isn't an
> option.
> 
> Change union processing to recognize field name prefixes that allow
> the user to ignore the union completely during symbol versioning with
> a __kabi_ignored prefix in a field name, or to replace the type of a
> placeholder field using a __kabi_reserved field name prefix.
> 
> For example, assume we want to add a new field to an existing
> alignment hole in a data structure, and ignore the new field when
> calculating symbol versions:
> 
>   struct struct1 {
>     int a;
>     /* a 4-byte alignment hole */
>     unsigned long b;
>   };
> 
> To add `int n` to the alignment hole, we can add a union that includes
> a __kabi_ignored field that causes gendwarfksyms to ignore the entire
> union:
> 
>   struct struct1 {
>     int a;
>     union {
>       char __kabi_ignored_0;
>       int n;
>     };
>     unsigned long b;
>   };
> 
> With --stable, both structs produce the same symbol version.
> 
> Alternatively, when a distribution expects future modification to a
> data structure, they can explicitly add reserved fields:
> 
>   struct struct2 {
>     long a;
>     long __kabi_reserved_0; /* reserved for future use */
>   };
> 
> To take the field into use, we can again replace it with a union, with
> one of the fields keeping the __kabi_reserved name prefix to indicate
> the original type:
> 
>   struct struct2 {
>     long a;
>     union {
>       long __kabi_reserved_0;
>       struct {
>           int b;
>           int v;
>       };
>     };
> 
> Here gendwarfksyms --stable replaces the union with the type of the
> placeholder field when calculating versions.
> 
> Signed-off-by: Sami Tolvanen <samitolvanen@google.com>
> Acked-by: Neal Gompa <neal@gompa.dev>
> ---
>  scripts/gendwarfksyms/dwarf.c             | 202 +++++++++++++++++++++-
>  scripts/gendwarfksyms/examples/kabi.h     |  80 +++++++++
>  scripts/gendwarfksyms/examples/kabi_ex0.c |  86 +++++++++
>  scripts/gendwarfksyms/examples/kabi_ex1.c |  89 ++++++++++
>  scripts/gendwarfksyms/examples/kabi_ex2.c |  98 +++++++++++
>  scripts/gendwarfksyms/gendwarfksyms.h     |  29 ++++
>  6 files changed, 583 insertions(+), 1 deletion(-)
>  create mode 100644 scripts/gendwarfksyms/examples/kabi_ex0.c
>  create mode 100644 scripts/gendwarfksyms/examples/kabi_ex1.c
>  create mode 100644 scripts/gendwarfksyms/examples/kabi_ex2.c
> 
> diff --git a/scripts/gendwarfksyms/dwarf.c b/scripts/gendwarfksyms/dwarf.c
> index b15f1a5db452..72e24140b6e3 100644
> --- a/scripts/gendwarfksyms/dwarf.c
> +++ b/scripts/gendwarfksyms/dwarf.c
> @@ -308,6 +308,9 @@ static void __process_list_type(struct state *state, struct die *cache,
>  {
>  	const char *name = get_name_attr(die);
>  
> +	if (stable && is_kabi_prefix(name))
> +		name = NULL;
> +
>  	process_list_comma(state, cache);
>  	process(cache, type);
>  	process_type_attr(state, cache, die);
> @@ -441,11 +444,193 @@ static void process_variant_part_type(struct state *state, struct die *cache,
>  	process(cache, "}");
>  }
>  
> +static int get_kabi_status(Dwarf_Die *die)
> +{
> +	const char *name = get_name_attr(die);
> +
> +	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;
> +	}
> +
> +	return KABI_NORMAL;
> +}
> +
> +static int check_struct_member_kabi_status(struct state *state,
> +					   struct die *__unused, Dwarf_Die *die)
> +{
> +	int res;
> +
> +	if (dwarf_tag(die) != DW_TAG_member_type)
> +		error("expected a member");

Nit: If I understand the code correctly, the failed tag check here would
indicate an error in the internal logic that should never happen, so
a plain assert() would be more appropriate? Similarly in
check_union_member_kabi_status().

> +
> +	/*
> +	 * 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);
> +
> +	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;
> +
> +	if (dwarf_tag(die) != DW_TAG_member_type)
> +		error("expected a member");
> +
> +	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);
> +
> +	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)
> +{
> +	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.
> +	 */
> +	state.kabi.members = 0;
> +
> +	res = checkp(process_die_container(&state, NULL, die,
> +					   check_union_member_kabi_status,
> +					   match_member_type));
> +
> +	if (placeholder && res == KABI_RESERVED)
> +		*placeholder = state.kabi.placeholder;
> +
> +	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)) == 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:
> @@ -503,7 +688,22 @@ 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));
> +
> +	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)
> diff --git a/scripts/gendwarfksyms/examples/kabi.h b/scripts/gendwarfksyms/examples/kabi.h
> index c53e8d4a7d2e..ec99c2fb9e96 100644
> --- a/scripts/gendwarfksyms/examples/kabi.h
> +++ b/scripts/gendwarfksyms/examples/kabi.h
> @@ -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_USE_ARRAY(fqn)
>   *   Treat the struct fqn as a declaration, i.e. even if a definition
> @@ -58,4 +80,62 @@
>  #define KABI_ENUMERATOR_IGNORE(fqn, field) \
>  	__KABI_RULE(enumerator_ignore, fqn, field)
>  
> +/*
> + * KABI_RESERVE
> + *   Reserve some "padding" in a structure for use by LTS backports.
> + *   This normally placed at the end of a structure.

Nit: s/This normally/This is normally/.

> + *   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_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__ */
> diff --git a/scripts/gendwarfksyms/examples/kabi_ex0.c b/scripts/gendwarfksyms/examples/kabi_ex0.c
> new file mode 100644
> index 000000000000..934324cba837
> --- /dev/null
> +++ b/scripts/gendwarfksyms/examples/kabi_ex0.c
> @@ -0,0 +1,86 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * kabi_ex0.c
> + *
> + * Copyright (C) 2024 Google LLC
> + *
> + * Reserved and ignored data structure field examples with --stable.
> + */
> +
> +/*
> + * The comments below each example contain the expected gendwarfksyms
> + * output, which can be verified using LLVM's FileCheck tool:
> + *
> + * https://llvm.org/docs/CommandGuide/FileCheck.html
> + *
> + * $ gcc -g -c examples/kabi_ex0.c examples/kabi_ex0.o
> + *
> + * Verify --stable output:
> + *
> + * $ echo -e "ex0a\nex0b\nex0c" | \
> + * 	./gendwarfksyms --stable --dump-dies \
> + * 		examples/kabi_ex0.o 2>&1 >/dev/null | \
> + * 	FileCheck examples/kabi_ex0.c --check-prefix=STABLE
> + *
> + * Verify that symbol versions match with --stable:
> + *
> + * $ echo -e "ex0a\nex0b\nex0c" | \
> + * 	./gendwarfksyms --stable examples/kabi_ex0.o | \
> + * 	sort | \
> + * 	FileCheck examples/kabi_ex0.c --check-prefix=VERSION
> + */
> +
> +#include "kabi.h"
> +
> +/*
> + * Example 0: Reserved fields.
> + */
> +
> +struct {
> +	int a;
> +	KABI_RESERVE(0);
> +	KABI_RESERVE(1);
> +} ex0a;
> +
> +/*
> + * STABLE:      variable structure_type {
> + * 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)
> + *
> + * VERSION-DAG: #SYMVER ex0a 0x[[#%.08x,EX0:]]
> + */
> +
> +struct {
> +	int a;
> +	KABI_RESERVE(0);
> +	KABI_USE2(1, int b, int c);
> +} ex0b;
> +
> +/*
> + * STABLE:      variable structure_type {
> + * 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)
> + *
> + * VERSION-DAG: #SYMVER ex0b 0x[[#%.08x,EX0]]
> + */
> +
> +struct {
> +	int a;
> +	KABI_USE(0, void *p);
> +	KABI_USE2(1, int b, int c);
> +} ex0c;
> +
> +/*
> + * STABLE:      variable structure_type {
> + * 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)
> + *
> + * VERSION-DAG: #SYMVER ex0c 0x[[#%.08x,EX0]]
> + */
> diff --git a/scripts/gendwarfksyms/examples/kabi_ex1.c b/scripts/gendwarfksyms/examples/kabi_ex1.c
> new file mode 100644
> index 000000000000..7bc34bc7dec8
> --- /dev/null
> +++ b/scripts/gendwarfksyms/examples/kabi_ex1.c
> @@ -0,0 +1,89 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * kabi_ex1.c
> + *
> + * Copyright (C) 2024 Google LLC
> + *
> + * Reserved and ignored data structure field examples with --stable.
> + */
> +
> +/*
> + * The comments below each example contain the expected gendwarfksyms
> + * output, which can be verified using LLVM's FileCheck tool:
> + *
> + * https://llvm.org/docs/CommandGuide/FileCheck.html
> + *
> + * $ gcc -g -c examples/kabi_ex1.c examples/kabi_ex1.o
> + *
> + * Verify --stable output:
> + *
> + * $ echo -e "ex1a\nex1b\nex1c" | \
> + * 	./gendwarfksyms --stable --dump-dies \
> + * 		examples/kabi_ex1.o 2>&1 >/dev/null | \
> + * 	FileCheck examples/kabi_ex1.c --check-prefix=STABLE
> + *
> + * Verify that symbol versions match with --stable:
> + *
> + * $ echo -e "ex1a\nex1b\nex1c" | \
> + * 	./gendwarfksyms --stable examples/kabi_ex1.o | \
> + * 	sort | \
> + * 	FileCheck examples/kabi_ex1.c --check-prefix=VERSION
> + */
> +
> +#include "kabi.h"
> +
> +/*
> + * Example 1: A reserved array.
> + */
> +
> +struct {
> +	unsigned int a;
> +	KABI_RESERVE_ARRAY(0, 64);
> +} ex1a;
> +
> +/*
> + * STABLE:      variable structure_type {
> + * 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)
> + *
> + * VERSION-DAG: #SYMVER ex1a 0x[[#%.08x,EX1:]]
> + */
> +
> +struct {
> +	unsigned int a;
> +	KABI_USE_ARRAY(
> +		0, 64, struct {
> +			void *p;
> +			KABI_RESERVE_ARRAY(1, 56);
> +		});
> +} ex1b;
> +
> +/*
> + * STABLE:      variable structure_type {
> + * 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)
> + *
> + * VERSION-DAG: #SYMVER ex1b 0x[[#%.08x,EX1]]
> + */
> +
> +struct {
> +	unsigned int a;
> +	KABI_USE_ARRAY(0, 64, void *p[8]);
> +} ex1c;
> +
> +/*
> + * STABLE:      variable structure_type {
> + * 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)
> + *
> + * VERSION-DAG: #SYMVER ex1c 0x[[#%.08x,EX1]]
> + */
> diff --git a/scripts/gendwarfksyms/examples/kabi_ex2.c b/scripts/gendwarfksyms/examples/kabi_ex2.c
> new file mode 100644
> index 000000000000..947ea5675b4f
> --- /dev/null
> +++ b/scripts/gendwarfksyms/examples/kabi_ex2.c
> @@ -0,0 +1,98 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * kabi_ex2.c
> + *
> + * Copyright (C) 2024 Google LLC
> + *
> + * Reserved and ignored data structure field examples with --stable.
> + */
> +
> +/*
> + * The comments below each example contain the expected gendwarfksyms
> + * output, which can be verified using LLVM's FileCheck tool:
> + *
> + * https://llvm.org/docs/CommandGuide/FileCheck.html
> + *
> + * $ gcc -g -c examples/kabi_ex2.c examples/kabi_ex2.o
> + *
> + * Verify --stable output:
> + *
> + * $ echo -e "ex2a\nex2b\nex2c" | \
> + * 	./gendwarfksyms --stable --dump-dies \
> + * 		examples/kabi_ex2.o 2>&1 >/dev/null | \
> + * 	FileCheck examples/kabi_ex2.c --check-prefix=STABLE
> + *
> + * Verify that symbol versions match with --stable:
> + *
> + * $ echo -e "ex2a\nex2b\nex2c" | \
> + * 	./gendwarfksyms --stable examples/kabi_ex2.o | \
> + * 	sort | \
> + * 	FileCheck examples/kabi_ex2.c --check-prefix=VERSION
> + */
> +
> +#include "kabi.h"
> +
> +/*
> + * Example 2: An ignored field added to an alignment hole.
> + */
> +
> +struct {
> +	int a;
> +	unsigned long b;
> +	int c;
> +	unsigned long d;
> +} ex2a;
> +
> +/*
> + * STABLE:      variable structure_type {
> + * 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)
> + *
> + * VERSION-DAG: #SYMVER ex2a 0x[[#%.08x,EX2:]]
> + */
> +
> +struct {
> +	int a;
> +	KABI_IGNORE(0, unsigned int n);
> +	unsigned long b;
> +	int c;
> +	unsigned long d;
> +} ex2b;
> +
> +_Static_assert(sizeof(ex2a) == sizeof(ex2b), "ex2a size doesn't match ex2b");
> +
> +/*
> + * STABLE:      variable structure_type {
> + * 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)
> + *
> + * VERSION-DAG: #SYMVER ex2b 0x[[#%.08x,EX2]]
> + */
> +
> +struct {
> +	int a;
> +	KABI_IGNORE(0, unsigned int n);
> +	unsigned long b;
> +	int c;
> +	KABI_IGNORE(1, unsigned int m);
> +	unsigned long d;
> +} ex2c;
> +
> +_Static_assert(sizeof(ex2a) == sizeof(ex2c), "ex2a size doesn't match ex2c");
> +
> +/*
> + * STABLE:      variable structure_type {
> + * 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)
> + *
> + * VERSION-DAG: #SYMVER ex2c 0x[[#%.08x,EX2]]
> + */
> diff --git a/scripts/gendwarfksyms/gendwarfksyms.h b/scripts/gendwarfksyms/gendwarfksyms.h
> index f32ad4389b58..1cff868bacdb 100644
> --- a/scripts/gendwarfksyms/gendwarfksyms.h
> +++ b/scripts/gendwarfksyms/gendwarfksyms.h
> @@ -222,6 +222,20 @@ void cache_clear_expanded(struct expansion_cache *ec);
>  /*
>   * dwarf.c
>   */
> +
> +/* See dwarf.c: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_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);
> +}
> +

Nit: The new KABI_* macros and the is_kabi_prefix() function are used
only in dwarf.c so could be all moved there.

>  struct expansion_state {
>  	bool expand;
>  	unsigned int ptr_depth;
> @@ -229,6 +243,18 @@ struct expansion_state {
>  	const char *current_fqn;
>  };
>  
> +enum kabi_status {
> +	/* >0 to stop DIE processing */
> +	KABI_NORMAL = 1,
> +	KABI_RESERVED,
> +	KABI_IGNORED,
> +};
> +
> +struct kabi_state {
> +	int members;
> +	Dwarf_Die placeholder;
> +};
> +
>  struct state {
>  	struct symbol *sym;
>  	Dwarf_Die die;
> @@ -239,6 +265,9 @@ struct state {
>  	/* Structure expansion */
>  	struct expansion_state expand;
>  	struct expansion_cache expansion_cache;
> +
> +	/* Reserved or ignored members */
> +	struct kabi_state kabi;
>  };
>  
>  typedef int (*die_callback_t)(struct state *state, struct die *cache,

I've noted some nits above which you might want to trivially address.
Otherwise this looks ok to me, feel free to add:

Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
Sami Tolvanen Oct. 23, 2024, 9:05 p.m. UTC | #2
On Wed, Oct 23, 2024 at 2:53 PM Petr Pavlu <petr.pavlu@suse.com> wrote:
>
> I've noted some nits above which you might want to trivially address.

These all looked reasonable to me, I'll address them in the next version.

> Otherwise this looks ok to me, feel free to add:
>
> Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>

Thanks!

Sami
diff mbox series

Patch

diff --git a/scripts/gendwarfksyms/dwarf.c b/scripts/gendwarfksyms/dwarf.c
index b15f1a5db452..72e24140b6e3 100644
--- a/scripts/gendwarfksyms/dwarf.c
+++ b/scripts/gendwarfksyms/dwarf.c
@@ -308,6 +308,9 @@  static void __process_list_type(struct state *state, struct die *cache,
 {
 	const char *name = get_name_attr(die);
 
+	if (stable && is_kabi_prefix(name))
+		name = NULL;
+
 	process_list_comma(state, cache);
 	process(cache, type);
 	process_type_attr(state, cache, die);
@@ -441,11 +444,193 @@  static void process_variant_part_type(struct state *state, struct die *cache,
 	process(cache, "}");
 }
 
+static int get_kabi_status(Dwarf_Die *die)
+{
+	const char *name = get_name_attr(die);
+
+	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;
+	}
+
+	return KABI_NORMAL;
+}
+
+static int check_struct_member_kabi_status(struct state *state,
+					   struct die *__unused, Dwarf_Die *die)
+{
+	int res;
+
+	if (dwarf_tag(die) != DW_TAG_member_type)
+		error("expected a member");
+
+	/*
+	 * 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);
+
+	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;
+
+	if (dwarf_tag(die) != DW_TAG_member_type)
+		error("expected a member");
+
+	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);
+
+	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)
+{
+	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.
+	 */
+	state.kabi.members = 0;
+
+	res = checkp(process_die_container(&state, NULL, die,
+					   check_union_member_kabi_status,
+					   match_member_type));
+
+	if (placeholder && res == KABI_RESERVED)
+		*placeholder = state.kabi.placeholder;
+
+	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)) == 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:
@@ -503,7 +688,22 @@  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));
+
+	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)
diff --git a/scripts/gendwarfksyms/examples/kabi.h b/scripts/gendwarfksyms/examples/kabi.h
index c53e8d4a7d2e..ec99c2fb9e96 100644
--- a/scripts/gendwarfksyms/examples/kabi.h
+++ b/scripts/gendwarfksyms/examples/kabi.h
@@ -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_USE_ARRAY(fqn)
  *   Treat the struct fqn as a declaration, i.e. even if a definition
@@ -58,4 +80,62 @@ 
 #define KABI_ENUMERATOR_IGNORE(fqn, field) \
 	__KABI_RULE(enumerator_ignore, fqn, field)
 
+/*
+ * KABI_RESERVE
+ *   Reserve some "padding" in a structure for use by LTS backports.
+ *   This 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_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__ */
diff --git a/scripts/gendwarfksyms/examples/kabi_ex0.c b/scripts/gendwarfksyms/examples/kabi_ex0.c
new file mode 100644
index 000000000000..934324cba837
--- /dev/null
+++ b/scripts/gendwarfksyms/examples/kabi_ex0.c
@@ -0,0 +1,86 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * kabi_ex0.c
+ *
+ * Copyright (C) 2024 Google LLC
+ *
+ * Reserved and ignored data structure field examples with --stable.
+ */
+
+/*
+ * The comments below each example contain the expected gendwarfksyms
+ * output, which can be verified using LLVM's FileCheck tool:
+ *
+ * https://llvm.org/docs/CommandGuide/FileCheck.html
+ *
+ * $ gcc -g -c examples/kabi_ex0.c examples/kabi_ex0.o
+ *
+ * Verify --stable output:
+ *
+ * $ echo -e "ex0a\nex0b\nex0c" | \
+ * 	./gendwarfksyms --stable --dump-dies \
+ * 		examples/kabi_ex0.o 2>&1 >/dev/null | \
+ * 	FileCheck examples/kabi_ex0.c --check-prefix=STABLE
+ *
+ * Verify that symbol versions match with --stable:
+ *
+ * $ echo -e "ex0a\nex0b\nex0c" | \
+ * 	./gendwarfksyms --stable examples/kabi_ex0.o | \
+ * 	sort | \
+ * 	FileCheck examples/kabi_ex0.c --check-prefix=VERSION
+ */
+
+#include "kabi.h"
+
+/*
+ * Example 0: Reserved fields.
+ */
+
+struct {
+	int a;
+	KABI_RESERVE(0);
+	KABI_RESERVE(1);
+} ex0a;
+
+/*
+ * STABLE:      variable structure_type {
+ * 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)
+ *
+ * VERSION-DAG: #SYMVER ex0a 0x[[#%.08x,EX0:]]
+ */
+
+struct {
+	int a;
+	KABI_RESERVE(0);
+	KABI_USE2(1, int b, int c);
+} ex0b;
+
+/*
+ * STABLE:      variable structure_type {
+ * 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)
+ *
+ * VERSION-DAG: #SYMVER ex0b 0x[[#%.08x,EX0]]
+ */
+
+struct {
+	int a;
+	KABI_USE(0, void *p);
+	KABI_USE2(1, int b, int c);
+} ex0c;
+
+/*
+ * STABLE:      variable structure_type {
+ * 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)
+ *
+ * VERSION-DAG: #SYMVER ex0c 0x[[#%.08x,EX0]]
+ */
diff --git a/scripts/gendwarfksyms/examples/kabi_ex1.c b/scripts/gendwarfksyms/examples/kabi_ex1.c
new file mode 100644
index 000000000000..7bc34bc7dec8
--- /dev/null
+++ b/scripts/gendwarfksyms/examples/kabi_ex1.c
@@ -0,0 +1,89 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * kabi_ex1.c
+ *
+ * Copyright (C) 2024 Google LLC
+ *
+ * Reserved and ignored data structure field examples with --stable.
+ */
+
+/*
+ * The comments below each example contain the expected gendwarfksyms
+ * output, which can be verified using LLVM's FileCheck tool:
+ *
+ * https://llvm.org/docs/CommandGuide/FileCheck.html
+ *
+ * $ gcc -g -c examples/kabi_ex1.c examples/kabi_ex1.o
+ *
+ * Verify --stable output:
+ *
+ * $ echo -e "ex1a\nex1b\nex1c" | \
+ * 	./gendwarfksyms --stable --dump-dies \
+ * 		examples/kabi_ex1.o 2>&1 >/dev/null | \
+ * 	FileCheck examples/kabi_ex1.c --check-prefix=STABLE
+ *
+ * Verify that symbol versions match with --stable:
+ *
+ * $ echo -e "ex1a\nex1b\nex1c" | \
+ * 	./gendwarfksyms --stable examples/kabi_ex1.o | \
+ * 	sort | \
+ * 	FileCheck examples/kabi_ex1.c --check-prefix=VERSION
+ */
+
+#include "kabi.h"
+
+/*
+ * Example 1: A reserved array.
+ */
+
+struct {
+	unsigned int a;
+	KABI_RESERVE_ARRAY(0, 64);
+} ex1a;
+
+/*
+ * STABLE:      variable structure_type {
+ * 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)
+ *
+ * VERSION-DAG: #SYMVER ex1a 0x[[#%.08x,EX1:]]
+ */
+
+struct {
+	unsigned int a;
+	KABI_USE_ARRAY(
+		0, 64, struct {
+			void *p;
+			KABI_RESERVE_ARRAY(1, 56);
+		});
+} ex1b;
+
+/*
+ * STABLE:      variable structure_type {
+ * 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)
+ *
+ * VERSION-DAG: #SYMVER ex1b 0x[[#%.08x,EX1]]
+ */
+
+struct {
+	unsigned int a;
+	KABI_USE_ARRAY(0, 64, void *p[8]);
+} ex1c;
+
+/*
+ * STABLE:      variable structure_type {
+ * 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)
+ *
+ * VERSION-DAG: #SYMVER ex1c 0x[[#%.08x,EX1]]
+ */
diff --git a/scripts/gendwarfksyms/examples/kabi_ex2.c b/scripts/gendwarfksyms/examples/kabi_ex2.c
new file mode 100644
index 000000000000..947ea5675b4f
--- /dev/null
+++ b/scripts/gendwarfksyms/examples/kabi_ex2.c
@@ -0,0 +1,98 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * kabi_ex2.c
+ *
+ * Copyright (C) 2024 Google LLC
+ *
+ * Reserved and ignored data structure field examples with --stable.
+ */
+
+/*
+ * The comments below each example contain the expected gendwarfksyms
+ * output, which can be verified using LLVM's FileCheck tool:
+ *
+ * https://llvm.org/docs/CommandGuide/FileCheck.html
+ *
+ * $ gcc -g -c examples/kabi_ex2.c examples/kabi_ex2.o
+ *
+ * Verify --stable output:
+ *
+ * $ echo -e "ex2a\nex2b\nex2c" | \
+ * 	./gendwarfksyms --stable --dump-dies \
+ * 		examples/kabi_ex2.o 2>&1 >/dev/null | \
+ * 	FileCheck examples/kabi_ex2.c --check-prefix=STABLE
+ *
+ * Verify that symbol versions match with --stable:
+ *
+ * $ echo -e "ex2a\nex2b\nex2c" | \
+ * 	./gendwarfksyms --stable examples/kabi_ex2.o | \
+ * 	sort | \
+ * 	FileCheck examples/kabi_ex2.c --check-prefix=VERSION
+ */
+
+#include "kabi.h"
+
+/*
+ * Example 2: An ignored field added to an alignment hole.
+ */
+
+struct {
+	int a;
+	unsigned long b;
+	int c;
+	unsigned long d;
+} ex2a;
+
+/*
+ * STABLE:      variable structure_type {
+ * 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)
+ *
+ * VERSION-DAG: #SYMVER ex2a 0x[[#%.08x,EX2:]]
+ */
+
+struct {
+	int a;
+	KABI_IGNORE(0, unsigned int n);
+	unsigned long b;
+	int c;
+	unsigned long d;
+} ex2b;
+
+_Static_assert(sizeof(ex2a) == sizeof(ex2b), "ex2a size doesn't match ex2b");
+
+/*
+ * STABLE:      variable structure_type {
+ * 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)
+ *
+ * VERSION-DAG: #SYMVER ex2b 0x[[#%.08x,EX2]]
+ */
+
+struct {
+	int a;
+	KABI_IGNORE(0, unsigned int n);
+	unsigned long b;
+	int c;
+	KABI_IGNORE(1, unsigned int m);
+	unsigned long d;
+} ex2c;
+
+_Static_assert(sizeof(ex2a) == sizeof(ex2c), "ex2a size doesn't match ex2c");
+
+/*
+ * STABLE:      variable structure_type {
+ * 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)
+ *
+ * VERSION-DAG: #SYMVER ex2c 0x[[#%.08x,EX2]]
+ */
diff --git a/scripts/gendwarfksyms/gendwarfksyms.h b/scripts/gendwarfksyms/gendwarfksyms.h
index f32ad4389b58..1cff868bacdb 100644
--- a/scripts/gendwarfksyms/gendwarfksyms.h
+++ b/scripts/gendwarfksyms/gendwarfksyms.h
@@ -222,6 +222,20 @@  void cache_clear_expanded(struct expansion_cache *ec);
 /*
  * dwarf.c
  */
+
+/* See dwarf.c: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_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);
+}
+
 struct expansion_state {
 	bool expand;
 	unsigned int ptr_depth;
@@ -229,6 +243,18 @@  struct expansion_state {
 	const char *current_fqn;
 };
 
+enum kabi_status {
+	/* >0 to stop DIE processing */
+	KABI_NORMAL = 1,
+	KABI_RESERVED,
+	KABI_IGNORED,
+};
+
+struct kabi_state {
+	int members;
+	Dwarf_Die placeholder;
+};
+
 struct state {
 	struct symbol *sym;
 	Dwarf_Die die;
@@ -239,6 +265,9 @@  struct state {
 	/* Structure expansion */
 	struct expansion_state expand;
 	struct expansion_cache expansion_cache;
+
+	/* Reserved or ignored members */
+	struct kabi_state kabi;
 };
 
 typedef int (*die_callback_t)(struct state *state, struct die *cache,