mirror of https://github.com/torvalds/linux.git
objtool/klp: Add --checksum option to generate per-function checksums
In preparation for the objtool klp diff subcommand, add a command-line option to generate a unique checksum for each function. This will enable detection of functions which have changed between two versions of an object file. Acked-by: Petr Mladek <pmladek@suse.com> Tested-by: Joe Lawrence <joe.lawrence@redhat.com> Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
This commit is contained in:
parent
f6b740ef5f
commit
0d83da43b1
|
|
@ -2,6 +2,27 @@
|
|||
include ../scripts/Makefile.include
|
||||
include ../scripts/Makefile.arch
|
||||
|
||||
ifeq ($(SRCARCH),x86)
|
||||
BUILD_ORC := y
|
||||
ARCH_HAS_KLP := y
|
||||
endif
|
||||
|
||||
ifeq ($(SRCARCH),loongarch)
|
||||
BUILD_ORC := y
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH_HAS_KLP),y)
|
||||
HAVE_XXHASH = $(shell echo "int main() {}" | \
|
||||
$(HOSTCC) -xc - -o /dev/null -lxxhash 2> /dev/null && echo y || echo n)
|
||||
ifeq ($(HAVE_XXHASH),y)
|
||||
LIBXXHASH_CFLAGS := $(shell $(HOSTPKG_CONFIG) libxxhash --cflags 2>/dev/null) \
|
||||
-DBUILD_KLP
|
||||
LIBXXHASH_LIBS := $(shell $(HOSTPKG_CONFIG) libxxhash --libs 2>/dev/null || echo -lxxhash)
|
||||
endif
|
||||
endif
|
||||
|
||||
export BUILD_ORC
|
||||
|
||||
ifeq ($(srctree),)
|
||||
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
|
||||
srctree := $(patsubst %/,%,$(dir $(srctree)))
|
||||
|
|
@ -36,10 +57,10 @@ INCLUDES := -I$(srctree)/tools/include \
|
|||
-I$(srctree)/tools/objtool/arch/$(SRCARCH)/include \
|
||||
-I$(LIBSUBCMD_OUTPUT)/include
|
||||
|
||||
OBJTOOL_CFLAGS := -std=gnu11 -fomit-frame-pointer -O2 -g \
|
||||
$(WARNINGS) $(INCLUDES) $(LIBELF_FLAGS) $(HOSTCFLAGS)
|
||||
OBJTOOL_CFLAGS := -std=gnu11 -fomit-frame-pointer -O2 -g $(WARNINGS) \
|
||||
$(INCLUDES) $(LIBELF_FLAGS) $(LIBXXHASH_CFLAGS) $(HOSTCFLAGS)
|
||||
|
||||
OBJTOOL_LDFLAGS := $(LIBSUBCMD) $(LIBELF_LIBS) $(HOSTLDFLAGS)
|
||||
OBJTOOL_LDFLAGS := $(LIBSUBCMD) $(LIBELF_LIBS) $(LIBXXHASH_LIBS) $(HOSTLDFLAGS)
|
||||
|
||||
# Allow old libelf to be used:
|
||||
elfshdr := $(shell echo '$(pound)include <libelf.h>' | $(HOSTCC) $(OBJTOOL_CFLAGS) -x c -E - 2>/dev/null | grep elf_getshdr)
|
||||
|
|
@ -51,17 +72,6 @@ HOST_OVERRIDES := CC="$(HOSTCC)" LD="$(HOSTLD)" AR="$(HOSTAR)"
|
|||
AWK = awk
|
||||
MKDIR = mkdir
|
||||
|
||||
BUILD_ORC := n
|
||||
|
||||
ifeq ($(SRCARCH),x86)
|
||||
BUILD_ORC := y
|
||||
endif
|
||||
|
||||
ifeq ($(SRCARCH),loongarch)
|
||||
BUILD_ORC := y
|
||||
endif
|
||||
|
||||
export BUILD_ORC
|
||||
export srctree OUTPUT CFLAGS SRCARCH AWK
|
||||
include $(srctree)/tools/build/Makefile.include
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ static int parse_hacks(const struct option *opt, const char *str, int unset)
|
|||
|
||||
static const struct option check_options[] = {
|
||||
OPT_GROUP("Actions:"),
|
||||
OPT_BOOLEAN(0, "checksum", &opts.checksum, "generate per-function checksums"),
|
||||
OPT_BOOLEAN(0, "cfi", &opts.cfi, "annotate kernel control flow integrity (kCFI) function preambles"),
|
||||
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"),
|
||||
|
|
@ -160,7 +161,15 @@ static bool opts_valid(void)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (opts.hack_jump_label ||
|
||||
#ifndef BUILD_KLP
|
||||
if (opts.checksum) {
|
||||
ERROR("--checksum not supported; install xxhash-devel and recompile");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (opts.checksum ||
|
||||
opts.hack_jump_label ||
|
||||
opts.hack_noinstr ||
|
||||
opts.ibt ||
|
||||
opts.mcount ||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <objtool/check.h>
|
||||
#include <objtool/special.h>
|
||||
#include <objtool/warn.h>
|
||||
#include <objtool/checksum.h>
|
||||
|
||||
#include <linux/objtool_types.h>
|
||||
#include <linux/hashtable.h>
|
||||
|
|
@ -971,6 +972,59 @@ static int create_direct_call_sections(struct objtool_file *file)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef BUILD_KLP
|
||||
static int create_sym_checksum_section(struct objtool_file *file)
|
||||
{
|
||||
struct section *sec;
|
||||
struct symbol *sym;
|
||||
unsigned int idx = 0;
|
||||
struct sym_checksum *checksum;
|
||||
size_t entsize = sizeof(struct sym_checksum);
|
||||
|
||||
sec = find_section_by_name(file->elf, ".discard.sym_checksum");
|
||||
if (sec) {
|
||||
if (!opts.dryrun)
|
||||
WARN("file already has .discard.sym_checksum section, skipping");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
for_each_sym(file->elf, sym)
|
||||
if (sym->csum.checksum)
|
||||
idx++;
|
||||
|
||||
if (!idx)
|
||||
return 0;
|
||||
|
||||
sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize,
|
||||
idx, idx);
|
||||
if (!sec)
|
||||
return -1;
|
||||
|
||||
idx = 0;
|
||||
for_each_sym(file->elf, sym) {
|
||||
if (!sym->csum.checksum)
|
||||
continue;
|
||||
|
||||
if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize,
|
||||
sym, 0, R_TEXT64))
|
||||
return -1;
|
||||
|
||||
checksum = (struct sym_checksum *)sec->data->d_buf + idx;
|
||||
checksum->addr = 0; /* reloc */
|
||||
checksum->checksum = sym->csum.checksum;
|
||||
|
||||
mark_sec_changed(file->elf, sec, true);
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int create_sym_checksum_section(struct objtool_file *file) { return -EINVAL; }
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Warnings shouldn't be reported for ignored functions.
|
||||
*/
|
||||
|
|
@ -1748,6 +1802,7 @@ static int handle_group_alt(struct objtool_file *file,
|
|||
nop->type = INSN_NOP;
|
||||
nop->sym = orig_insn->sym;
|
||||
nop->alt_group = new_alt_group;
|
||||
nop->fake = 1;
|
||||
}
|
||||
|
||||
if (!special_alt->new_len) {
|
||||
|
|
@ -2517,6 +2572,14 @@ static void mark_holes(struct objtool_file *file)
|
|||
}
|
||||
}
|
||||
|
||||
static bool validate_branch_enabled(void)
|
||||
{
|
||||
return opts.stackval ||
|
||||
opts.orc ||
|
||||
opts.uaccess ||
|
||||
opts.checksum;
|
||||
}
|
||||
|
||||
static int decode_sections(struct objtool_file *file)
|
||||
{
|
||||
mark_rodata(file);
|
||||
|
|
@ -2545,8 +2608,7 @@ static int decode_sections(struct objtool_file *file)
|
|||
* Must be before add_jump_destinations(), which depends on 'func'
|
||||
* being set for alternatives, to enable proper sibling call detection.
|
||||
*/
|
||||
if (opts.stackval || opts.orc || opts.uaccess || opts.noinstr ||
|
||||
opts.hack_jump_label) {
|
||||
if (validate_branch_enabled() || opts.noinstr || opts.hack_jump_label) {
|
||||
if (add_special_section_alts(file))
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -3518,6 +3580,50 @@ static bool skip_alt_group(struct instruction *insn)
|
|||
return alt_insn->type == INSN_CLAC || alt_insn->type == INSN_STAC;
|
||||
}
|
||||
|
||||
static void checksum_update_insn(struct objtool_file *file, struct symbol *func,
|
||||
struct instruction *insn)
|
||||
{
|
||||
struct reloc *reloc = insn_reloc(file, insn);
|
||||
unsigned long offset;
|
||||
struct symbol *sym;
|
||||
|
||||
if (insn->fake)
|
||||
return;
|
||||
|
||||
checksum_update(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
|
||||
|
||||
if (!reloc) {
|
||||
struct symbol *call_dest = insn_call_dest(insn);
|
||||
|
||||
if (call_dest)
|
||||
checksum_update(func, insn, call_dest->demangled_name,
|
||||
strlen(call_dest->demangled_name));
|
||||
return;
|
||||
}
|
||||
|
||||
sym = reloc->sym;
|
||||
offset = arch_insn_adjusted_addend(insn, reloc);
|
||||
|
||||
if (is_string_sec(sym->sec)) {
|
||||
char *str;
|
||||
|
||||
str = sym->sec->data->d_buf + sym->offset + offset;
|
||||
checksum_update(func, insn, str, strlen(str));
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_sec_sym(sym)) {
|
||||
sym = find_symbol_containing(reloc->sym->sec, offset);
|
||||
if (!sym)
|
||||
return;
|
||||
|
||||
offset -= sym->offset;
|
||||
}
|
||||
|
||||
checksum_update(func, insn, sym->demangled_name, strlen(sym->demangled_name));
|
||||
checksum_update(func, insn, &offset, sizeof(offset));
|
||||
}
|
||||
|
||||
/*
|
||||
* Follow the branch starting at the given instruction, and recursively follow
|
||||
* any other branches (jumps). Meanwhile, track the frame pointer state at
|
||||
|
|
@ -3538,6 +3644,9 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
|
|||
while (1) {
|
||||
next_insn = next_insn_to_validate(file, insn);
|
||||
|
||||
if (opts.checksum && func && insn->sec)
|
||||
checksum_update_insn(file, func, insn);
|
||||
|
||||
if (func && insn_func(insn) && func != insn_func(insn)->pfunc) {
|
||||
/* Ignore KCFI type preambles, which always fall through */
|
||||
if (is_prefix_func(func))
|
||||
|
|
@ -3787,7 +3896,13 @@ static int validate_unwind_hint(struct objtool_file *file,
|
|||
struct insn_state *state)
|
||||
{
|
||||
if (insn->hint && !insn->visited) {
|
||||
int ret = validate_branch(file, insn_func(insn), insn, *state);
|
||||
struct symbol *func = insn_func(insn);
|
||||
int ret;
|
||||
|
||||
if (opts.checksum)
|
||||
checksum_init(func);
|
||||
|
||||
ret = validate_branch(file, func, insn, *state);
|
||||
if (ret)
|
||||
BT_INSN(insn, "<=== (hint)");
|
||||
return ret;
|
||||
|
|
@ -4166,6 +4281,7 @@ static int validate_symbol(struct objtool_file *file, struct section *sec,
|
|||
struct symbol *sym, struct insn_state *state)
|
||||
{
|
||||
struct instruction *insn;
|
||||
struct symbol *func;
|
||||
int ret;
|
||||
|
||||
if (!sym->len) {
|
||||
|
|
@ -4183,9 +4299,18 @@ static int validate_symbol(struct objtool_file *file, struct section *sec,
|
|||
if (opts.uaccess)
|
||||
state->uaccess = sym->uaccess_safe;
|
||||
|
||||
ret = validate_branch(file, insn_func(insn), insn, *state);
|
||||
func = insn_func(insn);
|
||||
|
||||
if (opts.checksum)
|
||||
checksum_init(func);
|
||||
|
||||
ret = validate_branch(file, func, insn, *state);
|
||||
if (ret)
|
||||
BT_INSN(insn, "<=== (sym)");
|
||||
|
||||
if (opts.checksum)
|
||||
checksum_finish(func);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -4703,7 +4828,7 @@ int check(struct objtool_file *file)
|
|||
if (opts.retpoline)
|
||||
warnings += validate_retpoline(file);
|
||||
|
||||
if (opts.stackval || opts.orc || opts.uaccess) {
|
||||
if (validate_branch_enabled()) {
|
||||
int w = 0;
|
||||
|
||||
w += validate_functions(file);
|
||||
|
|
@ -4782,6 +4907,12 @@ int check(struct objtool_file *file)
|
|||
if (opts.noabs)
|
||||
warnings += check_abs_references(file);
|
||||
|
||||
if (opts.checksum) {
|
||||
ret = create_sym_checksum_section(file);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (opts.orc && nr_insns) {
|
||||
ret = orc_create(file);
|
||||
if (ret)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <libgen.h>
|
||||
#include <ctype.h>
|
||||
#include <linux/interval_tree_generic.h>
|
||||
#include <objtool/builtin.h>
|
||||
#include <objtool/elf.h>
|
||||
|
|
@ -412,7 +413,38 @@ static int read_sections(struct elf *elf)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void elf_add_symbol(struct elf *elf, struct symbol *sym)
|
||||
static const char *demangle_name(struct symbol *sym)
|
||||
{
|
||||
char *str;
|
||||
|
||||
if (!is_local_sym(sym))
|
||||
return sym->name;
|
||||
|
||||
if (!is_func_sym(sym) && !is_object_sym(sym))
|
||||
return sym->name;
|
||||
|
||||
if (!strstarts(sym->name, "__UNIQUE_ID_") && !strchr(sym->name, '.'))
|
||||
return sym->name;
|
||||
|
||||
str = strdup(sym->name);
|
||||
if (!str) {
|
||||
ERROR_GLIBC("strdup");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int i = strlen(str) - 1; i >= 0; i--) {
|
||||
char c = str[i];
|
||||
|
||||
if (!isdigit(c) && c != '.') {
|
||||
str[i + 1] = '\0';
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static int elf_add_symbol(struct elf *elf, struct symbol *sym)
|
||||
{
|
||||
struct list_head *entry;
|
||||
struct rb_node *pnode;
|
||||
|
|
@ -456,6 +488,12 @@ static void elf_add_symbol(struct elf *elf, struct symbol *sym)
|
|||
if (is_func_sym(sym) && strstr(sym->name, ".cold"))
|
||||
sym->cold = 1;
|
||||
sym->pfunc = sym->cfunc = sym;
|
||||
|
||||
sym->demangled_name = demangle_name(sym);
|
||||
if (!sym->demangled_name)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_symbols(struct elf *elf)
|
||||
|
|
@ -529,7 +567,8 @@ static int read_symbols(struct elf *elf)
|
|||
} else
|
||||
sym->sec = find_section_by_index(elf, 0);
|
||||
|
||||
elf_add_symbol(elf, sym);
|
||||
if (elf_add_symbol(elf, sym))
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (opts.stats) {
|
||||
|
|
@ -867,7 +906,8 @@ struct symbol *elf_create_symbol(struct elf *elf, const char *name,
|
|||
mark_sec_changed(elf, symtab_shndx, true);
|
||||
}
|
||||
|
||||
elf_add_symbol(elf, sym);
|
||||
if (elf_add_symbol(elf, sym))
|
||||
return NULL;
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,15 @@
|
|||
|
||||
struct opts {
|
||||
/* actions: */
|
||||
bool cfi;
|
||||
bool checksum;
|
||||
bool dump_orc;
|
||||
bool hack_jump_label;
|
||||
bool hack_noinstr;
|
||||
bool hack_skylake;
|
||||
bool ibt;
|
||||
bool mcount;
|
||||
bool noabs;
|
||||
bool noinstr;
|
||||
bool orc;
|
||||
bool retpoline;
|
||||
|
|
@ -25,8 +28,6 @@ struct opts {
|
|||
bool static_call;
|
||||
bool uaccess;
|
||||
int prefix;
|
||||
bool cfi;
|
||||
bool noabs;
|
||||
|
||||
/* options: */
|
||||
bool backtrace;
|
||||
|
|
|
|||
|
|
@ -65,8 +65,9 @@ struct instruction {
|
|||
unret : 1,
|
||||
visited : 4,
|
||||
no_reloc : 1,
|
||||
hole : 1;
|
||||
/* 10 bit hole */
|
||||
hole : 1,
|
||||
fake : 1;
|
||||
/* 9 bit hole */
|
||||
|
||||
struct alt_group *alt_group;
|
||||
struct instruction *jump_dest;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#ifndef _OBJTOOL_CHECKSUM_H
|
||||
#define _OBJTOOL_CHECKSUM_H
|
||||
|
||||
#include <objtool/elf.h>
|
||||
|
||||
#ifdef BUILD_KLP
|
||||
|
||||
static inline void checksum_init(struct symbol *func)
|
||||
{
|
||||
if (func && !func->csum.state) {
|
||||
func->csum.state = XXH3_createState();
|
||||
XXH3_64bits_reset(func->csum.state);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void checksum_update(struct symbol *func,
|
||||
struct instruction *insn,
|
||||
const void *data, size_t size)
|
||||
{
|
||||
XXH3_64bits_update(func->csum.state, data, size);
|
||||
}
|
||||
|
||||
static inline void checksum_finish(struct symbol *func)
|
||||
{
|
||||
if (func && func->csum.state) {
|
||||
func->csum.checksum = XXH3_64bits_digest(func->csum.state);
|
||||
func->csum.state = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#else /* !BUILD_KLP */
|
||||
|
||||
static inline void checksum_init(struct symbol *func) {}
|
||||
static inline void checksum_update(struct symbol *func,
|
||||
struct instruction *insn,
|
||||
const void *data, size_t size) {}
|
||||
static inline void checksum_finish(struct symbol *func) {}
|
||||
|
||||
#endif /* !BUILD_KLP */
|
||||
|
||||
#endif /* _OBJTOOL_CHECKSUM_H */
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _OBJTOOL_CHECKSUM_TYPES_H
|
||||
#define _OBJTOOL_CHECKSUM_TYPES_H
|
||||
|
||||
struct sym_checksum {
|
||||
u64 addr;
|
||||
u64 checksum;
|
||||
};
|
||||
|
||||
#ifdef BUILD_KLP
|
||||
|
||||
#include <xxhash.h>
|
||||
|
||||
struct checksum {
|
||||
XXH3_state_t *state;
|
||||
XXH64_hash_t checksum;
|
||||
};
|
||||
|
||||
#else /* !BUILD_KLP */
|
||||
|
||||
struct checksum {};
|
||||
|
||||
#endif /* !BUILD_KLP */
|
||||
|
||||
#endif /* _OBJTOOL_CHECKSUM_TYPES_H */
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/jhash.h>
|
||||
|
||||
#include <objtool/endianness.h>
|
||||
#include <objtool/checksum_types.h>
|
||||
#include <arch/elf.h>
|
||||
|
||||
#define SYM_NAME_LEN 512
|
||||
|
|
@ -61,7 +62,7 @@ struct symbol {
|
|||
struct elf_hash_node name_hash;
|
||||
GElf_Sym sym;
|
||||
struct section *sec;
|
||||
const char *name;
|
||||
const char *name, *demangled_name;
|
||||
unsigned int idx, len;
|
||||
unsigned long offset;
|
||||
unsigned long __subtree_last;
|
||||
|
|
@ -84,6 +85,7 @@ struct symbol {
|
|||
struct list_head pv_target;
|
||||
struct reloc *relocs;
|
||||
struct section *group_sec;
|
||||
struct checksum csum;
|
||||
};
|
||||
|
||||
struct reloc {
|
||||
|
|
|
|||
Loading…
Reference in New Issue