Message ID | 20241008183823.36676-36-samitolvanen@google.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | Implement DWARF modversions | expand |
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>
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 --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,