From: Alexandre Chartre <alexandre.chartre@oracle.com>
To: linux-kernel@vger.kernel.org, mingo@kernel.org,
jpoimboe@kernel.org, peterz@infradead.org
Cc: alexandre.chartre@oracle.com
Subject: [PATCH v4 08/28] objtool: Extract code to validate instruction from the validate branch loop
Date: Thu, 13 Nov 2025 17:48:57 +0100 [thread overview]
Message-ID: <20251113164917.2563486-9-alexandre.chartre@oracle.com> (raw)
In-Reply-To: <20251113164917.2563486-1-alexandre.chartre@oracle.com>
The code to validate a branch loops through all instructions of the
branch and validate each instruction. Move the code to validate an
instruction to a separated function.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/check.c | 392 ++++++++++++++++++++++--------------------
1 file changed, 208 insertions(+), 184 deletions(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 5b977bdb5512f..609994ad6ab41 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3535,256 +3535,280 @@ static bool skip_alt_group(struct instruction *insn)
return alt_insn->type == INSN_CLAC || alt_insn->type == INSN_STAC;
}
-/*
- * Follow the branch starting at the given instruction, and recursively follow
- * any other branches (jumps). Meanwhile, track the frame pointer state at
- * 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)
+ 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,
+ struct instruction *prev_insn, struct instruction *next_insn,
+ bool *dead_end)
{
struct alternative *alt;
- struct instruction *next_insn, *prev_insn = NULL;
- struct section *sec;
u8 visited;
int ret;
- if (func && func->ignore)
- return 0;
+ /*
+ * Any returns before the end of this function are effectively dead
+ * ends, i.e. validate_branch() has reached the end of the branch.
+ */
+ *dead_end = true;
- sec = insn->sec;
+ visited = VISITED_BRANCH << statep->uaccess;
+ if (insn->visited & VISITED_BRANCH_MASK) {
+ if (!insn->hint && !insn_cfi_match(insn, &statep->cfi))
+ return 1;
- while (1) {
- next_insn = next_insn_to_validate(file, insn);
+ if (insn->visited & visited)
+ return 0;
+ } else {
+ nr_insns_visited++;
+ }
- if (func && insn_func(insn) && func != insn_func(insn)->pfunc) {
- /* Ignore KCFI type preambles, which always fall through */
- if (!strncmp(func->name, "__cfi_", 6) ||
- !strncmp(func->name, "__pfx_", 6) ||
- !strncmp(func->name, "__pi___cfi_", 11) ||
- !strncmp(func->name, "__pi___pfx_", 11))
- return 0;
+ if (statep->noinstr)
+ statep->instr += insn->instr;
- if (file->ignore_unreachables)
- return 0;
+ if (insn->hint) {
+ if (insn->restore) {
+ struct instruction *save_insn, *i;
- WARN("%s() falls through to next function %s()",
- func->name, insn_func(insn)->name);
- func->warned = 1;
+ i = insn;
+ save_insn = NULL;
- return 1;
- }
+ sym_for_each_insn_continue_reverse(file, func, i) {
+ if (i->save) {
+ save_insn = i;
+ break;
+ }
+ }
- visited = VISITED_BRANCH << state.uaccess;
- if (insn->visited & VISITED_BRANCH_MASK) {
- if (!insn->hint && !insn_cfi_match(insn, &state.cfi))
+ if (!save_insn) {
+ WARN_INSN(insn, "no corresponding CFI save for CFI restore");
return 1;
+ }
- if (insn->visited & visited)
- return 0;
- } else {
- nr_insns_visited++;
+ if (!save_insn->visited) {
+ /*
+ * If the restore hint insn is at the
+ * beginning of a basic block and was
+ * branched to from elsewhere, and the
+ * save insn hasn't been visited yet,
+ * defer following this branch for now.
+ * It will be seen later via the
+ * straight-line path.
+ */
+ if (!prev_insn)
+ return 0;
+
+ WARN_INSN(insn, "objtool isn't smart enough to handle this CFI save/restore combo");
+ return 1;
+ }
+
+ insn->cfi = save_insn->cfi;
+ nr_cfi_reused++;
}
- if (state.noinstr)
- state.instr += insn->instr;
+ statep->cfi = *insn->cfi;
+ } else {
+ /* XXX track if we actually changed statep->cfi */
- if (insn->hint) {
- if (insn->restore) {
- struct instruction *save_insn, *i;
+ if (prev_insn && !cficmp(prev_insn->cfi, &statep->cfi)) {
+ insn->cfi = prev_insn->cfi;
+ nr_cfi_reused++;
+ } else {
+ insn->cfi = cfi_hash_find_or_add(&statep->cfi);
+ }
+ }
- i = insn;
- save_insn = NULL;
+ insn->visited |= visited;
- sym_for_each_insn_continue_reverse(file, func, i) {
- if (i->save) {
- save_insn = i;
- break;
- }
- }
+ if (propagate_alt_cfi(file, insn))
+ return 1;
- if (!save_insn) {
- WARN_INSN(insn, "no corresponding CFI save for CFI restore");
- return 1;
- }
+ if (insn->alts) {
+ for (alt = insn->alts; alt; alt = alt->next) {
+ ret = validate_branch(file, func, alt->insn, *statep);
+ if (ret) {
+ BT_INSN(insn, "(alt)");
+ return ret;
+ }
+ }
+ }
- if (!save_insn->visited) {
- /*
- * If the restore hint insn is at the
- * beginning of a basic block and was
- * branched to from elsewhere, and the
- * save insn hasn't been visited yet,
- * defer following this branch for now.
- * It will be seen later via the
- * straight-line path.
- */
- if (!prev_insn)
- return 0;
+ if (skip_alt_group(insn))
+ return 0;
- WARN_INSN(insn, "objtool isn't smart enough to handle this CFI save/restore combo");
- return 1;
- }
+ if (handle_insn_ops(insn, next_insn, statep))
+ return 1;
- insn->cfi = save_insn->cfi;
- nr_cfi_reused++;
- }
+ switch (insn->type) {
- state.cfi = *insn->cfi;
- } else {
- /* XXX track if we actually changed state.cfi */
+ case INSN_RETURN:
+ return validate_return(func, insn, statep);
- if (prev_insn && !cficmp(prev_insn->cfi, &state.cfi)) {
- insn->cfi = prev_insn->cfi;
- nr_cfi_reused++;
- } else {
- insn->cfi = cfi_hash_find_or_add(&state.cfi);
- }
+ case INSN_CALL:
+ case INSN_CALL_DYNAMIC:
+ ret = validate_call(file, insn, statep);
+ if (ret)
+ return ret;
+
+ if (opts.stackval && func && !is_special_call(insn) &&
+ !has_valid_stack_frame(statep)) {
+ WARN_INSN(insn, "call without frame pointer save/setup");
+ return 1;
}
- insn->visited |= visited;
+ break;
- if (propagate_alt_cfi(file, insn))
- return 1;
+ case INSN_JUMP_CONDITIONAL:
+ case INSN_JUMP_UNCONDITIONAL:
+ if (is_sibling_call(insn)) {
+ ret = validate_sibling_call(file, insn, statep);
+ if (ret)
+ return ret;
- if (insn->alts) {
- for (alt = insn->alts; alt; alt = alt->next) {
- ret = validate_branch(file, func, alt->insn, state);
- if (ret) {
- BT_INSN(insn, "(alt)");
- return ret;
- }
+ } else if (insn->jump_dest) {
+ ret = validate_branch(file, func,
+ insn->jump_dest, *statep);
+ if (ret) {
+ BT_INSN(insn, "(branch)");
+ return ret;
}
}
- if (skip_alt_group(insn))
+ if (insn->type == INSN_JUMP_UNCONDITIONAL)
return 0;
- if (handle_insn_ops(insn, next_insn, &state))
- return 1;
-
- switch (insn->type) {
-
- case INSN_RETURN:
- return validate_return(func, insn, &state);
+ break;
- case INSN_CALL:
- case INSN_CALL_DYNAMIC:
- ret = validate_call(file, insn, &state);
+ case INSN_JUMP_DYNAMIC:
+ case INSN_JUMP_DYNAMIC_CONDITIONAL:
+ if (is_sibling_call(insn)) {
+ ret = validate_sibling_call(file, insn, statep);
if (ret)
return ret;
+ }
- if (opts.stackval && func && !is_special_call(insn) &&
- !has_valid_stack_frame(&state)) {
- WARN_INSN(insn, "call without frame pointer save/setup");
- return 1;
- }
+ if (insn->type == INSN_JUMP_DYNAMIC)
+ return 0;
- break;
+ break;
- case INSN_JUMP_CONDITIONAL:
- case INSN_JUMP_UNCONDITIONAL:
- if (is_sibling_call(insn)) {
- ret = validate_sibling_call(file, insn, &state);
- if (ret)
- return ret;
+ case INSN_SYSCALL:
+ if (func && (!next_insn || !next_insn->hint)) {
+ WARN_INSN(insn, "unsupported instruction in callable function");
+ return 1;
+ }
- } else if (insn->jump_dest) {
- ret = validate_branch(file, func,
- insn->jump_dest, state);
- if (ret) {
- BT_INSN(insn, "(branch)");
- return ret;
- }
- }
+ break;
- if (insn->type == INSN_JUMP_UNCONDITIONAL)
- return 0;
+ case INSN_SYSRET:
+ if (func && (!next_insn || !next_insn->hint)) {
+ WARN_INSN(insn, "unsupported instruction in callable function");
+ return 1;
+ }
+ return 0;
+
+ case INSN_STAC:
+ if (!opts.uaccess)
break;
- case INSN_JUMP_DYNAMIC:
- case INSN_JUMP_DYNAMIC_CONDITIONAL:
- if (is_sibling_call(insn)) {
- ret = validate_sibling_call(file, insn, &state);
- if (ret)
- return ret;
- }
+ if (statep->uaccess) {
+ WARN_INSN(insn, "recursive UACCESS enable");
+ return 1;
+ }
- if (insn->type == INSN_JUMP_DYNAMIC)
- return 0;
+ statep->uaccess = true;
+ break;
+ case INSN_CLAC:
+ if (!opts.uaccess)
break;
- case INSN_SYSCALL:
- if (func && (!next_insn || !next_insn->hint)) {
- WARN_INSN(insn, "unsupported instruction in callable function");
- return 1;
- }
+ if (!statep->uaccess && func) {
+ WARN_INSN(insn, "redundant UACCESS disable");
+ return 1;
+ }
- break;
+ if (func_uaccess_safe(func) && !statep->uaccess_stack) {
+ WARN_INSN(insn, "UACCESS-safe disables UACCESS");
+ return 1;
+ }
- case INSN_SYSRET:
- if (func && (!next_insn || !next_insn->hint)) {
- WARN_INSN(insn, "unsupported instruction in callable function");
- return 1;
- }
+ statep->uaccess = false;
+ break;
- return 0;
+ case INSN_STD:
+ if (statep->df) {
+ WARN_INSN(insn, "recursive STD");
+ return 1;
+ }
- case INSN_STAC:
- if (!opts.uaccess)
- break;
+ statep->df = true;
+ break;
- if (state.uaccess) {
- WARN_INSN(insn, "recursive UACCESS enable");
- return 1;
- }
+ case INSN_CLD:
+ if (!statep->df && func) {
+ WARN_INSN(insn, "redundant CLD");
+ return 1;
+ }
- state.uaccess = true;
- break;
+ statep->df = false;
+ break;
- case INSN_CLAC:
- if (!opts.uaccess)
- break;
+ default:
+ break;
+ }
- if (!state.uaccess && func) {
- WARN_INSN(insn, "redundant UACCESS disable");
- return 1;
- }
+ *dead_end = insn->dead_end;
- if (func_uaccess_safe(func) && !state.uaccess_stack) {
- WARN_INSN(insn, "UACCESS-safe disables UACCESS");
- return 1;
- }
+ return 0;
+}
- state.uaccess = false;
- break;
+/*
+ * Follow the branch starting at the given instruction, and recursively follow
+ * any other branches (jumps). Meanwhile, track the frame pointer state at
+ * 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)
+{
+ struct instruction *next_insn, *prev_insn = NULL;
+ struct section *sec;
+ bool dead_end;
+ int ret;
- case INSN_STD:
- if (state.df) {
- WARN_INSN(insn, "recursive STD");
- return 1;
- }
+ if (func && func->ignore)
+ return 0;
- state.df = true;
- break;
+ sec = insn->sec;
- case INSN_CLD:
- if (!state.df && func) {
- WARN_INSN(insn, "redundant CLD");
- return 1;
- }
+ while (1) {
+ next_insn = next_insn_to_validate(file, insn);
- state.df = false;
- break;
+ if (func && insn_func(insn) && func != insn_func(insn)->pfunc) {
+ /* Ignore KCFI type preambles, which always fall through */
+ if (!strncmp(func->name, "__cfi_", 6) ||
+ !strncmp(func->name, "__pfx_", 6) ||
+ !strncmp(func->name, "__pi___cfi_", 11) ||
+ !strncmp(func->name, "__pi___pfx_", 11))
+ return 0;
- default:
- break;
+ if (file->ignore_unreachables)
+ return 0;
+
+ WARN("%s() falls through to next function %s()",
+ func->name, insn_func(insn)->name);
+ func->warned = 1;
+
+ return 1;
}
- if (insn->dead_end)
- return 0;
+ ret = validate_insn(file, func, insn, &state, prev_insn, next_insn,
+ &dead_end);
+ if (dead_end)
+ break;
if (!next_insn) {
if (state.cfi.cfa.base == CFI_UNDEFINED)
@@ -3802,7 +3826,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
insn = next_insn;
}
- return 0;
+ return ret;
}
static int validate_unwind_hint(struct objtool_file *file,
--
2.43.5
next prev parent reply other threads:[~2025-11-13 16:49 UTC|newest]
Thread overview: 49+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 01/28] objtool: Move disassembly functions to a separated file Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 02/28] objtool: Create disassembly context Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 03/28] objtool: Disassemble code with libopcodes instead of running objdump Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 04/28] tool build: Remove annoying newline in build output Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 05/28] objtool: Print symbol during disassembly Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 06/28] objtool: Store instruction disassembly result Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 07/28] objtool: Disassemble instruction on warning or backtrace Alexandre Chartre
2025-11-13 16:48 ` Alexandre Chartre [this message]
2025-11-13 16:48 ` [PATCH v4 09/28] objtool: Record symbol name max length Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 10/28] objtool: Add option to trace function validation Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 11/28] objtool: Trace instruction state changes during " Alexandre Chartre
2025-11-14 21:21 ` Josh Poimboeuf
2025-11-17 7:33 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 12/28] objtool: Improve register reporting " Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 13/28] objtool: Identify the different types of alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 14/28] objtool: Improve tracing of alternative instructions Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 15/28] objtool: Do not validate IBT for .return_sites and .call_sites Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 16/28] objtool: Add the --disas=<function-pattern> action Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 17/28] objtool: Print headers for alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 18/28] objtool: Disassemble group alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 19/28] objtool: Print addresses with alternative instructions Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 20/28] objtool: Disassemble exception table alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 21/28] objtool: Disassemble jump " Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 22/28] objtool: Fix address references in alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 23/28] objtool: Provide access to feature and flags of group alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 24/28] objtool: Function to get the name of a CPU feature Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 25/28] objtool: Improve naming of group alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 26/28] objtool: Get the destination name of a PV call Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 27/28] objtool: Improve the disassembly of the pv_ops call Alexandre Chartre
2025-11-15 1:34 ` kernel test robot
2025-11-17 7:37 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 28/28] objtool: Print single line for alternatives with one instruction Alexandre Chartre
2025-11-13 19:55 ` [PATCH v4 00/28] objtool: Function validation tracing David Laight
2025-11-14 8:53 ` Alexandre Chartre
2025-11-14 1:48 ` Josh Poimboeuf
2025-11-14 9:56 ` Alexandre Chartre
2025-11-14 21:34 ` Josh Poimboeuf
2025-11-17 7:50 ` Alexandre Chartre
2025-11-17 9:42 ` David Laight
2025-11-17 9:47 ` Alexandre Chartre
2025-11-17 12:37 ` David Laight
2025-11-17 13:11 ` Alexandre Chartre
2025-11-17 22:09 ` David Laight
2025-11-17 22:38 ` Josh Poimboeuf
2025-11-18 9:58 ` David Laight
2025-11-18 7:19 ` Alexandre Chartre
2025-11-18 9:12 ` Peter Zijlstra
2025-11-18 11:39 ` Alexandre Chartre
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20251113164917.2563486-9-alexandre.chartre@oracle.com \
--to=alexandre.chartre@oracle.com \
--cc=jpoimboe@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mingo@kernel.org \
--cc=peterz@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®