objtool: Improve detection of BUG() and other dead ends
authorJosh Poimboeuf <jpoimboe@redhat.com>
Tue, 21 Feb 2017 21:35:32 +0000 (15:35 -0600)
committerIngo Molnar <mingo@kernel.org>
Fri, 24 Feb 2017 08:10:52 +0000 (09:10 +0100)
The BUG() macro's use of __builtin_unreachable() via the unreachable()
macro tells gcc that the instruction is a dead end, and that it's safe
to assume the current code path will not execute past the previous
instruction.

On x86, the BUG() macro is implemented with the 'ud2' instruction.  When
objtool's branch analysis sees that instruction, it knows the current
code path has come to a dead end.

Peter Zijlstra has been working on a patch to change the WARN macros to
use 'ud2'.  That patch will break objtool's assumption that 'ud2' is
always a dead end.

Generally it's best for objtool to avoid making those kinds of
assumptions anyway.  The more ignorant it is of kernel code internals,
the better.

So create a more generic way for objtool to detect dead ends by adding
an annotation to the unreachable() macro.  The annotation stores a
pointer to the end of the unreachable code path in an '__unreachable'
section.  Objtool can read that section to find the dead ends.

Tested-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: http://lkml.kernel.org/r/41a6d33971462ebd944a1c60ad4bf5be86c17b77.1487712920.git.jpoimboe@redhat.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
arch/x86/kernel/vmlinux.lds.S
include/linux/compiler-gcc.h
tools/objtool/arch.h
tools/objtool/arch/x86/decode.c
tools/objtool/builtin-check.c

index e79f15f108a8d43d8d5644f7fc967f32c3282b47..ad0118fbce90d79e80211a923fced106c39f9316 100644 (file)
@@ -346,6 +346,7 @@ SECTIONS
        /DISCARD/ : {
                *(.eh_frame)
                *(__func_stack_frame_non_standard)
+               *(__unreachable)
        }
 }
 
index 0444b1336268d856e2dcf97f0d8fb41afe80b2cc..8ea159fc489dcb452ac5bc9e0c8c2dddc629c4af 100644 (file)
 #endif
 #endif
 
+#ifdef CONFIG_STACK_VALIDATION
+#define annotate_unreachable() ({                                      \
+       asm("1:\t\n"                                                    \
+           ".pushsection __unreachable, \"a\"\t\n"                     \
+           ".long 1b\t\n"                                              \
+           ".popsection\t\n");                                         \
+})
+#else
+#define annotate_unreachable()
+#endif
+
 /*
  * Mark a position in code as unreachable.  This can be used to
  * suppress control flow warnings after asm blocks that transfer
  * this in the preprocessor, but we can live with this because they're
  * unreleased.  Really, we need to have autoconf for the kernel.
  */
-#define unreachable() __builtin_unreachable()
+#define unreachable() annotate_unreachable(); __builtin_unreachable()
 
 /* Mark a function definition as prohibited from being cloned. */
 #define __noclone      __attribute__((__noclone__, __optimize__("no-tracer")))
index f7350fcedc70dc6c0ef04159d9fa6e33cc76bcdc..a59e061c0b4a0abcf3e9fc7a76e462fd2c41954b 100644 (file)
@@ -31,9 +31,8 @@
 #define INSN_CALL_DYNAMIC      8
 #define INSN_RETURN            9
 #define INSN_CONTEXT_SWITCH    10
-#define INSN_BUG               11
-#define INSN_NOP               12
-#define INSN_OTHER             13
+#define INSN_NOP               11
+#define INSN_OTHER             12
 #define INSN_LAST              INSN_OTHER
 
 int arch_decode_instruction(struct elf *elf, struct section *sec,
index 039636ffb6c8a3edb6c14fd9a2b3a854ab84f982..6ac99e3266eb8218aa485081501e9e0da17c4c77 100644 (file)
@@ -118,9 +118,6 @@ int arch_decode_instruction(struct elf *elf, struct section *sec,
                         op2 == 0x35)
                        /* sysenter, sysret */
                        *type = INSN_CONTEXT_SWITCH;
-               else if (op2 == 0x0b || op2 == 0xb9)
-                       /* ud2 */
-                       *type = INSN_BUG;
                else if (op2 == 0x0d || op2 == 0x1f)
                        /* nopl/nopw */
                        *type = INSN_NOP;
index e8a1f699058a29ba695bfbf24781562c665e4525..5fc52ee3264c1ad9b191c454c404b5df21486beb 100644 (file)
@@ -51,7 +51,7 @@ struct instruction {
        unsigned int len, state;
        unsigned char type;
        unsigned long immediate;
-       bool alt_group, visited;
+       bool alt_group, visited, dead_end;
        struct symbol *call_dest;
        struct instruction *jump_dest;
        struct list_head alts;
@@ -329,6 +329,54 @@ static int decode_instructions(struct objtool_file *file)
        return 0;
 }
 
+/*
+ * Find all uses of the unreachable() macro, which are code path dead ends.
+ */
+static int add_dead_ends(struct objtool_file *file)
+{
+       struct section *sec;
+       struct rela *rela;
+       struct instruction *insn;
+       bool found;
+
+       sec = find_section_by_name(file->elf, ".rela__unreachable");
+       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 .rela__unreachable");
+                       return -1;
+               }
+               insn = find_insn(file, rela->sym->sec, rela->addend);
+               if (insn)
+                       insn = list_prev_entry(insn, list);
+               else if (rela->addend == rela->sym->sec->len) {
+                       found = false;
+                       list_for_each_entry_reverse(insn, &file->insn_list, list) {
+                               if (insn->sec == rela->sym->sec) {
+                                       found = true;
+                                       break;
+                               }
+                       }
+
+                       if (!found) {
+                               WARN("can't find unreachable insn at %s+0x%x",
+                                    rela->sym->sec->name, rela->addend);
+                               return -1;
+                       }
+               } else {
+                       WARN("can't find unreachable insn at %s+0x%x",
+                            rela->sym->sec->name, rela->addend);
+                       return -1;
+               }
+
+               insn->dead_end = true;
+       }
+
+       return 0;
+}
+
 /*
  * Warnings shouldn't be reported for ignored functions.
  */
@@ -843,6 +891,10 @@ static int decode_sections(struct objtool_file *file)
        if (ret)
                return ret;
 
+       ret = add_dead_ends(file);
+       if (ret)
+               return ret;
+
        add_ignores(file);
 
        ret = add_jump_destinations(file);
@@ -1037,13 +1089,13 @@ static int validate_branch(struct objtool_file *file,
 
                        return 0;
 
-               case INSN_BUG:
-                       return 0;
-
                default:
                        break;
                }
 
+               if (insn->dead_end)
+                       return 0;
+
                insn = next_insn_same_sec(file, insn);
                if (!insn) {
                        WARN("%s: unexpected end of section", sec->name);