2020-04-14 15:18:35

by Alexandre Chartre

[permalink] [raw]
Subject: [PATCH V3 3/9] 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 <[email protected]>
---
include/linux/frame.h | 11 ++
.../Documentation/stack-validation.txt | 8 ++
tools/objtool/arch/x86/decode.c | 6 +
tools/objtool/check.c | 126 ++++++++++++++----
tools/objtool/check.h | 1 +
5 files changed, 128 insertions(+), 24 deletions(-)

diff --git a/include/linux/frame.h b/include/linux/frame.h
index 02d3ca2d9598..c7178e6c9d48 100644
--- a/include/linux/frame.h
+++ b/include/linux/frame.h
@@ -15,9 +15,20 @@
static void __used __section(.discard.func_stack_frame_non_standard) \
*__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 warning.
+ */
+#define ANNOTATE_INTRA_FUNCTION_CALL \
+ 999: \
+ .pushsection .discard.intra_function_call; \
+ .long 999b; \
+ .popsection;
+
#else /* !CONFIG_STACK_VALIDATION */

#define STACK_FRAME_NON_STANDARD(func)
+#define ANNOTATE_INTRA_FUNCTION_CALL

#endif /* CONFIG_STACK_VALIDATION */

diff --git a/tools/objtool/Documentation/stack-validation.txt b/tools/objtool/Documentation/stack-validation.txt
index de094670050b..09f863fd32d2 100644
--- a/tools/objtool/Documentation/stack-validation.txt
+++ b/tools/objtool/Documentation/stack-validation.txt
@@ -290,6 +290,14 @@ they mean, and suggestions for how to fix them.
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70646


+9. file.o: warning: unsupported 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.
Feel free to ask the objtool maintainer for help.

diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c
index a62e032863a8..f4d70b8835c4 100644
--- a/tools/objtool/arch/x86/decode.c
+++ b/tools/objtool/arch/x86/decode.c
@@ -437,6 +437,12 @@ int arch_decode_instruction(struct elf *elf, struct section *sec,

case 0xe8:
*type = INSN_CALL;
+ /*
+ * For the impact on the stack, a call behaves like
+ * a push of an immediate value (the return address).
+ */
+ op->src.type = OP_SRC_CONST;
+ op->dest.type = OP_DEST_PUSH;
break;

case 0xfc:
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 8f0525d5d895..ad362c5de281 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -654,44 +654,58 @@ static int add_jump_destinations(struct objtool_file *file)
return 0;
}

