Skip to content

Commit c3708cf

Browse files
committed
gendwarfksyms: Add support for reserved structure fields
Distributions that want to maintain a stable kABI need the ability to add reserved fields to kernel data structures that they anticipate will be modified during the ABI support timeframe, either by LTS updates or backports. With genksyms, developers would typically hide changes to the reserved fields from version calculation with #ifndef __GENKSYMS__, which would result in the symbol version not changing even though the actual type of the reserved field changes. When we process precompiled object files, this is again not an option. To support stable symbol versions for reserved fields, change the union type processing to recognize field name prefixes, and if the union contains a field name that starts with __kabi_reserved, only use the type of that field for computing symbol versions. In other words, let's assume we have a structure where we want to reserve space for future changes: struct struct1 { long a; long __kabi_reserved_0; /* reserved for future use */ }; struct struct1 exported; gendwarfksyms --debug produces the following output: variable structure_type struct1 { member base_type long int byte_size(8) encoding(5) data_member_location(0), member base_type long int byte_size(8) encoding(5) data_member_location(8), } byte_size(16); #SYMVER exported 0x67997f89 To take the reserved field into use, a distribution would replace it with a union, with one of the fields keeping the __kabi_reserved name prefix for the original type: struct struct1 { long a; union { long __kabi_reserved_0; struct { int b; int v; }; }; gendwarfksyms --debug now produces the following output: variable structure_type struct1 { member base_type long int byte_size(8) encoding(5) data_member_location(0), member union_type <unnamed> { member base_type long int byte_size(8) encoding(5), member structure_type <unnamed> { member base_type int byte_size(4) encoding(5) data_member_location(0), member base_type int byte_size(4) encoding(5) data_member_location(4), } byte_size(8), } byte_size(8) data_member_location(8), } byte_size(16); #SYMVER exported 0x66916c41 But with --stable, gendwarfksyms only uses the reserved field for the version calculation, thus leaving the symbol version unchanged: variable structure_type struct1 { member base_type long int byte_size(8) encoding(5) data_member_location(0), member base_type long int byte_size(8) encoding(5) data_member_location(8), } byte_size(16); #SYMVER exported 0x67997f89 Signed-off-by: Sami Tolvanen <samitolvanen@google.com>
1 parent d6de3e5 commit c3708cf

File tree

3 files changed

+229
-3
lines changed

3 files changed

+229
-3
lines changed

scripts/gendwarfksyms/dwarf.c

