objtool: Extract code to validate instruction from the validate branch loop

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>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Josh Poimboeuf <jpoimboe@kernel.org>
Link: https://patch.msgid.link/20251121095340.464045-9-alexandre.chartre@oracle.com
This commit is contained in:
Alexandre Chartre 2025-11-21 10:53:18 +01:00 committed by Peter Zijlstra
parent 0bb080ba64
commit a0e5bf9fd6
1 changed files with 235 additions and 211 deletions

View File

@ -3654,47 +3654,27 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func,
checksum_update(func, insn, &offset, sizeof(offset)); 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
* 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 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 alternative *alt;
struct instruction *next_insn, *prev_insn = NULL;
u8 visited; u8 visited;
int ret; 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;
while (1) { visited = VISITED_BRANCH << statep->uaccess;
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))
return 0;
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;
}
visited = VISITED_BRANCH << state.uaccess;
if (insn->visited & VISITED_BRANCH_MASK) { if (insn->visited & VISITED_BRANCH_MASK) {
if (!insn->hint && !insn_cfi_match(insn, &state.cfi)) if (!insn->hint && !insn_cfi_match(insn, &statep->cfi))
return 1; return 1;
if (insn->visited & visited) if (insn->visited & visited)
@ -3703,8 +3683,8 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
nr_insns_visited++; nr_insns_visited++;
} }
if (state.noinstr) if (statep->noinstr)
state.instr += insn->instr; statep->instr += insn->instr;
if (insn->hint) { if (insn->hint) {
if (insn->restore) { if (insn->restore) {
@ -3746,15 +3726,15 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
nr_cfi_reused++; nr_cfi_reused++;
} }
state.cfi = *insn->cfi; statep->cfi = *insn->cfi;
} else { } else {
/* XXX track if we actually changed state.cfi */ /* XXX track if we actually changed statep->cfi */
if (prev_insn && !cficmp(prev_insn->cfi, &state.cfi)) { if (prev_insn && !cficmp(prev_insn->cfi, &statep->cfi)) {
insn->cfi = prev_insn->cfi; insn->cfi = prev_insn->cfi;
nr_cfi_reused++; nr_cfi_reused++;
} else { } else {
insn->cfi = cfi_hash_find_or_add(&state.cfi); insn->cfi = cfi_hash_find_or_add(&statep->cfi);
} }
} }
@ -3765,7 +3745,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
if (insn->alts) { if (insn->alts) {
for (alt = insn->alts; alt; alt = alt->next) { for (alt = insn->alts; alt; alt = alt->next) {
ret = validate_branch(file, func, alt->insn, state); ret = validate_branch(file, func, alt->insn, *statep);
if (ret) { if (ret) {
BT_INSN(insn, "(alt)"); BT_INSN(insn, "(alt)");
return ret; return ret;
@ -3776,22 +3756,22 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
if (skip_alt_group(insn)) if (skip_alt_group(insn))
return 0; return 0;
if (handle_insn_ops(insn, next_insn, &state)) if (handle_insn_ops(insn, next_insn, statep))
return 1; return 1;
switch (insn->type) { switch (insn->type) {
case INSN_RETURN: case INSN_RETURN:
return validate_return(func, insn, &state); return validate_return(func, insn, statep);
case INSN_CALL: case INSN_CALL:
case INSN_CALL_DYNAMIC: case INSN_CALL_DYNAMIC:
ret = validate_call(file, insn, &state); ret = validate_call(file, insn, statep);
if (ret) if (ret)
return ret; return ret;
if (opts.stackval && func && !is_special_call(insn) && if (opts.stackval && func && !is_special_call(insn) &&
!has_valid_stack_frame(&state)) { !has_valid_stack_frame(statep)) {
WARN_INSN(insn, "call without frame pointer save/setup"); WARN_INSN(insn, "call without frame pointer save/setup");
return 1; return 1;
} }
@ -3801,13 +3781,13 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
case INSN_JUMP_CONDITIONAL: case INSN_JUMP_CONDITIONAL:
case INSN_JUMP_UNCONDITIONAL: case INSN_JUMP_UNCONDITIONAL:
if (is_sibling_call(insn)) { if (is_sibling_call(insn)) {
ret = validate_sibling_call(file, insn, &state); ret = validate_sibling_call(file, insn, statep);
if (ret) if (ret)
return ret; return ret;
} else if (insn->jump_dest) { } else if (insn->jump_dest) {
ret = validate_branch(file, func, ret = validate_branch(file, func,
insn->jump_dest, state); insn->jump_dest, *statep);
if (ret) { if (ret) {
BT_INSN(insn, "(branch)"); BT_INSN(insn, "(branch)");
return ret; return ret;
@ -3822,7 +3802,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
case INSN_JUMP_DYNAMIC: case INSN_JUMP_DYNAMIC:
case INSN_JUMP_DYNAMIC_CONDITIONAL: case INSN_JUMP_DYNAMIC_CONDITIONAL:
if (is_sibling_call(insn)) { if (is_sibling_call(insn)) {
ret = validate_sibling_call(file, insn, &state); ret = validate_sibling_call(file, insn, statep);
if (ret) if (ret)
return ret; return ret;
} }
@ -3852,55 +3832,99 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
if (!opts.uaccess) if (!opts.uaccess)
break; break;
if (state.uaccess) { if (statep->uaccess) {
WARN_INSN(insn, "recursive UACCESS enable"); WARN_INSN(insn, "recursive UACCESS enable");
return 1; return 1;
} }
state.uaccess = true; statep->uaccess = true;
break; break;
case INSN_CLAC: case INSN_CLAC:
if (!opts.uaccess) if (!opts.uaccess)
break; break;
if (!state.uaccess && func) { if (!statep->uaccess && func) {
WARN_INSN(insn, "redundant UACCESS disable"); WARN_INSN(insn, "redundant UACCESS disable");
return 1; return 1;
} }
if (func_uaccess_safe(func) && !state.uaccess_stack) { if (func_uaccess_safe(func) && !statep->uaccess_stack) {
WARN_INSN(insn, "UACCESS-safe disables UACCESS"); WARN_INSN(insn, "UACCESS-safe disables UACCESS");
return 1; return 1;
} }
state.uaccess = false; statep->uaccess = false;
break; break;
case INSN_STD: case INSN_STD:
if (state.df) { if (statep->df) {
WARN_INSN(insn, "recursive STD"); WARN_INSN(insn, "recursive STD");
return 1; return 1;
} }
state.df = true; statep->df = true;
break; break;
case INSN_CLD: case INSN_CLD:
if (!state.df && func) { if (!statep->df && func) {
WARN_INSN(insn, "redundant CLD"); WARN_INSN(insn, "redundant CLD");
return 1; return 1;
} }
state.df = false; statep->df = false;
break; break;
default: default:
break; break;
} }
if (insn->dead_end) *dead_end = insn->dead_end;
return 0; return 0;
}
/*
* 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;
bool dead_end;
int ret;
if (func && func->ignore)
return 0;
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))
return 0;
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;
}
ret = validate_insn(file, func, insn, &state, prev_insn, next_insn,
&dead_end);
if (dead_end)
break;
if (!next_insn) { if (!next_insn) {
if (state.cfi.cfa.base == CFI_UNDEFINED) if (state.cfi.cfa.base == CFI_UNDEFINED)
@ -3918,7 +3942,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
insn = next_insn; insn = next_insn;
} }
return 0; return ret;
} }
static int validate_unwind_hint(struct objtool_file *file, static int validate_unwind_hint(struct objtool_file *file,