objtool: Refactor sibling call detection logic

Simplify the sibling call detection logic a bit.

Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Tested-by: Nick Desaulniers <ndesaulniers@google.com>
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lkml.kernel.org/r/8357dbef9e7f5512e76bf83a76c81722fc09eb5e.1563413318.git.jpoimboe@redhat.com
This commit is contained in:
Josh Poimboeuf 2019-07-17 20:36:52 -05:00 committed by Thomas Gleixner
parent c9bab22bc4
commit 0c1ddd3317
1 changed files with 33 additions and 32 deletions

View File

@ -97,6 +97,20 @@ static struct instruction *next_insn_same_func(struct objtool_file *file,
for (insn = next_insn_same_sec(file, insn); insn; \ for (insn = next_insn_same_sec(file, insn); insn; \
insn = next_insn_same_sec(file, insn)) insn = next_insn_same_sec(file, insn))
static bool is_sibling_call(struct instruction *insn)
{
/* An indirect jump is either a sibling call or a jump to a table. */
if (insn->type == INSN_JUMP_DYNAMIC)
return list_empty(&insn->alts);
if (insn->type != INSN_JUMP_CONDITIONAL &&
insn->type != INSN_JUMP_UNCONDITIONAL)
return false;
/* add_jump_destinations() sets insn->call_dest for sibling calls. */
return !!insn->call_dest;
}
/* /*
* This checks to see if the given function is a "noreturn" function. * This checks to see if the given function is a "noreturn" function.
* *
@ -167,34 +181,25 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func,
* of the sibling call returns. * of the sibling call returns.
*/ */
func_for_each_insn_all(file, func, insn) { func_for_each_insn_all(file, func, insn) {
if (insn->type == INSN_JUMP_UNCONDITIONAL) { if (is_sibling_call(insn)) {
struct instruction *dest = insn->jump_dest; struct instruction *dest = insn->jump_dest;
if (!dest) if (!dest)
/* sibling call to another file */ /* sibling call to another file */
return false; return false;
if (dest->func && dest->func->pfunc != insn->func->pfunc) { /* local sibling call */
if (recursion == 5) {
/* local sibling call */ /*
if (recursion == 5) { * Infinite recursion: two functions have
/* * sibling calls to each other. This is a very
* Infinite recursion: two functions * rare case. It means they aren't dead ends.
* have sibling calls to each other. */
* This is a very rare case. It means return false;
* they aren't dead ends.
*/
return false;
}
return __dead_end_function(file, dest->func,
recursion + 1);
} }
}
if (insn->type == INSN_JUMP_DYNAMIC && list_empty(&insn->alts)) return __dead_end_function(file, dest->func, recursion+1);
/* sibling call */ }
return false;
} }
return true; return true;
@ -581,9 +586,8 @@ static int add_jump_destinations(struct objtool_file *file)
insn->retpoline_safe = true; insn->retpoline_safe = true;
continue; continue;
} else { } else {
/* sibling call */ /* external sibling call */
insn->call_dest = rela->sym; insn->call_dest = rela->sym;
insn->jump_dest = NULL;
continue; continue;
} }
@ -633,9 +637,8 @@ static int add_jump_destinations(struct objtool_file *file)
} else if (insn->jump_dest->func->pfunc != insn->func->pfunc && } else if (insn->jump_dest->func->pfunc != insn->func->pfunc &&
insn->jump_dest->offset == insn->jump_dest->func->offset) { insn->jump_dest->offset == insn->jump_dest->func->offset) {
/* sibling class */ /* internal sibling call */
insn->call_dest = insn->jump_dest->func; insn->call_dest = insn->jump_dest->func;
insn->jump_dest = NULL;
} }
} }
} }
@ -1889,7 +1892,7 @@ static inline bool func_uaccess_safe(struct symbol *func)
return false; return false;
} }
static inline const char *insn_dest_name(struct instruction *insn) static inline const char *call_dest_name(struct instruction *insn)
{ {
if (insn->call_dest) if (insn->call_dest)
return insn->call_dest->name; return insn->call_dest->name;
@ -1901,13 +1904,13 @@ static int validate_call(struct instruction *insn, struct insn_state *state)
{ {
if (state->uaccess && !func_uaccess_safe(insn->call_dest)) { if (state->uaccess && !func_uaccess_safe(insn->call_dest)) {
WARN_FUNC("call to %s() with UACCESS enabled", WARN_FUNC("call to %s() with UACCESS enabled",
insn->sec, insn->offset, insn_dest_name(insn)); insn->sec, insn->offset, call_dest_name(insn));
return 1; return 1;
} }
if (state->df) { if (state->df) {
WARN_FUNC("call to %s() with DF set", WARN_FUNC("call to %s() with DF set",
insn->sec, insn->offset, insn_dest_name(insn)); insn->sec, insn->offset, call_dest_name(insn));
return 1; return 1;
} }
@ -2088,14 +2091,12 @@ 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 (func && !insn->jump_dest) { if (func && is_sibling_call(insn)) {
ret = validate_sibling_call(insn, &state); ret = validate_sibling_call(insn, &state);
if (ret) if (ret)
return ret; return ret;
} else if (insn->jump_dest && } else if (insn->jump_dest) {
(!func || !insn->jump_dest->func ||
insn->jump_dest->func->pfunc == func)) {
ret = validate_branch(file, func, ret = validate_branch(file, func,
insn->jump_dest, state); insn->jump_dest, state);
if (ret) { if (ret) {
@ -2111,7 +2112,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
break; break;
case INSN_JUMP_DYNAMIC: case INSN_JUMP_DYNAMIC:
if (func && list_empty(&insn->alts)) { if (func && is_sibling_call(insn)) {
ret = validate_sibling_call(insn, &state); ret = validate_sibling_call(insn, &state);
if (ret) if (ret)
return ret; return ret;