objtool: Skip all "unreachable instruction" warnings for gcov kernels
[cascardo/linux.git] / tools / objtool / builtin-check.c
index 17fa7fc..4490601 100644 (file)
@@ -97,6 +97,19 @@ static struct instruction *next_insn_same_sec(struct objtool_file *file,
        return next;
 }
 
+static bool gcov_enabled(struct objtool_file *file)
+{
+       struct section *sec;
+       struct symbol *sym;
+
+       list_for_each_entry(sec, &file->elf->sections, list)
+               list_for_each_entry(sym, &sec->symbol_list, list)
+                       if (!strncmp(sym->name, "__gcov_.", 8))
+                               return true;
+
+       return false;
+}
+
 #define for_each_insn(file, insn)                                      \
        list_for_each_entry(insn, &file->insn_list, list)
 
@@ -107,6 +120,12 @@ static struct instruction *next_insn_same_sec(struct objtool_file *file,
                insn->offset < func->offset + func->len;                \
             insn = list_next_entry(insn, list))
 
+#define func_for_each_insn_continue_reverse(file, func, insn)          \
+       for (insn = list_prev_entry(insn, list);                        \
+            &insn->list != &file->insn_list &&                         \
+               insn->sec == func->sec && insn->offset >= func->offset; \
+            insn = list_prev_entry(insn, list))
+
 #define sec_for_each_insn_from(file, insn)                             \
        for (; insn; insn = next_insn_same_sec(file, insn))
 