+static int configure_call(struct objtool_file *file, struct instruction *insn)
+{
+ unsigned long dest_off;
+
+ dest_off = insn->offset + insn->len + insn->immediate;
+ insn->call_dest = find_func_by_offset(insn->sec, dest_off);
+ if (!insn->call_dest)
+ insn->call_dest = find_symbol_by_offset(insn->sec, dest_off);
+
+ if (insn->call_dest) {
+ /* regular call */
+ if (insn->func && insn->call_dest->type != STT_FUNC) {
+ WARN_FUNC("unsupported call to non-function",
+ insn->sec, insn->offset);
+ return -1;
+ }
+ return 0;
+ }
+
+ /* intra-function call */
+ if (!insn->intra_function_call)
+ WARN_FUNC("intra-function call", insn->sec, insn->offset);
+
+ 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;
+}
+
/*
* Find the destination instructions for all calls.
*/
static int add_call_destinations(struct objtool_file *file)
{
struct instruction *insn;
- unsigned long dest_off;
struct rela *rela;

for_each_insn(file, insn) {
- if (insn->type != INSN_CALL)
+ if (insn->type != INSN_CALL || insn->ignore)
continue;

rela = find_rela_by_dest_range(file->elf, insn->sec,
insn->offset, insn->len);
if (!rela) {
- dest_off = insn->offset + insn->len + insn->immediate;
- insn->call_dest = find_func_by_offset(insn->sec, dest_off);
- if (!insn->call_dest)
- insn->call_dest = find_symbol_by_offset(insn->sec, dest_off);
-
- if (insn->ignore)
- continue;
-
- if (!insn->call_dest) {
- WARN_FUNC("unsupported intra-function call",
- 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;
- }
-
- if (insn->func && insn->call_dest->type != STT_FUNC) {
- WARN_FUNC("unsupported call to non-function",
- insn->sec, insn->offset);
+ if (configure_call(file, insn))
return -1;
- }
-
} else if (rela->sym->type == STT_SECTION) {
insn->call_dest = find_func_by_offset(rela->sym->sec,
rela->addend+4);
@@ -1337,6 +1351,42 @@ static int read_retpoline_hints(struct objtool_file *file)
return 0;
}

+static int read_intra_function_call(struct objtool_file *file)
+{
+ struct section *sec;
+ struct instruction *insn;
+ struct rela *rela;
+
+ sec = find_section_by_name(file->elf,
+ ".rela.discard.intra_function_call");
+ if (!sec)
+ return 0;
+
+ list_for_each_entry(rela, &sec->rela_list, list) {
+ 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;
+ }
+
+ insn->intra_function_call = true;
+ }
+
+ return 0;
+}
+
static void mark_rodata(struct objtool_file *file)
{
struct section *sec;
@@ -1392,6 +1442,10 @@ static int decode_sections(struct objtool_file *file)
if (ret)
return ret;

+ ret = read_intra_function_call(file);
+ if (ret)
+ return ret;
+
ret = add_call_destinations(file);
if (ret)
return ret;
@@ -2155,7 +2209,8 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
return ret;

if (!no_fp && func && !is_fentry_call(insn) &&
- !has_valid_stack_frame(&state)) {
+ !has_valid_stack_frame(&state) &&
+ !insn->intra_function_call) {
WARN_FUNC("call without frame pointer save/setup",
sec, insn->offset);
return 1;
@@ -2164,6 +2219,29 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
if (dead_end_function(file, insn->call_dest))
return 0;

+ if (insn->intra_function_call) {
+ /*
+ * The call instruction can update the stack
+ * state. Then make the intra-function call
+ * behaves like and unconditional jump.
+ */
+ ret = update_insn_state(insn, &state);
+ if (ret)
+ return ret;
+
+ ret = validate_branch(file, func, insn,
+ insn->jump_dest, state);
+ if (ret) {
+ if (backtrace) {
+ BT_FUNC("(intra-function call)",
+ insn);
+ }
+ return ret;
+ }
+
+ return 0;
+ }
+
break;

case INSN_JUMP_CONDITIONAL:
diff --git a/tools/objtool/check.h b/tools/objtool/check.h
index 953db38dfc35..6a80903fc4aa 100644
--- a/tools/objtool/check.h
+++ b/tools/objtool/check.h
@@ -36,6 +36,7 @@ struct instruction {
int alt_group;
bool dead_end, ignore, ignore_alts;
bool hint, save, restore;
+ bool intra_function_call;
bool retpoline_safe;
u8 visited;
struct symbol *call_dest;
--
2.18.2


2020-04-14 15:27:51

by Julien Thierry

[permalink] [raw]
Subject: Re: [PATCH V3 3/9] objtool: Add support for intra-function calls

Hi Alexandre,

On 4/14/20 11:36 AM, Alexandre Chartre wrote:
> 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 <[email protected]>
> ---
> include/linux/frame.h | 11 ++
> .../Documentation/stack-validation.txt | 8 ++
> tools/objtool/arch/x86/decode.c | 6 +
> tools/objtool/check.c | 126 ++++++++++++++----
> tools/objtool/check.h | 1 +
> 5 files changed, 128 insertions(+), 24 deletions(-)
>
> diff --git a/include/linux/frame.h b/include/linux/frame.h
> index 02d3ca2d9598..c7178e6c9d48 100644
> --- a/include/linux/frame.h
> +++ b/include/linux/frame.h
> @@ -15,9 +15,20 @@
> static void __used __section(.discard.func_stack_frame_non_standard) \
> *__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 warning.
> + */
> +#define ANNOTATE_INTRA_FUNCTION_CALL \
> + 999: \
> + .pushsection .discard.intra_function_call; \

Very nit-pickish but my brain wants to add an 's' to that section name.
Like for the sections "unwind_hints" or "altinstructions".

> + .long 999b; \
> + .popsection;
> +
> #else /* !CONFIG_STACK_VALIDATION */
>
> #define STACK_FRAME_NON_STANDARD(func)
> +#define ANNOTATE_INTRA_FUNCTION_CALL
>
> #endif /* CONFIG_STACK_VALIDATION */
>
> diff --git a/tools/objtool/Documentation/stack-validation.txt b/tools/objtool/Documentation/stack-validation.txt
> index de094670050b..09f863fd32d2 100644
> --- a/tools/objtool/Documentation/stack-validation.txt
> +++ b/tools/objtool/Documentation/stack-validation.txt
> @@ -290,6 +290,14 @@ they mean, and suggestions for how to fix them.
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70646
>
>
> +9. file.o: warning: unsupported 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.
> Feel free to ask the objtool maintainer for help.
>
> diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c
> index a62e032863a8..f4d70b8835c4 100644
> --- a/tools/objtool/arch/x86/decode.c
> +++ b/tools/objtool/arch/x86/decode.c
> @@ -437,6 +437,12 @@ int arch_decode_instruction(struct elf *elf, struct section *sec,
>
> case 0xe8:
> *type = INSN_CALL;
> + /*
> + * For the impact on the stack, a call behaves like
> + * a push of an immediate value (the return address).
> + */
> + op->src.type = OP_SRC_CONST;
> + op->dest.type = OP_DEST_PUSH;
> break;
>
> case 0xfc:
> diff --git a/tools/objtool/check.c b/tools/objtool/check.c
> index 8f0525d5d895..ad362c5de281 100644
> --- a/tools/objtool/check.c
> +++ b/tools/objtool/check.c
> @@ -654,44 +654,58 @@ static int add_jump_destinations(struct objtool_file *file)
> return 0;
> }
>
> +static int configure_call(struct objtool_file *file, struct instruction *insn)

The name "configure_call()" is a bit vague. What about naming this
set_local_call_destination() ? (Since it deals with call destinations
that don't need relocations and sets their actual target destination).

> +{
> + unsigned long dest_off;
> +
> + dest_off = insn->offset + insn->len + insn->immediate;
> + insn->call_dest = find_func_by_offset(insn->sec, dest_off);
> + if (!insn->call_dest)
> + insn->call_dest = find_symbol_by_offset(insn->sec, dest_off);
> +
> + if (insn->call_dest) {
> + /* regular call */
> + if (insn->func && insn->call_dest->type != STT_FUNC) {
> + WARN_FUNC("unsupported call to non-function",
> + insn->sec, insn->offset);
> + return -1;
> + }
> + return 0;
> + }
> +
> + /* intra-function call */
> + if (!insn->intra_function_call)
> + WARN_FUNC("intra-function call", insn->sec, insn->offset);
> +
> + 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;
> +}
> +
> /*
> * Find the destination instructions for all calls.
> */
> static int add_call_destinations(struct objtool_file *file)
> {
> struct instruction *insn;
> - unsigned long dest_off;
> struct rela *rela;
>
> for_each_insn(file, insn) {
> - if (insn->type != INSN_CALL)
> + if (insn->type != INSN_CALL || insn->ignore)
> continue;
>
> rela = find_rela_by_dest_range(file->elf, insn->sec,
> insn->offset, insn->len);
> if (!rela) {
> - dest_off = insn->offset + insn->len + insn->immediate;
> - insn->call_dest = find_func_by_offset(insn->sec, dest_off);
> - if (!insn->call_dest)
> - insn->call_dest = find_symbol_by_offset(insn->sec, dest_off);
> -
> - if (insn->ignore)
> - continue;
> -
> - if (!insn->call_dest) {
> - WARN_FUNC("unsupported intra-function call",
> - 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;
> - }
> -
> - if (insn->func && insn->call_dest->type != STT_FUNC) {
> - WARN_FUNC("unsupported call to non-function",
> - insn->sec, insn->offset);
> + if (configure_call(file, insn))
> return -1;
> - }
> -
> } else if (rela->sym->type == STT_SECTION) {
> insn->call_dest = find_func_by_offset(rela->sym->sec,
> rela->addend+4);
> @@ -1337,6 +1351,42 @@ static int read_retpoline_hints(struct objtool_file *file)
> return 0;
> }
>
> +static int read_intra_function_call(struct objtool_file *file)
> +{
> + struct section *sec;
> + struct instruction *insn;
> + struct rela *rela;
> +
> + sec = find_section_by_name(file->elf,
> + ".rela.discard.intra_function_call");
> + if (!sec)
> + return 0;
> +
> + list_for_each_entry(rela, &sec->rela_list, list) {
> + 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;
> + }
> +
> + insn->intra_function_call = true;

Maybe you could directly lookup the target instruction here and set
insn->jump_dest here.

Then you wouldn't need as much changes in add_call_destination(), just
adding the following in the "!rela" branch:

if (insn->intra_function_call)
continue;

> + }
> +
> + return 0;
> +}
> +
> static void mark_rodata(struct objtool_file *file)
> {
> struct section *sec;
> @@ -1392,6 +1442,10 @@ static int decode_sections(struct objtool_file *file)
> if (ret)
> return ret;
>
> + ret = read_intra_function_call(file);
> + if (ret)
> + return ret;
> +
> ret = add_call_destinations(file);
> if (ret)
> return ret;
> @@ -2155,7 +2209,8 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
> return ret;
>
> if (!no_fp && func && !is_fentry_call(insn) &&
> - !has_valid_stack_frame(&state)) {
> + !has_valid_stack_frame(&state) &&
> + !insn->intra_function_call) {
> WARN_FUNC("call without frame pointer save/setup",
> sec, insn->offset);
> return 1;
> @@ -2164,6 +2219,29 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
> if (dead_end_function(file, insn->call_dest))
> return 0;
>
> + if (insn->intra_function_call) {
> + /*
> + * The call instruction can update the stack
> + * state. Then make the intra-function call
> + * behaves like and unconditional jump.
> + */
> + ret = update_insn_state(insn, &state);
> + if (ret)
> + return ret;
> +
> + ret = validate_branch(file, func, insn,
> + insn->jump_dest, state);
> + if (ret) {
> + if (backtrace) {
> + BT_FUNC("(intra-function call)",
> + insn);
> + }
> + return ret;
> + }
> +
> + return 0;
> + }
> +
> break;
>
> case INSN_JUMP_CONDITIONAL:
> diff --git a/tools/objtool/check.h b/tools/objtool/check.h
> index 953db38dfc35..6a80903fc4aa 100644
> --- a/tools/objtool/check.h
> +++ b/tools/objtool/check.h
> @@ -36,6 +36,7 @@ struct instruction {
> int alt_group;
> bool dead_end, ignore, ignore_alts;
> bool hint, save, restore;
> + bool intra_function_call;
> bool retpoline_safe;
> u8 visited;
> struct symbol *call_dest;
>

Cheers,

--
Julien Thierry

2020-04-16 17:45:55

by Miroslav Benes

[permalink] [raw]
Subject: Re: [PATCH V3 3/9] objtool: Add support for intra-function calls

> +static int configure_call(struct objtool_file *file, struct instruction *insn)
> +{
> + unsigned long dest_off;
> +
> + dest_off = insn->offset + insn->len + insn->immediate;
> + insn->call_dest = find_func_by_offset(insn->sec, dest_off);
> + if (!insn->call_dest)
> + insn->call_dest = find_symbol_by_offset(insn->sec, dest_off);
> +
> + if (insn->call_dest) {
> + /* regular call */
> + if (insn->func && insn->call_dest->type != STT_FUNC) {
> + WARN_FUNC("unsupported call to non-function",
> + insn->sec, insn->offset);
> + return -1;
> + }
> + return 0;
> + }
> +
> + /* intra-function call */
> + if (!insn->intra_function_call)
> + WARN_FUNC("intra-function call", insn->sec, insn->offset);

"unsupported intra-function call" ?

Miroslav

Subject: [tip: objtool/core] objtool: Add support for intra-function calls

The following commit has been merged into the objtool/core branch of tip:

Commit-ID: 8aa8eb2a8f5b3305a95f39957dd2b715fa668e21
Gitweb: https://git.kernel.org/tip/8aa8eb2a8f5b3305a95f39957dd2b715fa668e21
Author: Alexandre Chartre <[email protected]>
AuthorDate: Tue, 14 Apr 2020 12:36:12 +02:00
Committer: Peter Zijlstra <[email protected]>
CommitterDate: Thu, 30 Apr 2020 20:14:33 +02:00

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 <[email protected]>
Signed-off-by: Peter Zijlstra (Intel) <[email protected]>
Reviewed-by: Miroslav Benes <[email protected]>
Acked-by: Josh Poimboeuf <[email protected]>
Link: https://lkml.kernel.org/r/[email protected]
---
include/linux/frame.h | 11 ++-
tools/objtool/Documentation/stack-validation.txt | 8 ++-
tools/objtool/arch/x86/decode.c | 8 ++-
tools/objtool/check.c | 79 ++++++++++++++-
4 files changed, 102 insertions(+), 4 deletions(-)

diff --git a/include/linux/frame.h b/include/linux/frame.h
index 02d3ca2..303cda6 100644
--- a/include/linux/frame.h
+++ b/include/linux/frame.h
@@ -15,9 +15,20 @@
static void __used __section(.discard.func_stack_frame_non_standard) \
*__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 */

#define STACK_FRAME_NON_STANDARD(func)
+#define ANNOTATE_INTRA_FUNCTION_CALL

#endif /* CONFIG_STACK_VALIDATION */

diff --git a/tools/objtool/Documentation/stack-validation.txt b/tools/objtool/Documentation/stack-validation.txt
index 0189039..0542e46 100644
--- a/tools/objtool/Documentation/stack-validation.txt
+++ b/tools/objtool/Documentation/stack-validation.txt
@@ -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
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.
Feel free to ask the objtool maintainer for help.

diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c
index d7b5d10..4b504fc 100644
--- a/tools/objtool/arch/x86/decode.c
+++ b/tools/objtool/arch/x86/decode.c
@@ -496,6 +496,14 @@ int arch_decode_instruction(const struct elf *elf, const struct section *sec,

case 0xe8:
*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;

case 0xfc:
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index d822858..32dea5f 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -674,6 +674,16 @@ static int add_jump_destinations(struct objtool_file *file)
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.
*/
@@ -699,10 +709,7 @@ static int add_call_destinations(struct objtool_file *file)
continue;

if (!insn->call_dest) {
- WARN_FUNC("unsupported intra-function call",
- 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.");
+ WARN_FUNC("unannotated intra-function call", insn->sec, insn->offset);
return -1;
}

@@ -725,6 +732,15 @@ static int add_call_destinations(struct objtool_file *file)
}
} else
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;
@@ -1404,6 +1420,57 @@ static int read_instr_hints(struct objtool_file *file)
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)
{
struct section *sec;
@@ -1459,6 +1526,10 @@ static int decode_sections(struct objtool_file *file)
if (ret)
return ret;

+ ret = read_intra_function_calls(file);
+ if (ret)
+ return ret;
+
ret = add_call_destinations(file);
if (ret)
return ret;