]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
objtool: Add option to trace function validation
authorAlexandre Chartre <alexandre.chartre@oracle.com>
Fri, 21 Nov 2025 09:53:20 +0000 (10:53 +0100)
committerPeter Zijlstra <peterz@infradead.org>
Fri, 21 Nov 2025 14:30:09 +0000 (15:30 +0100)
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: Alexandre Chartre <alexandre.chartre@oracle.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Josh Poimboeuf <jpoimboe@kernel.org>
Link: https://patch.msgid.link/20251121095340.464045-11-alexandre.chartre@oracle.com
tools/objtool/Build
tools/objtool/builtin-check.c
tools/objtool/check.c
tools/objtool/disas.c
tools/objtool/include/objtool/builtin.h
tools/objtool/include/objtool/check.h
tools/objtool/include/objtool/disas.h
tools/objtool/include/objtool/trace.h [new file with mode: 0644]
tools/objtool/include/objtool/warn.h
tools/objtool/trace.c [new file with mode: 0644]

index 9d1e8f28ef953fd133392eb590fca86ab96f9b91..9982e665d58da698df43aec8a03c513cab8b481b 100644 (file)
@@ -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
index aab7fa9c7e00a1e284ee1ced2acc133b8a86bb47..3329d370006b4faf4b4fbfd6bf9739e629743060 100644 (file)
@@ -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"),
 
index 0fbf0eb370516c9793f7bce41f78c2a8b89d8c55..409dec9efb494f00ef5383285571a90d8f0bc6ba 100644 (file)
@@ -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,8 +3940,8 @@ 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,
-                          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)
 {
        struct instruction *next_insn, *prev_insn = NULL;
        bool dead_end;
@@ -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,7 +3996,20 @@ 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;
        }
 
index a030b06c121d2ed3c024c158fe78784bae5f7de8..0ca6e6c8559fd43cb27f0a3b180d6c2eff7057ab 100644 (file)
@@ -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.
  */
index bb0b25eb08ba4fb526ee815d9f7eaacbae019019..991365c10f0e9191133557a468d28e53882955df 100644 (file)
@@ -41,6 +41,7 @@ struct opts {
        const char *output;
        bool sec_address;
        bool stats;
+       const char *trace;
        bool verbose;
        bool werror;
 };
index f96aabd7d54dc06d847327be3066ca6ffaf00c0d..fde958683485f20d2f17c411cae244f5b4da2262 100644 (file)
@@ -66,7 +66,8 @@ struct instruction {
            visited             : 4,
            no_reloc            : 1,
            hole                : 1,
-           fake                : 1;
+           fake                : 1,
+           trace               : 1;
                /* 9 bit hole */
 
        struct alt_group *alt_group;
@@ -143,4 +144,7 @@ struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruc
 
 const char *objtool_disas_insn(struct instruction *insn);
 
+extern size_t sym_name_max_len;
+extern struct disas_context *objtool_disas_ctx;
+
 #endif /* _CHECK_H */
index 1aee1fbe0bb978bc095c265ce054292ddf8c29a4..5db75d06f21971d8703d6114248a85c6bf2808ff 100644 (file)
@@ -19,6 +19,11 @@ int disas_info_init(struct disassemble_info *dinfo,
                    const char *options);
 size_t disas_insn(struct disas_context *dctx, struct instruction *insn);
 char *disas_result(struct disas_context *dctx);
+void disas_print_info(FILE *stream, struct instruction *insn, int depth,
+                     const char *format, ...);
+void disas_print_insn(FILE *stream, struct disas_context *dctx,
+                     struct instruction *insn, int depth,
+                     const char *format, ...);
 
 #else /* DISAS */
 
@@ -51,6 +56,12 @@ static inline char *disas_result(struct disas_context *dctx)
        return NULL;
 }
 
+static inline void disas_print_info(FILE *stream, struct instruction *insn,
+                                   int depth, const char *format, ...) {}
+static inline void disas_print_insn(FILE *stream, struct disas_context *dctx,
+                                   struct instruction *insn, int depth,
+                                   const char *format, ...) {}
+
 #endif /* DISAS */
 
 #endif /* _DISAS_H */
diff --git a/tools/objtool/include/objtool/trace.h b/tools/objtool/include/objtool/trace.h
new file mode 100644 (file)
index 0000000..3f3c830
--- /dev/null
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates.
+ */
+
+#ifndef _TRACE_H
+#define _TRACE_H
+
+#include <objtool/check.h>
+#include <objtool/disas.h>
+
+#ifdef DISAS
+
+extern bool trace;
+extern int trace_depth;
+
+#define TRACE(fmt, ...)                                                \
+({     if (trace)                                              \
+               fprintf(stderr, fmt, ##__VA_ARGS__);            \
+})
+
+#define TRACE_INSN(insn, fmt, ...)                             \
+({                                                             \
+       if (trace) {                                            \
+               disas_print_insn(stderr, objtool_disas_ctx,     \
+                                insn, trace_depth - 1, \
+                                fmt, ##__VA_ARGS__);           \
+               fprintf(stderr, "\n");                          \
+               insn->trace = 1;                                \
+       }                                                       \
+})
+
+static inline void trace_enable(void)
+{
+       trace = true;
+       trace_depth = 0;
+}
+
+static inline void trace_disable(void)
+{
+       trace = false;
+}
+
+static inline void trace_depth_inc(void)
+{
+       if (trace)
+               trace_depth++;
+}
+
+static inline void trace_depth_dec(void)
+{
+       if (trace)
+               trace_depth--;
+}
+
+#else /* DISAS */
+
+#define TRACE(fmt, ...) ({})
+#define TRACE_INSN(insn, fmt, ...) ({})
+
+static inline void trace_enable(void) {}
+static inline void trace_disable(void) {}
+static inline void trace_depth_inc(void) {}
+static inline void trace_depth_dec(void) {}
+
+#endif
+
+#endif /* _TRACE_H */
index f32abc7b1be14dd5d9a8d86cd27bdcf98fcda52d..25ff7942b4d54d845bac459a0c3e220afd1143c0 100644 (file)
@@ -97,6 +97,7 @@ static inline char *offstr(struct section *sec, unsigned long offset)
                _len = (_len < 50) ? 50 - _len : 0;             \
                WARN("  %s: " format "  %*s%s", _str, ##__VA_ARGS__, _len, "", _istr); \
                free(_str);                                             \
+               __insn->trace = 1;                              \
        }                                                       \
 })
 
diff --git a/tools/objtool/trace.c b/tools/objtool/trace.c
new file mode 100644 (file)
index 0000000..134cc33
--- /dev/null
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates.
+ */
+
+#include <objtool/trace.h>
+
+bool trace;
+int trace_depth;