Lines changed: 145 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,12 @@ int process_die_container(struct state *state, struct die *cache,
274274

275275
res = checkp(dwarf_child(die, &current));
276276
while (!res) {
277-
if (match(&current))
278-
check(func(state, cache, &current));
277+
if (match(&current)) {
278+
/* <0 = error, 0 = continue, >0 = stop */
279+
res = checkp(func(state, cache, &current));
280+
if (res)
281+
return res;
282+
}
279283
res = checkp(dwarf_siblingof(&current, &current));
280284
}
281285

@@ -490,7 +494,145 @@ static int __process_structure_type(struct state *state, struct die *cache,
490494

491495
DEFINE_PROCESS_STRUCTURE_TYPE(class)
492496
DEFINE_PROCESS_STRUCTURE_TYPE(structure)
493-
DEFINE_PROCESS_STRUCTURE_TYPE(union)
497+
498+
static bool is_reserved_member(Dwarf_Die *die)
499+
{
500+
const char *name = get_name(die);
501+
502+
return name && !strncmp(name, RESERVED_PREFIX, RESERVED_PREFIX_LEN);
503+
}
504+
505+
static int __process_reserved_struct(struct state *state, struct die *cache,
506+
Dwarf_Die *die)
507+
{
508+
Dwarf_Die type;
509+
510+
/*
511+
* If the union member is a struct, expect the placeholder type to
512+
* be the first member, i.e.:
513+
*
514+
* union {
515+
* type replaced_member;
516+
* struct {
517+
* type placeholder; // <- type
518+
* }
519+
* };
520+
*
521+
* Stop processing if this member isn't reserved.
522+
*/
523+
if (!is_reserved_member(die))
524+
return NOT_RESERVED;
525+
526+
if (!get_ref_die_attr(die, DW_AT_type, &type)) {
527+
error("structure member missing a type?");
528+
return -1;
529+
}
530+
531+
check(process_type(state, cache, &type));
532+
return RESERVED;
533+
}
534+
535+
static int __process_reserved_union(struct state *state, struct die *cache,
536+
Dwarf_Die *die)
537+
{
538+
int res = NOT_RESERVED;
539+
Dwarf_Die type;
540+
541+
if (!get_ref_die_attr(die, DW_AT_type, &type)) {
542+
error("union member missing a type?");
543+
return -1;
544+
}
545+
546+
/*
547+
* We expect a union with two members. Check if either of them
548+
* has the reserved name prefix, i.e.:
549+
*
550+
* union {
551+
* ...
552+
* type memberN; // <- type, N = {0,1}
553+
* ...
554+
* };
555+
*
556+
* The member can also be a structure type, in which case we'll
557+
* check the first structure member.
558+
*
559+
* Stop processing after we've seen two members.
560+
*/
561+
if (is_reserved_member(die)) {
562+
check(process_type(state, cache, &type));
563+
return RESERVED;
564+
}
565+
566+
if (dwarf_tag(&type) == DW_TAG_structure_type)
567+
res = checkp(process_die_container(state, cache, &type,
568+
__process_reserved_struct,
569+
match_member_type));
570+
if (res != RESERVED && ++state->reserved.members < 2)
571+
return 0; /* Continue */
572+
573+
return res;
574+
}
575+
576+
static int process_reserved(struct state *state, struct die *cache,
577+
Dwarf_Die *die)
578+
{
579+
if (!stable)
580+
return NOT_RESERVED;
581+
582+
/*
583+
* To maintain a stable kABI, distributions may choose to reserve
584+
* space in structs for later use by adding placeholder members,
585+
* for example:
586+
*
587+
* struct s {
588+
* u64 a;
589+
* // placeholder
590+
* u64 __kabi_reserved_0;
591+
* };
592+
*
593+
* When the reserved member is taken into use, the type change
594+
* would normally cause the symbol version to change as well, but
595+
* if the replacement uses the following convention, gendwarfksyms
596+
* continues to use the placeholder type for versioning instead,
597+
* thus maintaining the same symbol version:
598+
*
599+
* struct s {
600+
* u64 a;
601+
* union {
602+
* // replaced member
603+
* struct t b;
604+
* struct {
605+
* // original placeholder
606+
* u64 __kabi_reserved_0;
607+
* };
608+
* };
609+
* };
610+
*
611+
* I.e., as long as the replaced member is in a union, and the
612+
* placeholder has a __kabi_reserved name prefix, we'll continue
613+
* to use the placeholder type (here u64) for version calculation
614+
* instead of the union type.
615+
*
616+
* Note that the user is responsible for ensuring that the
617+
* replacement type is ABI compatible with the placeholder type.
618+
*/
619+
state->reserved.members = 0;
620+
621+
return checkp(process_die_container(state, cache, die,
622+
__process_reserved_union,
623+
match_member_type));
624+
}
625+
626+
static int process_union_type(struct state *state, struct die *cache,
627+
Dwarf_Die *die)
628+
{
629+
if (checkp(process_reserved(state, cache, die)) == RESERVED)
630+
return 0;
631+
632+
return check(__process_structure_type(state, cache, die, "union_type ",
633+
___process_structure_type,
634+
match_all));
635+
}
494636

495637
static int process_enumerator_type(struct state *state, struct die *cache,
496638
Dwarf_Die *die)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (C) 2024 Google LLC
4+
*
5+
* Reserved data structure field example. See dwarf.c:process_reserved
6+
* for details.
7+
*
8+
* $ gcc -g -c examples/reserved.c
9+
*
10+
* With --stable, only the reserved field placeholder is used for calculating
11+
* symbol versions.
12+
*
13+
* $ echo exported0 | ./gendwarfksyms --stable --dump-dies reserved.o
14+
* variable structure_type struct0 {
15+
* member base_type int byte_size(4) encoding(5) data_member_location(0),
16+
* member base_type long int byte_size(8) encoding(5) data_member_location(8),
17+
* } byte_size(16)
18+
*
19+
* $ echo exported1 | ./gendwarfksyms --stable --dump-dies reserved.o
20+
* variable structure_type struct1 {
21+
* member base_type int byte_size(4) encoding(5) data_member_location(0),
22+
* member base_type long int byte_size(8) encoding(5) data_member_location(8),
23+
* } byte_size(16)
24+
*
25+
* $ echo exported2 | ./gendwarfksyms --stable --dump-dies reserved.o
26+
* variable structure_type struct2 {
27+
* member base_type int byte_size(4) encoding(5) data_member_location(0),
28+
* member base_type long int byte_size(8) encoding(5) data_member_location(8),
29+
* } byte_size(16)
30+
*/
31+
32+
struct struct0 {
33+
int a;
34+
union {
35+
long __kabi_reserved_0;
36+
struct {
37+
int b;
38+
int c;
39+
};
40+
};
41+
};
42+
43+
struct struct1 {
44+
int a;
45+
union {
46+
struct {
47+
int b;
48+
int c;
49+
};
50+
long __kabi_reserved_1;
51+
};
52+
};
53+
54+
struct struct2 {
55+
int a;
56+
union {
57+
unsigned long b;
58+
struct {
59+
long __kabi_reserved_1;
60+
};
61+
};
62+
};
63+
64+
struct struct0 exported0;
65+
struct struct1 exported1;
66+
struct struct2 exported2;

scripts/gendwarfksyms/gendwarfksyms.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,27 @@ extern void cache_clear_expanded(struct expansion_cache *ec);
220220
/*
221221
* dwarf.c
222222
*/
223+
224+
/* See dwarf.c:process_reserved */
225+
#define RESERVED_PREFIX "__kabi_reserved"
226+
#define RESERVED_PREFIX_LEN (sizeof(RESERVED_PREFIX) - 1)
227+
223228
struct expansion_state {
224229
bool expand;
225230
bool in_pointer_type;
226231
unsigned int ptr_expansion_depth;
227232
};
228233

234+
enum reserved_status {
235+
/* >0 to stop DIE processing */
236+
NOT_RESERVED = 1,
237+
RESERVED
238+
};
239+
240+
struct reserved_state {
241+
int members;
242+
};
243+
229244
struct state {
230245
Dwfl_Module *mod;
231246
Dwarf *dbg;
@@ -235,6 +250,9 @@ struct state {
235250
/* Structure expansion */
236251
struct expansion_state expand;
237252
struct expansion_cache expansion_cache;
253+
254+
/* Reserved members */
255+
struct reserved_state reserved;
238256
};
239257

240258
typedef int (*die_callback_t)(struct state *state, struct die *cache,

0 commit comments

Comments
 (0)