objtool: Allow STT_NOTYPE -> STT_FUNC+0 sibling-calls
authorPeter Zijlstra <peterz@infradead.org>
Mon, 11 Jul 2022 09:49:50 +0000 (11:49 +0200)
committerPeter Zijlstra <peterz@infradead.org>
Mon, 17 Oct 2022 14:41:09 +0000 (16:41 +0200)
Teach objtool about STT_NOTYPE -> STT_FUNC+0 sibling calls. Doing do
allows slightly simpler .S files.

There is a slight complication in that we specifically do not want to
allow sibling calls from symbol holes (previously covered by STT_WEAK
symbols) -- such things exist where a weak function has a .cold
subfunction for example.

Additionally, STT_NOTYPE tail-calls are allowed to happen with a
modified stack frame, they don't need to obey the normal rules after
all.

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
tools/objtool/check.c

index e532efb9b5ab334ea3d744d4ab5b2016d8cf194b..7936312e10c7955b6335f1b3b8814e8e95fd1693 100644 (file)
@@ -129,16 +129,13 @@ static bool is_jump_table_jump(struct instruction *insn)
 static bool is_sibling_call(struct instruction *insn)
 {
        /*
-        * Assume only ELF functions can make sibling calls.  This ensures
-        * sibling call detection consistency between vmlinux.o and individual
-        * objects.
+        * Assume only STT_FUNC calls have jump-tables.
         */
-       if (!insn_func(insn))
-               return false;
-
-       /* An indirect jump is either a sibling call or a jump to a table. */
-       if (insn->type == INSN_JUMP_DYNAMIC)
-               return !is_jump_table_jump(insn);
+       if (insn_func(insn)) {
+               /* An indirect jump is either a sibling call or a jump to a table. */
+               if (insn->type == INSN_JUMP_DYNAMIC)
+                       return !is_jump_table_jump(insn);
+       }
 
        /* add_jump_destinations() sets insn->call_dest for sibling calls. */
        return (is_static_jump(insn) && insn->call_dest);
@@ -1400,27 +1397,50 @@ static void add_return_call(struct objtool_file *file, struct instruction *insn,
                list_add_tail(&insn->call_node, &file->return_thunk_list);
 }
 
-static bool same_function(struct instruction *insn1, struct instruction *insn2)
-{
-       return insn_func(insn1)->pfunc == insn_func(insn2)->pfunc;
-}
-
-static bool is_first_func_insn(struct objtool_file *file, struct instruction *insn)
+static bool is_first_func_insn(struct objtool_file *file,
+                              struct instruction *insn, struct symbol *sym)
 {
-       if (insn->offset == insn_func(insn)->offset)
+       if (insn->offset == sym->offset)
                return true;
 
+       /* Allow direct CALL/JMP past ENDBR */
        if (opts.ibt) {
                struct instruction *prev = prev_insn_same_sym(file, insn);
 
                if (prev && prev->type == INSN_ENDBR &&
-                   insn->offset == insn_func(insn)->offset + prev->len)
+                   insn->offset == sym->offset + prev->len)
                        return true;
        }
 
        return false;
 }
 
+/*
+ * A sibling call is a tail-call to another symbol -- to differentiate from a
+ * recursive tail-call which is to the same symbol.
+ */
+static bool jump_is_sibling_call(struct objtool_file *file,
+                                struct instruction *from, struct instruction *to)
+{
+       struct symbol *fs = from->sym;
+       struct symbol *ts = to->sym;
+
+       /* Not a sibling call if from/to a symbol hole */
+       if (!fs || !ts)
+               return false;
+
+       /* Not a sibling call if not targeting the start of a symbol. */
+       if (!is_first_func_insn(file, to, ts))
+               return false;
+
+       /* Disallow sibling calls into STT_NOTYPE */
+       if (ts->type == STT_NOTYPE)
+               return false;
+
+       /* Must not be self to be a sibling */
+       return fs->pfunc != ts->pfunc;
+}
+
 /*
  * Find the destination instructions for all jumps.
  */
@@ -1519,18 +1539,18 @@ static int add_jump_destinations(struct objtool_file *file)
                            strstr(insn_func(jump_dest)->name, ".cold")) {
                                insn_func(insn)->cfunc = insn_func(jump_dest);
                                insn_func(jump_dest)->pfunc = insn_func(insn);
-
-                       } else if (!same_function(insn, jump_dest) &&
-                                  is_first_func_insn(file, jump_dest)) {
-                               /*
-                                * Internal sibling call without reloc or with
-                                * STT_SECTION reloc.
-                                */
-                               add_call_dest(file, insn, insn_func(jump_dest), true);
-                               continue;
                        }
                }
 
+               if (jump_is_sibling_call(file, insn, jump_dest)) {
+                       /*
+                        * Internal sibling call without reloc or with
+                        * STT_SECTION reloc.
+                        */
+                       add_call_dest(file, insn, insn_func(jump_dest), true);
+                       continue;
+               }
+
                insn->jump_dest = jump_dest;
        }
 
@@ -3309,7 +3329,7 @@ static int validate_sibling_call(struct objtool_file *file,
                                 struct instruction *insn,
                                 struct insn_state *state)
 {
-       if (has_modified_stack_frame(insn, state)) {
+       if (insn_func(insn) && has_modified_stack_frame(insn, state)) {
                WARN_FUNC("sibling call from callable instruction with modified stack frame",
                                insn->sec, insn->offset);
                return 1;