// 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 <objtool/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; char *sym; bool mod_ns;}; bool debug, debug_correlate, debug_clone;int indent; 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 all debug output"), OPT_BOOLEAN(0, "debug-correlate", &debug_correlate, "enable correlation debug output"), OPT_BOOLEAN(0, "debug-clone", &debug_clone, "enable cloning debug output"), OPT_END(),}; static DEFINE_HASHTABLE(exports, 15); 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;} /* * Convert a build-tree object path to a runtime module name: strip * directory components, replace '-' with '_', and remove file * extensions. Examples: * * "arch/x86/kvm/kvm" -> "kvm" * "arch/x86/kvm/kvm-intel" -> "kvm_intel". * * Used by read_exports() to normalize Module.symvers entries and by * __find_modname() as a fallback when .modinfo lacks a "name=" tag. */static char *normalize_modname(char *name){ char *slash = strrchr(name, '/'); if (slash) name = slash + 1; for (char *c = name; *c; c++) { if (*c == '-') *c = '_'; else if (*c == '.') { *c = '\0'; break; } } return name;} static int read_exports(void){ const char *symvers = "Module.symvers"; char line[1024], *path = NULL; unsigned int line_num = 0; 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, *namespace; struct export *export; line_num++; 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'; namespace = strchr(type, '\t'); if (!namespace) { ERROR("malformed Module.symvers (namespace) at line %d", line_num); return -1; } *namespace++ = '\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; } if (strcmp(export->mod, "vmlinux")) export->mod = normalize_modname(export->mod); export->sym = strdup(sym); if (!export->sym) { ERROR_GLIBC("strdup"); return -1; } /* EXPORT_SYMBOL_FOR_MODULES() */ export->mod_ns = strstarts(namespace, "module:"); 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 klp 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) || is_object_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", }; const char *dot; if (!is_object_sym(sym) || !is_local_sym(sym)) return false; /* WARN_ONCE, etc */ if (!strcmp(sym->sec->name, ".data..once")) return true; dot = strchr(sym->name, '.'); if (!dot) return false; for (int i = 0; i < ARRAY_SIZE(vars); i++) { size_t len = strlen(vars[i]); /* GCC: <var>.<id> */ if (strstarts(sym->name, vars[i]) && (sym->name[len] == '.')) return true; /* Clang: <func>.<var>[.<id>] */ if (strstarts(dot + 1, vars[i]) && (dot[1 + len] == '.' || dot[1 + len] == '\0')) return true; } return false;} /* * .L symbols are assembler-local labels not present in kallsyms. They must * never become KLP relocations; instead their data is cloned into the patch * module. This covers .Ltmp* (Clang temp labels), .L__const.* (Clang local * constants), and any other assembler-local pattern. */static bool is_local_label(struct symbol *sym){ return strstarts(sym->name, ".L");} static bool is_special_section(struct section *sec){ static const char * const specials[] = { ".altinstructions", ".kcfi_traps", "__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;} /* * Symbols created by ___ADDRESSABLE() are only used to convince the toolchain * not to optimize out the referenced symbol. */static bool is_addressable_sym(struct symbol *sym){ return !strcmp(sym->sec->name, ".discard.addressable");} /* * ABS symbols are typically assembly .set/.equ constants which are never * referenced by relocations. (Exclude FILE symbols which are also SHN_ABS.) */static bool is_abs_sym(struct symbol *sym){ return sym->sym.st_shndx == SHN_ABS && !is_file_sym(sym);} static bool is_initcall_sym(struct symbol *sym){ return strstarts(sym->name, "__initcall__") || strstarts(sym->name, "__initstub__");} /* * Some .rodata is anonymous and can't be correlated due to there being no * symbol names. * * The .rodata.cst* sections aren't technically anonymous, they're SHF_MERGE * constant pool sections containing small fixed-size data (lookup tables, * bitmasks) which are only read by value, so pointer equivalence isn't needed. * They are typically referenced by UBSAN data sections. */static bool is_anonymous_rodata(struct symbol *sym){ return is_rodata_sec(sym->sec) && (!is_object_sym(sym) || strstarts(sym->sec->name, ".rodata.cst"));} /* * 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_abs_sym(sym) || is_prefix_func(sym) || is_uncorrelated_static_local(sym) || is_local_label(sym) || is_string_sec(sym->sec) || is_anonymous_rodata(sym) || is_initcall_sym(sym) || is_addressable_sym(sym) || is_special_section(sym->sec) || is_special_section_aux(sym->sec);} static const char *llvm_suffix(const char *name){ return strstr(name, ".llvm.");} static bool is_llvm_sym(struct symbol *sym){ return llvm_suffix(sym->name);} /* * Determine if two symbols have compatible source file origins: * * - If both symbols are local, only return true if they belong to the same * ELF file symbol. * * - If both symbols are global, always return true, as globals don't have * file associations. * * - If they have different scopes, also return true, as the patch might have * changed the symbol's scope. * * Works for both same-ELF (direct pointer compare) and cross-ELF * (compare via file->twin) cases. */static bool maybe_same_file(struct symbol *sym1, struct symbol *sym2){ if (!sym1->file || !sym2->file) return true; if (sym1->file == sym2->file) return true; return sym1->file->twin == sym2->file;} /* * Similar to maybe_same_file(), but strict: no scope changes allowed. * * Works for both same-ELF (direct pointer compare) and cross-ELF * (compare via file->twin) cases.Lines 1–500 of 2294. 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 <objtool/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; char *sym; bool mod_ns; }; bool debug, debug_correlate, debug_clone; int indent; 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 all debug output"), OPT_BOOLEAN(0, "debug-correlate", &debug_correlate, "enable correlation debug output"), OPT_BOOLEAN(0, "debug-clone", &debug_clone, "enable cloning debug output"), OPT_END(), }; static DEFINE_HASHTABLE(exports, 15); 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; } /* * Convert a build-tree object path to a runtime module name: strip * directory components, replace '-' with '_', and remove file * extensions. Examples: * * "arch/x86/kvm/kvm" -> "kvm" * "arch/x86/kvm/kvm-intel" -> "kvm_intel". * * Used by read_exports() to normalize Module.symvers entries and by * __find_modname() as a fallback when .modinfo lacks a "name=" tag. */ static char *normalize_modname(char *name) { char *slash = strrchr(name, '/'); if (slash) name = slash + 1; for (char *c = name; *c; c++) { if (*c == '-') *c = '_'; else if (*c == '.') { *c = '\0'; break; } } return name; } static int read_exports(void) { const char *symvers = "Module.symvers"; char line[1024], *path = NULL; unsigned int line_num = 0; 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, *namespace; struct export *export; line_num++; 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'; namespace = strchr(type, '\t'); if (!namespace) { ERROR("malformed Module.symvers (namespace) at line %d", line_num); return -1; } *namespace++ = '\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; } if (strcmp(export->mod, "vmlinux")) export->mod = normalize_modname(export->mod); export->sym = strdup(sym); if (!export->sym) { ERROR_GLIBC("strdup"); return -1; } /* EXPORT_SYMBOL_FOR_MODULES() */ export->mod_ns = strstarts(namespace, "module:"); 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 klp 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) || is_object_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", }; const char *dot; if (!is_object_sym(sym) || !is_local_sym(sym)) return false; /* WARN_ONCE, etc */ if (!strcmp(sym->sec->name, ".data..once")) return true; dot = strchr(sym->name, '.'); if (!dot) return false; for (int i = 0; i < ARRAY_SIZE(vars); i++) { size_t len = strlen(vars[i]); /* GCC: <var>.<id> */ if (strstarts(sym->name, vars[i]) && (sym->name[len] == '.')) return true; /* Clang: <func>.<var>[.<id>] */ if (strstarts(dot + 1, vars[i]) && (dot[1 + len] == '.' || dot[1 + len] == '\0')) return true; } return false; } /* * .L symbols are assembler-local labels not present in kallsyms. They must * never become KLP relocations; instead their data is cloned into the patch * module. This covers .Ltmp* (Clang temp labels), .L__const.* (Clang local * constants), and any other assembler-local pattern. */ static bool is_local_label(struct symbol *sym) { return strstarts(sym->name, ".L"); } static bool is_special_section(struct section *sec) { static const char * const specials[] = { ".altinstructions", ".kcfi_traps", "__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; } /* * Symbols created by ___ADDRESSABLE() are only used to convince the toolchain * not to optimize out the referenced symbol. */ static bool is_addressable_sym(struct symbol *sym) { return !strcmp(sym->sec->name, ".discard.addressable"); } /* * ABS symbols are typically assembly .set/.equ constants which are never * referenced by relocations. (Exclude FILE symbols which are also SHN_ABS.) */ static bool is_abs_sym(struct symbol *sym) { return sym->sym.st_shndx == SHN_ABS && !is_file_sym(sym); } static bool is_initcall_sym(struct symbol *sym) { return strstarts(sym->name, "__initcall__") || strstarts(sym->name, "__initstub__"); } /* * Some .rodata is anonymous and can't be correlated due to there being no * symbol names. * * The .rodata.cst* sections aren't technically anonymous, they're SHF_MERGE * constant pool sections containing small fixed-size data (lookup tables, * bitmasks) which are only read by value, so pointer equivalence isn't needed. * They are typically referenced by UBSAN data sections. */ static bool is_anonymous_rodata(struct symbol *sym) { return is_rodata_sec(sym->sec) && (!is_object_sym(sym) || strstarts(sym->sec->name, ".rodata.cst")); } /* * 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_abs_sym(sym) || is_prefix_func(sym) || is_uncorrelated_static_local(sym) || is_local_label(sym) || is_string_sec(sym->sec) || is_anonymous_rodata(sym) || is_initcall_sym(sym) || is_addressable_sym(sym) || is_special_section(sym->sec) || is_special_section_aux(sym->sec); } static const char *llvm_suffix(const char *name) { return strstr(name, ".llvm."); } static bool is_llvm_sym(struct symbol *sym) { return llvm_suffix(sym->name); } /* * Determine if two symbols have compatible source file origins: * * - If both symbols are local, only return true if they belong to the same * ELF file symbol. * * - If both symbols are global, always return true, as globals don't have * file associations. * * - If they have different scopes, also return true, as the patch might have * changed the symbol's scope. * * Works for both same-ELF (direct pointer compare) and cross-ELF * (compare via file->twin) cases. */ static bool maybe_same_file(struct symbol *sym1, struct symbol *sym2) { if (!sym1->file || !sym2->file) return true; if (sym1->file == sym2->file) return true; return sym1->file->twin == sym2->file; } /* * Similar to maybe_same_file(), but strict: no scope changes allowed. * * Works for both same-ELF (direct pointer compare) and cross-ELF * (compare via file->twin) cases.