@@ -169,6 +188,7 @@ static int __dead_end_function(struct objtool_file *file, struct symbol *func,
                "__stack_chk_fail",
                "panic",
                "do_exit",
+               "do_task_dead",
                "__module_put_and_exit",
                "complete_and_exit",
                "kvm_spurious_fault",
@@ -664,65 +684,102 @@ static int add_switch_table(struct objtool_file *file, struct symbol *func,
        return 0;
 }
 
-static int add_func_switch_tables(struct objtool_file *file,
-                                 struct symbol *func)
+/*
+ * find_switch_table() - Given a dynamic jump, find the switch jump table in
+ * .rodata associated with it.
+ *
+ * There are 3 basic patterns:
+ *
+ * 1. jmpq *[rodata addr](,%reg,8)
+ *
+ *    This is the most common case by far.  It jumps to an address in a simple
+ *    jump table which is stored in .rodata.
+ *
+ * 2. jmpq *[rodata addr](%rip)
+ *
+ *    This is caused by a rare GCC quirk, currently only seen in three driver
+ *    functions in the kernel, only with certain obscure non-distro configs.
+ *
+ *    As part of an optimization, GCC makes a copy of an existing switch jump
+ *    table, modifies it, and then hard-codes the jump (albeit with an indirect
+ *    jump) to use a single entry in the table.  The rest of the jump table and
+ *    some of its jump targets remain as dead code.
+ *
+ *    In such a case we can just crudely ignore all unreachable instruction
+ *    warnings for the entire object file.  Ideally we would just ignore them
+ *    for the function, but that would require redesigning the code quite a
+ *    bit.  And honestly that's just not worth doing: unreachable instruction
+ *    warnings are of questionable value anyway, and this is such a rare issue.
+ *
+ * 3. mov [rodata addr],%reg1
+ *    ... some instructions ...
+ *    jmpq *(%reg1,%reg2,8)
+ *
+ *    This is a fairly uncommon pattern which is new for GCC 6.  As of this
+ *    writing, there are 11 occurrences of it in the allmodconfig kernel.
+ *
+ *    TODO: Once we have DWARF CFI and smarter instruction decoding logic,
+ *    ensure the same register is used in the mov and jump instructions.
+ */
+static struct rela *find_switch_table(struct objtool_file *file,
+                                     struct symbol *func,
+                                     struct instruction *insn)
 {
-       struct instruction *insn, *prev_jump;
-       struct rela *text_rela, *rodata_rela, *prev_rela = NULL;
-       int ret;
+       struct rela *text_rela, *rodata_rela;
+       struct instruction *orig_insn = insn;
 
-       prev_jump = NULL;
+       text_rela = find_rela_by_dest_range(insn->sec, insn->offset, insn->len);
+       if (text_rela && text_rela->sym == file->rodata->sym) {
+               /* case 1 */
+               rodata_rela = find_rela_by_dest(file->rodata,
+                                               text_rela->addend);
+               if (rodata_rela)
+                       return rodata_rela;
 
-       func_for_each_insn(file, func, insn) {
-               if (insn->type != INSN_JUMP_DYNAMIC)
-                       continue;
+               /* case 2 */
+               rodata_rela = find_rela_by_dest(file->rodata,
+                                               text_rela->addend + 4);
+               if (!rodata_rela)
+                       return NULL;
+               file->ignore_unreachables = true;
+               return rodata_rela;
+       }
+
+       /* case 3 */
+       func_for_each_insn_continue_reverse(file, func, insn) {
+               if (insn->type == INSN_JUMP_DYNAMIC)
+                       break;
+
+               /* allow small jumps within the range */
+               if (insn->type == INSN_JUMP_UNCONDITIONAL &&
+                   insn->jump_dest &&
+                   (insn->jump_dest->offset <= insn->offset ||
+                    insn->jump_dest->offset >= orig_insn->offset))
+                   break;
 
                text_rela = find_rela_by_dest_range(insn->sec, insn->offset,
                                                    insn->len);
-               if (!text_rela || text_rela->sym != file->rodata->sym)
-                       continue;
+               if (text_rela && text_rela->sym == file->rodata->sym)
+                       return find_rela_by_dest(file->rodata,
+                                                text_rela->addend);
+       }
 
-               /* common case: jmpq *[addr](,%rax,8) */
-               rodata_rela = find_rela_by_dest(file->rodata,
-                                               text_rela->addend);
+       return NULL;
+}
 
-               /*
-                * rare case:   jmpq *[addr](%rip)
-                *
-                * This check is for a rare gcc quirk, currently only seen in
-                * three driver functions in the kernel, only with certain
-                * obscure non-distro configs.
-                *
-                * As part of an optimization, gcc makes a copy of an existing
-                * switch jump table, modifies it, and then hard-codes the jump
-                * (albeit with an indirect jump) to use a single entry in the
-                * table.  The rest of the jump table and some of its jump
-                * targets remain as dead code.
-                *
-                * In such a case we can just crudely ignore all unreachable
-                * instruction warnings for the entire object file.  Ideally we
-                * would just ignore them for the function, but that would
-                * require redesigning the code quite a bit.  And honestly
-                * that's just not worth doing: unreachable instruction
-                * warnings are of questionable value anyway, and this is such
-                * a rare issue.
-                *
-                * kbuild reports:
-                * - https://lkml.kernel.org/r/201603231906.LWcVUpxm%25fengguang.wu@intel.com
-                * - https://lkml.kernel.org/r/201603271114.K9i45biy%25fengguang.wu@intel.com
-                * - https://lkml.kernel.org/r/201603291058.zuJ6ben1%25fengguang.wu@intel.com
-                *
-                * gcc bug:
-                * - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70604
-                */
-               if (!rodata_rela) {
-                       rodata_rela = find_rela_by_dest(file->rodata,
-                                                       text_rela->addend + 4);
-                       if (rodata_rela)
-                               file->ignore_unreachables = true;
-               }
+static int add_func_switch_tables(struct objtool_file *file,
+                                 struct symbol *func)
+{
+       struct instruction *insn, *prev_jump = NULL;
+       struct rela *rela, *prev_rela = NULL;
+       int ret;
 
-               if (!rodata_rela)
+       func_for_each_insn(file, func, insn) {
+               if (insn->type != INSN_JUMP_DYNAMIC)
+                       continue;
+
+               rela = find_switch_table(file, func, insn);
+               if (!rela)
                        continue;
 
                /*
@@ -732,13 +789,13 @@ static int add_func_switch_tables(struct objtool_file *file,
                 */
                if (prev_jump) {
                        ret = add_switch_table(file, func, prev_jump, prev_rela,
-                                              rodata_rela);
+                                              rela);
                        if (ret)
                                return ret;
                }
 
                prev_jump = insn;
-               prev_rela = rodata_rela;
+               prev_rela = rela;
        }
 
        if (prev_jump) {
@@ -997,34 +1054,6 @@ static int validate_branch(struct objtool_file *file,
        return 0;
 }
 
-static bool is_gcov_insn(struct instruction *insn)
-{
-       struct rela *rela;
-       struct section *sec;
-       struct symbol *sym;
-       unsigned long offset;
-
-       rela = find_rela_by_dest_range(insn->sec, insn->offset, insn->len);
-       if (!rela)
-               return false;
-
-       if (rela->sym->type != STT_SECTION)
-               return false;
-
-       sec = rela->sym->sec;
-       offset = rela->addend + insn->offset + insn->len - rela->offset;
-
-       list_for_each_entry(sym, &sec->symbol_list, list) {
-               if (sym->type != STT_OBJECT)
-                       continue;
-
-               if (offset >= sym->offset && offset < sym->offset + sym->len)
-                       return (!memcmp(sym->name, "__gcov0.", 8));
-       }
-
-       return false;
-}
-
 static bool is_kasan_insn(struct instruction *insn)
 {
        return (insn->type == INSN_CALL &&
@@ -1046,9 +1075,6 @@ static bool ignore_unreachable_insn(struct symbol *func,
        if (insn->type == INSN_NOP)
                return true;
 
-       if (is_gcov_insn(insn))
-               return true;
-
        /*
         * Check if this (or a subsequent) instruction is related to
         * CONFIG_UBSAN or CONFIG_KASAN.
@@ -1109,6 +1135,19 @@ static int validate_functions(struct objtool_file *file)
                                    ignore_unreachable_insn(func, insn))
                                        continue;
 
+                               /*
+                                * gcov produces a lot of unreachable
+                                * instructions.  If we get an unreachable
+                                * warning and the file has gcov enabled, just
+                                * ignore it, and all other such warnings for
+                                * the file.
+                                */
+                               if (!file->ignore_unreachables &&
+                                   gcov_enabled(file)) {
+                                       file->ignore_unreachables = true;
+                                       continue;
+                               }
+
                                WARN_FUNC("function has unreachable instruction", insn->sec, insn->offset);
                                warnings++;
                        }