Commit 70589843 authored by Alexandre Chartre's avatar Alexandre Chartre Committed by Peter Zijlstra
Browse files

objtool: Add option to trace function validation



Add an option to trace and have information during the validation
of specified functions. Functions are specified with the --trace
option which can be a single function name (e.g. --trace foo to
trace the function with the name "foo"), or a shell wildcard
pattern (e.g. --trace foo* to trace all functions with a name
starting with "foo").

Signed-off-by: default avatarAlexandre Chartre <alexandre.chartre@oracle.com>
Signed-off-by: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: default avatarJosh Poimboeuf <jpoimboe@kernel.org>
Link: https://patch.msgid.link/20251121095340.464045-11-alexandre.chartre@oracle.com
parent de0248fb
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ objtool-y += elf.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-diff.o klp-post-link.o
+1 −0
Original line number Diff line number Diff line
@@ -103,6 +103,7 @@ static const struct option check_options[] = {
	OPT_STRING('o',		 "output", &opts.output, "file", "output file name"),
	OPT_BOOLEAN(0,		 "sec-address", &opts.sec_address, "print section addresses in warnings"),
	OPT_BOOLEAN(0,		 "stats", &opts.stats, "print statistics"),
	OPT_STRING(0,		 "trace", &opts.trace, "func", "trace function validation"),
	OPT_BOOLEAN('v',	 "verbose", &opts.verbose, "verbose warnings"),
	OPT_BOOLEAN(0,		 "werror", &opts.werror, "return error on warnings"),

+87 −17
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
 */

#define _GNU_SOURCE /* memmem() */
#include <fnmatch.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
@@ -15,6 +16,7 @@
#include <objtool/disas.h>
#include <objtool/check.h>
#include <objtool/special.h>
#include <objtool/trace.h>
#include <objtool/warn.h>
#include <objtool/checksum.h>
#include <objtool/util.h>
@@ -37,7 +39,9 @@ static struct cfi_state init_cfi;
static struct cfi_state func_cfi;
static struct cfi_state force_undefined_cfi;

static size_t sym_name_max_len;
struct disas_context *objtool_disas_ctx;

size_t sym_name_max_len;

struct instruction *find_insn(struct objtool_file *file,
			      struct section *sec, unsigned long offset)
@@ -3556,8 +3560,10 @@ static bool skip_alt_group(struct instruction *insn)
		return false;

	/* ANNOTATE_IGNORE_ALTERNATIVE */
	if (insn->alt_group->ignore)
	if (insn->alt_group->ignore) {
		TRACE_INSN(insn, "alt group ignored");
		return true;
	}

	/*
	 * For NOP patched with CLAC/STAC, only follow the latter to avoid
@@ -3663,6 +3669,8 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func,

static int validate_branch(struct objtool_file *file, struct symbol *func,
			   struct instruction *insn, struct insn_state state);
static int do_validate_branch(struct objtool_file *file, struct symbol *func,
			      struct instruction *insn, struct insn_state state);

static int validate_insn(struct objtool_file *file, struct symbol *func,
			 struct instruction *insn, struct insn_state *statep,
@@ -3684,8 +3692,10 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
		if (!insn->hint && !insn_cfi_match(insn, &statep->cfi))
			return 1;

		if (insn->visited & visited)
		if (insn->visited & visited) {
			TRACE_INSN(insn, "already visited");
			return 0;
		}
	} else {
		nr_insns_visited++;
	}
@@ -3722,8 +3732,10 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
				 * It will be seen later via the
				 * straight-line path.
				 */
				if (!prev_insn)
				if (!prev_insn) {
					TRACE_INSN(insn, "defer restore");
					return 0;
				}

				WARN_INSN(insn, "objtool isn't smart enough to handle this CFI save/restore combo");
				return 1;
@@ -3751,13 +3763,23 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
		return 1;

	if (insn->alts) {
		int i, num_alts;

		num_alts = 0;
		for (alt = insn->alts; alt; alt = alt->next)
			num_alts++;

		i = 1;
		for (alt = insn->alts; alt; alt = alt->next) {
			TRACE_INSN(insn, "alternative %d/%d", i, num_alts);
			ret = validate_branch(file, func, alt->insn, *statep);
			if (ret) {
				BT_INSN(insn, "(alt)");
				return ret;
			}
			i++;
		}
		TRACE_INSN(insn, "alternative DEFAULT");
	}

	if (skip_alt_group(insn))
@@ -3769,10 +3791,16 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
	switch (insn->type) {

	case INSN_RETURN:
		TRACE_INSN(insn, "return");
		return validate_return(func, insn, statep);

	case INSN_CALL:
	case INSN_CALL_DYNAMIC:
		if (insn->type == INSN_CALL)
			TRACE_INSN(insn, "call");
		else
			TRACE_INSN(insn, "indirect call");

		ret = validate_call(file, insn, statep);
		if (ret)
			return ret;
@@ -3788,13 +3816,18 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
	case INSN_JUMP_CONDITIONAL:
	case INSN_JUMP_UNCONDITIONAL:
		if (is_sibling_call(insn)) {
			TRACE_INSN(insn, "sibling call");
			ret = validate_sibling_call(file, insn, statep);
			if (ret)
				return ret;

		} else if (insn->jump_dest) {
			ret = validate_branch(file, func,
					      insn->jump_dest, *statep);
			if (insn->type == INSN_JUMP_UNCONDITIONAL)
				TRACE_INSN(insn, "unconditional jump");
			else
				TRACE_INSN(insn, "jump taken");

			ret = validate_branch(file, func, insn->jump_dest, *statep);
			if (ret) {
				BT_INSN(insn, "(branch)");
				return ret;
@@ -3804,10 +3837,12 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
		if (insn->type == INSN_JUMP_UNCONDITIONAL)
			return 0;

		TRACE_INSN(insn, "jump not taken");
		break;

	case INSN_JUMP_DYNAMIC:
	case INSN_JUMP_DYNAMIC_CONDITIONAL:
		TRACE_INSN(insn, "indirect jump");
		if (is_sibling_call(insn)) {
			ret = validate_sibling_call(file, insn, statep);
			if (ret)
@@ -3820,6 +3855,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
		break;

	case INSN_SYSCALL:
		TRACE_INSN(insn, "syscall");
		if (func && (!next_insn || !next_insn->hint)) {
			WARN_INSN(insn, "unsupported instruction in callable function");
			return 1;
@@ -3828,6 +3864,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
		break;

	case INSN_SYSRET:
		TRACE_INSN(insn, "sysret");
		if (func && (!next_insn || !next_insn->hint)) {
			WARN_INSN(insn, "unsupported instruction in callable function");
			return 1;
@@ -3836,6 +3873,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
		return 0;

	case INSN_STAC:
		TRACE_INSN(insn, "stac");
		if (!opts.uaccess)
			break;

@@ -3848,6 +3886,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
		break;

	case INSN_CLAC:
		TRACE_INSN(insn, "clac");
		if (!opts.uaccess)
			break;

@@ -3865,6 +3904,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
		break;

	case INSN_STD:
		TRACE_INSN(insn, "std");
		if (statep->df) {
			WARN_INSN(insn, "recursive STD");
			return 1;
@@ -3874,6 +3914,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
		break;

	case INSN_CLD:
		TRACE_INSN(insn, "cld");
		if (!statep->df && func) {
			WARN_INSN(insn, "redundant CLD");
			return 1;
@@ -3886,8 +3927,10 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
		break;
	}

	*dead_end = insn->dead_end;
	if (insn->dead_end)
		TRACE_INSN(insn, "dead end");

	*dead_end = insn->dead_end;
	return 0;
}

@@ -3897,7 +3940,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 * each instruction and validate all the rules described in
 * tools/objtool/Documentation/objtool.txt.
 */
static int validate_branch(struct objtool_file *file, struct symbol *func,
static int do_validate_branch(struct objtool_file *file, struct symbol *func,
			      struct instruction *insn, struct insn_state state)
{
	struct instruction *next_insn, *prev_insn = NULL;
@@ -3907,7 +3950,8 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
	if (func && func->ignore)
		return 0;

	while (1) {
	do {
		insn->trace = 0;
		next_insn = next_insn_to_validate(file, insn);

		if (opts.checksum && func && insn->sec)
@@ -3930,10 +3974,15 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,

		ret = validate_insn(file, func, insn, &state, prev_insn, next_insn,
				    &dead_end);
		if (dead_end)
			break;

		if (!next_insn) {
		if (!insn->trace) {
			if (ret)
				TRACE_INSN(insn, "warning (%d)", ret);
			else
				TRACE_INSN(insn, NULL);
		}

		if (!dead_end && !next_insn) {
			if (state.cfi.cfa.base == CFI_UNDEFINED)
				return 0;
			if (file->ignore_unreachables)
@@ -3947,8 +3996,21 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,

		prev_insn = insn;
		insn = next_insn;

	} while (!dead_end);

	return ret;
}

static int validate_branch(struct objtool_file *file, struct symbol *func,
			   struct instruction *insn, struct insn_state state)
{
	int ret;

	trace_depth_inc();
	ret = do_validate_branch(file, func, insn, state);
	trace_depth_dec();

	return ret;
}

@@ -4408,10 +4470,18 @@ static int validate_symbol(struct objtool_file *file, struct section *sec,
	if (opts.checksum)
		checksum_init(func);

	if (opts.trace && !fnmatch(opts.trace, sym->name, 0)) {
		trace_enable();
		TRACE("%s: validation begin\n", sym->name);
	}

	ret = validate_branch(file, func, insn, *state);
	if (ret)
		BT_INSN(insn, "<=== (sym)");

	TRACE("%s: validation %s\n\n", sym->name, ret ? "failed" : "end");
	trace_disable();

	if (opts.checksum)
		checksum_finish(func);

@@ -4823,8 +4893,6 @@ static void free_insns(struct objtool_file *file)
		free(chunk->addr);
}

static struct disas_context *objtool_disas_ctx;

const char *objtool_disas_insn(struct instruction *insn)
{
	struct disas_context *dctx = objtool_disas_ctx;
@@ -4846,8 +4914,10 @@ int check(struct objtool_file *file)
	 * disassembly context to disassemble instruction or function
	 * on warning or backtrace.
	 */
	if (opts.verbose || opts.backtrace) {
	if (opts.verbose || opts.backtrace || opts.trace) {
		disas_ctx = disas_context_create(file);
		if (!disas_ctx)
			opts.trace = false;
		objtool_disas_ctx = disas_ctx;
	}

+115 −0
Original line number Diff line number Diff line
@@ -308,6 +308,121 @@ char *disas_result(struct disas_context *dctx)
	return dctx->result;
}

#define DISAS_INSN_OFFSET_SPACE		10
#define DISAS_INSN_SPACE		60

/*
 * Print a message in the instruction flow. If insn is not NULL then
 * the instruction address is printed in addition of the message,
 * otherwise only the message is printed. In all cases, the instruction
 * itself is not printed.
 */
static int disas_vprint(FILE *stream, struct section *sec, unsigned long offset,
			int depth, const char *format, va_list ap)
{
	const char *addr_str;
	int i, n;
	int len;

	len = sym_name_max_len + DISAS_INSN_OFFSET_SPACE;
	if (depth < 0) {
		len += depth;
		depth = 0;
	}

	n = 0;

	if (sec) {
		addr_str = offstr(sec, offset);
		n += fprintf(stream, "%6lx:  %-*s  ", offset, len, addr_str);
		free((char *)addr_str);
	} else {
		len += DISAS_INSN_OFFSET_SPACE + 1;
		n += fprintf(stream, "%-*s", len, "");
	}

	/* print vertical bars to show the code flow */
	for (i = 0; i < depth; i++)
		n += fprintf(stream, "| ");

	if (format)
		n += vfprintf(stream, format, ap);

	return n;
}

/*
 * Print a message in the instruction flow. If insn is not NULL then
 * the instruction address is printed in addition of the message,
 * otherwise only the message is printed. In all cases, the instruction
 * itself is not printed.
 */
void disas_print_info(FILE *stream, struct instruction *insn, int depth,
		      const char *format, ...)
{
	struct section *sec;
	unsigned long off;
	va_list args;

	if (insn) {
		sec = insn->sec;
		off = insn->offset;
	} else {
		sec = NULL;
		off = 0;
	}

	va_start(args, format);
	disas_vprint(stream, sec, off, depth, format, args);
	va_end(args);
}

/*
 * Print an instruction address (offset and function), the instruction itself
 * and an optional message.
 */
void disas_print_insn(FILE *stream, struct disas_context *dctx,
		      struct instruction *insn, int depth,
		      const char *format, ...)
{
	char fake_nop_insn[32];
	const char *insn_str;
	bool fake_nop;
	va_list args;
	int len;

	/*
	 * Alternative can insert a fake nop, sometimes with no
	 * associated section so nothing to disassemble.
	 */
	fake_nop = (!insn->sec && insn->type == INSN_NOP);
	if (fake_nop) {
		snprintf(fake_nop_insn, 32, "<fake nop> (%d bytes)", insn->len);
		insn_str = fake_nop_insn;
	} else {
		disas_insn(dctx, insn);
		insn_str = disas_result(dctx);
	}

	/* print the instruction */
	len = (depth + 1) * 2 < DISAS_INSN_SPACE ? DISAS_INSN_SPACE - (depth+1) * 2 : 1;
	disas_print_info(stream, insn, depth, "%-*s", len, insn_str);

	/* print message if any */
	if (!format)
		return;

	if (strcmp(format, "\n") == 0) {
		fprintf(stream, "\n");
		return;
	}

	fprintf(stream, " - ");
	va_start(args, format);
	vfprintf(stream, format, args);
	va_end(args);
}

/*
 * Disassemble a single instruction. Return the size of the instruction.
 */
+1 −0
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ struct opts {
	const char *output;
	bool sec_address;
	bool stats;
	const char *trace;
	bool verbose;
	bool werror;
};
Loading