Skip to content

Commit d6de3e5

Browse files
committed
gendwarfksyms: Add support for declaration-only data structures
If a source file refers to an opaque data structure, the DWARF debugging information in the resulting object file will only contain a structure declation, which means the contents of the structure are not included when computing symbol versions. For example: Source code: struct struct0; int func(struct struct0 *); gendwarfksyms --debug: subprogram( formal_parameter pointer_type <unnamed> { structure_type struct0 { <declaration> } } byte_size(8), ) -> base_type int byte_size(4) encoding(5); #SYMVER func 0x7e8284f9 The declaration can change into a full definition when an additional include is added to the TU, which changes the version CRC, even though the ABI has not changed. If this happens during an LTS update, a distribution that wants to maintain a stable ABI needs a way to ensure symbol versions remain unchanged. With genksyms, the usual solution is to use #ifndef __GENKSYMS__ to skip the newly added header file when computing symbol versions, but that's not an option when we're processing a precompiled object file. To support this use case, add a --stable command line flag that gates kABI stability features that are not needed in mainline, but can be useful for distributions, and add support for symbol annotations that allow structures to always be treated as declarations when versions are computed. If a __gendwarfksyms_declonly_<structname> symbol exists in the object file's symbol table, the "structname" structure is treated as a declaration only, and not expanded when computing versions. The symbol can be defined using the following macro, for example, which discards it from the final kernel binary: #define GENDWARFKSYMS_DECLONLY(structname) \ static void *__gendwarfksyms_declonly_##structname __used \ __section(".discard.gendwarfksyms") Now, if we include struct0 definition in our source code, and add a declaration-only annotation, we have: struct struct0 { int a; }; GENDWARFKSYMS_DECLONLY(struct0); int func(struct struct0 *); gendwarfksyms --debug reflects the added definition: subprogram( formal_parameter pointer_type <unnamed> { structure_type struct0 { member base_type int byte_size(4) encoding(5) data_member_location(0), } byte_size(4) } byte_size(8), ) -> base_type int byte_size(4) encoding(5); #SYMVER func 0xc0de983d But with --debug --stable, the definition is ignored and we again have the original symbol version: subprogram( formal_parameter pointer_type <unnamed> { structure_type struct0 { <declaration> } } byte_size(8), ) -> base_type int byte_size(4) encoding(5); #SYMVER func 0x7e8284f9 Signed-off-by: Sami Tolvanen <samitolvanen@google.com>
1 parent 774aab6 commit d6de3e5

File tree

5 files changed

+124
-1
lines changed

5 files changed

+124
-1
lines changed

scripts/gendwarfksyms/dwarf.c

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,30 @@ static bool is_declaration(Dwarf_Die *die)
7474
{
7575
bool value;
7676

77-
return get_flag_attr(die, DW_AT_declaration, &value) && value;
77+
/*
78+
* If the object file has a __gendwarfksyms_declonly_<structname>
79+
* symbol, we treat structures named "structname" as declarations,
80+
* i.e. they won't be expanded when calculating symbol versions.
81+
* This helps distributions maintain a stable kABI e.g., if extra
82+
* includes change a declaration into a definition.
83+
*
84+
* A simple way to mark a structure declaration-only in the source
85+
* code is to define a discarded symbol:
86+
*
87+
* #define GENDWARFKSYMS_DECLONLY(structname) \
88+
* static void *__gendwarfksyms_declonly_##structname __used \
89+
* __section(".discard.gendwarfksyms")
90+
*
91+
* For example:
92+
*
93+
* struct struct0 { int a; }
94+
* GENDWARFKSYMS_DECLONLY(struct0);
95+
*
96+
* Here, struct0 would be always be considered a declaration even
97+
* though the definition is visible.
98+
*/
99+
return (get_flag_attr(die, DW_AT_declaration, &value) && value) ||
100+
is_struct_declonly(get_name(die));
78101
}
79102

80103
/*
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (C) 2024 Google LLC
4+
*
5+
* Declaration-only data structure example. See dwarf.c:is_declaration
6+
* for details.
7+
*
8+
* $ gcc -g -c examples/declonly.c
9+
* $ echo exported | ./gendwarfksyms --dump-dies declonly.o
10+
* variable structure_type struct0 {
11+
* member base_type int byte_size(4) encoding(5) data_member_location(0),
12+
* } byte_size(4)
13+
*
14+
* With --stable, struct0 is treated as a declaration:
15+
*
16+
* $ echo exported | ./gendwarfksyms --stable --dump-dies declonly.o
17+
* variable structure_type struct0 {
18+
* }
19+
*/
20+
21+
#define GENDWARFKSYMS_DECLONLY(structname) \
22+
static void *__gendwarfksyms_declonly_##structname \
23+
__attribute__((__used__)) \
24+
__attribute__((__section__(".discard.gendwarfksyms")));
25+
26+
struct struct0 {
27+
int a;
28+
};
29+
30+
struct struct0 exported;
31+
GENDWARFKSYMS_DECLONLY(struct0);

