Skip to content

Commit 1a067da

Browse files
committed
gendwarfksyms: Limit structure expansion
Expand each structure type only once per exported symbol. This is necessary to support self-referential structures, which would otherwise result in infinite recursion, but is still sufficient for catching ABI changes. For pointers to structure types, limit type expansion inside the pointer to two levels. This should be plenty for detecting ABI differences, but it stops us from pulling in half the kernel for types that contain pointers to large kernel data structures, like task_struct, for example. Signed-off-by: Sami Tolvanen <samitolvanen@google.com>
1 parent 3669dfd commit 1a067da

File tree

4 files changed

+189
-9
lines changed

4 files changed

+189
-9
lines changed

scripts/gendwarfksyms/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
hostprogs-always-y += gendwarfksyms
22

33
gendwarfksyms-objs += gendwarfksyms.o
4+
gendwarfksyms-objs += cache.o
45
gendwarfksyms-objs += die.o
56
gendwarfksyms-objs += dwarf.o
67
gendwarfksyms-objs += symbols.o

scripts/gendwarfksyms/cache.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (C) 2024 Google LLC
4+
*/
5+
6+
#include "gendwarfksyms.h"
7+
8+
struct expanded {
9+
uintptr_t addr;
10+
struct hlist_node hash;
11+
};
12+
13+
int __cache_mark_expanded(struct expansion_cache *ec, uintptr_t addr)
14+
{
15+
struct expanded *es;
16+
17+
es = malloc(sizeof(struct expanded));
18+
if (!es) {
19+
error("malloc failed");
20+
return -1;
21+
}
22+
23+
es->addr = addr;
24+
hash_add(ec->cache, &es->hash, addr_hash(addr));
25+
return 0;
26+
}
27+
28+
bool __cache_was_expanded(struct expansion_cache *ec, uintptr_t addr)
29+
{
30+
struct expanded *es;
31+
32+
hash_for_each_possible(ec->cache, es, hash, addr_hash(addr)) {
33+
if (es->addr == addr)
34+
return true;
35+
}
36+
37+
return false;
38+
}
39+
40+
void cache_clear_expanded(struct expansion_cache *ec)
41+
{
42+
struct hlist_node *tmp;
43+
struct expanded *es;
44+
int i;
45+
46+
hash_for_each_safe(ec->cache, i, tmp, es, hash) {
47+
free(es);
48+
}
49+
50+
hash_init(ec->cache);
51+
}

scripts/gendwarfksyms/dwarf.c

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ static int process_linebreak(struct die *cache, int n)
2525
!dwarf_form##attr(&da, value); \
2626
}
2727

28+
DEFINE_GET_ATTR(flag, bool)
2829
DEFINE_GET_ATTR(udata, Dwarf_Word)
2930

3031
static bool get_ref_die_attr(Dwarf_Die *die, unsigned int id, Dwarf_Die *value)
@@ -69,6 +70,13 @@ static bool is_export_symbol(struct state *state, Dwarf_Die *die)
6970
return !!state->sym;
7071
}
7172

73+
static bool is_declaration(Dwarf_Die *die)
74+
{
75+
bool value;
76+
77+
return get_flag_attr(die, DW_AT_declaration, &value) && value;
78+
}
79+
7280
/*
7381
* Type string processing
7482
*/
@@ -421,19 +429,26 @@ static int __process_structure_type(struct state *state, struct die *cache,
421429
die_callback_t process_func,
422430
die_match_callback_t match_func)
423431
{
432+
bool is_decl = is_declaration(die);
433+
424434
check(process(state, cache, type));
425435
check(process_fqn(state, cache, die));
426436
check(process(state, cache, " {"));
427437
check(process_linebreak(cache, 1));
428438

429-
check(process_die_container(state, cache, die, process_func,
430-
match_func));
439+
if (!is_decl && state->expand.expand) {
440+
check(cache_mark_expanded(&state->expansion_cache, die->addr));
441+
check(process_die_container(state, cache, die, process_func,
442+
match_func));
443+
}
431444

432445
check(process_linebreak(cache, -1));
433446
check(process(state, cache, "}"));
434447

435-
check(process_byte_size_attr(state, cache, die));
436-
check(process_alignment_attr(state, cache, die));
448+
if (!is_decl && state->expand.expand) {
449+
check(process_byte_size_attr(state, cache, die));
450+
check(process_alignment_attr(state, cache, die));
451+
}
437452

438453
return 0;
439454
}
@@ -519,25 +534,99 @@ static int process_cached(struct state *state, struct die *cache,
519534
return 0;
520535
}
521536

