diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-18 12:18:33 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-18 12:18:33 -0700 |
| commit | a0acd94e3819fcd8346ae3c16df987e0fabb3128 (patch) | |
| tree | 42b1fd69eac025cc5883778d63cbe055393b182c | |
| parent | 1200d84f4c0a929a0780180d25063d93773be79c (diff) | |
| parent | d8a2860b4a366bfa8acb3d64da2c546ea26d2091 (diff) | |
Merge tag 'objtool-core-2026-08-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull objtool updates from Ingo Molnar:
- Fix various klp-build bugs reported by Joe Lawrence (Josh Poimboeuf,
Joe Lawrence)
- Misc fixes and cleanups (Puranjay Mohan, Thomas Huth and Ingo Molnar)
* tag 'objtool-core-2026-08-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
objtool/klp: Fix vmlinux klp relocations for EXPORT_SYMBOL_FOR_MODULES()
objtool/klp: Fix .kcfi_traps special section extraction
objtool/klp: Fix vmlinux .klp.symid link error for .exitcall.exit symbols
objtool/klp: Fix line numbers in Module.symvers parse errors
objtool/klp: Fix relocations for EXPORT_SYMBOL_FOR_MODULES() symbols
objtool/klp: Allow new references to module exports
objtool/klp: Don't match local symbols against exports
objtool/klp: Fix cross-module klp relocation section naming
objtool/klp: Explicitly disallow patching or referencing init code/data
objtool/klp: Ignore replacement offset of empty x86 alternatives
objtool/klp: Fix size of empty special section entries
objtool/klp: Fix vmlinux .klp.symid link error for .no_trim_symbol symbols
objtool/headers: Sync tools/include/linux/objtool_types.h with include/linux/objtool_types.h
objtool: Replace __ASSEMBLY__ with __ASSEMBLER__ in header files
objtool/klp: Fix symbol resolution for duplicate data symbols
objtool/klp: Add .klp.symid for sympos disambiguation
objtool/klp: Skip hidden directories when finding objects
objtool/klp: Fix false module dependencies caused by dead relocs
objtool/klp: Normalize Module.symvers paths to module names
objtool/klp: Fix module name normalization for paths with dots
| -rw-r--r-- | include/asm-generic/vmlinux.lds.h | 10 | ||||
| -rw-r--r-- | include/linux/objtool.h | 8 | ||||
| -rw-r--r-- | include/linux/objtool_types.h | 4 | ||||
| -rw-r--r-- | scripts/Makefile.vmlinux_o | 3 | ||||
| -rwxr-xr-x | scripts/livepatch/klp-build | 14 | ||||
| -rw-r--r-- | scripts/mod/modpost.c | 1 | ||||
| -rw-r--r-- | tools/include/linux/objtool_types.h | 4 | ||||
| -rw-r--r-- | tools/objtool/Build | 4 | ||||
| -rw-r--r-- | tools/objtool/arch/x86/special.c | 27 | ||||
| -rw-r--r-- | tools/objtool/builtin-check.c | 7 | ||||
| -rw-r--r-- | tools/objtool/check.c | 7 | ||||
| -rw-r--r-- | tools/objtool/elf.c | 13 | ||||
| -rw-r--r-- | tools/objtool/include/objtool/builtin.h | 1 | ||||
| -rw-r--r-- | tools/objtool/include/objtool/elf.h | 1 | ||||
| -rw-r--r-- | tools/objtool/include/objtool/klp.h | 33 | ||||
| -rw-r--r-- | tools/objtool/include/objtool/special.h | 7 | ||||
| -rw-r--r-- | tools/objtool/klp-diff.c | 272 | ||||
| -rw-r--r-- | tools/objtool/klp-post-link.c | 53 | ||||
| -rw-r--r-- | tools/objtool/klp-symid.c | 119 | ||||
| -rw-r--r-- | tools/objtool/klp-sympos.c | 421 |
20 files changed, 877 insertions, 132 deletions
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 5659f4b5a125..ee9c5d354a85 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -839,12 +839,20 @@ .stab.index 0 : { *(.stab.index) } \ .stab.indexstr 0 : { *(.stab.indexstr) } +#ifdef CONFIG_KLP_BUILD +#define KLP_SYMID \ + .klp.symid 0 : { *(.klp.symid) } +#else +#define KLP_SYMID +#endif + /* Required sections not related to debugging. */ #define ELF_DETAILS \ .comment 0 : { *(.comment) } \ .symtab 0 : { *(.symtab) } \ .strtab 0 : { *(.strtab) } \ - .shstrtab 0 : { *(.shstrtab) } + .shstrtab 0 : { *(.shstrtab) } \ + KLP_SYMID #define MODINFO \ .modinfo : { *(.modinfo) . = ALIGN(8); } diff --git a/include/linux/objtool.h b/include/linux/objtool.h index 9a00e701454c..af2e68e496e5 100644 --- a/include/linux/objtool.h +++ b/include/linux/objtool.h @@ -7,7 +7,7 @@ #ifdef CONFIG_OBJTOOL -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #define UNWIND_HINT(type, sp_reg, sp_offset, signal) \ "987: \n\t" \ @@ -53,7 +53,7 @@ #define __ASM_BREF(label) label ## b -#else /* __ASSEMBLY__ */ +#else /* __ASSEMBLER__ */ /* * In asm, there are two kinds of code: normal C-type callable functions and @@ -102,11 +102,11 @@ #endif .endm -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ #else /* !CONFIG_OBJTOOL */ -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #define UNWIND_HINT(type, sp_reg, sp_offset, signal) "\n\t" #define STACK_FRAME_NON_STANDARD(func) diff --git a/include/linux/objtool_types.h b/include/linux/objtool_types.h index c6def4049b1a..c24e9ea39269 100644 --- a/include/linux/objtool_types.h +++ b/include/linux/objtool_types.h @@ -2,7 +2,7 @@ #ifndef _LINUX_OBJTOOL_TYPES_H #define _LINUX_OBJTOOL_TYPES_H -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #include <linux/types.h> @@ -18,7 +18,7 @@ struct unwind_hint { u8 signal; }; -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ /* * UNWIND_HINT_TYPE_UNDEFINED: A blind spot in ORC coverage which can result in diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o index 527352c222ff..24a3a4fd271c 100644 --- a/scripts/Makefile.vmlinux_o +++ b/scripts/Makefile.vmlinux_o @@ -47,6 +47,9 @@ endif vmlinux-objtool-args-$(CONFIG_NOINSTR_VALIDATION) += --noinstr \ $(if $(or $(CONFIG_MITIGATION_UNRET_ENTRY),$(CONFIG_MITIGATION_SRSO)), --unret) +# Only used for builds initiated by klp-build +vmlinux-objtool-args-$(if $(KLP_SYMIDS),y) += --klp-symids + objtool-args = $(vmlinux-objtool-args-y) --link # Link of vmlinux.o used for section mismatch analysis diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index c4a7acf8edc3..b52a8489d9f6 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -271,6 +271,9 @@ validate_config() { [[ -v CONFIG_GCC_PLUGIN_RANDSTRUCT ]] && \ die "kernel option 'CONFIG_GCC_PLUGIN_RANDSTRUCT' not supported" + [[ -v CONFIG_LD_DEAD_CODE_DATA_ELIMINATION ]] && \ + die "kernel option 'CONFIG_LD_DEAD_CODE_DATA_ELIMINATION' not supported" + [[ -v CONFIG_AS_IS_LLVM ]] && \ [[ "$CONFIG_AS_VERSION" -lt 200000 ]] && \ die "Clang assembler version < 20 not supported" @@ -555,6 +558,8 @@ build_kernel() { # cmd+=("KBUILD_MODPOST_WARN=1") + cmd+=("KLP_SYMIDS=1") + if [[ -v VERBOSE ]]; then cmd+=("V=1") else @@ -575,8 +580,9 @@ find_objects() { local opts=("$@") # Find root-level vmlinux.o and non-root-level .ko files, - # excluding klp-tmp/ and .git/ - find "$PWD" \( -path "$TMP_DIR" -o -path "$PWD/.git" -o -regex "$PWD/[^/][^/]*\.ko" \) -prune -o \ + # excluding klp-tmp/ and hidden directories. + find "$PWD" -mindepth 1 \ + \( -path "$TMP_DIR" -o -name ".*" -o -regex "$PWD/[^/][^/]*\.ko" \) -prune -o \ -type f "${opts[@]}" \ \( -name "*.ko" -o -path "$PWD/vmlinux.o" \) \ -printf '%P\n' @@ -605,6 +611,8 @@ copy_orig_objects() { done xtrace_restore + cp -f "$PWD/vmlinux" "$ORIG_DIR" || die "missing vmlinux" + mv -f "$TMP_DIR/build.log" "$ORIG_DIR" touch "$TIMESTAMP" touch "$ORIG_DIR/.complete" @@ -675,6 +683,8 @@ generate_checksums() { "$OBJTOOL" klp checksum "$dest" done + [[ -f "$src_dir/vmlinux" ]] && cp -f "$src_dir/vmlinux" "$dest_dir" + touch "$dest_dir/.complete" } diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c index b06c59d03ef7..75374c64b8cc 100644 --- a/scripts/mod/modpost.c +++ b/scripts/mod/modpost.c @@ -772,6 +772,7 @@ static const char *const section_white_list[] = ".llvm.call-graph-profile", /* call graph */ "__llvm_covfun", "__llvm_covmap", + ".klp.symid", /* objtool --klp-symids */ NULL }; diff --git a/tools/include/linux/objtool_types.h b/tools/include/linux/objtool_types.h index c6def4049b1a..c24e9ea39269 100644 --- a/tools/include/linux/objtool_types.h +++ b/tools/include/linux/objtool_types.h @@ -2,7 +2,7 @@ #ifndef _LINUX_OBJTOOL_TYPES_H #define _LINUX_OBJTOOL_TYPES_H -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #include <linux/types.h> @@ -18,7 +18,7 @@ struct unwind_hint { u8 signal; }; -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ /* * UNWIND_HINT_TYPE_UNDEFINED: A blind spot in ORC coverage which can result in diff --git a/tools/objtool/Build b/tools/objtool/Build index 93a37b0dfd31..59f948628098 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -6,13 +6,15 @@ objtool-y += check.o objtool-y += special.o objtool-y += builtin-check.o objtool-y += elf.o +objtool-y += klp-symid.o objtool-y += objtool.o objtool-$(BUILD_DISAS) += disas.o objtool-$(BUILD_DISAS) += trace.o objtool-$(BUILD_ORC) += orc_gen.o orc_dump.o -objtool-$(BUILD_KLP) += builtin-klp.o klp-checksum.o klp-diff.o klp-post-link.o +objtool-$(BUILD_KLP) += builtin-klp.o klp-checksum.o klp-diff.o \ + klp-post-link.o klp-sympos.o objtool-y += libstring.o objtool-y += libctype.o diff --git a/tools/objtool/arch/x86/special.c b/tools/objtool/arch/x86/special.c index e817a3fff449..1e84c81bfcd8 100644 --- a/tools/objtool/arch/x86/special.c +++ b/tools/objtool/arch/x86/special.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <string.h> +#include <arch/special.h> #include <objtool/special.h> #include <objtool/builtin.h> #include <objtool/warn.h> @@ -9,6 +10,32 @@ /* cpu feature name array generated from cpufeatures.h */ #include "cpu-feature-names.c" +/* + * An alternative with an empty replacement, e.g. the second entry of + * + * ALTERNATIVE_2("orig", "repl", ft1, "", ft2) + * + * still gets a relocation for its replacement offset. But the label it points + * at is the end of the previous entry's replacement, which is also the + * beginning of the *next* entry's replacement. The value is meaningless: it's + * only ever used with a length of zero. + */ +bool arch_alt_ignore_new_reloc(struct section *sec, unsigned long offset) +{ + unsigned long entry_off; + + if (strcmp(sec->name, ".altinstructions")) + return false; + + entry_off = offset - (offset % ALT_ENTRY_SIZE); + + if (offset - entry_off != ALT_NEW_OFFSET) + return false; + + return !*(unsigned char *)(sec->data->d_buf + entry_off + + ALT_NEW_LEN_OFFSET); +} + void arch_handle_alternative(struct special_alt *alt) { static struct special_alt *group, *prev; diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 118c3de2f293..75b11dc85010 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -76,6 +76,7 @@ static const struct option check_options[] = { OPT_STRING_OPTARG('d', "disas", &opts.disas, "function-pattern", "disassemble functions", "*"), OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr,skylake", "patch toolchain bugs/limitations", parse_hacks), OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"), + OPT_BOOLEAN(0, "klp-symids", &opts.klp_symids, "generate .klp.symids for duplicate symbol disambiguation"), OPT_BOOLEAN('m', "mcount", &opts.mcount, "annotate mcount/fentry calls for ftrace"), OPT_BOOLEAN(0, "noabs", &opts.noabs, "reject absolute references in allocatable sections"), OPT_BOOLEAN('n', "noinstr", &opts.noinstr, "validate noinstr rules"), @@ -174,10 +175,16 @@ static bool opts_valid(void) return false; } + if (opts.klp_symids && !opts.link) { + ERROR("--klp-symids requires --link"); + return false; + } + if (opts.disas || opts.hack_jump_label || opts.hack_noinstr || opts.ibt || + opts.klp_symids || opts.mcount || opts.noabs || opts.noinstr || diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 87db9f4ed9e2..288a48dff11c 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -15,6 +15,7 @@ #include <objtool/arch.h> #include <objtool/disas.h> #include <objtool/check.h> +#include <objtool/klp.h> #include <objtool/special.h> #include <objtool/trace.h> #include <objtool/warn.h> @@ -4924,6 +4925,12 @@ int check(struct objtool_file *file) goto out; } + if (opts.klp_symids) { + ret = klp_create_symid_sections(file); + if (ret) + goto out; + } + if (opts.noabs) warnings += check_abs_references(file); diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 33c95a74a51b..a791f4ea6ec1 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -23,6 +23,7 @@ #include <linux/log2.h> #include <objtool/builtin.h> #include <objtool/elf.h> +#include <objtool/klp.h> #include <objtool/warn.h> static ssize_t demangled_name_len(const char *name); @@ -626,6 +627,18 @@ static int read_symbols(struct elf *elf) return -1; } + /* + * "klp diff" renames the placeholder symbols of KLP relocs to + * hide them from modpost. Hide the prefix from the rest of + * objtool so its many name-based heuristics (noreturns, + * uaccess safe list, ...) still see the original symbol name. + * + * st_name is left alone, so the renamed symbol is preserved in + * the output file. + */ + if (strstarts(sym->name, KLP_TOMBSTONE_PREFIX)) + sym->name += strlen(KLP_TOMBSTONE_PREFIX); + if ((sym->sym.st_shndx > SHN_UNDEF && sym->sym.st_shndx < SHN_LORESERVE) || (shndx_data && sym->sym.st_shndx == SHN_XINDEX)) { diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index e844e9c82b7b..349690bb1c50 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -16,6 +16,7 @@ struct opts { bool hack_noinstr; bool hack_skylake; bool ibt; + bool klp_symids; bool mcount; bool noabs; bool noinstr; diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index d9c44df9cc76..a82517a76a0f 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -97,6 +97,7 @@ struct symbol { u8 included : 1; u8 klp : 1; u8 dont_correlate : 1; + u8 fake : 1; struct list_head pv_target; struct reloc *relocs; struct section *group_sec; diff --git a/tools/objtool/include/objtool/klp.h b/tools/objtool/include/objtool/klp.h index 6f60cf05db86..c57775d78c71 100644 --- a/tools/objtool/include/objtool/klp.h +++ b/tools/objtool/include/objtool/klp.h @@ -14,21 +14,48 @@ #define KLP_FUNCS_SEC ".init.klp_funcs" /* - * __klp_relocs is an intermediate section which are created by klp diff and - * converted into KLP symbols/relas by "objtool klp post-link". This is needed - * to work around the linker, which doesn't preserve SHN_LIVEPATCH or + * __klp_relocs.<objname> are intermediate sections which are created by klp + * diff and converted into KLP symbols/relas by "objtool klp post-link". This + * is needed to work around the linker, which doesn't preserve SHN_LIVEPATCH or * SHF_RELA_LIVEPATCH, nor does it support having two RELA sections for a * single PROGBITS section. + * + * "objname" is the object whose loading gates the relocation: "vmlinux" for + * references to vmlinux symbols, otherwise the name of the module being + * patched. post-link uses it to name the resulting + * .klp.rela.objname.section_name sections. */ #define KLP_RELOCS_SEC "__klp_relocs" #define KLP_STRINGS_SEC ".rodata.klp.str1.1" +#define KLP_TOMBSTONE_PREFIX ".klp.tombstone." + struct klp_reloc { void *offset; void *sym; u32 type; }; +/* + * .klp.symid is used to correlate symbols between vmlinux.o and vmlinux, for + * calculating sympos to disambiguate duplicately-named symbols. + */ +#define KLP_SYMID_SEC ".klp.symid" + +struct klp_symid { + u64 id; + u64 addr; +}; + +struct objtool_file; +struct elf; +struct symbol; + +int klp_create_symid_sections(struct objtool_file *file); + +int klp_sympos_init(struct elf *orig); +unsigned long klp_find_sympos(struct elf *elf, struct symbol *sym); + int cmd_klp_checksum(int argc, const char **argv); int cmd_klp_diff(int argc, const char **argv); int cmd_klp_post_link(int argc, const char **argv); diff --git a/tools/objtool/include/objtool/special.h b/tools/objtool/include/objtool/special.h index 121c3761899c..620dbf6cb0e5 100644 --- a/tools/objtool/include/objtool/special.h +++ b/tools/objtool/include/objtool/special.h @@ -32,6 +32,13 @@ int special_get_alts(struct elf *elf, struct list_head *alts); void arch_handle_alternative(struct special_alt *alt); +/* + * Should the reloc at @offset -- the "new" (replacement) field of a special + * section group entry -- be ignored? The meaning of a zero-length replacement + * is arch specific, so the arch decides. + */ +bool arch_alt_ignore_new_reloc(struct section *sec, unsigned long offset); + bool arch_support_alt_relocation(struct special_alt *special_alt, struct instruction *insn, struct reloc *reloc); diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index f8787d7d1454..16681a76f13d 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -12,7 +12,7 @@ #include <objtool/arch.h> #include <objtool/klp.h> #include <objtool/util.h> -#include <arch/special.h> +#include <objtool/special.h> #include <linux/align.h> #include <linux/objtool_types.h> @@ -30,7 +30,9 @@ struct elfs { struct export { struct hlist_node hash; - char *mod, *sym; + char *mod; + char *sym; + bool mod_ns; }; bool debug, debug_correlate, debug_clone; @@ -83,11 +85,40 @@ static char *escape_str(const char *orig) 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 = 1; + unsigned int line_num = 0; FILE *file; file = fopen(symvers, "r"); @@ -106,9 +137,11 @@ static int read_exports(void) } while (fgets(line, 1024, file)) { - char *sym, *mod, *type; + 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); @@ -133,6 +166,14 @@ static int read_exports(void) *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; @@ -150,12 +191,18 @@ static int read_exports(void) 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)); } @@ -866,65 +913,6 @@ static int correlate_symbols(struct elfs *e) 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 && is_func_sym(sym)) { - /* - * 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; - } - } - 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; - } - } - } else { - for_each_sym(elf, s) { - if (!strcmp(s->name, sym->name)) { - nr_matches++; - if (s == sym) - sympos = nr_matches; - else - has_dup = true; - } - } - } - - if (!sympos) { - ERROR("can't find sympos for %s", sym->name); - return ULONG_MAX; - } - - return has_dup ? sympos : 0; -} - static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym); static struct symbol *__clone_symbol(struct elf *elf, struct symbol *patched_sym, @@ -1129,6 +1117,9 @@ static struct export *find_export(struct symbol *sym) { struct export *export; + if (is_local_sym(sym)) + return NULL; + hash_for_each_possible(exports, export, hash, str_hash(sym->name)) { if (!strcmp(export->sym, sym->name)) return export; @@ -1158,18 +1149,7 @@ static const char *__find_modname(struct elfs *e) return NULL; } - for (char *c = name; *c; c++) { - if (*c == '/') - name = c + 1; - else if (*c == '-') - *c = '_'; - else if (*c == '.') { - *c = '\0'; - break; - } - } - - return name; + return normalize_modname(name); } /* Get the object's module name as defined by the kernel (and klp_object) */ @@ -1210,11 +1190,16 @@ static bool klp_reloc_needed(struct reloc *patched_reloc) * clusterfunk that is late module patching, the patch module is * allowed to be loaded before any modules it depends on. * - * If exported by vmlinux, a normal reloc will do. + * If exported by vmlinux to all modules, a normal reloc will do. */ export = find_export(patched_sym); - if (export) - return strcmp(export->mod, "vmlinux"); + if (export) { + if (strcmp(export->mod, "vmlinux")) + return true; + + /* EXPORT_SYMBOL_FOR_MODULES() gets a klp reloc */ + return export->mod_ns; + } if (!patched_sym->twin) { /* @@ -1331,39 +1316,79 @@ static int convert_reloc_sym(struct elf *elf, struct reloc *reloc) } /* + * Check if the original module already has a dependency on dep_mod, i.e. it + * already references at least one export from that module. + */ +static bool has_module_dep(struct elfs *e, const char *dep_mod) +{ + struct symbol *sym; + + for_each_sym(e->orig, sym) { + struct export *exp; + + if (!is_undef_sym(sym) || is_weak_sym(sym)) + continue; + + exp = find_export(sym); + if (exp && !strcmp(exp->mod, dep_mod)) + return true; + } + + return false; +} + +/* * Convert a regular relocation to a klp relocation (sort of). */ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, struct section *sec, unsigned long offset, struct export *export) { + const char *sym_modname, *sym_orig_name, *sec_objname; struct symbol *patched_sym = patched_reloc->sym; s64 addend = reloc_addend(patched_reloc); - const char *sym_modname, *sym_orig_name; - static struct section *klp_relocs; + char tombstone_name[SYM_NAME_LEN]; struct symbol *sym, *klp_sym; unsigned long klp_reloc_off; + struct section *klp_relocs; + char sec_name[SEC_NAME_LEN]; char sym_name[SYM_NAME_LEN]; struct klp_reloc klp_reloc; unsigned long sympos; if (!patched_sym->twin) { - ERROR("unexpected klp reloc for new symbol %s", patched_sym->name); - return -1; + if (!export) { + ERROR("unexpected klp reloc for new symbol %s", patched_sym->name); + return -1; + } + + if (strcmp(export->mod, "vmlinux") && + !has_module_dep(e, export->mod)) { + ERROR("%s: new reference to %s (exported by %s) would create an undeclared module dependency", + patched_sym->name, export->sym, export->mod); + return -1; + } } /* * Keep the original reloc intact for now to avoid breaking objtool run * which relies on proper relocations for many of its features. This - * will be disabled later by "objtool klp post-link". + * reloc now targets a functionally dead tombstone symbol and will be + * disabled later by "objtool klp post-link". * - * Convert it to UNDEF (and WEAK to avoid modpost warnings). + * Convert the symbol to UNDEF/WEAK and rename to + * .klp.tombstone.sym_name to prevent modpost from printing warnings or + * creating false module dependencies. The prefix is hidden from the + * objtool run itself by read_symbols(). */ sym = patched_sym->clone; if (!sym) { - /* STB_WEAK: avoid modpost undefined symbol warnings */ - sym = elf_create_symbol(e->out, patched_sym->name, NULL, + if (snprintf_check(tombstone_name, SYM_NAME_LEN, + KLP_TOMBSTONE_PREFIX "%s", patched_sym->name)) + return -1; + + sym = elf_create_symbol(e->out, tombstone_name, NULL, STB_WEAK, patched_sym->type, 0, 0); if (!sym) return -1; @@ -1389,7 +1414,7 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, return -1; sym_orig_name = patched_sym->twin->name; - sympos = find_sympos(e->orig, patched_sym->twin); + sympos = klp_find_sympos(e->orig, patched_sym->twin); if (sympos == ULONG_MAX) return -1; } @@ -1411,16 +1436,35 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, } /* - * Create the __klp_relocs entry. This will be converted to an actual - * KLP rela by "objtool klp post-link". + * Create the __klp_relocs.<objname> entry. This will be converted to + * an actual KLP rela by "objtool klp post-link". * * This intermediate step is necessary to prevent corruption by the * linker, which doesn't know how to properly handle two rela sections * applying to the same base section. + * + * The objname decides when the reloc gets applied. A reference to a + * vmlinux symbol goes in the vmlinux section so it gets applied when + * the patch module loads. Everything else goes in the patched + * object's section, applied when the patched module is loaded. */ + if (!strcmp(sym_modname, "vmlinux")) { + sec_objname = "vmlinux"; + } else { + sec_objname = find_modname(e); + if (!sec_objname) + return -1; + } + + /* section format: __klp_relocs.objname */ + if (snprintf_check(sec_name, SEC_NAME_LEN, + KLP_RELOCS_SEC ".%s", sec_objname)) + return -1; + + klp_relocs = find_section_by_name(e->out, sec_name); if (!klp_relocs) { - klp_relocs = elf_create_section(e->out, KLP_RELOCS_SEC, 0, + klp_relocs = elf_create_section(e->out, sec_name, 0, 0, SHT_PROGBITS, 8, SHF_ALLOC); if (!klp_relocs) return -1; @@ -1568,6 +1612,10 @@ static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym) !strcmp(patched_reloc->sym->sec->name, ".altinstr_aux")) continue; + if (arch_alt_ignore_new_reloc(patched_sym->sec, + reloc_offset(patched_reloc))) + continue; + ret = convert_reloc_sym(e->patched, patched_reloc); if (ret < 0) { ERROR_FUNC(patched_rsec->base, reloc_offset(patched_reloc), @@ -1591,6 +1639,7 @@ static int create_fake_symbol(struct elf *elf, struct section *sec, unsigned long offset, size_t size) { char name[SYM_NAME_LEN]; + struct symbol *sym; unsigned int type; static int ctr; char *c; @@ -1607,7 +1656,24 @@ static int create_fake_symbol(struct elf *elf, struct section *sec, * while still allowing objdump to disassemble it. */ type = is_text_sec(sec) ? STT_NOTYPE : STT_OBJECT; - return elf_create_symbol(elf, name, sec, STB_LOCAL, type, offset, size) ? 0 : -1; + + sym = elf_create_symbol(elf, name, sec, STB_LOCAL, type, offset, size); + if (!sym) + return -1; + + sym->fake = 1; + return 0; +} + +static bool has_fake_symbols(struct section *sec) +{ + struct symbol *sym; + + sec_for_each_sym(sec, sym) + if (sym->fake) + return true; + + return false; } /* @@ -1658,13 +1724,17 @@ static int create_fake_symbols(struct elf *elf) for_each_reloc(sec->rsec, reloc) { unsigned long offset, size; struct reloc *next_reloc; + bool last = true; if (annotype(elf, sec, reloc) != ANNOTYPE_DATA_SPECIAL) continue; offset = reloc_addend(reloc); - size = 0; + /* + * Find the start of the next entry so the fake symbol size can + * be calculated. + */ next_reloc = reloc; for_each_reloc_continue(sec->rsec, next_reloc) { if (annotype(elf, sec, next_reloc) != ANNOTYPE_DATA_SPECIAL || @@ -1672,10 +1742,15 @@ static int create_fake_symbols(struct elf *elf) continue; size = reloc_addend(next_reloc) - offset; + last = false; break; } - if (!size) + /* + * If no next entry found, this is the last entry, so its size + * is from the current offset to the end of the section. + */ + if (last) size = sec_size(reloc->sym->sec) - offset; if (create_fake_symbol(elf, reloc->sym->sec, offset, size)) @@ -1690,7 +1765,11 @@ entsize: unsigned int entry_size; unsigned long offset; - if (!is_special_section(sec) || find_symbol_by_offset(sec, 0)) + if (!is_special_section(sec)) + continue; + + /* Skip sections already handled by step 1 above */ + if (has_fake_symbols(sec)) continue; if (!sec->rsec) { @@ -2007,7 +2086,7 @@ static int create_klp_sections(struct elfs *e) /* klp_func_ext.sympos */ BUILD_BUG_ON(sizeof(sympos) != sizeof_field(struct klp_func_ext, sympos)); - sympos = find_sympos(e->orig, sym->clone->twin); + sympos = klp_find_sympos(e->orig, sym->clone->twin); if (sympos == ULONG_MAX) return -1; memcpy(func_data + offsetof(struct klp_func_ext, sympos), &sympos, @@ -2161,6 +2240,9 @@ int cmd_klp_diff(int argc, const char **argv) if (!e.orig || !e.patched) return -1; + if (klp_sympos_init(e.orig)) + return -1; + if (read_exports()) return -1; diff --git a/tools/objtool/klp-post-link.c b/tools/objtool/klp-post-link.c index c013e39957b1..350d20495897 100644 --- a/tools/objtool/klp-post-link.c +++ b/tools/objtool/klp-post-link.c @@ -19,19 +19,11 @@ #include <objtool/util.h> #include <linux/livepatch_external.h> -static int fix_klp_relocs(struct elf *elf) +static int fix_klp_reloc_sec(struct elf *elf, struct section *symtab, + struct section *klp_relocs) { - struct section *symtab, *klp_relocs; - - klp_relocs = find_section_by_name(elf, KLP_RELOCS_SEC); - if (!klp_relocs) - return 0; - - symtab = find_section_by_name(elf, ".symtab"); - if (!symtab) { - ERROR("missing .symtab"); - return -1; - } + /* section format: __klp_relocs.sec_objname */ + const char *sec_objname = klp_relocs->name + strlen(KLP_RELOCS_SEC "."); for (int i = 0; i < sec_size(klp_relocs) / sizeof(struct klp_reloc); i++) { struct klp_reloc *klp_reloc; @@ -39,7 +31,6 @@ static int fix_klp_relocs(struct elf *elf) struct section *sec, *tmp, *klp_rsec; unsigned long offset; struct reloc *reloc; - char sym_modname[64]; char rsec_name[SEC_NAME_LEN]; u64 addend; struct symbol *sym, *klp_sym; @@ -55,7 +46,7 @@ static int fix_klp_relocs(struct elf *elf) reloc = find_reloc_by_dest(elf, klp_relocs, klp_reloc_off + offsetof(struct klp_reloc, offset)); if (!reloc) { - ERROR("malformed " KLP_RELOCS_SEC " section"); + ERROR("malformed %s section", klp_relocs->name); return -1; } @@ -66,17 +57,13 @@ static int fix_klp_relocs(struct elf *elf) reloc = find_reloc_by_dest(elf, klp_relocs, klp_reloc_off + offsetof(struct klp_reloc, sym)); if (!reloc) { - ERROR("malformed " KLP_RELOCS_SEC " section"); + ERROR("malformed %s section", klp_relocs->name); return -1; } klp_sym = reloc->sym; addend = reloc_addend(reloc); - /* symbol format: .klp.sym.modname.sym_name,sympos */ - if (sscanf(klp_sym->name + strlen(KLP_SYM_PREFIX), "%55[^.]", sym_modname) != 1) - ERROR("can't find modname in klp symbol '%s'", klp_sym->name); - /* * Create the KLP rela: */ @@ -84,7 +71,7 @@ static int fix_klp_relocs(struct elf *elf) /* section format: .klp.rela.sec_objname.section_name */ if (snprintf_check(rsec_name, SEC_NAME_LEN, KLP_RELOC_SEC_PREFIX "%s.%s", - sym_modname, sec->name)) + sec_objname, sec->name)) return -1; klp_rsec = find_section_by_name(elf, rsec_name); @@ -134,10 +121,32 @@ static int fix_klp_relocs(struct elf *elf) return 0; } +static int fix_klp_relocs(struct elf *elf) +{ + struct section *symtab, *sec; + + symtab = find_section_by_name(elf, ".symtab"); + if (!symtab) { + ERROR("missing .symtab"); + return -1; + } + + for_each_sec(elf, sec) { + if (strncmp(sec->name, KLP_RELOCS_SEC ".", + strlen(KLP_RELOCS_SEC "."))) + continue; + + if (fix_klp_reloc_sec(elf, symtab, sec)) + return -1; + } + + return 0; +} + /* * This runs on the livepatch module after all other linking has been done. It - * converts the intermediate __klp_relocs section into proper KLP relocs to be - * processed by livepatch. This needs to run last to avoid linker wreckage. + * converts the intermediate __klp_relocs.* sections into proper KLP relocs to + * be processed by livepatch. This needs to run last to avoid linker wreckage. * Linkers don't tend to handle the "two rela sections for a single base * section" case very well, nor do they appreciate SHN_LIVEPATCH. */ diff --git a/tools/objtool/klp-symid.c b/tools/objtool/klp-symid.c new file mode 100644 index 000000000000..b19f76dff13e --- /dev/null +++ b/tools/objtool/klp-symid.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Emit the .klp.symid table which allows "objtool klp diff" to reliably + * disambiguate duplicate-named local symbols in vmlinux. + * + * Livepatch identifies a duplicate-named symbol by its position (sympos) + * among the same-named kallsyms entries, counted in ascending address order + * in the final linked vmlinux. That order can't be derived from vmlinux.o + * alone: the final link reorders sub-sections (.text.unlikely*, .data..*, + * etc). + * + * Bridge the gap with a table which survives the final link: a single + * non-alloc section containing an array of { id, addr } entries, where + * 'id' is a unique counter identifier and 'addr' has a relocation to the + * symbol. The linker copies 'id' verbatim and resolves 'addr' to the symbol's + * final address. + * + * The table is only emitted for vmlinux.o, and only when klp-build asks for it + * with KLP_SYMIDS=1, which adds --klp-symids to the vmlinux.o objtool run. + * + * It can't survive --gc-sections, which sweeps the whole section; klp-build + * rejects CONFIG_LD_DEAD_CODE_DATA_ELIMINATION. + */ +#include <linux/string.h> + +#include <objtool/objtool.h> +#include <objtool/warn.h> +#include <objtool/endianness.h> +#include <objtool/klp.h> + +static const char * const discarded_secs[] = { + ".discard", + ".exitcall.exit", + ".modinfo", + ".no_trim_symbol", + "__tracepoint_check", +}; + +static bool discarded_sec(struct section *sec) +{ + if (!(sec->sh.sh_flags & SHF_ALLOC)) + return true; + + for (int i = 0; i < ARRAY_SIZE(discarded_secs); i++) + if (strstarts(sec->name, discarded_secs[i])) + return true; + + return false; +} + +static bool symid_needed(struct elf *elf, struct symbol *sym) +{ + struct symbol *s; + + if (!is_local_sym(sym) || is_undef_sym(sym)) + return false; + + if (!is_func_sym(sym) && !is_object_sym(sym)) + return false; + + if (is_prefix_func(sym)) + return false; + + if (discarded_sec(sym->sec)) + return false; + + for_each_sym_by_name(elf, sym->name, s) { + if (s == sym || is_sec_sym(s) || is_file_sym(s) || is_undef_sym(s)) + continue; + return true; + } + + return false; +} + +int klp_create_symid_sections(struct objtool_file *file) +{ + struct elf *elf = file->elf; + struct klp_symid *symids; + struct section *sec; + struct symbol *sym; + u64 nr = 0, i = 0; + + if (!str_ends_with(objname, "vmlinux.o")) + return 0; + + for_each_sym(elf, sym) + if (symid_needed(elf, sym)) + nr++; + + if (!nr) + return 0; + + sec = elf_create_section(elf, KLP_SYMID_SEC, 0, sizeof(struct klp_symid), + SHT_PROGBITS, 8, 0); + if (!sec) + return -1; + + symids = elf_add_data(elf, sec, NULL, nr * sizeof(struct klp_symid)); + if (!symids) + return -1; + + for_each_sym(elf, sym) { + if (!symid_needed(elf, sym)) + continue; + + symids[i].id = bswap_if_needed(elf, i); + + if (!elf_create_reloc(elf, sec, + i * sizeof(struct klp_symid) + + offsetof(struct klp_symid, addr), + sym, 0, R_ABS64)) + return -1; + + i++; + } + + return 0; +} diff --git a/tools/objtool/klp-sympos.c b/tools/objtool/klp-sympos.c new file mode 100644 index 000000000000..dfca9dd74681 --- /dev/null +++ b/tools/objtool/klp-sympos.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Compute "sympos", the position used by livepatch to disambiguate + * duplicate symbol names in the patched object. + */ +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> + +#include <objtool/objtool.h> +#include <objtool/warn.h> +#include <objtool/endianness.h> +#include <objtool/klp.h> + +#include <linux/string.h> + +struct vmlinux_sym { + struct hlist_node hash; + const char *name; + u64 addr; +}; + +struct vmlinux_symid { + struct hlist_node hash; + u64 id; + u64 addr; +}; + +struct vmlinux_o_symid { + struct hlist_node hash; + u64 id; + unsigned int sym_idx; +}; + +static DEFINE_HASHTABLE(vmlinux_o_symids, 16); + +/* + * The original linked kernel, found next to the orig vmlinux.o. Read with raw + * libelf rather than elf_open_read(): only the symbol table and the resolved + * .klp.symid table are needed, not the (huge) instruction/reloc machinery. + * + * Both tables are built once by read_orig_vmlinux(). The Elf handle stays + * open because the hashed names point into its mmapped string table. + */ +static struct { + Elf *elf; + DECLARE_HASHTABLE(syms, 16); /* name -> address */ + DECLARE_HASHTABLE(symids, 16); /* .klp.symid id -> address */ +} vmlinux; + +/* + * Would the symbol be visible to the runtime's kallsyms-based symbol lookup? + */ +static bool vmlinux_sym_in_kallsyms(Elf *elf, GElf_Sym *sym) +{ + unsigned int type = GELF_ST_TYPE(sym->st_info); + GElf_Shdr shdr; + Elf_Scn *scn; + + if (sym->st_shndx == SHN_UNDEF || sym->st_shndx >= SHN_LORESERVE) + return false; + + if (type == STT_SECTION || type == STT_FILE) + return false; + + scn = elf_getscn(elf, sym->st_shndx); + if (!scn || !gelf_getshdr(scn, &shdr)) + return false; + + return shdr.sh_flags & SHF_ALLOC; +} + +static int read_orig_vmlinux(const char *filename) +{ + size_t shstrndx, nr_syms = 0, nr_symids = 0, strtab_idx = 0; + Elf_Data *symtab_data = NULL, *symid_data = NULL; + struct klp_symid *symids; + Elf_Scn *scn = NULL; + GElf_Ehdr ehdr; + int fd; + + fd = open(filename, O_RDONLY); + if (fd == -1) { + ERROR_GLIBC("can't open '%s'", filename); + return -1; + } + + if (elf_version(EV_CURRENT) == EV_NONE) { + ERROR_ELF("elf_version"); + return -1; + } + + vmlinux.elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); + if (!vmlinux.elf) { + ERROR_ELF("elf_begin"); + return -1; + } + + if (!gelf_getehdr(vmlinux.elf, &ehdr)) { + ERROR_ELF("gelf_getehdr"); + return -1; + } + + if (elf_getshdrstrndx(vmlinux.elf, &shstrndx)) { + ERROR_ELF("elf_getshdrstrndx"); + return -1; + } + + while ((scn = elf_nextscn(vmlinux.elf, scn))) { + const char *name; + GElf_Shdr shdr; + + if (!gelf_getshdr(scn, &shdr)) { + ERROR_ELF("gelf_getshdr"); + return -1; + } + + if (shdr.sh_type == SHT_SYMTAB) { + symtab_data = elf_getdata(scn, NULL); + if (!symtab_data) { + ERROR_ELF("elf_getdata"); + return -1; + } + nr_syms = shdr.sh_size / shdr.sh_entsize; + strtab_idx = shdr.sh_link; + continue; + } + + name = elf_strptr(vmlinux.elf, shstrndx, shdr.sh_name); + if (name && !strcmp(name, KLP_SYMID_SEC)) { + if (shdr.sh_size % sizeof(struct klp_symid)) { + ERROR("%s: %s: struct klp_symid size mismatch", + filename, KLP_SYMID_SEC); + return -1; + } + symid_data = elf_getdata(scn, NULL); + if (!symid_data) { + ERROR_ELF("elf_getdata"); + return -1; + } + nr_symids = shdr.sh_size / sizeof(struct klp_symid); + } + } + + if (!symtab_data) { + ERROR("%s: missing symbol table", filename); + return -1; + } + + if (!symid_data) { + ERROR("%s: missing %s section, kernel not built with CONFIG_KLP_BUILD?", + filename, KLP_SYMID_SEC); + return -1; + } + + for (size_t i = 0; i < nr_syms; i++) { + struct vmlinux_sym *vsym; + const char *name; + GElf_Sym s; + + if (!gelf_getsym(symtab_data, i, &s)) { + ERROR_ELF("gelf_getsym"); + return -1; + } + + if (!vmlinux_sym_in_kallsyms(vmlinux.elf, &s)) + continue; + + name = elf_strptr(vmlinux.elf, strtab_idx, s.st_name); + if (!name) + continue; + + vsym = calloc(1, sizeof(*vsym)); + if (!vsym) { + ERROR_GLIBC("calloc"); + return -1; + } + + vsym->name = name; + vsym->addr = s.st_value; + hash_add(vmlinux.syms, &vsym->hash, str_hash(name)); + } + + symids = symid_data->d_buf; + + for (size_t i = 0; i < nr_symids; i++) { + struct vmlinux_symid *vsymid; + + vsymid = calloc(1, sizeof(*vsymid)); + if (!vsymid) { + ERROR_GLIBC("calloc"); + return -1; + } + + vsymid->id = __bswap_if_needed(&ehdr, symids[i].id); + vsymid->addr = __bswap_if_needed(&ehdr, symids[i].addr); + hash_add(vmlinux.symids, &vsymid->hash, vsymid->id); + } + + /* the fd and Elf handle stay open, the hashed names live in the mmap */ + return 0; +} + +/* + * Read the orig vmlinux.o's .klp.symid table, an array of entries whose 'addr' + * fields have relocs to the symbols they describe. + */ +static int read_vmlinux_o_symids(struct elf *vmlinux_o) +{ + struct section *sec; + + for_each_sec(vmlinux_o, sec) { + unsigned long nr; + + if (strcmp(sec->name, KLP_SYMID_SEC)) + continue; + + if (sec_size(sec) % sizeof(struct klp_symid)) { + ERROR("%s: %s: struct klp_symid size mismatch", + vmlinux_o->name, KLP_SYMID_SEC); + return -1; + } + + nr = sec_size(sec) / sizeof(struct klp_symid); + + for (unsigned long i = 0; i < nr; i++) { + unsigned long offset = i * sizeof(struct klp_symid); + struct vmlinux_o_symid *entry; + struct klp_symid *symid; + struct reloc *reloc; + + entry = calloc(1, sizeof(*entry)); + if (!entry) { + ERROR_GLIBC("calloc"); + return -1; + } + + symid = sec->data->d_buf + offset; + entry->id = bswap_if_needed(vmlinux_o, symid->id); + + reloc = find_reloc_by_dest(vmlinux_o, sec, + offset + offsetof(struct klp_symid, addr)); + if (!reloc) { + ERROR("%s: missing reloc for %s entry", + vmlinux_o->name, KLP_SYMID_SEC); + return -1; + } + entry->sym_idx = reloc->sym->idx; + + hash_add(vmlinux_o_symids, &entry->hash, entry->sym_idx); + } + } + + return 0; +} + +int klp_sympos_init(struct elf *orig) +{ + char *filename; + int ret; + + if (!str_ends_with(objname, "vmlinux.o")) + return 0; + + if (read_vmlinux_o_symids(orig)) + return -1; + + filename = strndup(objname, strlen(objname) - 2); + if (!filename) { + ERROR_GLIBC("strndup"); + return -1; + } + + ret = read_orig_vmlinux(filename); + free(filename); + + return ret; +} + +/* Find the symbol's id in the orig vmlinux.o's .klp.symid table */ +static int find_vmlinux_o_symid(struct symbol *sym, u64 *id) +{ + struct vmlinux_o_symid *entry; + + hash_for_each_possible(vmlinux_o_symids, entry, hash, sym->idx) { + if (entry->sym_idx == sym->idx) { + *id = entry->id; + return 0; + } + } + + ERROR("no %s entry for symbol %s in orig vmlinux.o", KLP_SYMID_SEC, + sym->name); + return -1; +} + +/* Find the symbol's final address in the orig vmlinux's .klp.symid table */ +static int find_vmlinux_symid_addr(u64 id, u64 *addr) +{ + struct vmlinux_symid *symid; + + hash_for_each_possible(vmlinux.symids, symid, hash, id) { + if (symid->id == id) { + *addr = symid->addr; + return 0; + } + } + + return -1; +} + +/* + * Find the sympos of a vmlinux-local symbol by ranking its final address + * among the duplicately named symbols in the linked orig vmlinux, replicating + * the order in which kallsyms_on_each_match_symbol() counts them. + */ +static unsigned long find_vmlinux_sympos(struct symbol *sym) +{ + unsigned long nr_matches = 0, sympos = 1; + u32 key = str_hash(sym->name); + struct vmlinux_sym *vsym; + bool found = false; + u64 id, addr; + + hash_for_each_possible(vmlinux.syms, vsym, hash, key) + if (!strcmp(vsym->name, sym->name)) + nr_matches++; + + if (!nr_matches) { + ERROR("can't find symbol %s in orig vmlinux", sym->name); + return ULONG_MAX; + } + + /* + * Unique symbols don't need disambiguating. They also have no + * .klp.symid entry, which is only emitted for names duplicated in + * vmlinux.o, so the lookups below would fail. + */ + if (nr_matches == 1) + return 0; + + if (find_vmlinux_o_symid(sym, &id)) + return ULONG_MAX; + + if (find_vmlinux_symid_addr(id, &addr)) { + ERROR("no %s entry for symbol %s in orig vmlinux", KLP_SYMID_SEC, + sym->name); + return ULONG_MAX; + } + + hash_for_each_possible(vmlinux.syms, vsym, hash, key) { + if (strcmp(vsym->name, sym->name)) + continue; + + if (vsym->addr < addr) + sympos++; + else if (vsym->addr == addr) + found = true; + } + + if (!found) { + ERROR("%s address mismatch for symbol %s, stale orig vmlinux?", + KLP_SYMID_SEC, sym->name); + return ULONG_MAX; + } + + return sympos; +} + +static bool is_init_sym(struct symbol *sym) +{ + return strstarts(sym->sec->name, ".init"); +} + +/* + * "sympos" is used by livepatch to disambiguate duplicate symbol names. + */ +unsigned long klp_find_sympos(struct elf *elf, struct symbol *sym) +{ + unsigned long sympos = 0, nr_matches = 0; + bool has_dup = false; + struct symbol *s; + + if (is_init_sym(sym)) { + ERROR("%s: can't patch or reference init code/data", sym->name); + return ULONG_MAX; + } + + if (sym->bind != STB_LOCAL) + return 0; + + /* + * vmlinux: the final link reorders symbols relative to vmlinux.o, + * so the position needs to be derived from the linked orig vmlinux via + * the .klp.symid table. + */ + if (vmlinux.elf) + return find_vmlinux_sympos(sym); + + /* + * modules: the final .ko preserves symbol table order, so a + * symtab-order count here matches the runtime count done by + * module_kallsyms_on_each_symbol(). + */ + for_each_sym(elf, s) { + if (!strcmp(s->name, sym->name)) { + nr_matches++; + if (s == sym) + sympos = nr_matches; + else + has_dup = true; + } + } + + if (!sympos) { + ERROR("can't find sympos for %s", sym->name); + return ULONG_MAX; + } + + return has_dup ? sympos : 0; +} |
