objtool: Add support for intra-function calls
authorAlexandre Chartre <alexandre.chartre@oracle.com>
Tue, 14 Apr 2020 10:36:12 +0000 (12:36 +0200)
committerPeter Zijlstra <peterz@infradead.org>
Thu, 30 Apr 2020 18:14:33 +0000 (20:14 +0200)
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
include/linux/frame.h
tools/objtool/Documentation/stack-validation.txt
tools/objtool/arch/x86/decode.c
tools/objtool/check.c

index 02d3ca2d959853f3e6b7847a07aea0f81192e37f..303cda600e56acce0f05e5508a9843bbc828de61 100644 (file)
        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 */
 
index 0189039489e98a4c9f48f0e28297d0b28af8266a..0542e46c755286ba8dd85111a8f21f1880ddbf34 100644 (file)
@@ -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.
 
index d7b5d1096913852cd875c459494d110e5a694661..4b504fc90bbb37a50dcb63f103c7214498fd56c4 100644 (file)
@@ -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:
index d822858764fe8cc96b1d8e9848127412256015ec..32dea5f3feedc8fb9e0782b3811637cf62539690 100644 (file)
@@ -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;