]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
objtool/klp: Add --checksum option to generate per-function checksums
authorJosh Poimboeuf <jpoimboe@kernel.org>
Wed, 17 Sep 2025 16:03:57 +0000 (09:03 -0700)
committerJosh Poimboeuf <jpoimboe@kernel.org>
Tue, 14 Oct 2025 21:50:17 +0000 (14:50 -0700)
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>
tools/objtool/Makefile
tools/objtool/builtin-check.c
tools/objtool/check.c
tools/objtool/elf.c
tools/objtool/include/objtool/builtin.h
tools/objtool/include/objtool/check.h
tools/objtool/include/objtool/checksum.h [new file with mode: 0644]
tools/objtool/include/objtool/checksum_types.h [new file with mode: 0644]
tools/objtool/include/objtool/elf.h

index fc82d47f2b9a71a063cefe9db32016f91044fb51..958761c05b7c3b6ab669c1ab836092bc610e4ac2 100644 (file)
@@ -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
 
index 99697141964a3d087e4feafc968af131213a43e2..14daa1357c8b98f46cf7b5473e230c3cf1f5f34f 100644 (file)
@@ -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                 ||
index 13ccfe0c7ebaf841038d5ca99cbadf4ebf74263c..f5adbd23c42d7529d9a535d645c6bc49f66cc52e 100644 (file)
@@ -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)
index 6095baba8e9c5d38d81ea226848f4b7f49d2238e..0119b3b4c55403fd7786d81bffdfa018eb026fee 100644 (file)
@@ -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 @@ non_local:
                mark_sec_changed(elf, symtab_shndx, true);
        }
 
-       elf_add_symbol(elf, sym);
+       if (elf_add_symbol(elf, sym))
+               return NULL;
 
        return sym;
 }
index 7d559a2c13b7b3ade34dae62c0063df912999740..338bdab6b9ad8cf2b409e233b2a2310384a35172 100644 (file)
@@ -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;
index 0f4e7ac929ef0c769c2413d2fca1436b352c1be5..d73b0c3ae1ee3a372d3e1deff74f68643f56b4b2 100644 (file)
@@ -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;
diff --git a/tools/objtool/include/objtool/checksum.h b/tools/objtool/include/objtool/checksum.h
new file mode 100644 (file)
index 0000000..927ca74
--- /dev/null
@@ -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 */
diff --git a/tools/objtool/include/objtool/checksum_types.h b/tools/objtool/include/objtool/checksum_types.h
new file mode 100644 (file)
index 0000000..507efdd
--- /dev/null
@@ -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 */
index 814cfc0bbf16b0fcdec42b6a904c365d91239c31..bc7d8a6167f8fd38b6a45f56601de9f5d336cf3a 100644 (file)
@@ -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 {