scripts/gendwarfksyms/gendwarfksyms.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ bool dump_die_map;
2424
bool dump_types;
2525
/* Print out expanded type strings used for version calculations */
2626
bool dump_versions;
27+
/* Support kABI stability features */
28+
bool stable;
2729
/* Produce a symtypes file */
2830
bool symtypes;
2931
static const char *symtypes_file;
@@ -38,6 +40,7 @@ static const struct {
3840
{ "--dump-die-map", &dump_die_map, NULL },
3941
{ "--dump-types", &dump_types, NULL },
4042
{ "--dump-versions", &dump_versions, NULL },
43+
{ "--stable", &stable, NULL },
4144
{ "--symtypes", &symtypes, &symtypes_file },
4245
};
4346

@@ -186,6 +189,7 @@ int main(int argc, const char **argv)
186189

187190
dwfl_end(dwfl);
188191
close(fd);
192+
symbol_free_declonly();
189193
}
190194

191195
if (symfile)

scripts/gendwarfksyms/gendwarfksyms.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ extern bool dump_dies;
2424
extern bool dump_die_map;
2525
extern bool dump_types;
2626
extern bool dump_versions;
27+
extern bool stable;
2728
extern bool symtypes;
2829

2930
#define MAX_INPUT_FILES 128
@@ -134,6 +135,9 @@ extern int symbol_set_crc(struct symbol *sym, unsigned long crc);
134135
extern int symbol_for_each(symbol_callback_t func, void *arg);
135136
extern void symbol_print_versions(void);
136137

138+
extern bool is_struct_declonly(const char *name);
139+
extern void symbol_free_declonly(void);
140+
137141
/*
138142
* die.c
139143
*/

scripts/gendwarfksyms/symbols.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,20 @@
55

66
#include "gendwarfksyms.h"
77

8+
struct declonly {
9+
const char *name;
10+
struct hlist_node hash;
11+
};
12+
813
#define SYMBOL_HASH_BITS 15
14+
#define DECLONLY_HASH_BITS 10
915

1016
/* struct symbol_addr -> struct symbol */
1117
static DEFINE_HASHTABLE(symbol_addrs, SYMBOL_HASH_BITS);
1218
/* name -> struct symbol */
1319
static DEFINE_HASHTABLE(symbol_names, SYMBOL_HASH_BITS);
20+
/* name -> struct declonly */
21+
static DEFINE_HASHTABLE(declonly_structs, DECLONLY_HASH_BITS);
1422

1523
static inline u32 symbol_addr_hash(const struct symbol_addr *addr)
1624
{
@@ -296,12 +304,36 @@ static int process_symbol(const char *name, GElf_Sym *sym, Elf32_Word xndx,
296304
void *arg)
297305
{
298306
struct symbol_addr addr = { .section = xndx, .address = sym->st_value };
307+
struct declonly *d;
299308

300309
/* Set addresses for exported symbols */
301310
if (GELF_ST_BIND(sym->st_info) != STB_LOCAL &&
302311
addr.section != SHN_UNDEF)
303312
checkp(for_each(name, true, set_symbol_addr, &addr));
304313

314+
if (!stable)
315+
return 0;
316+
317+
/* Process declonly structs */
318+
if (strncmp(name, SYMBOL_DECLONLY_PREFIX, SYMBOL_DECLONLY_PREFIX_LEN))
319+
return 0;
320+
321+
d = malloc(sizeof(struct declonly));
322+
if (!d) {
323+
error("malloc failed");
324+
return -1;
325+
}
326+
327+
name += SYMBOL_DECLONLY_PREFIX_LEN;
328+
d->name = strdup(name);
329+
if (!d->name) {
330+
error("strdup failed");
331+
return -1;
332+
}
333+
334+
hash_add(declonly_structs, &d->hash, name_hash(d->name));
335+
debug("declaration-only: %s", d->name);
336+
305337
return 0;
306338
}
307339

@@ -310,6 +342,35 @@ int symbol_read_symtab(int fd)
310342
return elf_for_each_symbol(fd, process_symbol, NULL);
311343
}
312344

345+
bool is_struct_declonly(const char *name)
346+
{
347+
struct declonly *d;
348+
349+
if (!stable || !name)
350+
return false;
351+
352+
hash_for_each_possible(declonly_structs, d, hash, name_hash(name)) {
353+
if (!strcmp(name, d->name))
354+
return true;
355+
}
356+
357+
return false;
358+
}
359+
360+
void symbol_free_declonly(void)
361+
{
362+
struct hlist_node *tmp;
363+
struct declonly *d;
364+
int i;
365+
366+
hash_for_each_safe(declonly_structs, i, tmp, d, hash) {
367+
free((void *)d->name);
368+
free(d);
369+
}
370+
371+
hash_init(declonly_structs);
372+
}
373+
313374
void symbol_print_versions(void)
314375
{
315376
struct hlist_node *tmp;

0 commit comments

Comments
 (0)