objtool: Add support for intra-function calls
Change objtool to support intra-function calls. On x86, an intra-function call is represented in objtool as a push onto the stack (of the return address), and a jump to the destination address. That way the stack information is correctly updated and the call flow is still accurate. Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Reviewed-by: Miroslav Benes <mbenes@suse.cz> Acked-by: Josh Poimboeuf <jpoimboe@redhat.com> Link: https://lkml.kernel.org/r/20200414103618.12657-4-alexandre.chartre@oracle.com
This commit is contained in:

committed by
Peter Zijlstra

parent
b490f45362
commit
8aa8eb2a8f
@@ -15,9 +15,20 @@
|
|||||||
static void __used __section(.discard.func_stack_frame_non_standard) \
|
static void __used __section(.discard.func_stack_frame_non_standard) \
|
||||||
*__func_stack_frame_non_standard_##func = func
|
*__func_stack_frame_non_standard_##func = func
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This macro indicates that the following intra-function call is valid.
|
||||||
|
* Any non-annotated intra-function call will cause objtool to issue a warning.
|
||||||
|
*/
|
||||||
|
#define ANNOTATE_INTRA_FUNCTION_CALL \
|
||||||
|
999: \
|
||||||
|
.pushsection .discard.intra_function_calls; \
|
||||||
|
.long 999b; \
|
||||||
|
.popsection;
|
||||||
|
|
||||||
#else /* !CONFIG_STACK_VALIDATION */
|
#else /* !CONFIG_STACK_VALIDATION */
|
||||||
|
|
||||||
#define STACK_FRAME_NON_STANDARD(func)
|
#define STACK_FRAME_NON_STANDARD(func)
|
||||||
|
#define ANNOTATE_INTRA_FUNCTION_CALL
|
||||||
|
|
||||||
#endif /* CONFIG_STACK_VALIDATION */
|
#endif /* CONFIG_STACK_VALIDATION */
|
||||||
|
|
||||||
|
@@ -323,6 +323,14 @@ they mean, and suggestions for how to fix them.
|
|||||||
The easiest way to enforce this is to ensure alternatives do not contain
|
The easiest way to enforce this is to ensure alternatives do not contain
|
||||||
any ORC entries, which in turn implies the above constraint.
|
any ORC entries, which in turn implies the above constraint.
|
||||||
|
|
||||||
|
11. file.o: warning: unannotated intra-function call
|
||||||
|
|
||||||
|
This warning means that a direct call is done to a destination which
|
||||||
|
is not at the beginning of a function. If this is a legit call, you
|
||||||
|
can remove this warning by putting the ANNOTATE_INTRA_FUNCTION_CALL
|
||||||
|
directive right before the call.
|
||||||
|
|
||||||
|
|
||||||
If the error doesn't seem to make sense, it could be a bug in objtool.
|
If the error doesn't seem to make sense, it could be a bug in objtool.
|
||||||
Feel free to ask the objtool maintainer for help.
|
Feel free to ask the objtool maintainer for help.
|
||||||
|
|
||||||
|
@@ -496,6 +496,14 @@ int arch_decode_instruction(const struct elf *elf, const struct section *sec,
|
|||||||
|
|
||||||
case 0xe8:
|
case 0xe8:
|
||||||
*type = INSN_CALL;
|
*type = INSN_CALL;
|
||||||
|
/*
|
||||||
|
* For the impact on the stack, a CALL behaves like
|
||||||
|
* a PUSH of an immediate value (the return address).
|
||||||
|
*/
|
||||||
|
ADD_OP(op) {
|
||||||
|
op->src.type = OP_SRC_CONST;
|
||||||
|
op->dest.type = OP_DEST_PUSH;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xfc:
|
case 0xfc:
|
||||||
|
@@ -674,6 +674,16 @@ static int add_jump_destinations(struct objtool_file *file)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void remove_insn_ops(struct instruction *insn)
|
||||||
|
{
|
||||||
|
struct stack_op *op, *tmp;
|
||||||
|
|
||||||
|
list_for_each_entry_safe(op, tmp, &insn->stack_ops, list) {
|
||||||
|
list_del(&op->list);
|
||||||
|
free(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Find the destination instructions for all calls.
|
* Find the destination instructions for all calls.
|
||||||
*/
|
*/
|
||||||
@@ -699,10 +709,7 @@ static int add_call_destinations(struct objtool_file *file)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!insn->call_dest) {
|
if (!insn->call_dest) {
|
||||||
WARN_FUNC("unsupported intra-function call",
|
WARN_FUNC("unannotated intra-function call", insn->sec, insn->offset);
|
||||||
insn->sec, insn->offset);
|
|
||||||
if (retpoline)
|
|
||||||
WARN("If this is a retpoline, please patch it in with alternatives and annotate it with ANNOTATE_NOSPEC_ALTERNATIVE.");
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,6 +732,15 @@ static int add_call_destinations(struct objtool_file *file)
|
|||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
insn->call_dest = rela->sym;
|
insn->call_dest = rela->sym;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Whatever stack impact regular CALLs have, should be undone
|
||||||
|
* by the RETURN of the called function.
|
||||||
|
*
|
||||||
|
* Annotated intra-function calls retain the stack_ops but
|
||||||
|
* are converted to JUMP, see read_intra_function_calls().
|
||||||
|
*/
|
||||||
|
remove_insn_ops(insn);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1404,6 +1420,57 @@ static int read_instr_hints(struct objtool_file *file)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int read_intra_function_calls(struct objtool_file *file)
|
||||||
|
{
|
||||||
|
struct instruction *insn;
|
||||||
|
struct section *sec;
|
||||||
|
struct rela *rela;
|
||||||
|
|
||||||
|
sec = find_section_by_name(file->elf, ".rela.discard.intra_function_calls");
|
||||||
|
if (!sec)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
list_for_each_entry(rela, &sec->rela_list, list) {
|
||||||
|
unsigned long dest_off;
|
||||||
|
|
||||||
|
if (rela->sym->type != STT_SECTION) {
|
||||||
|
WARN("unexpected relocation symbol type in %s",
|
||||||
|
sec->name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
insn = find_insn(file, rela->sym->sec, rela->addend);
|
||||||
|
if (!insn) {
|
||||||
|
WARN("bad .discard.intra_function_call entry");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insn->type != INSN_CALL) {
|
||||||
|
WARN_FUNC("intra_function_call not a direct call",
|
||||||
|
insn->sec, insn->offset);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Treat intra-function CALLs as JMPs, but with a stack_op.
|
||||||
|
* See add_call_destinations(), which strips stack_ops from
|
||||||
|
* normal CALLs.
|
||||||
|
*/
|
||||||
|
insn->type = INSN_JUMP_UNCONDITIONAL;
|
||||||
|
|
||||||
|
dest_off = insn->offset + insn->len + insn->immediate;
|
||||||
|
insn->jump_dest = find_insn(file, insn->sec, dest_off);
|
||||||
|
if (!insn->jump_dest) {
|
||||||
|
WARN_FUNC("can't find call dest at %s+0x%lx",
|
||||||
|
insn->sec, insn->offset,
|
||||||
|
insn->sec->name, dest_off);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void mark_rodata(struct objtool_file *file)
|
static void mark_rodata(struct objtool_file *file)
|
||||||
{
|
{
|
||||||
struct section *sec;
|
struct section *sec;
|
||||||
@@ -1459,6 +1526,10 @@ static int decode_sections(struct objtool_file *file)
|
|||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
ret = read_intra_function_calls(file);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
ret = add_call_destinations(file);
|
ret = add_call_destinations(file);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
Reference in New Issue
Block a user