537+
static void state_init(struct state *state)
538+
{
539+
state->expand.expand = true;
540+
state->expand.in_pointer_type = false;
541+
state->expand.ptr_expansion_depth = 0;
542+
hash_init(state->expansion_cache.cache);
543+
}
544+
545+
static void expansion_state_restore(struct expansion_state *state,
546+
struct expansion_state *saved)
547+
{
548+
state->ptr_expansion_depth = saved->ptr_expansion_depth;
549+
state->in_pointer_type = saved->in_pointer_type;
550+
state->expand = saved->expand;
551+
}
552+
553+
static void expansion_state_save(struct expansion_state *state,
554+
struct expansion_state *saved)
555+
{
556+
expansion_state_restore(saved, state);
557+
}
558+
559+
static bool is_pointer_type(int tag)
560+
{
561+
return tag == DW_TAG_pointer_type || tag == DW_TAG_reference_type;
562+
}
563+
564+
static bool is_expanded_type(int tag)
565+
{
566+
return tag == DW_TAG_class_type || tag == DW_TAG_structure_type ||
567+
tag == DW_TAG_union_type || tag == DW_TAG_enumeration_type;
568+
}
569+
570+
/* The maximum depth for expanding structures in pointers */
571+
#define MAX_POINTER_EXPANSION_DEPTH 2
572+
522573
#define PROCESS_TYPE(type) \
523574
case DW_TAG_##type##_type: \
524575
check(process_##type##_type(state, cache, die)); \
525576
break;
526577

527578
static int process_type(struct state *state, struct die *parent, Dwarf_Die *die)
528579
{
580+
enum die_state want_state = COMPLETE;
529581
struct die *cache = NULL;
582+
struct expansion_state saved;
530583
int tag = dwarf_tag(die);
531584

585+
expansion_state_save(&state->expand, &saved);
586+
532587
/*
533-
* If we have the DIE already cached, use it instead of walking
588+
* Structures and enumeration types are expanded only once per
589+
* exported symbol. This is sufficient for detecting ABI changes
590+
* within the structure.
591+
*
592+
* If the exported symbol contains a pointer to a structure,
593+
* at most MAX_POINTER_EXPANSION_DEPTH levels are expanded into
594+
* the referenced structure.
595+
*/
596+
state->expand.in_pointer_type = saved.in_pointer_type ||
597+
is_pointer_type(tag);
598+
599+
if (state->expand.in_pointer_type &&
600+
state->expand.ptr_expansion_depth >= MAX_POINTER_EXPANSION_DEPTH)
601+
state->expand.expand = false;
602+
else
603+
state->expand.expand =
604+
saved.expand &&
605+
!cache_was_expanded(&state->expansion_cache, die->addr);
606+
607+
/* Keep track of pointer expansion depth */
608+
if (state->expand.expand && state->expand.in_pointer_type &&
609+
is_expanded_type(tag))
610+
state->expand.ptr_expansion_depth++;
611+
612+
/*
613+
* If we have want_state already cached, use it instead of walking
534614
* through DWARF.
535615
*/
536-
check(die_map_get(die, COMPLETE, &cache));
616+
if (!state->expand.expand && is_expanded_type(tag))
617+
want_state = UNEXPANDED;
618+
619+
check(die_map_get(die, want_state, &cache));
620+
621+
if (cache->state == want_state) {
622+
if (want_state == COMPLETE && is_expanded_type(tag))
623+
check(cache_mark_expanded(&state->expansion_cache,
624+
die->addr));
537625

538-
if (cache->state == COMPLETE) {
539626
check(process_cached(state, cache, die));
540627
check(die_map_add_die(parent, cache));
628+
629+
expansion_state_restore(&state->expand, &saved);
541630
return 0;
542631
}
543632

@@ -578,9 +667,10 @@ static int process_type(struct state *state, struct die *parent, Dwarf_Die *die)
578667

579668
/* Update cache state and append to the parent (if any) */
580669
cache->tag = tag;
581-
cache->state = COMPLETE;
670+
cache->state = want_state;
582671
check(die_map_add_die(parent, cache));
583672

673+
expansion_state_restore(&state->expand, &saved);
584674
return 0;
585675
}
586676

@@ -643,6 +733,7 @@ static int process_exported_symbols(struct state *state, struct die *cache,
643733
return 0;
644734

645735
debug("%s", state->sym->name);
736+
state_init(state);
646737

647738
if (is_symbol_ptr(get_name(&state->die)))
648739
check(process_symbol_ptr(state, &state->die));
@@ -651,6 +742,7 @@ static int process_exported_symbols(struct state *state, struct die *cache,
651742
else
652743
check(process_variable(state, &state->die));
653744

745+
cache_clear_expanded(&state->expansion_cache);
654746
return 0;
655747
default:
656748
return 0;

scripts/gendwarfksyms/gendwarfksyms.h

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ extern struct symbol *symbol_get(const char *name);
106106
* die.c
107107
*/
108108

109-
enum die_state { INCOMPLETE, COMPLETE, LAST = COMPLETE };
109+
enum die_state { INCOMPLETE, UNEXPANDED, COMPLETE, LAST = COMPLETE };
110110
enum die_fragment_type { EMPTY, STRING, LINEBREAK, DIE };
111111

112112
struct die_fragment {
@@ -128,6 +128,7 @@ static inline const char *die_state_name(enum die_state state)
128128
switch (state) {
129129
default:
130130
CASE_CONST_TO_STR(INCOMPLETE)
131+
CASE_CONST_TO_STR(UNEXPANDED)
131132
CASE_CONST_TO_STR(COMPLETE)
132133
}
133134
}
@@ -150,15 +151,50 @@ extern int die_map_add_linebreak(struct die *pd, int linebreak);
150151
extern int die_map_add_die(struct die *pd, struct die *child);
151152
extern void die_map_free(void);
152153

154+
/*
155+
* cache.c
156+
*/
157+
158+
#define EXPANSION_CACHE_HASH_BITS 11
159+
160+
/* A cache for addresses we've already seen. */
161+
struct expansion_cache {
162+
DECLARE_HASHTABLE(cache, EXPANSION_CACHE_HASH_BITS);
163+
};
164+
165+
extern int __cache_mark_expanded(struct expansion_cache *ec, uintptr_t addr);
166+
extern bool __cache_was_expanded(struct expansion_cache *ec, uintptr_t addr);
167+
168+
static inline int cache_mark_expanded(struct expansion_cache *ec, void *addr)
169+
{
170+
return __cache_mark_expanded(ec, (uintptr_t)addr);
171+
}
172+
173+
static inline bool cache_was_expanded(struct expansion_cache *ec, void *addr)
174+
{
175+
return __cache_was_expanded(ec, (uintptr_t)addr);
176+
}
177+
178+
extern void cache_clear_expanded(struct expansion_cache *ec);
179+
153180
/*
154181
* dwarf.c
155182
*/
183+
struct expansion_state {
184+
bool expand;
185+
bool in_pointer_type;
186+
unsigned int ptr_expansion_depth;
187+
};
156188

157189
struct state {
158190
Dwfl_Module *mod;
159191
Dwarf *dbg;
160192
struct symbol *sym;
161193
Dwarf_Die die;
194+
195+
/* Structure expansion */
196+
struct expansion_state expand;
197+
struct expansion_cache expansion_cache;
162198
};
163199

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

0 commit comments

Comments
 (0)