Commit 0d83da43 authored by Josh Poimboeuf's avatar Josh Poimboeuf
Browse files

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: default avatarPetr Mladek <pmladek@suse.com>
Tested-by: default avatarJoe Lawrence <joe.lawrence@redhat.com>
Signed-off-by: default avatarJosh Poimboeuf <jpoimboe@kernel.org>
parent f6b740ef
Loading
Loading
Loading
Loading
+24 −14
Original line number Diff line number Diff line
@@ -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

+10 −1
Original line number Diff line number Diff line
@@ -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			||
+136 −5
Original line number Diff line number Diff line
@@ -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)
+43 −3
Original line number Diff line number Diff line
@@ -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;
}
+3 −2
Original line number Diff line number Diff line
@@ -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;
Loading