// SPDX-License-Identifier: GPL-2.0-or-later#define _GNU_SOURCE /* memmem() */#include <subcmd/parse-options.h>#include <stdlib.h>#include <string.h>#include <libgen.h>#include <stdio.h>#include <ctype.h> #include <objtool/objtool.h>#include <objtool/warn.h>#include <objtool/arch.h>#include <objtool/klp.h>#include <objtool/util.h>#include <arch/special.h> #include <linux/align.h>#include <linux/objtool_types.h>#include <linux/livepatch_external.h>#include <linux/stringify.h>#include <linux/string.h>#include <linux/jhash.h> #define sizeof_field(TYPE, MEMBER) sizeof((((TYPE *)0)->MEMBER)) struct elfs { struct elf *orig, *patched, *out; const char *modname;}; struct export { struct hlist_node hash; char *mod, *sym;}; static const char * const klp_diff_usage[] = { "objtool klp diff [<options>] <in1.o> <in2.o> <out.o>", NULL,}; static const struct option klp_diff_options[] = { OPT_GROUP("Options:"), OPT_BOOLEAN('d', "debug", &debug, "enable debug output"), OPT_END(),}; static DEFINE_HASHTABLE(exports, 15); static inline u32 str_hash(const char *str){ return jhash(str, strlen(str), 0);} static char *escape_str(const char *orig){ size_t len = 0; const char *a; char *b, *new; for (a = orig; *a; a++) { switch (*a) { case '\001': len += 5; break; case '\n': case '\t': len += 2; break; default: len++; } } new = malloc(len + 1); if (!new) return NULL; for (a = orig, b = new; *a; a++) { switch (*a) { case '\001': memcpy(b, "<SOH>", 5); b += 5; break; case '\n': *b++ = '\\'; *b++ = 'n'; break; case '\t': *b++ = '\\'; *b++ = 't'; break; default: *b++ = *a; } } *b = '\0'; return new;} static int read_exports(void){ const char *symvers = "Module.symvers"; char line[1024], *path = NULL; unsigned int line_num = 1; FILE *file; file = fopen(symvers, "r"); if (!file) { path = top_level_dir(symvers); if (!path) { ERROR("can't open '%s', \"objtool diff\" should be run from the kernel tree", symvers); return -1; } file = fopen(path, "r"); if (!file) { ERROR_GLIBC("fopen"); return -1; } } while (fgets(line, 1024, file)) { char *sym, *mod, *type; struct export *export; sym = strchr(line, '\t'); if (!sym) { ERROR("malformed Module.symvers (sym) at line %d", line_num); return -1; } *sym++ = '\0'; mod = strchr(sym, '\t'); if (!mod) { ERROR("malformed Module.symvers (mod) at line %d", line_num); return -1; } *mod++ = '\0'; type = strchr(mod, '\t'); if (!type) { ERROR("malformed Module.symvers (type) at line %d", line_num); return -1; } *type++ = '\0'; if (*sym == '\0' || *mod == '\0') { ERROR("malformed Module.symvers at line %d", line_num); return -1; } export = calloc(1, sizeof(*export)); if (!export) { ERROR_GLIBC("calloc"); return -1; } export->mod = strdup(mod); if (!export->mod) { ERROR_GLIBC("strdup"); return -1; } export->sym = strdup(sym); if (!export->sym) { ERROR_GLIBC("strdup"); return -1; } hash_add(exports, &export->hash, str_hash(sym)); } free(path); fclose(file); return 0;} static int read_sym_checksums(struct elf *elf){ struct section *sec; sec = find_section_by_name(elf, ".discard.sym_checksum"); if (!sec) { ERROR("'%s' missing .discard.sym_checksum section, file not processed by 'objtool --checksum'?", elf->name); return -1; } if (!sec->rsec) { ERROR("missing reloc section for .discard.sym_checksum"); return -1; } if (sec_size(sec) % sizeof(struct sym_checksum)) { ERROR("struct sym_checksum size mismatch"); return -1; } for (int i = 0; i < sec_size(sec) / sizeof(struct sym_checksum); i++) { struct sym_checksum *sym_checksum; struct reloc *reloc; struct symbol *sym; sym_checksum = (struct sym_checksum *)sec->data->d_buf + i; reloc = find_reloc_by_dest(elf, sec, i * sizeof(*sym_checksum)); if (!reloc) { ERROR("can't find reloc for sym_checksum[%d]", i); return -1; } sym = reloc->sym; if (is_sec_sym(sym)) { ERROR("not sure how to handle section %s", sym->name); return -1; } if (is_func_sym(sym)) sym->csum.checksum = sym_checksum->checksum; } return 0;} static struct symbol *first_file_symbol(struct elf *elf){ struct symbol *sym; for_each_sym(elf, sym) { if (is_file_sym(sym)) return sym; } return NULL;} static struct symbol *next_file_symbol(struct elf *elf, struct symbol *sym){ for_each_sym_continue(elf, sym) { if (is_file_sym(sym)) return sym; } return NULL;} /* * Certain static local variables should never be correlated. They will be * used in place rather than referencing the originals. */static bool is_uncorrelated_static_local(struct symbol *sym){ static const char * const vars[] = { "__already_done.", "__func__.", "__key.", "__warned.", "_entry.", "_entry_ptr.", "_rs.", "descriptor.", "CSWTCH.", }; if (!is_object_sym(sym) || !is_local_sym(sym)) return false; if (!strcmp(sym->sec->name, ".data.once")) return true; for (int i = 0; i < ARRAY_SIZE(vars); i++) { if (strstarts(sym->name, vars[i])) return true; } return false;} /* * Clang emits several useless .Ltmp_* code labels. */static bool is_clang_tmp_label(struct symbol *sym){ return sym->type == STT_NOTYPE && is_text_sec(sym->sec) && strstarts(sym->name, ".Ltmp") && isdigit(sym->name[5]);} static bool is_special_section(struct section *sec){ static const char * const specials[] = { ".altinstructions", ".smp_locks", "__bug_table", "__ex_table", "__jump_table", "__mcount_loc", /* * Extract .static_call_sites here to inherit non-module * preferential treatment. The later static call processing * during klp module build will be skipped when it sees this * section already exists. */ ".static_call_sites", }; static const char * const non_special_discards[] = { ".discard.addressable", ".discard.sym_checksum", }; if (is_text_sec(sec)) return false; for (int i = 0; i < ARRAY_SIZE(specials); i++) { if (!strcmp(sec->name, specials[i])) return true; } /* Most .discard data sections are special */ for (int i = 0; i < ARRAY_SIZE(non_special_discards); i++) { if (!strcmp(sec->name, non_special_discards[i])) return false; } return strstarts(sec->name, ".discard.");} /* * These sections are referenced by special sections but aren't considered * special sections themselves. */static bool is_special_section_aux(struct section *sec){ static const char * const specials_aux[] = { ".altinstr_replacement", ".altinstr_aux", }; for (int i = 0; i < ARRAY_SIZE(specials_aux); i++) { if (!strcmp(sec->name, specials_aux[i])) return true; } return false;} /* * These symbols should never be correlated, so their local patched versions * are used instead of linking to the originals. */static bool dont_correlate(struct symbol *sym){ return is_file_sym(sym) || is_null_sym(sym) || is_sec_sym(sym) || is_prefix_func(sym) || is_uncorrelated_static_local(sym) || is_clang_tmp_label(sym) || is_string_sec(sym->sec) || is_special_section(sym->sec) || is_special_section_aux(sym->sec) || strstarts(sym->name, "__initcall__");} /* * For each symbol in the original kernel, find its corresponding "twin" in the * patched kernel. */static int correlate_symbols(struct elfs *e){ struct symbol *file1_sym, *file2_sym; struct symbol *sym1, *sym2; file1_sym = first_file_symbol(e->orig); file2_sym = first_file_symbol(e->patched); /* * Correlate any locals before the first FILE symbol. This has been * seen when LTO inexplicably strips the initramfs_data.o FILE symbol * due to the file only containing data and no code. */ for_each_sym(e->orig, sym1) { if (sym1 == file1_sym || !is_local_sym(sym1)) break; if (dont_correlate(sym1)) continue; for_each_sym(e->patched, sym2) { if (sym2 == file2_sym || !is_local_sym(sym2)) break; if (sym2->twin || dont_correlate(sym2)) continue; if (strcmp(sym1->demangled_name, sym2->demangled_name)) continue; sym1->twin = sym2; sym2->twin = sym1; break; } } /* Correlate locals after the first FILE symbol */ for (; ; file1_sym = next_file_symbol(e->orig, file1_sym), file2_sym = next_file_symbol(e->patched, file2_sym)) { if (!file1_sym && file2_sym) { ERROR("FILE symbol mismatch: NULL != %s", file2_sym->name); return -1; } if (file1_sym && !file2_sym) { ERROR("FILE symbol mismatch: %s != NULL", file1_sym->name); return -1; } if (!file1_sym) break; if (strcmp(file1_sym->name, file2_sym->name)) { ERROR("FILE symbol mismatch: %s != %s", file1_sym->name, file2_sym->name); return -1; } file1_sym->twin = file2_sym; file2_sym->twin = file1_sym; sym1 = file1_sym; for_each_sym_continue(e->orig, sym1) { if (is_file_sym(sym1) || !is_local_sym(sym1)) break; if (dont_correlate(sym1)) continue; sym2 = file2_sym; for_each_sym_continue(e->patched, sym2) { if (is_file_sym(sym2) || !is_local_sym(sym2)) break; if (sym2->twin || dont_correlate(sym2)) continue; if (strcmp(sym1->demangled_name, sym2->demangled_name)) continue; sym1->twin = sym2; sym2->twin = sym1; break; } } } /* Correlate globals */ for_each_sym(e->orig, sym1) { if (sym1->bind == STB_LOCAL) continue; sym2 = find_global_symbol_by_name(e->patched, sym1->name); if (sym2 && !sym2->twin && !strcmp(sym1->name, sym2->name)) { sym1->twin = sym2; sym2->twin = sym1; } } for_each_sym(e->orig, sym1) { if (sym1->twin || dont_correlate(sym1)) continue; WARN("no correlation: %s", sym1->name); } return 0;} /* "sympos" is used by livepatch to disambiguate duplicate symbol names */static unsigned long find_sympos(struct elf *elf, struct symbol *sym){ bool vmlinux = str_ends_with(objname, "vmlinux.o"); unsigned long sympos = 0, nr_matches = 0; bool has_dup = false; struct symbol *s; if (sym->bind != STB_LOCAL) return 0; if (vmlinux && sym->type == STT_FUNC) { /* * HACK: Unfortunately, symbol ordering can differ between * vmlinux.o and vmlinux due to the linker script emitting * .text.unlikely* before .text*. Count .text.unlikely* first. * * TODO: Disambiguate symbols more reliably (checksums?) */ for_each_sym(elf, s) { if (strstarts(s->sec->name, ".text.unlikely") && !strcmp(s->name, sym->name)) { nr_matches++; if (s == sym) sympos = nr_matches; else has_dup = true; }Lines 1–500 of 1774. Load more
// SPDX-License-Identifier: GPL-2.0-or-later #define _GNU_SOURCE /* memmem() */ #include <subcmd/parse-options.h> #include <stdlib.h> #include <string.h> #include <libgen.h> #include <stdio.h> #include <ctype.h> #include <objtool/objtool.h> #include <objtool/warn.h> #include <objtool/arch.h> #include <objtool/klp.h> #include <objtool/util.h> #include <arch/special.h> #include <linux/align.h> #include <linux/objtool_types.h> #include <linux/livepatch_external.h> #include <linux/stringify.h> #include <linux/string.h> #include <linux/jhash.h> #define sizeof_field(TYPE, MEMBER) sizeof((((TYPE *)0)->MEMBER)) struct elfs { struct elf *orig, *patched, *out; const char *modname; }; struct export { struct hlist_node hash; char *mod, *sym; }; static const char * const klp_diff_usage[] = { "objtool klp diff [<options>] <in1.o> <in2.o> <out.o>", NULL, }; static const struct option klp_diff_options[] = { OPT_GROUP("Options:"), OPT_BOOLEAN('d', "debug", &debug, "enable debug output"), OPT_END(), }; static DEFINE_HASHTABLE(exports, 15); static inline u32 str_hash(const char *str) { return jhash(str, strlen(str), 0); } static char *escape_str(const char *orig) { size_t len = 0; const char *a; char *b, *new; for (a = orig; *a; a++) { switch (*a) { case '\001': len += 5; break; case '\n': case '\t': len += 2; break; default: len++; } } new = malloc(len + 1); if (!new) return NULL; for (a = orig, b = new; *a; a++) { switch (*a) { case '\001': memcpy(b, "<SOH>", 5); b += 5; break; case '\n': *b++ = '\\'; *b++ = 'n'; break; case '\t': *b++ = '\\'; *b++ = 't'; break; default: *b++ = *a; } } *b = '\0'; return new; } static int read_exports(void) { const char *symvers = "Module.symvers"; char line[1024], *path = NULL; unsigned int line_num = 1; FILE *file; file = fopen(symvers, "r"); if (!file) { path = top_level_dir(symvers); if (!path) { ERROR("can't open '%s', \"objtool diff\" should be run from the kernel tree", symvers); return -1; } file = fopen(path, "r"); if (!file) { ERROR_GLIBC("fopen"); return -1; } } while (fgets(line, 1024, file)) { char *sym, *mod, *type; struct export *export; sym = strchr(line, '\t'); if (!sym) { ERROR("malformed Module.symvers (sym) at line %d", line_num); return -1; } *sym++ = '\0'; mod = strchr(sym, '\t'); if (!mod) { ERROR("malformed Module.symvers (mod) at line %d", line_num); return -1; } *mod++ = '\0'; type = strchr(mod, '\t'); if (!type) { ERROR("malformed Module.symvers (type) at line %d", line_num); return -1; } *type++ = '\0'; if (*sym == '\0' || *mod == '\0') { ERROR("malformed Module.symvers at line %d", line_num); return -1; } export = calloc(1, sizeof(*export)); if (!export) { ERROR_GLIBC("calloc"); return -1; } export->mod = strdup(mod); if (!export->mod) { ERROR_GLIBC("strdup"); return -1; } export->sym = strdup(sym); if (!export->sym) { ERROR_GLIBC("strdup"); return -1; } hash_add(exports, &export->hash, str_hash(sym)); } free(path); fclose(file); return 0; } static int read_sym_checksums(struct elf *elf) { struct section *sec; sec = find_section_by_name(elf, ".discard.sym_checksum"); if (!sec) { ERROR("'%s' missing .discard.sym_checksum section, file not processed by 'objtool --checksum'?", elf->name); return -1; } if (!sec->rsec) { ERROR("missing reloc section for .discard.sym_checksum"); return -1; } if (sec_size(sec) % sizeof(struct sym_checksum)) { ERROR("struct sym_checksum size mismatch"); return -1; } for (int i = 0; i < sec_size(sec) / sizeof(struct sym_checksum); i++) { struct sym_checksum *sym_checksum; struct reloc *reloc; struct symbol *sym; sym_checksum = (struct sym_checksum *)sec->data->d_buf + i; reloc = find_reloc_by_dest(elf, sec, i * sizeof(*sym_checksum)); if (!reloc) { ERROR("can't find reloc for sym_checksum[%d]", i); return -1; } sym = reloc->sym; if (is_sec_sym(sym)) { ERROR("not sure how to handle section %s", sym->name); return -1; } if (is_func_sym(sym)) sym->csum.checksum = sym_checksum->checksum; } return 0; } static struct symbol *first_file_symbol(struct elf *elf) { struct symbol *sym; for_each_sym(elf, sym) { if (is_file_sym(sym)) return sym; } return NULL; } static struct symbol *next_file_symbol(struct elf *elf, struct symbol *sym) { for_each_sym_continue(elf, sym) { if (is_file_sym(sym)) return sym; } return NULL; } /* * Certain static local variables should never be correlated. They will be * used in place rather than referencing the originals. */ static bool is_uncorrelated_static_local(struct symbol *sym) { static const char * const vars[] = { "__already_done.", "__func__.", "__key.", "__warned.", "_entry.", "_entry_ptr.", "_rs.", "descriptor.", "CSWTCH.", }; if (!is_object_sym(sym) || !is_local_sym(sym)) return false; if (!strcmp(sym->sec->name, ".data.once")) return true; for (int i = 0; i < ARRAY_SIZE(vars); i++) { if (strstarts(sym->name, vars[i])) return true; } return false; } /* * Clang emits several useless .Ltmp_* code labels. */ static bool is_clang_tmp_label(struct symbol *sym) { return sym->type == STT_NOTYPE && is_text_sec(sym->sec) && strstarts(sym->name, ".Ltmp") && isdigit(sym->name[5]); } static bool is_special_section(struct section *sec) { static const char * const specials[] = { ".altinstructions", ".smp_locks", "__bug_table", "__ex_table", "__jump_table", "__mcount_loc", /* * Extract .static_call_sites here to inherit non-module * preferential treatment. The later static call processing * during klp module build will be skipped when it sees this * section already exists. */ ".static_call_sites", }; static const char * const non_special_discards[] = { ".discard.addressable", ".discard.sym_checksum", }; if (is_text_sec(sec)) return false; for (int i = 0; i < ARRAY_SIZE(specials); i++) { if (!strcmp(sec->name, specials[i])) return true; } /* Most .discard data sections are special */ for (int i = 0; i < ARRAY_SIZE(non_special_discards); i++) { if (!strcmp(sec->name, non_special_discards[i])) return false; } return strstarts(sec->name, ".discard."); } /* * These sections are referenced by special sections but aren't considered * special sections themselves. */ static bool is_special_section_aux(struct section *sec) { static const char * const specials_aux[] = { ".altinstr_replacement", ".altinstr_aux", }; for (int i = 0; i < ARRAY_SIZE(specials_aux); i++) { if (!strcmp(sec->name, specials_aux[i])) return true; } return false; } /* * These symbols should never be correlated, so their local patched versions * are used instead of linking to the originals. */ static bool dont_correlate(struct symbol *sym) { return is_file_sym(sym) || is_null_sym(sym) || is_sec_sym(sym) || is_prefix_func(sym) || is_uncorrelated_static_local(sym) || is_clang_tmp_label(sym) || is_string_sec(sym->sec) || is_special_section(sym->sec) || is_special_section_aux(sym->sec) || strstarts(sym->name, "__initcall__"); } /* * For each symbol in the original kernel, find its corresponding "twin" in the * patched kernel. */ static int correlate_symbols(struct elfs *e) { struct symbol *file1_sym, *file2_sym; struct symbol *sym1, *sym2; file1_sym = first_file_symbol(e->orig); file2_sym = first_file_symbol(e->patched); /* * Correlate any locals before the first FILE symbol. This has been * seen when LTO inexplicably strips the initramfs_data.o FILE symbol * due to the file only containing data and no code. */ for_each_sym(e->orig, sym1) { if (sym1 == file1_sym || !is_local_sym(sym1)) break; if (dont_correlate(sym1)) continue; for_each_sym(e->patched, sym2) { if (sym2 == file2_sym || !is_local_sym(sym2)) break; if (sym2->twin || dont_correlate(sym2)) continue; if (strcmp(sym1->demangled_name, sym2->demangled_name)) continue; sym1->twin = sym2; sym2->twin = sym1; break; } } /* Correlate locals after the first FILE symbol */ for (; ; file1_sym = next_file_symbol(e->orig, file1_sym), file2_sym = next_file_symbol(e->patched, file2_sym)) { if (!file1_sym && file2_sym) { ERROR("FILE symbol mismatch: NULL != %s", file2_sym->name); return -1; } if (file1_sym && !file2_sym) { ERROR("FILE symbol mismatch: %s != NULL", file1_sym->name); return -1; } if (!file1_sym) break; if (strcmp(file1_sym->name, file2_sym->name)) { ERROR("FILE symbol mismatch: %s != %s", file1_sym->name, file2_sym->name); return -1; } file1_sym->twin = file2_sym; file2_sym->twin = file1_sym; sym1 = file1_sym; for_each_sym_continue(e->orig, sym1) { if (is_file_sym(sym1) || !is_local_sym(sym1)) break; if (dont_correlate(sym1)) continue; sym2 = file2_sym; for_each_sym_continue(e->patched, sym2) { if (is_file_sym(sym2) || !is_local_sym(sym2)) break; if (sym2->twin || dont_correlate(sym2)) continue; if (strcmp(sym1->demangled_name, sym2->demangled_name)) continue; sym1->twin = sym2; sym2->twin = sym1; break; } } } /* Correlate globals */ for_each_sym(e->orig, sym1) { if (sym1->bind == STB_LOCAL) continue; sym2 = find_global_symbol_by_name(e->patched, sym1->name); if (sym2 && !sym2->twin && !strcmp(sym1->name, sym2->name)) { sym1->twin = sym2; sym2->twin = sym1; } } for_each_sym(e->orig, sym1) { if (sym1->twin || dont_correlate(sym1)) continue; WARN("no correlation: %s", sym1->name); } return 0; } /* "sympos" is used by livepatch to disambiguate duplicate symbol names */ static unsigned long find_sympos(struct elf *elf, struct symbol *sym) { bool vmlinux = str_ends_with(objname, "vmlinux.o"); unsigned long sympos = 0, nr_matches = 0; bool has_dup = false; struct symbol *s; if (sym->bind != STB_LOCAL) return 0; if (vmlinux && sym->type == STT_FUNC) { /* * HACK: Unfortunately, symbol ordering can differ between * vmlinux.o and vmlinux due to the linker script emitting * .text.unlikely* before .text*. Count .text.unlikely* first. * * TODO: Disambiguate symbols more reliably (checksums?) */ for_each_sym(elf, s) { if (strstarts(s->sec->name, ".text.unlikely") && !strcmp(s->name, sym->name)) { nr_matches++; if (s == sym) sympos = nr_matches; else has_dup = true; }