mirror of
https://github.com/torvalds/linux.git
synced 2026-09-14 16:10:02 +02:00
Objtool updates for v7.3:
- Fix various klp-build bugs reported by Joe Lawrence
(Josh Poimboeuf, Joe Lawrence)
- Misc fixes and cleanups (Puranjay Mohan, Thomas Huth and Ingo Molnar).
Signed-off-by: Ingo Molnar <mingo@kernel.org>
-----BEGIN PGP SIGNATURE-----
iQJFBAABCgAvFiEEBpT5eoXrXCwVQwEKEnMQ0APhK1gFAmqCz54RHG1pbmdvQGtl
cm5lbC5vcmcACgkQEnMQ0APhK1gD/A/8DRBAjlv+aj6knJgmAP6GZI6Wc2pfjj1C
5UPUZQf9/ib+/19D2YfOEvUM2T5RZg9fy2OhPGM3Qbe8GiCbKKpB1quJg3U7JB0P
Z1jO7ViSt0n1ako04xWfDAOjRld32u7idDu/RD+qw5Sahzsy/HPejnIZS6AXYe8V
Jw1KDviPfx3d2xPAcizp92Zg9SywcPcBI+jQVn61+ly1ZNbVTvUOlsARIWYOB4LI
wTfeNt3G1rbrmODsm6bXcz7y+anuO8OmjYJbZKoGraiXwnbuZewocBXK00NPDAVx
tuxnbwQ0g+dQ8gNQBL9bqEG/jeJzG7h/2n5s5/EnGV4SelQC/m+HPK/E40aHbwjv
tCLIe9rVYXRpiMVgfR4xUDfTVM3EPiu6chIVPFYG8YDXZ0lMdUccGZEZEAIuflxT
sSdHJT5P1ARzzuVHriPe3DS/jXEcd7UTVW99jcis3Xbl4TlfhQC5X2IueBfSYEFj
hSMizFzaJJ1S7wC5x7lO6nNQklyzivLB1fARVT84uYCfdKcxPleobT6mSggW290Z
u6kznFOLhiFt2eGAcWEEPzgIp9PjMKpXlHXzZnCl8TJyXVvwUGZgE6xEzKjJGa0B
ZsGJyeJUwpnKPfS/1dULsFub/zTtviVIluCWs0teWhp5FAdbkGXGRBF8K1CqZpOw
5tOwtn/kIDU=
=4+gg
-----END PGP SIGNATURE-----
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
This commit is contained in:
commit
a0acd94e38
|
|
@ -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); }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 ||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ struct opts {
|
|||
bool hack_noinstr;
|
||||
bool hack_skylake;
|
||||
bool ibt;
|
||||
bool klp_symids;
|
||||
bool mcount;
|
||||
bool noabs;
|
||||
bool noinstr;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
/*
|
||||
|
|
@ -1330,6 +1315,28 @@ static int convert_reloc_sym(struct elf *elf, struct reloc *reloc)
|
|||
return convert_reloc_secsym_to_sym(elf, 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).
|
||||
*/
|
||||
|
|
@ -1337,33 +1344,51 @@ 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 @@ static int create_fake_symbols(struct elf *elf)
|
|||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
119
tools/objtool/klp-symid.c
Normal file
119
tools/objtool/klp-symid.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
421
tools/objtool/klp-sympos.c
Normal file
421
tools/objtool/klp-sympos.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user