Commit 64e6c1e1 authored by Andreas Gruenbacher's avatar Andreas Gruenbacher Committed by Sam Ravnborg

genksyms: track symbol checksum changes

Sometimes it is preferable to avoid changes of exported symbol checksums
(to avoid breaking externally provided modules).  When a checksum change
occurs, it can be hard to figure out what caused this change: underlying
types may have changed, or additional type information may simply have
become available at the point where a symbol is exported.

Add a new --reference option to genksyms which allows it to report why
checksums change, based on the type information dumps it creates with the
--dump-types flag.  Genksyms will read in such a dump from a previous run,
and report which symbols have changed (and why).

The behavior can be controlled for an entire build as follows: If
KBUILD_SYMTYPES is set, genksyms uses --dump-types to produce *.symtypes
dump files.  If any *.symref files exist, those will be used as the
reference to check against.  If KBUILD_PRESERVE is set, checksum changes
will fail the build.
Signed-off-by: default avatarAndreas Gruenbacher <agruen@suse.de>
Cc: Randy Dunlap <randy.dunlap@oracle.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarSam Ravnborg <sam@ravnborg.org>
parent a680eedc
...@@ -153,12 +153,18 @@ $(obj)/%.i: $(src)/%.c FORCE ...@@ -153,12 +153,18 @@ $(obj)/%.i: $(src)/%.c FORCE
quiet_cmd_cc_symtypes_c = SYM $(quiet_modtag) $@ quiet_cmd_cc_symtypes_c = SYM $(quiet_modtag) $@
cmd_cc_symtypes_c = \ cmd_cc_symtypes_c = \
set -e; \
$(CPP) -D__GENKSYMS__ $(c_flags) $< \ $(CPP) -D__GENKSYMS__ $(c_flags) $< \
| $(GENKSYMS) -T $@ >/dev/null; \ | $(GENKSYMS) -T $@ \
-r $(firstword $(wildcard \
$(@:.symtypes=.symref) /dev/null)) \
$(if $(KBUILD_PRESERVE),-p) \
-a $(ARCH) \
>/dev/null; \
test -s $@ || rm -f $@ test -s $@ || rm -f $@
$(obj)/%.symtypes : $(src)/%.c FORCE $(obj)/%.symtypes : $(src)/%.c FORCE
$(call if_changed_dep,cc_symtypes_c) $(call cmd,cc_symtypes_c)
# C (.c) files # C (.c) files
# The C file is compiled and updated dependency information is generated. # The C file is compiled and updated dependency information is generated.
...@@ -187,7 +193,11 @@ cmd_modversions = \ ...@@ -187,7 +193,11 @@ cmd_modversions = \
if $(OBJDUMP) -h $(@D)/.tmp_$(@F) | grep -q __ksymtab; then \ if $(OBJDUMP) -h $(@D)/.tmp_$(@F) | grep -q __ksymtab; then \
$(CPP) -D__GENKSYMS__ $(c_flags) $< \ $(CPP) -D__GENKSYMS__ $(c_flags) $< \
| $(GENKSYMS) $(if $(KBUILD_SYMTYPES), \ | $(GENKSYMS) $(if $(KBUILD_SYMTYPES), \
-T $(@D)/$(@F:.o=.symtypes)) -a $(ARCH) \ -T $(@:.o=.symtypes)) \
-r $(firstword $(wildcard \
$(@:.o=.symref) /dev/null)) \
$(if $(KBUILD_PRESERVE),-p) \
-a $(ARCH) \
> $(@D)/.tmp_$(@F:.o=.ver); \ > $(@D)/.tmp_$(@F:.o=.ver); \
\ \
$(LD) $(LDFLAGS) -r -o $@ $(@D)/.tmp_$(@F) \ $(LD) $(LDFLAGS) -r -o $@ $(@D)/.tmp_$(@F) \
......
...@@ -42,7 +42,8 @@ static FILE *debugfile; ...@@ -42,7 +42,8 @@ static FILE *debugfile;
int cur_line = 1; int cur_line = 1;
char *cur_filename; char *cur_filename;
static int flag_debug, flag_dump_defs, flag_dump_types, flag_warnings; static int flag_debug, flag_dump_defs, flag_reference, flag_dump_types,
flag_preserve, flag_warnings;
static const char *arch = ""; static const char *arch = "";
static const char *mod_prefix = ""; static const char *mod_prefix = "";
...@@ -58,6 +59,8 @@ static const char *const symbol_type_name[] = { ...@@ -58,6 +59,8 @@ static const char *const symbol_type_name[] = {
static int equal_list(struct string_list *a, struct string_list *b); static int equal_list(struct string_list *a, struct string_list *b);
static void print_list(FILE * f, struct string_list *list); static void print_list(FILE * f, struct string_list *list);
static void print_location(void);
static void print_type_name(enum symbol_type type, const char *name);
/*----------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/
...@@ -151,25 +154,66 @@ struct symbol *find_symbol(const char *name, enum symbol_type ns) ...@@ -151,25 +154,66 @@ struct symbol *find_symbol(const char *name, enum symbol_type ns)
for (sym = symtab[h]; sym; sym = sym->hash_next) for (sym = symtab[h]; sym; sym = sym->hash_next)
if (map_to_ns(sym->type) == map_to_ns(ns) && if (map_to_ns(sym->type) == map_to_ns(ns) &&
strcmp(name, sym->name) == 0) strcmp(name, sym->name) == 0 &&
sym->is_declared)
break; break;
return sym; return sym;
} }
struct symbol *add_symbol(const char *name, enum symbol_type type, static int is_unknown_symbol(struct symbol *sym)
struct string_list *defn, int is_extern) {
struct string_list *defn;
return ((sym->type == SYM_STRUCT ||
sym->type == SYM_UNION ||
sym->type == SYM_ENUM) &&
(defn = sym->defn) && defn->tag == SYM_NORMAL &&
strcmp(defn->string, "}") == 0 &&
(defn = defn->next) && defn->tag == SYM_NORMAL &&
strcmp(defn->string, "UNKNOWN") == 0 &&
(defn = defn->next) && defn->tag == SYM_NORMAL &&
strcmp(defn->string, "{") == 0);
}
struct symbol *__add_symbol(const char *name, enum symbol_type type,
struct string_list *defn, int is_extern,
int is_reference)
{ {
unsigned long h = crc32(name) % HASH_BUCKETS; unsigned long h = crc32(name) % HASH_BUCKETS;
struct symbol *sym; struct symbol *sym;
enum symbol_status status = STATUS_UNCHANGED;
for (sym = symtab[h]; sym; sym = sym->hash_next) { for (sym = symtab[h]; sym; sym = sym->hash_next) {
if (map_to_ns(sym->type) == map_to_ns(type) if (map_to_ns(sym->type) == map_to_ns(type) &&
&& strcmp(name, sym->name) == 0) { strcmp(name, sym->name) == 0) {
if (!equal_list(sym->defn, defn)) if (is_reference)
/* fall through */ ;
else if (sym->type == type &&
equal_list(sym->defn, defn)) {
sym->is_declared = 1;
return sym;
} else if (!sym->is_declared) {
status = is_unknown_symbol(sym) ?
STATUS_DEFINED : STATUS_MODIFIED;
} else {
error_with_pos("redefinition of %s", name); error_with_pos("redefinition of %s", name);
return sym; return sym;
} }
break;
}
}
if (sym) {
struct symbol **psym;
for (psym = &symtab[h]; *psym; psym = &(*psym)->hash_next) {
if (*psym == sym) {
*psym = sym->hash_next;
break;
}
}
--nsyms;
} }
sym = xmalloc(sizeof(*sym)); sym = xmalloc(sizeof(*sym));
...@@ -183,6 +227,9 @@ struct symbol *add_symbol(const char *name, enum symbol_type type, ...@@ -183,6 +227,9 @@ struct symbol *add_symbol(const char *name, enum symbol_type type,
sym->hash_next = symtab[h]; sym->hash_next = symtab[h];
symtab[h] = sym; symtab[h] = sym;
sym->is_declared = !is_reference;
sym->status = status;
if (flag_debug) { if (flag_debug) {
fprintf(debugfile, "Defn for %s %s == <", fprintf(debugfile, "Defn for %s %s == <",
symbol_type_name[type], name); symbol_type_name[type], name);
...@@ -196,6 +243,18 @@ struct symbol *add_symbol(const char *name, enum symbol_type type, ...@@ -196,6 +243,18 @@ struct symbol *add_symbol(const char *name, enum symbol_type type,
return sym; return sym;
} }
struct symbol *add_symbol(const char *name, enum symbol_type type,
struct string_list *defn, int is_extern)
{
return __add_symbol(name, type, defn, is_extern, 0);
}
struct symbol *add_reference_symbol(const char *name, enum symbol_type type,
struct string_list *defn, int is_extern)
{
return __add_symbol(name, type, defn, is_extern, 1);
}
/*----------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/
void free_node(struct string_list *node) void free_node(struct string_list *node)
...@@ -236,6 +295,82 @@ static int equal_list(struct string_list *a, struct string_list *b) ...@@ -236,6 +295,82 @@ static int equal_list(struct string_list *a, struct string_list *b)
return !a && !b; return !a && !b;
} }
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
struct string_list *read_node(FILE *f)
{
char buffer[256];
struct string_list node = {
.string = buffer,
.tag = SYM_NORMAL };
int c;
while ((c = fgetc(f)) != EOF) {
if (c == ' ') {
if (node.string == buffer)
continue;
break;
} else if (c == '\n') {
if (node.string == buffer)
return NULL;
ungetc(c, f);
break;
}
if (node.string >= buffer + sizeof(buffer) - 1) {
fprintf(stderr, "Token too long\n");
exit(1);
}
*node.string++ = c;
}
if (node.string == buffer)
return NULL;
*node.string = 0;
node.string = buffer;
if (node.string[1] == '#') {
int n;
for (n = 0; n < ARRAY_SIZE(symbol_type_name); n++) {
if (node.string[0] == symbol_type_name[n][0]) {
node.tag = n;
node.string += 2;
return copy_node(&node);
}
}
fprintf(stderr, "Unknown type %c\n", node.string[0]);
exit(1);
}
return copy_node(&node);
}
static void read_reference(FILE *f)
{
while (!feof(f)) {
struct string_list *defn = NULL;
struct string_list *sym, *def;
int is_extern = 0;
sym = read_node(f);
if (!sym)
continue;
def = read_node(f);
if (def && def->tag == SYM_NORMAL &&
!strcmp(def->string, "extern")) {
is_extern = 1;
free_node(def);
def = read_node(f);
}
while (def) {
def->next = defn;
defn = def;
def = read_node(f);
}
add_reference_symbol(xstrdup(sym->string), sym->tag,
defn, is_extern);
free_node(sym);
}
}
static void print_node(FILE * f, struct string_list *list) static void print_node(FILE * f, struct string_list *list)
{ {
if (list->tag != SYM_NORMAL) { if (list->tag != SYM_NORMAL) {
...@@ -311,6 +446,7 @@ static unsigned long expand_and_crc_sym(struct symbol *sym, unsigned long crc) ...@@ -311,6 +446,7 @@ static unsigned long expand_and_crc_sym(struct symbol *sym, unsigned long crc)
case SYM_TYPEDEF: case SYM_TYPEDEF:
subsym = find_symbol(cur->string, cur->tag); subsym = find_symbol(cur->string, cur->tag);
/* FIXME: Bad reference files can segfault here. */
if (subsym->expansion_trail) { if (subsym->expansion_trail) {
if (flag_dump_defs) if (flag_dump_defs)
fprintf(debugfile, "%s ", cur->string); fprintf(debugfile, "%s ", cur->string);
...@@ -347,9 +483,22 @@ static unsigned long expand_and_crc_sym(struct symbol *sym, unsigned long crc) ...@@ -347,9 +483,22 @@ static unsigned long expand_and_crc_sym(struct symbol *sym, unsigned long crc)
t = n; t = n;
n = xmalloc(sizeof(*n)); n = xmalloc(sizeof(*n));
n->string = xstrdup("{ UNKNOWN }"); n->string = xstrdup("{");
n->tag = SYM_NORMAL;
n->next = t;
t = n;
n = xmalloc(sizeof(*n));
n->string = xstrdup("UNKNOWN");
n->tag = SYM_NORMAL;
n->next = t;
t = n;
n = xmalloc(sizeof(*n));
n->string = xstrdup("}");
n->tag = SYM_NORMAL; n->tag = SYM_NORMAL;
n->next = t; n->next = t;
t = n;
subsym = subsym =
add_symbol(cur->string, cur->tag, n, 0); add_symbol(cur->string, cur->tag, n, 0);
...@@ -397,20 +546,42 @@ void export_symbol(const char *name) ...@@ -397,20 +546,42 @@ void export_symbol(const char *name)
error_with_pos("export undefined symbol %s", name); error_with_pos("export undefined symbol %s", name);
else { else {
unsigned long crc; unsigned long crc;
int has_changed = 0;
if (flag_dump_defs) if (flag_dump_defs)
fprintf(debugfile, "Export %s == <", name); fprintf(debugfile, "Export %s == <", name);
expansion_trail = (struct symbol *)-1L; expansion_trail = (struct symbol *)-1L;
sym->expansion_trail = expansion_trail;
expansion_trail = sym;
crc = expand_and_crc_sym(sym, 0xffffffff) ^ 0xffffffff; crc = expand_and_crc_sym(sym, 0xffffffff) ^ 0xffffffff;
sym = expansion_trail; sym = expansion_trail;
while (sym != (struct symbol *)-1L) { while (sym != (struct symbol *)-1L) {
struct symbol *n = sym->expansion_trail; struct symbol *n = sym->expansion_trail;
if (sym->status != STATUS_UNCHANGED) {
if (!has_changed) {
print_location();
fprintf(stderr, "%s: %s: modversion "
"changed because of changes "
"in ", flag_preserve ? "error" :
"warning", name);
} else
fprintf(stderr, ", ");
print_type_name(sym->type, sym->name);
if (sym->status == STATUS_DEFINED)
fprintf(stderr, " (became defined)");
has_changed = 1;
if (flag_preserve)
errors++;
}
sym->expansion_trail = 0; sym->expansion_trail = 0;
sym = n; sym = n;
} }
if (has_changed)
fprintf(stderr, "\n");
if (flag_dump_defs) if (flag_dump_defs)
fputs(">\n", debugfile); fputs(">\n", debugfile);
...@@ -421,13 +592,26 @@ void export_symbol(const char *name) ...@@ -421,13 +592,26 @@ void export_symbol(const char *name)
} }
/*----------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/
static void print_location(void)
{
fprintf(stderr, "%s:%d: ", cur_filename ? : "<stdin>", cur_line);
}
static void print_type_name(enum symbol_type type, const char *name)
{
if (type != SYM_NORMAL)
fprintf(stderr, "%s %s", symbol_type_name[type], name);
else
fprintf(stderr, "%s", name);
}
void error_with_pos(const char *fmt, ...) void error_with_pos(const char *fmt, ...)
{ {
va_list args; va_list args;
if (flag_warnings) { if (flag_warnings) {
fprintf(stderr, "%s:%d: ", cur_filename ? : "<stdin>", print_location();
cur_line);
va_start(args, fmt); va_start(args, fmt);
vfprintf(stderr, fmt, args); vfprintf(stderr, fmt, args);
...@@ -445,7 +629,9 @@ static void genksyms_usage(void) ...@@ -445,7 +629,9 @@ static void genksyms_usage(void)
" -a, --arch Select architecture\n" " -a, --arch Select architecture\n"
" -d, --debug Increment the debug level (repeatable)\n" " -d, --debug Increment the debug level (repeatable)\n"
" -D, --dump Dump expanded symbol defs (for debugging only)\n" " -D, --dump Dump expanded symbol defs (for debugging only)\n"
" -T, --dump-types file Dump expanded types into file (for debugging only)\n" " -r, --reference file Read reference symbols from a file\n"
" -T, --dump-types file Dump expanded types into file\n"
" -p, --preserve Preserve reference modversions or fail\n"
" -w, --warnings Enable warnings\n" " -w, --warnings Enable warnings\n"
" -q, --quiet Disable warnings (default)\n" " -q, --quiet Disable warnings (default)\n"
" -h, --help Print this message\n" " -h, --help Print this message\n"
...@@ -454,7 +640,9 @@ static void genksyms_usage(void) ...@@ -454,7 +640,9 @@ static void genksyms_usage(void)
" -a Select architecture\n" " -a Select architecture\n"
" -d Increment the debug level (repeatable)\n" " -d Increment the debug level (repeatable)\n"
" -D Dump expanded symbol defs (for debugging only)\n" " -D Dump expanded symbol defs (for debugging only)\n"
" -T file Dump expanded types into file (for debugging only)\n" " -r file Read reference symbols from a file\n"
" -T file Dump expanded types into file\n"
" -p Preserve reference modversions or fail\n"
" -w Enable warnings\n" " -w Enable warnings\n"
" -q Disable warnings (default)\n" " -q Disable warnings (default)\n"
" -h Print this message\n" " -h Print this message\n"
...@@ -465,7 +653,7 @@ static void genksyms_usage(void) ...@@ -465,7 +653,7 @@ static void genksyms_usage(void)
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
FILE *dumpfile = NULL; FILE *dumpfile = NULL, *ref_file = NULL;
int o; int o;
#ifdef __GNU_LIBRARY__ #ifdef __GNU_LIBRARY__
...@@ -475,16 +663,18 @@ int main(int argc, char **argv) ...@@ -475,16 +663,18 @@ int main(int argc, char **argv)
{"warnings", 0, 0, 'w'}, {"warnings", 0, 0, 'w'},
{"quiet", 0, 0, 'q'}, {"quiet", 0, 0, 'q'},
{"dump", 0, 0, 'D'}, {"dump", 0, 0, 'D'},
{"reference", 1, 0, 'r'},
{"dump-types", 1, 0, 'T'}, {"dump-types", 1, 0, 'T'},
{"preserve", 0, 0, 'p'},
{"version", 0, 0, 'V'}, {"version", 0, 0, 'V'},
{"help", 0, 0, 'h'}, {"help", 0, 0, 'h'},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
while ((o = getopt_long(argc, argv, "a:dwqVDT:h", while ((o = getopt_long(argc, argv, "a:dwqVDr:T:ph",
&long_opts[0], NULL)) != EOF) &long_opts[0], NULL)) != EOF)
#else /* __GNU_LIBRARY__ */ #else /* __GNU_LIBRARY__ */
while ((o = getopt(argc, argv, "a:dwqVDT:h")) != EOF) while ((o = getopt(argc, argv, "a:dwqVDr:T:ph")) != EOF)
#endif /* __GNU_LIBRARY__ */ #endif /* __GNU_LIBRARY__ */
switch (o) { switch (o) {
case 'a': case 'a':
...@@ -505,6 +695,14 @@ int main(int argc, char **argv) ...@@ -505,6 +695,14 @@ int main(int argc, char **argv)
case 'D': case 'D':
flag_dump_defs = 1; flag_dump_defs = 1;
break; break;
case 'r':
flag_reference = 1;
ref_file = fopen(optarg, "r");
if (!ref_file) {
perror(optarg);
return 1;
}
break;
case 'T': case 'T':
flag_dump_types = 1; flag_dump_types = 1;
dumpfile = fopen(optarg, "w"); dumpfile = fopen(optarg, "w");
...@@ -513,6 +711,9 @@ int main(int argc, char **argv) ...@@ -513,6 +711,9 @@ int main(int argc, char **argv)
return 1; return 1;
} }
break; break;
case 'p':
flag_preserve = 1;
break;
case 'h': case 'h':
genksyms_usage(); genksyms_usage();
return 0; return 0;
...@@ -533,6 +734,9 @@ int main(int argc, char **argv) ...@@ -533,6 +734,9 @@ int main(int argc, char **argv)
/* setlinebuf(debugfile); */ /* setlinebuf(debugfile); */
} }
if (flag_reference)
read_reference(ref_file);
yyparse(); yyparse();
if (flag_dump_types && visited_symbols) { if (flag_dump_types && visited_symbols) {
......
...@@ -29,6 +29,10 @@ enum symbol_type { ...@@ -29,6 +29,10 @@ enum symbol_type {
SYM_NORMAL, SYM_TYPEDEF, SYM_ENUM, SYM_STRUCT, SYM_UNION SYM_NORMAL, SYM_TYPEDEF, SYM_ENUM, SYM_STRUCT, SYM_UNION
}; };
enum symbol_status {
STATUS_UNCHANGED, STATUS_DEFINED, STATUS_MODIFIED
};
struct string_list { struct string_list {
struct string_list *next; struct string_list *next;
enum symbol_type tag; enum symbol_type tag;
...@@ -43,6 +47,8 @@ struct symbol { ...@@ -43,6 +47,8 @@ struct symbol {
struct symbol *expansion_trail; struct symbol *expansion_trail;
struct symbol *visited; struct symbol *visited;
int is_extern; int is_extern;
int is_declared;
enum symbol_status status;
}; };
typedef struct string_list **yystype; typedef struct string_list **yystype;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment